DI Container Extensions

Configurator does not generate the code itself, that is the task of Nette\DI\Compiler and Nette\DI\ContainerBuilder classes. First configuration files are loaded and passed to Compiler. Add your own extension to Compiler via config.neon:

extensions:
    blog: MyBlogExtension

Each Compiler extension must extend Nette\DI\CompilerExtension and can implement three different methods that are called successively during the Container compilation.

CompilerExtension::loadConfiguration()

This method is called first and loads additional configuration files, creates methods using Nette\DI\ContainerBuilder and most importantly processes the application's configuration.

Config can contain a section bearing the same name as your extension. Using the last example, following lines can appear in your config file:

blog: # same name as your extension
    postsPerPage: 10
    comments: FALSE

Use $this->config in the extension to list its configuration.

class MyBlogExtension extends Nette\DI\CompilerExtension
{
    public function loadConfiguration()
    {
        dump($this->config);
        // [2] [ 'postsPerPage' => 10, 'comments' => FALSE ]

Honoring the convention over configuration principle we can set default values and be able to use the application without explicitly setting anything, and merge them with configuration.

class MyBlogExtension extends Nette\DI\CompilerExtension
{
    public $defaults = [
        'postsPerPage' => 5,
        'comments' => TRUE
    ];

    public function loadConfiguration()
    {
        $this->config += $this->defaults;

Now we have the complete configuration in the $this->config array and can use it while creating the services. ContainerBuilder class allows you to use the same way of service description as NEON used in the configuration files.

$builder = $this->getContainerBuilder();

Let's create an articles service as an example: an object MyBlog\ArticlesModel and we'll call its method setLogger.

Convention is to prefix services by their name to avoid conflicts. Using prefix() method we can then access the service via $container->getService('blog.articles).

$builder->addDefinition($this->prefix('articles'))
    ->setFactory('MyBlog\ArticlesModel', ['@connection'])
    ->addSetup('setLogger', ['@logger']);

Loading additional configurations

If you prefer configuration files over extensions, you can move some of the definitions into separate configuration file.

services:
    articles:
        factory: MyBlog\ArticlesModel(@connection)

    comments:
        factory: MyBlog\CommentsModel(@connection, @blog_articles)

    articlesList:
        factory: MyBlog\Components\ArticlesList(@blog_articles)

Load the file and set additional services

public function loadConfiguration()
{
    $this->config += $this->defaults;
    $builder = $this->getContainerBuilder();

    // load additional config file for this extension
    $this->compiler->loadDefinitions($builder, $this->loadFromFile(__DIR__ . '/blog.neon')['services'], $this->name);

    // set a number of articles per page in the component
    $builder->getDefinition('blog_articlesList')
        ->addSetup('setPostsPerPage', [$this->config['postsPerPage']]);

    // optional disabling of commenting
    if (!$this->config['comments']) {
        $builder->getDefinition('blog_comments')
            ->addSetup('disableComments');
    }
}

CompilerExtension::beforeCompile()

In beforeCompile phase we should not add any more services, however you can modify already existing ones or add relations between services (for example using tags).

CompilerExtension::afterCompile()

In this phase the Container instance is already generated and contains all service methods and is ready to be stored into cache. Thanks to Nette\PhpGenerator\ClassType you can add your own code to the container to modify the service generation.

To inspire yourself take a look at Nette Framework's initialize method, which Nette adds to process some of the user settings. This method is always called after instantiation of the container.

Here is a piece of code of initialize where Nette adds a session start and autorun of services marked with run tag.

public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
    $container = $this->getContainerBuilder();

    // initialize method
    $initialize = $class->getMethod('initialize');

    // automatic session start
    if ($this->config['session']['autoStart']) {
        $initialize->addBody('$this->session->start();');
    }

    // services with run tag must be run after instantition of the container
    foreach ($container->findByTag('run') as $name => $foo) {
        $initialize->addBody('$this->getService(?);', [$name]);
    }
}