core.classloading

Alex Kiesel edited this page Jun 7, 2012 · 7 revisions

Class loading

In the XP framework, all classes are organized into packages. A package contains subpackages and classes. Any such item has a fully qualified name consisting of the containing package's name and the name itself.

Naming

Qualified names may consist of any letter from [a..zA..Z], the underscore (_) and may contain numbers [0..9] (except at the beginning!). The name parts are separated by dots (".").

Examples

  • lang.XPClass - the class "XPClass" in the package "lang" - represented by lang/XPClass.class.php
  • util.collections - the package "collections", a a subpackage of the "util" package (util/collections).

Additional classes to the framework should be named according to the "reverse domain" principle also used in Java. The classes that form the Dialog application (a photoblog software) reside in de.thekid.

uses()

Static dependencies can be written using the uses() statement as follows:

<?php
  uses('util.cmd.Command', 'util.collections.HashTable');
  
  class PrintHash extends Command {
    public function run() {
      $hash= new HashTable();
      // ...
    }
  }
?>

Dynamically loading classes

To load a class using the default classloader the following can be used:

<?php
  ClassLoader::getDefault()->loadClass('util.collections.HashTable');
?>

Usually we want to actually do something with this class, so we'd create an lang.XPClass instance from it. So instead of the above, the common practice is to write:

<?php
  $class= XPClass::forName('util.collections.HashTable');  // or:
  $class= Package::forName('util.collections')->loadClass('HashTable');
  
  // Create an instance
  $hash= $class->newInstance();
?>

For details on what can be done with these XPClass objects have a look at the Reflection documentation.

Exceptions

Failure to load classes will result in a lang.ClassNotFoundException. Here's an abbreviated example from the scriptlet package:

<?php
  $name= $request->getURL()->getPath();
  try {
    return $this->package->loadClass($name)->newInstance();
  } catch (ClassNotFoundException $e) {
    throw new HttpScriptletException($name.' not found', HTTP_NOT_FOUND, $e);
  }
?>

Anonymous classes

Used primarily as "throw-away" objects or as a substitution for closures, the XP framework allows for anonymous classes to be created via newinstance():

<?php
  $cleanup= newinstance('lang.Runnable', array($this->base), '{
    private $base;
    
    public function __construct(Folder $base) { 
      $this->base= $base; 
    }
    
    public function run() {
      Console::writeLine("Cleaning temporary files in ", $this->base);
    }
  }');
?>

Classes created by newinstance() will be named using the base class' local name (here: Runnable) and a unique identifier. Typically, this class will be named Runnable·1.

Proxy classes

To create anonymous instances from interfaces and pass control to a separate instances, the lang.reflect.Proxy class can be used:

<?php
  $handler= newinstance('lang.reflect.InvocationHandler', array(), '{
    public function invoke($method, $args) {
      Console::writeLine("Invoke ", $method, "(", $args, ")");
    }
  }');
  $proxy= Proxy::newProxyInstance(
    ClassLoader::getDefault(),
    array(XPClass::forName('lang.Runnable')),
    $handler
  );
  
  // Will print "Invoke run([])" on the console output
  $proxy->run();
?>

This method is used in the remote package for example, where the invocation handler serializes the invocation, sends it to server, reads the answer, unserializes it and return the obtained value.

Classes created by the Proxy class will be named using the prefix "Proxy·" and a unique identifier.

ClassLoader

The lang.ClassLoader apidocs: lang.ClassLoader] class functions as an entry point for all class loading operations. It maintains a list of delegates which it asks to load classes, packages and resources. For each distinct element in PHP's include_path setting, a class loader delegate will be created.

Given the following setting:

  include_path=".:/home/thekid/classes:/usr/local/xp/5.6.6/xp-rt-5.6.6.xar"

...the following instances will be created:

  • lang.FilesystemClassLoader<.>
  • lang.FilesystemClassLoader</home/thekid/classes/>
  • lang.archive.ArchiveClassLoader</usr/local/xp/5.6.6/xp-rt-5.6.6.xar>

A note on distinction: In case the current working directory is /home/thekid/classes, there will only be two delegates (#2 and #3 from above). Uniqueness is determined by comparing the elements after having applied realpath() on them.

To programmatically add delegates, use the following:

<?php
  with ($f= new File('/home/thekid/lib', 'de-thekid.xar')); {
    ClassLoader::registerLoader(new ArchiveClassLoader(new Archive($f)));
  }
?>

The class loading mechanism will search for classes by asking each of its delegates if they provide classes by the given fully qualified names. The first delegate classloader to return true will be asked to load the given class.

Inner workings

Once a class is found and has not been previously loaded, the following is typically performed:

  • Transform the fully qualified name to the storage name. On Windows systems using the released XAR files, lang.XPClass becomes something like xar://c:/Opt/XP/5.6.6/lib/xp-rt-5.6.6.xar?lang/XPClass.class.php.
  • Using PHP's stream wrapper functionality, the storage name is passed to the include() statement.
  • The class bytes are loaded and evaluated.
  • The class is registered. Its fully qualified name and the classloader are stored for later reverse lookup.