AutoLoader for unknown project levels - php

I would like to make my projects so fexible that one can simply copy+paste them into each other.
My project structure is ProjectName/(bin, config, lib).
After copy+paste I would have a chain like ProjectName1/lib/ProjectName2/lib/ProjectName3/.
Now it would be great if the Autoloader searches files acording to the level where the file is been called.
For example if a class within ProjectName1/lib/ProjectName2/lib/ calls "new Config" it should receive the config file located in ProjectName1/lib/ProjectName2/config/config.php.
(But not the one in ProjectName1/config/config.php and neither the on in ProjectName1/lib/ProjectName2/lib/ProjectName3/config/config.php).
Is there a way to do this?
Edit:
Does it make sense to make files unique? For example: If 'config.php' was 'ProjectName2Config.php' there is (almost) no chance for conflicts. So the autoloader could search everywhere and will eventually find it's file.
Edit:
Each project would have his own autoloader availible for copy+paste reasons. However I thought that I would load just the one for ProjectName1. Is it better to load them all so that each one can stay simple?
Cheers,
Peter
PS: I just came back from a 3 years programming break. I am also happy, if you tell me that above is gennerally a bad idea and give me the reason why :-)

I think this is a strange way to manage your projects.
Where would be the main script of Project1 and the one of Project2 ?
For example, what I do is I write this in my index.php or main project/lib include file :
<?php define('ROOT_PATH', dirname(__FILE__) . '/'); ?>
And I always use this ROOT_PATH as a start of inclusion paths.
In your case Project1 ROOT_PATH would be something like "/var/www/Project1/" and Project2 ROOT_PATH would be "/var/www/Project1/lib/Project2/"
Thus a new Config in Project1 and in Project2 would use their own config.php file.

Related

Setting the path for include / require files

