Classloading in PHP - and how it is done in PIWI

von

In PHP4 world a developer wrote scripts and included this scripts in other scripts with the well-known include(), include_once(), require() or require_once() statements. As a PHP developer for long time I remember the pain: thousands of scripts, thousand ways to name them and tons of includes. It hasn’t become better with PHP4 first support of object-oriented programming. Actually it has gone even more worse, since a OOP developer usually uses one file per class. Again, the thousands of includes become more and more and finally we all used required_once() since we couldn’t know if another class already included the file we want now.

Additionally, include (and similar, from now on I always use “include” if I refer for such kind of statements) is very static. If you want to move your class into another folder you have to search/replace some stuff. Sometimes I even found an empty file when a file has been moved, just with another include with the new location! Really, include is very bad. In the past I developed several loading mechanisms, but they always were quite hard and complex and didn’t turn on automatically.

Things changed with PHP5. There is a new magic method called __autoload(). And guess what - this method is usually called from the interpreter if you call a class which definition hasn’t been found in memory yet. We at PIWI use this new function as a core concept. Let me explain how.

Basically we don’t use any include statements anymore. They are evil and static. And of course we don’t have any procedural scripts in PIWI (except the starter file index.php). The latter one gives us the option to say:”whatever we need to be included, it must be a class or an interface”. That is the point were the __autoload function steps in. If we need a class or an interface, this method is called and our own Classloader concept is working. To make things happen, we wrote a ClassLoader class which does the dirty work. It’s the only include statement we have. And that’s OK.

/**
 * ClassLoader which makes other
 * includes dispensable.
 */
require_once ("lib/piwi/classloader/ClassLoader.class.php");

Then we need to define the __autoload method. You should know that only one __autoload method definition is allowed per script. I would say it’s a good style if you only have one algorithm for looking up your classes per project. Then we create the variable which holds the reference to the classloader.

/** Instance of the Classloader. */
$classloader = null;

As you may have noticed, I do this outside of the __autoload function. This is cause I don’t want to instanciate my ClassLoader each time I call the __autoload. To see how we make sure, that this variable is only filled once, we have to take a look into the __autoload method itself:

function __autoload($class) {
  global $classloader;
  if ($classloader == null) {
    $classloader = new ClassLoader($GLOBALS['PIWI_ROOT'] . 'cache/classloader.cache.xml');
  }

  $directorys = array ('lib', CUSTOM_CLASSES_PATH);

  foreach ($directorys as $directory) {
    $result = $classloader->loadClass($directory, $class);
    if ($result == true) {
      return;
    }
  }
}

First we make $classloader global to the function. Then we check if it isn’t null. If it has been already filled, we go on. If not, we create the ClassLoader object. You see in the above example, we are using a cache file too. Its an XML file and we put an entry into it whenever we found a location to a class.

After that we iterate about our class folders and call loadClass on each one. This method looks in the cache first. If the class is found in the cache, it is simply returned. If it’s found in the cache and the class has moved, the cache entry is deleted and normal load procedure starts. If even this fails, an error occurs. The ClassLoader checks recursive in the parameter classfolder for the class. It loads the first file with the name of the class and if.php or class.php or even only .php (since PIWI 0.0.9) into the system.

Example: if you lookup a class name MyClass, it should be in the file MyClass.class.php, MyClass.if.php or MyClass.php. We recommend using .class.php for Classes, .if.php for Interfaces. We support .php only cause other projects may not have this convention—like log4php. Integration is important to PIWI, since there are numerous projects outside we want to support and use ourselves. You can easily use the ClassLoader classes yourself, even if you don’t use PIWI. Just download the PIWI package and grab the necessary classes in the ClassLoader folder.

However, there are some drawbacks of using __autoload. Reading files from the harddisk (that is what you do if you include classes) is not very performant compared to having everything in one huge file. But for the sake of readability, we always had success with putting one class per file. I never came to the point were this is really important.

Additionally we have a html cache at PIWI working. This reduces waste of performance. Second, but not really important (imho): __autoload can only load classes of course :-) Half procedural, half objectoriented is no more. If you are a traditional developer and use classes only where necessary, you probably will not have much benefits from __autoload. Last thing to know: be rigorous with your exceptions in ClassLoader: log them (with Log4php) where ever they occur - those exception occuring in __autoload cannot be catched!

Tags: #Open Source #PHP #Classloading #PIWI

Newsletter

ABMELDEN