Selective PHP autoload - php

I am writing a add-on module which is integrated with an existing PHP application; Because I am using a MVC pattern, and which may requires lot of inclusion of classes (which might not be used at all depending on the action of the user), I decide to use autoloading of classes.
However, I have to ensure that the autoload function does not interferes with the normal operations of the existing applications.
Does autoload only kicks in if a class name is not defined?
Say I have to write another module which uses its own autoload functions (say, I have an autoload for a module, since they each reside in their own folder), how do I differentiate which module is it for?
For #2, I thought of 2 options. Either prefix the class name with the module name (Such as 'MyNewModule_View_Default' and 'AnotherModule_View_Default'), or use file_exists to check the include file exists.
Other suggestions are welcomed too!

Just check if the class that is to be loaded already exists with class_exists() before actually loading it in your autoloader implementation. Especially if you have multiple registered autoloaders (see 2).
You can specify multiple autoloaders in a stack via spl_autoload_register(). The registered functions are executed in the order in which they where registered until the class is successfully loaded. You can specify different autoloaders for different modules for example. Or you can do a namespacing approach like in the Zend_Framework, if you have control over class names.

Yes, autoloader is only called when class name is not found.
Usually you'd check the class' namespace (pre 5.3 you use pseudo-namespaces, usually separated by an underscore). So your autoloader would only load classes that are under the namespace(s) of your application.

Related

How does the PHP 'use' keyword work (in Symfony 2)?

In Symfony 2, all requests are getting routed through app_dev.php (or app.php).
The first few lines look like:
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;
...
In theory, I get that this just imports the specified namespace (or class) to the current scope. What I don't understand is how PHP maps this to a file. For example, the Debug class is located in:
vendor/symfony/symfony/src/Symfony/Component/Debug/Debug.php
How does PHP know to look in vendor/symfony/symfony/src/?
I'm probably misunderstanding what is happening, but any clarification would be appreciated.
Knowing which file a class lives in isn't the job of the language, it's the job of the autoloader.
All the use keyword does is this instance is create an alias:
use Symfony\Component\HttpFoundation\Request;
This is saying in the following script, when I refer to Request I really mean Symfony\Component\HttpFoundation\Request. If I use Request object in some way (by either creating an instance or calling a static method on it) then the autoloader will go and try to load the file that class is defined in if it hasn't been loaded already.
The inner workings of the autoloader, though, is different from project to project. There has been a move to standardize autoloader behavior à la PSR-4, but there's nothing in the language saying that you have to adhere to that standard. You could, for instance, have a scheme where all class files reside in a single directory and your autoloader just loads all classes from there, regardless of what namespace they're in.
That scheme would suffer from the fact that you could only have a single class of every name, but there's nothing stopping you from doing it that way.
To answer your question: "How does it know that "Symfony\Component\Debug\Debug" is a valid namespace?"
It doesn't, because it's not actually going out and trying to load that class at this point. If in any random PHP script you put something like this at the top:
use \Non\Existant\ObjectClass;
But don't ever actually use ObjectClass anywhere, you will get no errors. If you try to say new ObjectClass, though, you will get a class not found error.
Simplistically speaking, you have to load all the files beforehand into memory for PHP. PHP does not inherently have any standards to where files are located, the rules for loading files has to be done by your code.
When PHP attempts to load a file, it will check its memory if the class definition exists, if not it will attempt to autoload the file that may contain your class definition; see PHP: Autoloading Classes and spl_autoload_register
Some common autoloading strategies have been defined by the PHP Framework Interop Group:
PSR-0 (Autoloading standard)
PSR-4 (Improved Autoloading standard)
In this case, autoloading is done by Composer, all you need to do is include vendor/autoload.php in any of your scripts and vendor code downloaded via composer can be autoloaded. You can manually add classes into the composer autoloader as well. See: Composer Autoloading

When I auto-load a class/file with composer, what is actually happening behind the scenes?

