Dependency Injection¶
Core Concepts¶
SiteSupra Dependency Injection layer or DI in short is based on Pimple. If you’re curious about what DI is you can read the following Wiki articles:
We also recommend read Symfony’s documentation about basic DI principles.
SiteSupra main container class is Supra\Core\DependencyInjection\Container
.
It extends Pimple’s Container, implements SiteSupra’s
Supra\Core\DependencyInjection\ContainerInterface
, implements some hard-coded methods (remember, we’re a CMS, and
not a full-stack framework, some items like Doctrine or Cache are always present), and provides parameters
handling.
Note
It’s almost certain that we will drop hard-coded getters later and build container definition on-the-fly in the same way Symfony does.
Everything is simple, right? Last but not least to note would be that any object implementing
Supra\Core\DependencyInjection\Container\ContainerAware
will be provided with Container
on instantiation
(calling setContainer).
Container Building Process¶
Note
The code will be refactored soon.
SiteSupra core class (Supra\Core\Supra
, extending Supra\Core\DependencyInjection\ContainerBuilder
) builds and
returns Container
object during call to buildContainer
. This is done in the following steps:
- Pre-setting some basic variables and objects (like directories, HttpFoundation objects, Command Line Interface, and so on);
- Injecting packages (allowing to expose their basic configuration);
- Building configuration (there the configuration is being validated, default values set, container parameters are substituted, and so on);
- Finishing configuration when packages can override or extend config values of other packages;
- Firing
Supra::EVENT_CONTAINER_BUILD_COMPLETE
event.
Package Integration and Two-pass Container Building¶
First of all, a package needs to be registered. This is done by overriding registerPackages
in SupraApplication
class (located in supra/SupraApplication.php
). This method simply returns array of package instances, like in the example below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php
use Supra\Core\Supra;
class SupraApplication extends Supra
{
protected function registerPackages()
{
return array(
new \Supra\Package\Framework\SupraPackageFramework(),
new \Supra\Package\Cms\SupraPackageCms(),
new \Supra\Package\CmsAuthentication\SupraPackageCmsAuthentication(),
new \Supra\Package\DebugBar\SupraPackageDebugBar(),
new \Sample\SamplePackage()
);
}
}
|
Each package must extend Supra\Core\Package\AbstractSupraPackage
.
You can override any of the following methods to alter SiteSupra behavior:
boot()
method will be called during SiteSupra boot, see HTTP Kernel and Bootstrap Process;inject(ContainerInterface $container)
method will be called during Container building in package injection phase (see above);finish(ContainerInterface $container)
method will be called finishing Container build after the configuration is processed;shutdown()
method will be called during SiteSupra shutdown, see HTTP Kernel and Bootstrap Process.
Package Configuration¶
As mentioned above package configuration may occur in two phases - injection phase and finishing phase. Let’s look at both of them starting from inject()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php
public function inject(ContainerInterface $container)
{
$this->loadConfiguration($container);
$container->getConsole()->add(new DoFooBarCommand());
$container[$this->name.'.some_service_name'] = function (ContainerInterface $container) {
return new SomeService();
};
if ($container->getParameter('debug')) {
//prepare some extended logging, for example
}
}
|
The most important call would be $this->loadConfiguration()
(line 5). This method loads configuration file (by
default it is Resources/config/config.yml
). To load your own configuration pass the file name to the method as a second parameter .
This call parses config file, processes the configuration using package configuration definition (more on that on Symfony configuration component article), and stores the values for further processing.
Later you can access already defined services (see line line 7
, which though is not a very good approach since it
instantiates the service), add your own service definitions (lines 9-11
), and access container parameters (line 13
).
Each package has it’s own configuration definition. Concrete configuration object is created during call to getConfiguration()
method. By default, if there is a package named SupraPackageFooBar
in namespace Com\Package\FooBar
, then the method will search
for configuration definition SupraPackageFooBarConfiguration
in namespace Com\Package\FooBar\Configuration
. Of
course, you can always override you package’s method getConfiguration()
and implement your own logic.
The configuration class should extend Supra\Core\Configuration\AbstractPackageConfiguration
and implement
ConfigurationInterface
. This forces you to implement function getConfigTreeBuilder()
, returning instance of
Symfony\Component\Config\Definition\Builder\TreeBuilder
. If you’re curious about what is a TreeBuilder
and how
exactly the configuration is being defined, please read Defining a Hierarchy of Configuration Values Using the TreeBuilder
on official Symfony documentation web site. Let’s take configuration of SupraPackageFrameworkConfiguration
as an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?php
class SupraPackageFrameworkConfiguration extends AbstractPackageConfiguration implements ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$treeBuilder->root('framework')
->children()
->append($this->getAuditDefinition())
//some other definitions are skipped for illustrative purposes
->append($this->getServicesDefinition())
->end();
return $treeBuilder;
}
public function getAuditDefinition()
{
$definition = new ArrayNodeDefinition('doctrine_audit');
$definition->children()
->arrayNode('entities')
->prototype('scalar')->end()
->end()
->arrayNode('ignore_columns')
->prototype('scalar')->end()
->end()
->end();
return $definition;
}
}
|
Root node (line 14
) must match your package name. The rest of configuration definition is standard for
Symfony-based applications (lines 24-38
), except for call of ->append($this->getServicesDefinition())
, which is
inherited from AbstractPackageConfiguration
and enables parsing of services
section of your configuration file.
Package configuration files are simple yml files as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | services:
supra.framework.session_storage_native:
class: \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage
parameters: [[], "@supra.framework.session_handler_doctrine"]
supra.framework.session_handler_doctrine:
class: \Supra\Package\Framework\Session\DoctrineSessionHandler
#some config parts are skipped for illustrative purposes
doctrine:
#some config parts are skipped for illustrative purposes
credentials:
hostname: localhost
username: root
password: ~
charset: utf8
database: supra9
connections:
default:
host: %framework.doctrine.credentials.hostname%
user: %framework.doctrine.credentials.username%
password: %framework.doctrine.credentials.password%
dbname: %framework.doctrine.credentials.database%
charset: %framework.doctrine.credentials.charset%
driver: mysql
event_manager: public
entity_managers:
public:
connection: default
event_manager: public
default_entity_manager: public
default_connection: default
doctrine_audit:
entities: []
ignore_columns:
- created_at
- updated_at
- lock
|
Lines 1-6
define services. Key is service ID, ‘class’ defines class name and ‘parameters’ section enables setter injection
(note that you can inject other services referenced with ‘@’ as shown in line 4
). Setter injection is not yet supported.
First level keys will become container parameters prefixed with package name. In the example above,
container parameters are ‘framework.doctrine’ and ‘framework.doctrine_audit’, and you can call something like
$container->getParameter('framework.doctrine_audit')['entities']
later in your code.
You may also reference any parameter using percent notation (%parameter.name%
). In the example above, line 18
references value from line 11
, possibly overridden by another package or main SiteSupra’s config.yml
.
After calling inject()
method of all packages, container builder merges configuration values (also replacing /
referencing parameters), and starts calling finish()
method of all packages, in load order. You finish()
method
can look like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php
public function finish(ContainerInterface $container)
{
//extend some other package service
$container->extend('some.other.service', function ($originalService, $container) {
$originalService->callSomeMethod();
return new SomeWrapper($originalService);
};
$doctrineConfig = $container->getParameter('framework.doctrine');
//processed configuration from example above. with merged parameters and optionally overridden by main config.yml
$connectionDetails = $doctrineConfig['connections']['default'];
}
|
So, summing up:
- You define your configuration in
inject()
method; - Container processes your configuration and merges it;
- You retrieve processed values from container in
finish()
method and define your services; - Resulting container is available throughout SiteSupra.
Main SiteSupra Configuration File (config.yml)¶
Default SiteSupra configuration file supra/config.yml.example
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | cms:
active_theme: default
framework:
doctrine:
credentials:
hostname: localhost
username: root
password: ~
charset: utf8
database: supra9
cms_authentication:
users:
shared_connection: null
user_providers:
doctrine:
supra.authentication.user_provider.public:
em: public
entity: CmsAuthentication:User
provider_chain: [ doctrine.entity_managers.public ]
|
Top-level keys correspond to package names, corresponding values are deep-merged with default values resolved in injection phase. Here you can see how default ‘doctrine.configuration’ values are merged with defaults from SupraPackageFramework; any part of configuration can be overridden.
Container Parameter Handling, Parameter Substitution¶
Parameters are SiteSupra-specific extension to Pimple. Basically they represent simple key-value storage (with all
the getters and setters. Refer to Supra\Core\DependencyInjection\Container
for more information. However, some
of the methods are worth to be noted separately:
replaceParameters
searches array of data and replaces all parameters enclosed in percent signs (like %foo.bar%) to their respective values;replaceParametersScalar
replaces all parameters enclosed in percent signs (like %foo.bar%) to their respective values in a scalar variable (string);getParameter
threads dots inside parameter name as internal array keys (thus allowing you to call$container->getParameter('foo.bar.buz.example')
instead of$container->getParameter('foo.bar')['buz']['example']
).
Standard Container Parameters¶
Standard container parameters that can help you in development process are listed below.
Directories¶
There is a number of container parameters reflecting SiteSupra directory structure:
directories.project_root
for project root folder (withcomposer.json
and other core files);directories.supra_root
for directory whereSupra.php
andconfig.yml
reside;directories.storage
for storage folder;directories.cache
for cache folder (inside storage root);directories.web
for webroot (this is where SiteSupra entry point,index.php
, is);directories.public
for asset root,Resources\public
folders of every package are symlinked there.
Environments and Debugging¶
Some parameters are affected by current development settings:
environment
shows current environment - currently on ofcli
,prod
, ordev
;debug
shows current debug state - eithertrue
orfalse
.
Service Definition¶
Adding ->addServiceDefinition()
to package configuration will allow that package to define services.
Service definition has to reside under section services
in configuration file.
A simple service definition contains service id and class name:
1 2 3 | services:
locale.manager:
class: \Supra\Core\Locale\LocaleManager
|
you can provide constructor arguments as an array:
1 2 3 4 | services:
supra.doctrine.event_subscriber.table_name_prefixer:
class: \Supra\Core\Doctrine\Subscriber\TableNamePrefixer
parameters: ['su_', '']
|
or even use container parameters as arguments:
1 2 3 4 | services:
supra.framework.session_storage_native:
class: \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage
parameters: [[], "@supra.framework.session_handler_doctrine"]
|
Unfortunately, caller injections are not possible with SiteSupra yet. But still you can use common Pimple’s approach
during inject()
or finish()
:
1 2 3 4 5 6 7 8 9 10 11 | <?php
$container['some.service'] = function ($container) use ($dependency1, $dependency2) {
$service = new SomeService($dependency1);
$service->setDependency2($dependency2);
$service->intialize();
return $service;
};
|