Recently I decided to upgrade some projects from my own (...) autoloader solution to a Composer PSR-4 autoloader. My code already followed PSR-4 so, no big deal there.
On a specific case, I had the following code:
public static function isAutoLoadable($className)
{
$className = ltrim($className, "\\");
$file = $GLOBALS["Path"] . "/src/" . str_replace("\\", "/", $className) . ".php";
if (false !== stream_resolve_include_path($file))
return $file;
return false;
}
It allowed me to check whatever a given class name could be loaded, without actually trying to load it and result on a PHP Class * not found error.
Use case:
I'm currently using it to replace controllers that by some reason couldn't be found with a generic error one, avoiding an App crash and just telling the user something went wrong... then extra internal logging is done.
A function would be good since I can call it before trying to load a controller... I don't want this behavior to spread to any other classes on the app
So my question is: Is there a equivalent way to check if Composer is able to autoload some class without forcing it to load it and cause an error? -- So I can take further actions in case the class wasn't found?
Related
I have an interface which I named Manager, and two classes UtilisateurManager and StageManager, and the both of them implements the Manager interface.
In each class I included the interface Manager as the following :
require '../helpers/Manager.class.php';
Then I needed to use these two classes at once in a php script, but I got the error that I can't redeclare class Manager.
I tried to work with the function class_exists() but it's not useful in my case.
How can I solve this problem ?
Maybe you can use require_once instead of require.
require_once '../helpers/Manager.class.php';
You need to implement a simple autoloader which will try to load class/interface only if it isn't already loaded, and forget about direct require/include then. It is considered good practice to leave the filesystem details out of the class file: you may reuse it in other project where your directories system might change, so you'll need to edit the class again.
A simple example:
class Autoloader
{
protected $directories = array();
public function register()
{
// register 'loadClass' method as an autoloader
spl_autoload_register(array($this, 'loadClass'));
}
public function addSource($dir)
{
$this->directories[] = $dir;
}
public function loadClass($className)
{
foreach ($this->directories as $dir) { // search in every registered sources root
$fileName = $dir.DIRECTORY_SEPARATOR.$className.'.class.php';
if (file_exists($fileName) {
include($fileName);
return; // halt when file was found
}
}
}
}
// bootstrap file
$autoloader = new Autoloader;
$autoloader->register();
(spl_autoload_register documentation)
Please note that this realization will cycle through the folders, and this is certainly not the desired solution. The best option today is to build project according to PSR-4 spec - autoloader implementation will have to check existence of a single file only. It may be a little hard to dive into namespaces without any preparation, but once you get familiar with this coding style, you will forget about direct file load.
The last thing, i want to warn you about your naming convention - your Interface is stored in a .class.php file, and this is confusing.
How smart is spl_autoload_register? Does it know to "look ahead" for the class definition in other files before trying to include?
When I run my index.php page I'm seeing
Fatal error: Class 'input\input' not found in /var/www/php/classes/input/date.php on line 4
index.php runs the following (after this block it creates an object from one of the classes, if I uncomment the one line everything works perfectly)
function my_autoloader() {
//include_once "/var/www/php/classes/input/input.php"; //if this is uncommented, it fixes the problem, but I'm not sure why
foreach (glob("/var/www/php/classes/*/*.php") as $filename)
{
require_once $filename;
}
}
spl_autoload_register('my_autoloader');
is my syntax wrong? is spl_autoload_register supposed to function this way? many other files in these folders depend on eachother and the autoloader seems to "figure it out". I'm not sure why it is suddenly hung up on the "input" class in particular
You're using the autoloader in a wrong way. It is only supposed to load the classes you need for a specific request, not the whole folder.
Read more about autoloading classes properly with some good examples: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
You are using it wrongly.
The autoload is used for autoloading only the given class name on the 1st argument of callback, not for loading all classes like you're doing.
For example:
spl_autoload_register(function ($class) {
// ...
$class = stream_resolve_include_path(sprintf("%s.php", $class));
if ($class !== false) {
require $class
}
});
$class will contains the class to load it, so, you can use it to find the file containing this class at your filesystem.
I understand how to register autoloaders and even how to create one, that's no issue at all. How ever the main issue is - how do you have two auto loaders running side by side for something like:
class project_one_folder_class extends project_two_folder_class{}
You'll notice that the child class belongs to a project which is reaching out and calling the parent class which is locate in a different project.
The way the projects are linked project two's classes are always seen by the auto loader, how ever project one's classes are never seen.
So the way I thought around this was to write two auto loaders and register them because php will look in on then the other. How ever php seems to be looking in only one and not the other.
how would you solve this?
Edit
Project two is parent, Project one is child. This is more expanded question then What was posted on this question.
To better expand this is my class.
class AisisCore_Loader_AutoLoader{
protected static $_instance;
public function get_instance(){
if(null == self::$_instance){
self::$_instance = new self();
}
return self::$_instance;
}
public function reset_instance(){
self::$_instance = null;
}
public function register_auto_loader(){
spl_autoload_register(array($this, 'load_class'));
spl_autoload_register(array($this, 'load_child_class'));
}
public function load_class($class){
$path = str_replace('_', '/', $class);
if(file_exists(get_template_directory() . '/' . $path . '.php')){
require_once(get_template_directory() . '/' . $path . '.php');
}
}
public function load_child_class($class){
$path = str_replace('_', '/', $class);
if(file_exists(get_stylesheet_directory() . '/' . $path . '.php')){
require_once(get_stylesheet_directory() . '/' . $path . '.php');
}
}
}
Currently this class will load anything in the parent project. It will even load parent project objects in the child project. How ever no child object can be loaded using this class as it is not found.
Those familiar with WordPress will instantly say, yes its because you have get_template_directory when you want get_stylesheet_directory How ever - Knowing this - I want to write then two auto loaders, one that will load child projects objects using get_stylesheet_directory and then one that will load parent objects via get_stylesheet_directory so that:
class project_one_folder_class extends project_two_folder_class{}
works and loads, with out error.
This is a little rough, but you actually only need one autoloader that just checks within multiple directories:
abstract class Hakre_Theme_AutoLoader
{
public static $directories;
public static function init()
{
// configure paths
self::$directories = array(
get_template_directory(), // current theme, parent in case of childtheme
get_stylesheet_directory(), // current theme, child in case of childtheme
);
// please double check if you really need to prepend
return spl_autoload_register('Hakre_Theme_AutoLoader::autoload', true, true);
}
public static function autoload($class)
{
$filename = str_replace('_', '/', $class) . '.php';
foreach (self::$directories as $directory) {
$path = $directory . '/' . $filename;
if (is_file($path)) {
require($path);
return; # leave because the class has been loaded
}
}
}
}
Hakre_Theme_AutoLoader::init();
## If you're unsure all worked out properly:
var_dump(Hakre_Theme_AutoLoader::$directories);
This should actually do it, however I've not tested it. See the var_dump, you can debug if the correct directories are registered or not. No need to have multiple callbacks only because you want to check in two directories.
Well, of course you could just look up a second path when the first file_exists returns false - in the very same autoloader.
You can also register a second autoloader for the second path.
PHP docs addresses this:
If there must be multiple autoload functions, spl_autoload_register() allows for this. It effectively creates a queue of autoload functions, and runs through each of them in the order they are defined. By contrast, __autoload() may only be defined once.
Separate the autoloading process!
PHP is working like this: At some point, the code execution stumbles upon a class reference that is not yet loaded. $obj = new project_one_folder_class()
Now the registered autoloader functions are called, starting with the one added first (unless the ones added later are using the "prepend" parameter).
The first (and all subsequent) autoload function gets the name of the class that should be loaded. The function now has the task to decide
Is it it's task to load this class? If not, do nothing, let the other functions try to load it.
If this function is responsible to load the class, try to make up a path and filename out of the known class name, and require this file.
Step 1 is usually solved by looking if the class name to be loaded starts with a certain prefix. In your case, the autoloader responsible for loading classes starting with "project_one_" should NOT try to load any other classes starting with something else.
Step 2 takes place if the autoloader knows that it must perform the autoloading. It now transforms the class name into a relative path and filename, adds the base directory, and then requires the file.
Requiring a file that defines a class which extends another class (or implements interfaces) can then trigger another run of autoloading with any still unknown classes. In your case, the autoload process is started again with the class name "project_two_folder_class". Note that the first autoloader for "project_one_" classes will be called again with this second class name, but it must not do anything, because it does not know how to load these classes. This is up to the autoloader that knows about "project_two_" classes.
Order of registration should not matter for the different autoloaders. If it does, autoloaders misbehave.
Because it is a common pattern to simply transform any underscore characters in class names into DIRECTORY_SEPARATOR, as well as the backslashes from namespaces, add ".php" at the end, and then try to load this file from a defined base directory, the code for two of these autoloaders will be identical, and the problem is reduced to configuring only one autoload function with these two cases: Which prefix should be present, and in which base directory should the file be expected to be, provided that the class name can be transformed into a relative path to the file.
Checking in every case whether the file exists in a certain directory or not is not optimal for performance. You'll end up doing plenty of checks for files that you will be able to know you cannot load by simply looking at the class name.
And by the way: Autoloader functions do not need to be singletons. In fact, your singleton implementation is broken, and useless. Instantiate the autoloader with new, maybe pass some configuration values into the constructor, and call spl_autoload_register(), and you should be done.
You can add more than one loader, just register each function with spl_autoload_register().
procedural style:
spl_autoload_register('loader_function1'));
// your second class location
spl_autoload_register('loader_function2'));
or in a OO style
spl_autoload_register(array($this, 'loader_function1'));
spl_autoload_register(array($this, 'loader_function2'));
However based on your question: you probably can accomplish the same thing by having a more flexible autoloader
see my answer in a different question here:
Class autoloader in wordpress plugin
I'm currently programming on my Framework in PHP (mainly for education reasons, and a way of passing time). And I wrote an Autoloader which automatically scans the directory tree and detects all the classes in it and creates an array in the following format:
Array(
'MyAwesomeClass' => 'path/to/file/in/some/subdirectory/MyAwesomeClass.php',
'MyOtherAwesomeClass' => 'path/to/file/MyOtherAwesomeClass.php'
// ....
);
At first this was very neat. Because i don't needed to worry where I put my classes.
But later I discovered this is very resource intense. So I created an cache mechanism and the index is serialized and saved to a file. This decreased the response time of the PHP-Script by at least 50%. But it brought some issues with it.
In other classes in the framework I often use things like:
class_exists('MyAwesomeClassController');
The problem is if I added the new class MyAwesomeClassController the class is not yet in the index and class_exists returns false. The problem now is I have to delete the cached file (to let the autoloader recreate the index again) every time I add a new class to the Framework.
Another thing I tried (which is a bit hacky):
In the Autoloader class I checked if the auto load method was called using class_exists(). If this was the case I recreated the Index to see if I can find the requested class somewhere. But this doesn't seem to work out as well because sometimes class_exists is called using a class which mostly doesn't exists, so this is basicly the same solution as recreating the index on every request.
Any solutions? Or do I have to rewrite my Autoloader completely?
I went this path a time ago, and came to the same conclusion: to resource intensive. What you are actually doing here is building some kind of linker.
I switched to a combination of namespaces and a map. I map a namespace to a folder and when the autoloader is called, it strips of the namespace, looks up the corresponding folder, and includes the file.
my map looks like this (I choose xml for the format) :
<?xml version="1.0" encoding="UTF-8"?>
<autoloader>
<namespace id="Ganymedes\Core">/system/core</namespace>
<namespace id="Ganymedes\Core\Debug">/system/core/debug</namespace>
</autoloader>
And the class loader:
public function ClassLoader( $class )
{
if( class_exists( $class, false ))
return true;
$classparts = explode( '\\', $class );
$classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
$namespace = implode( '\\', $classparts );
if( isset( $this->namespaces[$namespace] )) {
foreach ( $this->namespaces[$namespace] as $path )
if( is_readable( $path . $classfile ))
include_once $path . $classfile;
return true;
}
return false;
}
Maybe I haven't understood properly what you are trying to do... but what is wrong with get_include_path/set_include_path and the __autoload($class) built in function?
EDIT to explain my comment:
In "header.php" (file which gets included at the start of every script):
set_include_path(get_include_path().PATH_SEPARATOR."my/first/directory"
.PATH_SEPARATOR."my/second/directory"
.PATH_SEPARATOR."this/is/another/directory"
...
.PATH_SEPARATOR."and/here/is/the/last/one");
function __autoload($class) {
require_once($class.".class.php");
}
The autoload function checks each directory in the path, until it finds the required file. I've never checked it for performace, but I'm guessing that it is fairly insignificant unless you have 100s of directories. If you are organised, you can put your frequently used (base) classes in the first "extra" path so that they are found immediately.
I've just noticed in the PHP manual online that __autoload is no longer advised, and that spl_autoload_register should be used instead, but the concept is the same.
Under the assumption that the classes you're trying to load should always exist, your autoloader could do the following:
Look up the class name inside the map (which is cached). If it exists, load the file.
If it doesn't exist, perform another scan and look up the class name in the newly generated map. If it exists, write the new map to disc and load the file.
If it still doesn't exist, raise the alarm.
The third step could be improved by introducing a negative cache as well; if the class is present in that map, you know the class doesn't exist based on a previous attempt. The negative cache would be shorter lived but just long enough to not trash your server.
You could also consider creating the cache offline as part of a deployment system.
I'm using Codeigniter 2.1.0.
I'm including the Amazon Web Services SDK in a custom model using require_once APPPATH . "/libraries/aws_sdk/sdk.class.php";
This works well. However when CI decides it later needs to load it's db cache class, it calls (via CI_DB_driver::_cache_init):
if ( ! class_exists('CI_DB_Cache'))
{
if ( ! #include(BASEPATH.'database/DB_cache.php'))
{
return $this->cache_off();
}
}
This triggers the autoload of the AWS SDK (the exact method being CFLoader::autoloader).
I can get around this by telling class_exists not to autoload, as it seems that DB_cache.php is included anyway if class_exists returns false:
if ( ! class_exists('CI_DB_Cache', false))
{
if ( ! #include(BASEPATH.'database/DB_cache.php'))
{
return $this->cache_off();
}
}
This dirty fix works, but obviously only fixes the immediate problem. In general, what is the best way to ensure that CodeIgniter doesn't get confused with the autoload from other libraries?
Please note:
I've read bits and pieces about using spl_autoload_register. It seems that Codeigniter doesn't use this and I'm not sure how I should implement this safely. I would find a solid example of how and where I should implement this most useful (if, of course, this is the solution).
It appears that the AWS SDK already uses spl_autoload_register: spl_autoload_register(array('CFLoader', 'autoloader'));
As you probably know, you don't really want to be modifying the core methods in CI. So, to prevent CI from conflicting with your class, you want to do something very similar to the following:
Leave your folder structure as is, but create a new file outside your aws_sdk folder. Name it something like *Aws_loader* or something that makes sense to you. If you want to autoload the sdk, then in the CI autoloader file, add:
CI Autoload file (application/config/autoload.php):
$autoload['libraries'] = array('Aws_loader');
Your init file:
class CI_Aws_sdk{
// for use with PHP < version 5
/*public function CI_Aws_sdk(){
require dirname(__FILE__) . DIRECTORY_SEPARATOR . "aws_sdk" .DIRECTORY_SEPARATOR . 'sdk.class.php';
}*/
// for use with PHP >= 5
public function __construct(){
require dirname(__FILE__) . DIRECTORY_SEPARATOR . "aws_sdk" .DIRECTORY_SEPARATOR . 'sdk.class.php';
}
}
So your directory structure looks like this now:
application --
config --
...
libraries --
Aws_loader.php
aws_sdk --
sdk.class.php
If you aren't autoloading the sdk, then in your controller, you can do this:
$this->load->library('Aws_loader');
Either way, CI with load the class for you and effectively separate any methods within it and now you can operate within that class just like any other library or model that you've loaded previously, without interfering with CI's methods, similar to this:
$this->Aws_loader->do_something();
You can use the same method for any third party class library or even one that you wrote yourself. A very similar arrangement can be used for models, libraries, helpers and the like.
Hope this helps!