About:Code customization and class loading

From ZenMagick Wiki

Jump to: navigation, search

Contents

Introduction

ZenMagick uses a custom system to resolve and load classes and other code on demand. This allows to easily replace code/classes with custom implementations. Alternatively, method injection may be used to add new methods to existing classes without the need to extend and override base classes. Both methods have their place; extending allows to replace existing methods without the need to touch core files, while method injections avoids the possible issues with multiple replacements for the same class.

Class loading

Why a class loader?

All class loading is handled by an the class ZMLoader. Reasons for doing this are:

  • Late binding of the actual implementation of classes.
  • Having a central place that handles file loading.
  • In case core.php is used (merging all code into a single, stripped file) all that is not necessary, so it's handy to have a central place to enable/disable things.

Customizing core classes

One of the central features in ZenMagick is its extensibility. In order to allow to replace central classes (and their methods), it seemed appropriate to not using new, but a custom method to instantiate classes. Using the loaders late binding allows then to resolve the class only then. One problem here is that PHP (so far) doesn't allow two classes with the same name (this is going to change with namespaces). Therefore a convention exists to resolve a class name to either the name with a 'ZM' prefix or without.

Example

$address = ZMLoader::make('Address');

The above example uses the loader to instantiate a new address object. To decide which class to use, the loader will check if:

  1. A class with name "Address" exists
  2. If not, look for a class named "ZMAddress"

This pattern is used throughout all of the ZenMagick core code. All that is then required to make ZenMagick use an alternative Address implementation is to add a custom Address class (filename: Address.php) anywhere where the class loader can find it.

The class path

In ZenMagick, filenames have to reflect the contained class name, similar to what Java does. Filenames in the style of MyClass.class.php are supported too. In any case, Filenames are treated case sensitive!

In ZenMagick, class names start with a capital letter. Files starting with lower case are assumed to contain static code (functions, defines, etc) and are loaded directly, before any other code is loaded.

The class path consists of three different locations:

  • Everything under core.
  • Plugin files, according to the plugins loader policy.
  • The active theme's extra folder (and the default theme's extra folder if theme inheritance is enabled).

__autoload

As of version 0.9.6, PHP's __autoload function is used to handle unresolved classes. This allows to use the new keyword to instantiate new class objects without having to worry about including class files, etc.

Method Injection

What is this?

If replacing an existing core class is not an possible or desired, there is also the option to attach/inject new methods to existing classes that extend from ZMObject (which are the majority of ZenMagick classes) programmatically at runtime.

With that in mind, it's easily possible to replace custom code like this:

$value = MyService ::instance()->getFoo($product);

with:

$value = $product->getFoo();


Example1: Inject a method implemented by another class

class MyService {
  /**
   * @param mixed target The instance the method was invoked on (here an instance of ZMProduct).
   * @param var ... some Some single parameter.
   */
  public function getFoo($target, $some) {
     return "foo: ".$some.": ".$target->getName();
  }
}

// inject...
ZMObject::attachMethod(’getFoo’, ‘ZMProduct’, array(new MyService (), ‘getFoo’));

// now let's try it...
$product = ZMProducts::instance()->getProductForId(3);
echo $product->getFoo(’xx’);


How does it work?

  • First there is a new class that implements the calculation of the new property.
  • Next we attach an instance of MyService as handler of getFoo calls on ZMProduct objects
  • Load a product from the database
  • call getFoo('xx') on $product


Example2: Injecting using anonymous functions

it is also possible to inject using anonymous functions like this:

ZMObject::attachMethod('getFoo', 'ZMProduct', create_function('$target, $some', 'return "foo: ".$some.': '.$target->getName();'));

Another of those things that are not strictly necessary, but nice to look at :)

Personal tools