I haven't been able to find a strait answer to this question yet elsewhere online and was wondering how exactly composer autoloading worked.
When I autoload a class using PSR-0 or classmap what is actually happening behind the scenes? Is it just calling include(or some include variant) on the the specified file in the specified path. Is it actually skimming the file for class definitions and constructing its own file to include? Is it doing something that isn't analogous to a file include?
Thanks in advance!
A PSR-0 autoloader is simply a function attached to the global PHP process with spl_autoload_register(). That registered function is called whenever PHP needs to instantiate a class that isn't yet known, so this is the last moment to make the classes code known before PHP fails.
And the implementation of that autoloading can be either pretty sophisticated, or pretty simple, but in every case it will use either include() or require() (possibly with _once, but this is not really needed) to make the class code known to PHP. You could also implement a call to eval() to dynamically add some code that declares the class needed, but this would just be for academic used - I haven't seen it being used in real cases.
The same applies to the classmap loading. The classmap array contains names of classes as keys, and the filename of the containing file as value. This is for cases where there is no PSR-0-compatible ruleset mapping between class name and file path.
If you want more details of how Composer does the autoloading, you should have a look at the generated files inside vendor/composer. Basic knowledge about how PHP autoloading works in general would help understand what happens there.
Behind the scenes composer use spl_autoload_register to register an autoloader function which include your class.
The registered function follows a standardized namespace/path resolution algorithm (basically consider all "\" or "_" in your class name as path separators from a specified base directory) to find the php file to include.
Also, when you run composer install it create a cached index of relation between paths and namespace to speed up the path resolution.
You can dig in the Github repository and see it for yourself.

PHP 5.4 vs 5.3: Autoloading multiple classes in the same file

Firstly, this is a bit of a long one, so thank you for reading.
My issue is similar to this one:
Class not found in the same file
I have a custom-built framework originally written in 2008 for PHP 5 and it's been upgraded over the years to work with PHP 5.3. I've been looking at 5.4 compatibility and have hit a serious issue.
The ORM layer automatically generates classes for each DB table. These classes all sit in one file per table and our autoloader loads that file when required.
For example, a 'customer' table in the 'public' schema (postgresql) would have the following classes:
PublicCustomer, PublicCustomerDBReader, PublicCustomerDBWriter.
Now this may not be the ideal set up, but it is what we currently have.
In PHP 5.3, if PublicCustomer was required, the file would be included, parsed and all of the above classes would become available. So if, for example, a static method is called on PublicCustomer, and that method calls something in PublicCustomerDBReader, that would work fine, since that class is in the same file.
In PHP 5.4, it looks like some optimisations have been done in the core. In the above scenario:
A static method gets called in PublicCustomer.
The autoloader finds and loads the correct file.
The PHP parser only parses up to where it needs; the PublicCustomer class. It has not parsed or instantiated the PublicCustomerDBReader class. I can confirm this by testing if the class exists and by seeing if the parser reaches the end of the file when it gets included, when the method is called (it doesn't).
The method in PublicCustomer then tries to call a method in PublicCustomerDBReader. This fails, since our autoloader has already required the file once.
It seems to me that I have two solutions:
Separate these classes out so that there is one file for each (this will produce a huge number of files)
Redesign the ORM layer so that multiple classes are not required.
Have I understood the issue above properly?
Does anyone know if an optimisation or change was made in PHP 5.4 that would cause this behaviour?
Are there any other potential solutions to the problem that I have not considered?
Place the reader/writer classes at the head of the file. Also you might consider filing a bug report, since the parser should only halt on errors.
Referring to the PHP Framework Interop Group and their autoloading standards PSR-0 and PSR-4 each class must have its own file with a name ending in .php. Even if you have thousands of classes, this should't be a problem for the file system.
With more than one class in a file you have to consider following aspects:
For each class the autoloader must decide which file to load. If you have multiple classes in a file, each used class causes a load. Class files should be loaded once and not more, because classes cannot be redeclared. I do not recommend it, but you could handle this with your own autoloader, which remembers loaded files or tests for loaded classes.
Classes placed within the same file and using each other is problematic. The problem is described in Derived class defined later in the same file “does not exist”? and answered by Jon.

Is registering the autoloader needed zend_loader::registerAutoload()

I'm guessing this line registers the autoload function, which in turn loads needed Zend classes.
Zend_Loader::registerAutoload();
My question: is this line meant to be used in applications that call some zend components but aren't fully zend applications? or is needed also in applications that are fully zend and use zend MVC?
Well, first we should note that Zend_Loader::registerAutload() is deprecated (since 1.8.0). Better is:
Zend_Loader_Autoload::getInstance();
What this does is register an SPL __autoload($classname) function that attempts load classes when they are called for but not-yet-loaded. The default behavior of this autoloader in a non-framework application is to map a class name to a file name (relative to the currently defined include_path) and include() that file in the hopes that the requested class will be defined there.
The specific mapping uses the PEAR 1-class-1-file convention in which a class named something like My_ComponentName_ClassName will reside in the file My/ComponentName/ClassName.php.
See this answer for more details.

Import package or autoloading for PHP?

What solution would you recommend for including files in a PHP project?
There aren't manual calls of require/include functions - everything loads through autoload functions
Package importing, when needed.
Here is the package importing API:
import('util.html.HTMLParser');
import('template.arras.*');
In this function declaration you can explode the string with dots (package hierarchy delimeter), looping through files in particular package (folder) to include just one of them or all of them if the asterisk symbol is found at the end of the string, e.g. ('template.arras.*').
One of the benefits I can see in package importing method, is that it can force you to use better object decomposition and class grouping.
One of the drawbacks I can see in autoload method - is that autoload function can become very big and not very obvious/readable.
What do you think about it?
What benefits/drawbacks can you name in each of this methods?
How can I find the best solution for the project?
How can I know if there will be any performance problems if package management is used?
I use __autoload() extensively. The autload function that we use in our application has a few tweaks for backwards compatibility of older classes, but we generally follow a convention when creating new classes that allow the autoload() to work fairly seemlessly:
Consistent Class Naming: each class in its own file, each class is named with camel-case separated by an underscore. This maps to the class path. For example, Some_CoolClass maps to our class directory then 'Some/CoolClass.class.php'. I think some frameworks use this convention.
Explicitly Require External Classes: since we don't have control over the naming of any external libraries that we use, we load them using PHP's require_once() function.
The import method is an improvement but still loads up more than needed.
Either by using the asterisk or loading them up in the beginning of the script (because importing before every "new Classname" will become cumbersome)
I'm a fan of __autoload() or the even better spl_autoload_register()
Because it will include only the classes you're using and the extra benefit of not caring where the class is located. If your colleges moves a file to another directory you are not effected.
The downside is that it need additional logic
to make it work properly with directories.
I use require_once("../path-to-auto-load-script.php.inc") with auto load
I have a standard naming convention for all classes and inc files which makes it easier to programaticaly determine what class name is currently being requested.
for example, all classes have a certain extension like inc.php (so I know that they'll be in the /cls directory)
and
all inc files start with .ht (so they'll be in the /inc directory)
auto load accepts one parameter: className, which I then use to determine where the file is actually located. looping once I know what my target directory is, each time adding "../" to account for sub sub pages, (which seemed to break auto load for me) and finally require_once'ing the actual code file once found.
I strongly suggest doing the following instead:
Throw all your classes into a static array, className => filepath/classFile. The auto load function can use that to load classes.
This ensures that you always load the minimum amount of files. This also means you avoid completely silly class names, and parsing of said names.
If it's slow, you can throw on some accelerator, and that will gain you a whole lot more, if it still is slow, you can run things through a 'compile' process, where often used files are just dumped into common files, and the autoload references can be updated to point to the correct place.
If you start running into issues where your autoloading is too slow, which I find hard to believe, you can split that up according to packages, and have multiple autoloading functions, this way only subsets of the array are required, this works best if your packages are defined around modules of your software (login, admin, email, ...)
I'm not a fan of __autoload(). In a lot of libraries (some PEAR libraries, for instance), developersuse class_exists() without passing in the relatively new second parameter. Any legacy code you have could also have this issue. This can cause warnings and errors if you have an __autoload() defined.
If your libraries are clear though, and you don't have legacy code to deal with, it's a fantastic tool. I sometimes wish PHP had been a little smarter about how they managed the behavior of class_exists(), because I think the problem is with that functionality rather than __autoload().
Rolling your own packaging system is probably a bad idea. I would suggest that you go with explicit manual includes, or with autoload (or a combination for that matter).

Categories