Previous Next

Simplest usage case with Compiled Definition

Without going into the gritty details, as you might expect, PHP at its core is not DI friendly. Out-of-the-box, the DependencyInjector uses a RuntimeDefinition which does all class map resolution via PHP's Reflection extension. Couple that with the fact that PHP does not have a true application layer capable of storing objects in-memory between requests, and you get a recipe that is less performant than similar solutions you'll find in Java and .Net (where there is an application layer with in-memory object storage.)

To mitigate this shortcoming, Zend\Di has several features built in capable of pre-compiling the most expensive tasks that surround dependency injection. It is worth noting that the RuntimeDefition, which is used by default, is the only definition that does lookups on-demand. The rest of the Definition objects are capable of being aggregated and stored to disk in a very performant way.

Ideally, 3rd party code will ship with a pre-compiled Definition that will describe the various relationships and parameter/property needs of each class that is to be instantiated. This Definition would have been built as part of some deployment or packaging task by this 3rd party. When this is not the case, you can create these Definitions via any of the Definition types provided with the exception of the RuntimeDefinition. Here is a breakdown of the job of each definition type:

  • AggregateDefinition - Aggregates multiple definitions of various types. When looking for a class, it looks it up in the order the definitions were provided to this aggregate.

  • ArrayDefinition - This definition takes an array of information and exposes it via the interface provided by Zend\Di\Definition suitable for usage by DependencyInjector or an AggregateDefinition

  • BuilderDefinition - Creates a definition based on an object graph consisting of various Builder\PhpClass objects and Builder\InectionMethod objects that describe the mapping needs of the target codebase and …

  • Compiler - This is not actually a definition, but produces an ArrayDefinition based off of a code scanner (Zend\Code\Scanner\DirectoryScanner or Zend\Code\Scanner\FileScanner).

The following is an example of producing a definition via a DirectoryScanner:

$compiler = new Zend\Di\Definition\Compiler();
$compiler->addCodeScannerDirectory(
    new Zend\Code\Scanner\ScannerDirectory('path/to/library/My/')
);
$definition = $compiler->compile();

This definition can then be directly used by the DependencyInjector (assuming the above A, B, C scenario was actually a file per class on disk):

$di = new Zend\Di\DependencyInjector;
$di->setDefinition($definition);
$di->getInstanceManager()->setProperty('My\A', 'username', 'foo');
$di->getInstanceManager()->setProperty('My\A', 'password', 'bar');
$c = $di->get('My\C');

One strategy for persisting these compiled definitions would be the following:

if (!file_exists(__DIR__ . '/di-definition.php') && $isProduction) {
    $compiler = new Zend\Di\Definition\Compiler();
    $compiler->addCodeScannerDirectory(
        new Zend\Code\Scanner\ScannerDirectory('path/to/library/My/')
    );
    $definition = $compiler->compile();
    file_put_contents(
        __DIR__ . '/di-definition.php', 
        'toArray(), true) . ';'
    );
} else {
    $definition = new Zend\Di\Definition\ArrayDefinition(
        include __DIR__ . '/di-definition.php'
    );
}

// $definition can now be used; in a production system it will be written 
// to disk.

Since Zend\Code\Scanner does not include files, the classes contained within are not loaded into memory. Instead, Zend\Code\Scanner uses tokenization to determine the structure of your files. This makes this suitable to use this solution during development and within the same request as any one of your application's dispatched actions.

Previous Next