Configuring
System container is a DI container holding all the services and parameters needed for the application to run. Soon you will be able to:
- create the system container
- configure your application using NEON files
- handle production and development modes
- create and use your own container extensions
This page is still being prepared
System container is a static Dependency Injection container, that holds all services and parameters the application needs. This means not only services of the framework itself, but of each library you have decided to use. Take a look at an example of such system container.
Developers learned that writing containers is a very tedious work. And it surely gets difficult to manage the application as it grows. Nette will do the work for you! With a simple configuration language we will describe services of the application and the framework will generate the PHP code. In all seriousness – the container linked above was generated this way as well.
Configurator
The task of code generation is given to Nette\Config\Configurator class. The compilation process is triggered only once and its result cached. For that reason we need to choose a directory for storing this code.
$configurator = new Nette\Config\Configurator;
$configurator->setTempDirectory(__DIR__ . '/../temp');
Now we just add a path to the config file:
$configurator->addConfig(__DIR__ . '/config/config.neon');
And getting container's instance is the simplest one:
// vrací objekt třídy SystemContainer
$container = $configurator->createContainer();
Running in different modes
Configurator attempts to detect if the application is running on production
or development server. As long as the server's IP address is 127.0.0.1 it is
considered as in development mode. Get the current mode using
isProductionMode() method and change it using
setProductionMode().
RobotLoader
To compile the container Configurator needs to load all the classes mentioned
in configuration files. It is most handy to have auto-loading at your disposal
and Configurator offers a method creating an instance of RobotLoader. We only
list the directories to be indexed and activate the loader. Do not forget to
place this code before calling createContainer().
$configurator->createRobotLoader()
->addDirectory(APP_DIR)
->addDirectory(LIBS_DIR)
->register();
Configuration using NEON files
Have fun trying out the syntax at http://ne-on.org.
Services definitions
services:
database:
class: Nette\Database\Connection(%dsn%, %user%, %password%)
#or in two lines
class: Nette\Database\Connection
arguments: [%dsn%, %user%, %password%]
Generates:
function createServiceDatabase()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
Service definition:
services:
database:
class: Nette\Database\Connection
factory: DbFactory::createConnection
DbFactory::createConnection:
class DbFactory
{
static function createConnection(Nette\DI\Container $container)
{
...
}
}
Generates:
function createServiceDatabase()
{
return DbFactory::createConnection($this);
}
Setup
services:
database:
class: Nette\Database\Connection(%dsn%, %user%, %password%)
setup:
- setCacheStorage(@cacheStorage)
Generates:
function createServiceDatabase()
{
$service = new Nette\Database\Connection(...);
$service->setCacheStorage($this->cacheStorage);
return $service;
}
Autowiring feature adds dependencies automatically, so they don't even have to be mentioned:
setup:
- setCacheStorage
In case the cacheStorage service does not exist, it is possible
to use a result of a call as a parameter:
setup:
- setCacheStorage( Factory::createStorage() )
# or a method of other service:
- setCacheStorage( @factory::createStorage() )
Alternatively a newly created class instance:
setup:
- setCacheStorage( Nette\Caching\Storages\FileStorage(%tempDir%) )
# generates: $service->setCacheStorage(new Nette\Caching\Storages\FileStorage(...));
You can also set a value of a property:
substitutions
setup:
- $substitutions( [db: test] )
# generates: $service->substitutions = array('db' => 'test');
Full example:
parameters:
database:
driver: mysql
host: localhost
dbname: test
user: jim
password: beam
substitutions:
db: test
services:
database:
class: Nette\Database\Connection(
'%database.driver%:host=%database.host%;dbname=%database.dbname%',
%database.user%, %database.password%, null,
Nette\Database\Reflection\DiscoveredReflection()
)
setup:
- setCacheStorage
- $substitutions( %database.substitutions% )
Service definitions inheritance
services:
dev_database < database
setup:
- Diagnostics\ConnectionPanel::initialize
Factories
Factories are defined in almost the same way as services:
factories:
article:
class: Article
Generates:
function createArticle()
{
return new Article($this->database);
}
Factory is called directly like $article =
$container->createArticle();
Unlike services a factory can accept parameters:
factories:
article:
parameters: [id, title: null]
class: Article(..., %id%)
setup:
- $title(%title%)
Generates:
function createArticle($id, $title = NULL)
{
$service = new Article($this->database, $id);
$service->title = $title;
return $service;
}
Auto-wiring
Auto-wiring feature can automatically pass dependencies into constructor and
methods of the service. It uses typehinting and @return
annotations. There can be only one service matching the type in the container,
otherwise an exception is thrown.
To define more than one service of the same type we need to exclude them from auto-wiring:
services:
cacheStorage:
class: Nette\Caching\Storages\FileStorage(%tempDir%)
tempCacheStorage:
class: Nette\Caching\Storages\DevNullStorage
autowired: no
When modifying the Nette Framework's core services we need to make sure the
container is aware of the classes we want to use. That means using fully
qualified class names in @return annotations or set the FQ class
name with class entry.
Multiple configuration files
Use includes section to add more configuration files.
includes:
- parameters.php
- services.neon
- presenters.neon
Configuration merging process assigns the highest priority to the file
containing includes section and the lowest priority to the first
included file. To prevent merging of a certain array use exclamation mark right
after the name of the array:
argument!: [1, 2, 3]
Container extensions
Configurator does not generate the code itself, that is the task
of Nette\Config\Compiler
and Nette\DI\ContainerBuilder
classes. Nette\Config\Loader
starts first and loads the configuration files and passes them to
Compiler. Add your own extension to Compiler as
shown below:
$configurator->onCompile[] = function ($configurator, $compiler) {
$compiler->addExtension('blog', new MyBlogExtension);
};
Each Compiler extension must extend Nette\Config\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: # stejné jméno jako má extension
postsPerPage: 10
comments: FALSE
Use getConfig() method in the extension to list its configuration.
class MyBlogExtension extends Nette\Config\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\Config\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->blog->articles instead of
$container->blog_articles.
$builder->addDefinition($this->prefix('articles'))
->setClass('MyBlog\ArticlesModel', array('@connection'));
You can also create a component factory, pass it blog_articles
service and set a number of posts per page option. How to use component
factories will be shown soon.
$builder->addDefinition($this->prefix('articlesList'))
->setClass('MyBlog\Components\ArticlesList', array($this->prefix('@articles')))
->addSetup('setPostsPerPage', $config['postsPerPage'])
->setShared(FALSE)->setAutowired(FALSE); // turns service into factory
We will also need a blog_comment class and pass it connection to
database and blog_articles instance. If the comments are disabled
we can reflect it using addSetup method:
$comments = $builder->addDefinition($this->prefix('comments'))
->setClass('MyBlog\CommentsModel', array('@connection', $this->prefix('@articles')));
if (!$config['comments']) { // optional disabling of commenting
$comments->addSetup('disableComments');
}
Of course comments needs to be written and shown, so we add one more
component factory – blog_commentsControl, that will allow us to
list comments and to post new ones, unless they have been closed.
$builder->addDefinition($this->prefix('commentsControl'))
->setClass('MyBlog\Components\CommentsControl', array($this->prefix('@comments')))
->setShared(FALSE)->setAutowired(FALSE); // turns service into factory
This division into models and components is purely illustrative.
Loading additional configurations
If you prefer configuration files over extensions, you can move some of the definitions into separate configuration file.
services:
blog_articles:
class: MyBlog\ArticlesModel(@connection)
blog_comments:
class: MyBlog\CommentsModel(@connection, @blog_articles)
factories:
blog_articlesList:
class: MyBlog\Components\ArticlesList(@blog_articles)
blog_commentsControl:
class: MyBlog\Components\CommentsControl(@blog_comments)
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');
}
}
Thanks to NEON syntax you now have cleaner container extension.
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(Nette\Utils\PhpGenerator\ClassType $class)
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\Utils\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\Utils\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));
}
}
Methods' beforeCompile() and afterCompile()
difference is that beforeCompile() has access to container and
services while afterCompile() accesses its code (via Nette\Utils\PhpGenerator).

