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 getConfig() method in the extension to
list its configuration.
class MyBlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$config = $this->getConfig();
// array(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. First argument of
getConfig()
accepts an array of default values, into which the config section (as mentioned above) will be
merged.
class MyBlogExtension extends Nette\DI\CompilerExtension
{
public $defaults = array(
'postsPerPage' => 5,
'comments' => TRUE
);
public function loadConfiguration()
{
$config = $this->getConfig($this->defaults);
Now we have the configuration in the $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();
Create blog_articles
service representing a model for handling and loading articles.
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', array('@connection'))
->addSetup('setLogger', array('@logger'));
Loading additional configurations
If you prefer configuration files over extensions, you can move some of the definitions into separate configuration file.
services:
blog_articles:
factory: MyBlog\ArticlesModel(@connection)
blog_comments:
factory: MyBlog\CommentsModel(@connection, @blog_articles)
blog_articlesList:
factory: MyBlog\Components\ArticlesList(@blog_articles)
Load the file and set additional services
public function loadConfiguration()
{
$config = $this->getConfig($this->defaults);
$builder = $this->getContainerBuilder();
// load additional config file for this extension
$this->compiler->parseServices($builder, $this->loadFromFile(__DIR__ . '/blog.neon'));
// set a number of articles per page in the component
$builder->getDefinition('blog_articlesList')
->addSetup('setPostsPerPage', $config['postsPerPage']);
// optional disabling of commenting
if (!$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();
$config = $this->getConfig($this->defaults);
// initialize method
$initialize = $class->methods['initialize'];
// automatic session start
if ($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(?);', array($name));
}
}