All my PHP include files are in a single directory:
https://www.mywebsite.com/includes
Inserting these files in top level pages is easy enough:
<?php include 'includes/logo.php'; ?>
<?php include 'includes/main_nav.php'; ?>
<?php include 'includes/news.php'; ?>
etc.
For sub-directory pages I've been doing this:
<?php include '../includes/logo.php'; ?>
<?php include '../includes/main_nav.php'; ?>
<?php include '../includes/news.php'; ?>
and this:
<?php include '../../includes/logo.php'; ?>
<?php include '../../includes/main_nav.php'; ?>
<?php include '../../includes/news.php'; ?>
So far so good, but I suspected it wasn't going to continuing being this easy.
Now I need to include this file:
top_five_news_stories.php
in this:
news.php
At this point, my relative path strategy fails because the include in the include can have only one path structure.
I've read several posts recommending absolute paths using:
dirname(__FILE__)
realpath(dirname(__FILE__)
$_SERVER["DOCUMENT_ROOT"]
However, they all come with some kind of caveat relating to PHP configuration, server configuration or operating system. In other words, there's often a comment by somebody saying it didn't work in their case, or doesn't work in IIS, or doesn't work in UNIX, or something else.
A solution I haven't seen is one I thought would be most simple: Just set a variable:
$base = "https://www.mywebsite.com/includes";
then:
<?php include $base . "logo.php" ?>
Considering that I already use the HTML base element, which works in a similar way, this method strikes me as simple and efficient.
But since it wasn't mentioned in any of the posts I read, I'm wondering if I'm overlooking a potential problem.
Frankly, if I had to go to production to today, I would use this:
<?php include $_SERVER['DOCUMENT_ROOT'] . '/logo.php" ?>
which works for me and is commonly mentioned.
But I'm wondering if using a variable is a reliable, efficient method?
Don't
I would advise against using anything that needs something outside of PHP, like the $_SERVER variable.
$_SERVER['DOCUMENT_ROOT'] is usually set by the webserver, which makes it unusable for scripts running from the command line. So don't use this.
Also don't use url's. The path-part in a url is not the same as the path of the file on disk. In fact, that path can not even exist on disk (think Apache rewrites).
Including url's also needs you to turn allow_url_include on, which introduces (severe) security risks if used improperly.
Do
If your minimal supported PHP version is 5.3 (I hope it is!), you can use the magic constant __DIR__. 2 examples:
define(ROOT_DIR, __DIR__);
define(ROOT_DIR, realpath(__DIR__ . '/..'));
If you need to support lower versions, use dirname(__FILE__). 2 examples:
define(ROOT_DIR, dirname(__FILE__));
define(ROOT_DIR, realpath(dirname(__FILE__) . '/..'));
Make sure ROOT_DIR points to the root of you project, not some subdirectory inside it.
You can then safely use ROOT_DIR to include other files:
include ROOT_DIR . '/some/other/file.php';
Note that I'm defining a constant (ROOT_DIR), not a variable. Variables can change, but the root directory of you project doesn't, so a constant fits better.
realpath()
realpath() will resolve any relative parts and symlinks to the canonicalized absolute pathname.
So given the following files and symlink:
/path/to/some/file.php
/path/to/another/file.php
/path/to/symlink => /path/to/another
and /path/to/file.php contains:
define(ROOT_DIR, realpath(__DIR__ . '/../symlink'));
then ROOT_DIR would become /path/to/another, because:
__DIR__ equals to /path/to/some (so we get /path/to/some/../symlink)
.. is 1 directory up (so we get /path/to/symlink)
symlink points to /path/to/another
You don't really need to use realpath(), but it does tidy up the path if you're relying on relative parts or symlinks. It's also easier to debug.
Autoloading
If you need to include files that contain classes, you'd best use autoloading. That way you won't need include statements at all.
Use a framework
One last pease of advise: This problem has been solved many many times over. I suggest you go look into a framework like Symfony, Zend Framework, Laravel, etc. If you don't want a "full stack" solution, look into micro-frameworks like Silex, Slim, Lumen, etc.
Jasper makes some good points, and a further reason for not using DOCUMENT_ROOT is that content accessible via a URL does not have to be within this directory (consider Apache's alias, scriptalias and mod_user_dir, for example).
As Barmar points out PHP explicitly provides functionality for declaring a base directory for includes. While this is typically set in the config it can be overridden/added to at runtime in your code. You never want to see a variable in your include/require directives. It breaks automatic tools and hides vulnerabilities. Nor should you ever include using the file wrappers.
There is an argument in OO programming for never using include/require explicitly but just autoloading class definitions. However the problem of locating the code remains.
The short answer is that there is no best solution for the problem you describe. Each method has its drawbacks - the best solution is completely dependent on the context. For an enterprise application, setting the include_path simplifies development processes and, if not directly accessible from the webserver enhances security. It also allows for selectively overlaying functionality by manipulating the order of multiple entries in the path.
On the other hand this is not a good model for software you intend to distribute to less technical users likely to be confused about multiple paths whom may not have access to directories outside the document root or to change the default config.
Using relative paths is a robust and portable solution. I don't understand your problem with including top_five_news_stories.php.
A solution which gives you the benefits of both the enterprise and low-end hosting is shown below. This however has the drawback that it needs code added to each entry point in the site (and requires the app to be installed in a named sub directory):
define('APP_NAME', 'mikesdemo');
$base=substr(__DIR__, 0, strrpos(__DIR__, APP_NAME))
. APP_NAME . '/include';
set_include_path(get_include_path() . PATH_SEPARATOR . $base);
The more sophisticated user can then simply....
mv /var/www/html/mikesdemo/include/* /usr/local/php/include/
File Arquitecture Rework:
define a path for every tipe of file like that:
+root
|
+------app(D)(all php script MVC)
|
+------conf(D)(all php Config file)
|
+------assets(D)(all file js and css static image)
|
+------fileup(D)(all file Uploades)
|
+------index.php(F)(Procesor of petition http)
in your index you need include all File of config Like style C++:
Example:
require_once ('conf/config.security.php'); #Configuration around Security in PHP
require_once ('conf/config.conpro.php'); #Configuration around Constantent
require_once ('conf/config.classlib.php'); #Lib class around Generic DB Conection ETC
require_once ('conf/config.classlibmvc.php'); #Lib class around MVC specific class
And example of config file:
Declare Root Directory + Path that shared the file
$APP_DIR_CLASS = $_SERVER['DOCUMENT_ROOT'] . '/app/classgeneric/';
Define library file:
if (!defined('DBMANAGER_CLASS')) define('DBMANAGER_CLASS' ,'class.managerdb.php' );
Include or require the class
require_once $APP_DIR_CLASS . DBMANAGER_CLASS;
when you are in a class and need use DB class you can call it easy:
class Class_Exmaple{
public function __construct(){
$this -> DBMANAGER = new Class_BDManager();
}
public function __destruct(){
$this -> DBMANAGER = new Class_BDManager();
}
public function ConsultDB(){
$query ='Selec * From Tablename';
$result = $this -> DBMANAGER -> ExecuteQ($query);
print_r(result);
}
}
is a easy way to implement but you need learn more about injection and Class Loaders.
There is no "correct way" to require/include an internal script in your project. A lot (most) MVC frameworks use similar best practices to global file access in a router object.
Let's take an example, here is our directory infrastructure:
App/
Controllers/
Controller.php
Models/
Model.php
Views/
View.php
404/
index.php
index.php
.htaccess
Inside our .htaccess we would have a rewrite rule to the index.php in the root directory of your server.
Inside this file, is where we actually run the whole of your Software. For example, this is a great router I use AltoRouter.
First things first, we need to add a way to stop direct browser access and an error path to any controllers, models and views:
define( 'ERROR_PATH', strtolower(explode( '/', $_SERVER['SERVER_PROTOCOL'][0]) . '://' . $_SERVER['SERVER_NAME'] . '/404' );
define( 'IN_APP', 0 );
Which is then used in your controllers, models and views like:
if( !defined( 'IN_APP' ) ) {
header('Location: ' . ERROR_PATH);
exit();
}
Your file path will be the root file path if you declare __FILE__ in this instance (index.php) so we can use it any way (best practice is global defines).
define( '_DIR_', dirname( __FILE__ ) );
Then start requiring your files:
$includeFiles = [
glob( _DIR_ . '/Controllers/*.php' ),
glob( _DIR_ . '/Models/*.php' )
];
foreach( $includeFiles as $dir ):
foreach( $dir as $file ):
require_once( $file );
endforeach;
endforeach;

Reloading a Class

I have a PHP daemon script running on the command line that can be connected to via telnet etc and be fed commands.
What it does with the command is based on what modules are loaded, which is currently done at the start. (psuedocode below for brevity)
$modules = LoadModules();
StartConnection();
while(true){
ListenForCommands();
}
function LoadModules(){
$modules = Array();
$dir = scandir("modules");
foreach($dir as $folder){
include("modules/".$folder."/".$folder.".php");
$modules[$folder] = new $folder;
}
}
function ListenForCommands(){
if(($command = GetData())!==false){
if(isset($modules[$command])){
$modules[$command]->run();
}
}
}
So, an example module called "bustimes" would be a class called bustimes, living in /modules/bustimes/bustimes.php
This works fine. However, I'd like to make it so modules can be updated on the fly, so as part of ListenForCommands it looks at the filemtime of the module, works out if it's changed, and if so, effectively reloads the class.
This is where the problem comes in, obviously if I include the class file again, it'll error as the class already exists.
All of the ideas I have of how to get around this problem so far are pretty sick and I'd like to avoid doing.
I have a few potential solutions so far, but I'm happy with none of them.
when a module updates, make it in a new namespace and point the reference there
I don't like this option, nor am I sure it can be done (as if I'm right, namespaces have to be defined at the top of the file? That's definitely workaroundable with a file_get_contents(), but I'd prefer to avoid it)
Parsing the PHP file then using runkit-method-redefine to redefine all of the methods.
Anything that involves that kind of parsing is a bad plan.
Instead of including the file, make a copy of the file with everything the same but str_replacing the class name to something with a rand() on the end or similar to make it unique.
Does anyone have any better ideas about how to either a) get around this problem or b) restructure the module system so this problem doesn't occur?
Any advice/ideas/constructive criticism would be extremely welcome!
You should probably load the files on demand in a forked process.
You receive a request
=> fork the main process, include the module and run it.
This will also allow you to run several commands at once, instead of having to wait for each one to run before launching the next.
Fork in php :
http://php.net/manual/en/function.pcntl-fork.php
Tricks with namespaces will fail if module uses external classes (with relative paths in namespace).
Trick with parsing is very dangerous - what if module should keep state? What if not only methods changed, but, for example, name of implemented interface? How it will affect other objects if they have link to instance of reloaded class?
I think #Kethryweryn is something you can try.

Developing/using a custom Resource Plugin in Zend Framework

We have used Zend_Log, which is configured in application.ini differently for different circumstances. We initialize it/get it in the bootstrap and store it in the registry:
$r = $this->getPluginResource('log');
$logger = $r->getLog();
But we've subclassed Zend_Log (say, Our_Log) to add customized features, and want to get it the same way. So then we have to make a new Resource Plugin. That seems quite easy - just copy Application/Resource/Log.php, rename the file to Ourlog.php, rename the class to class Zend_Application_Resource_Ourlog. For now, let's not worry about "Our_Log", the class -- just use the new Resource Plugin to get a Zend_Log, to reduce the variables.
So then, our new code in the bootstrap is:
$r = $this->getPluginResource('ourlog');
$logger = $r->getLog();
but of course this doesn't work, error applying method to non-object "r". According to the documentation,
"As long as you register the prefix path for this resource plugin, you
can then use it in your application."
but how do you register a prefix path? That would have been helpful. But that shouldn't matter, I used the same prefix path as the default, and I know the file is being read because I "require" it.
Anyway, any guidance on what simple step I'm missing would be greatly appreciated.
Thanks for the pointers -- so close, so close (I think). I thought I was getting it...
Okay, so I renamed the class Xyz_Resource_Xyzlog, I put it in library/Xyz/Resource/Xyzlog.php
Then, because I don't love ini files, in the bootstrap I put:
$loader=$this->getPluginLoader();
$loader->addPrefixPath('Xyz_Resource','Xyz/Resource/');
$r = $this->getPluginResource('xyzlog');
if (!is_object($r)) die ('Not an object!!');
Sudden Death. So, okay, do the ini:
pluginPaths.Xyz_Resource='Xyz/Resource/'
The same. No help. I believed that the basepaths of the plugin paths would include the PHP "include" paths. Am I mistaken in that? Any other ideas? I'll happily write up what finally works for me to help some other poor soul in this circumstance. Something to do with Name Spaces, maybe?
Plugin classes are resolved using the plugin loader, which works slightly differently to the autoloader; so just requiring the class in doesn't help you here. Instead, add this to your application.ini:
pluginPaths.Application_Resource = "Application/Resource"
you should then be able to use the class as normal. Since your path above will be checked before the default Zend one, you can also name your class 'Log' and still extend the Logger resource to override the standard functionality.

Doctrine 2 Classloader weird behaviour - wrong paths to classes

I'm having a bit of a problem with Classloader added to Doctrine 2 project.
I have simple directory structure like this:
config (bootstrap file)
html (docroot with templates/images/js etc)
php
Entities (doctrine 2 entities)
Responses (some transport objects)
Services (processing api and business logic - like session beans in java)
Each of the php subdirectories belongs to its own namespace (same as the name of directory).
I want to use aforementioned classloader to load these three different packages, so my bootstrap looks like this:
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', $lib );
$classLoader->register();
$responsesCL = new \Doctrine\Common\ClassLoader('Responses', __DIR__.'/../php');
$responsesCL->register();
$entitiesCL = new \Doctrine\Common\ClassLoader('Entities', __DIR__.'/../php');
$entitiesCL->register();
$servicesCL = new \Doctrine\Common\ClassLoader('Services', __DIR__.'/../php');
$servicesCL->register();
Bold DIR is actually __ DIR __ php constant.
Now, I am referring in my services package to entities and this is where the problem starts, for some reason I get errors due to file not found problem, for example:
Failed opening required
'/var/www/projects/PlatformManagement/config/../php/Services/Entities/User.php'
(include_path='.:/usr/share/pear:/usr/share/php')
in
/usr/share/pear/Doctrine/Common/ClassLoader.php
on line 148
Somehow, there is extra 'Services' in the path, and obviously it's not valid path. I am a bit puzzled why that extra directory there? I tripple checked all namespaces, calls, and they are ok.
I need another pair of eyes to have look, I'm assuming I'm missing something obvious here, but can't spot it :|
Oh, this is latest Doctrine 2 Beta (4) and php 5.3.3 on fedora if that's of any help.
Thanks,
Greg
Is there any something like Doctrine\ORM\Tools\Setup::registerAutoloadPEAR()?
Doctrine's Autoloader may produce unexpected behaviors because it loads classes based on PHP include paths.
refer to this
In your case, Entities should have
namespace Entities;
declaration, not anything else.
And use entities like below,
use Entities\User;
new User;

__autoload mix up?

I have a server with many customers on, when I develop I include my init.php in which I have an __autoloader() function that includes the file with dir_name(__FILE__)."/classes/".$className for instance.
But yesterday I saw that the server could not find the specific class, I restartat apache and then it worked again.
Every customer has this own init.php... ( and therefore many __autoloads on the same server )
customer1/init.php : holds __autoload()
customer1/classes/class.php
customer2/init.php : holds __autoload()
customer2/classes/class.php
I have not done some tests and I hope someone can answer my question before I try to reproduce the problem, but do you think it is possible for php to take the wrong autoload function when you get 2 or more requests at the same time?
Is spl_autoload_register the solution?
Many thanks for some ideas or brainstorming.
My guess is that you should have a typo in either one of your __autoload() functions or you are including the wrong init.php file.
Also, dir_name() does not exist, you should change that to dirname() instead or you can also use the new DIR constant for the same effect if you're using PHP >= 5.3.
EDIT: In light of your comment, use should use:
require(realpath(dirname(__FILE__)) . '/classes/' . $className);
or
require(realpath(__DIR__) . '/classes/' . $className);
Each PHP request is completely separate, in fact it is impossible for you to have two functions named __autoload() in the same PHP request, so they cannot interfere. Possible problems:
You are including the wrong customer's init.php
You forgot to include the init.php file, in which case there is no autoloading at all.

Categories