The Symfony DependencyInjection component is very powerful, if not a little bit verbose. Nice allows the integration of custom DI extensions, which allow you to leverage the full power of the Symfony service container in your application.
Custom Extensions allow you to register new services with the service container and override built-in services with your own implementations.
Create a file in your project called DemoExtension.php
. By convention, this should go in a child
namespace of your main project called Extension
or DependencyInjection
.
Note: You'll need to let Composer know about your project source files. Do this by modifying your
composer.json
's "autoload" section
In your new Extension file, create a class called DemoExtension and subclass Symfony's DI Extension base class,
Symfony\Component\DependencyInjection\Extension\Extension
.
<?php
namespace Acme\Extension;
use Symfony\Component\DependencyInjection\Extension\Extension;
class DemoExtension extends Extension
{
/**
* Loads a specific configuration.
*
* @param array $configs An array of configuration values
* @param ContainerBuilder $container A ContainerBuilder instance
*/
public function load(array $configs, ContainerBuilder $container)
{
// Register your services here
}
}
The load
method receives a ContainerBuilder
which is used to register custom services and add parameters.
Note: The
$configs
array can be used to allow configuration of services. See this section for more information.
Back in your front controller, call appendExtension
on your Nice\Application
instance.
<?php
use Symfony\Component\HttpFoundation\Response;
use Nice\Application;
use Nice\Router\RouteCollector;
use Acme\Extension\DemoExtension;
require __DIR__ . '/../vendor/autoload.php';
$app = new Application();
$app->appendExtension(new DemoExtension());
Tip: The order of your extensions usually doesn't matter, but if you need to control the order, you can use
prependExtension
to prioritize an extension over other extensions.
For this example, assume you have an Acme\Mailer
class. This class takes a single dependency in its constructor,
an Acme\Mailer\DriverInterface
.
The Mailer
class might look something like:
<?php
namespace Acme;
use Acme\Mailer\DriverInterface;
class Mailer
{
public function __construct(DriverInterface $driver)
{
// ...
}
// ...
}
And an implementation of DriverInterface
might be:
<?php
namespace Acme\Mailer;
class SendmailDriver implements DriverInterface
{
// ...
}
Inside your extension's load
method, you can leverage the full power of a ContainerBuilder
to define
the relationship between our example classes.
<?php
namespace Acme\Extension;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Reference;
class DemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
// Register your custom classes
$container->register('acme.mailer.driver', 'Acme\Mailer\SendmailDriver')
->setPublic(false);
$container->register('acme.mailer', 'Acme\Mailer')
->addArgument(new Reference('acme.mailer.driver'));
}
}
We defined a service called acme.mailer.driver
. Since this service is not intended to be used outside of this
specific context, we've marked it as private by calling setPublic(false)
.
Then, we defined another service called acme.mailer
. This service will be an instance of Acme\Mailer
and will receive the configured acme.mailer.driver
service as the first argument to its constructor.
Now you can use your acme.mailer
service like any other.
# Back in web/index.php...
// ...
$mailer = $app->get('acme.mailer');
// ...
Info: For a more complete reference, see the DependencyInjection reference.
Nice also makes use of the Symfony 2 Config component to expose configuration options. This can be used to configure your application using a variety of mechanisms: in PHP, YAML, or XML.
Start by creating a Configuration
class. Nice's naming convention is LogExtension
and LogConfiguration
.
<?php
namespace Acme\Extension;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class AcmeConfiguration implements ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('acme');
$rootNode
->children()
->scalarNode('my_class')->end()
->end();
return $treeBuilder;
}
}
Info: See the Symfony documentation for additional information on using a
TreeBuilder
.
Next, you need to override the getConfiguration
method in your Extension class.
<?php
namespace Acme\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class AcmeExtension extends Extension
{
/**
* @var array
*/
private $options = array();
/**
* Constructor
*
* @param array $options
*/
public function __construct(array $options = array())
{
$this->options = $options;
}
/**
* Returns extension configuration
*
* @param array $config An array of configuration values
* @param ContainerBuilder $container A ContainerBuilder instance
*
* @return AcmeConfiguration
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new AcmeConfiguration();
}
/**
* Loads a specific configuration.
*
* @param array $configs An array of configuration values
* @param ContainerBuilder $container A ContainerBuilder instance
*
* @throws \InvalidArgumentException When provided tag is not defined in this extension
*/
public function load(array $configs, ContainerBuilder $container)
{
$configs[] = $this->options;
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
// Use $config to access the normalized configuration
$container->register('acme.my_service', $config['my_class']);
}
}
In the above example, the Extension is modified to allow configuration via its constructor.
In web/index.php
, pass in the necessary configuration values:
<?php
use Nice\Application;
use Acme\AcmeExtension;
$app = new Application();
$app->appendExtension(new AcmeExtension(array(
'my_class' => 'Acme\SomeServiceClass'
)));
Nice includes integration with the Symfony Config component, which allows simple loading of application configuration from various sources.
A ConfigurationProvider tells your application how and where to load its configuration. Included is a file-based provider, though you could use any means such as database backed, etcd, etc.
Assuming the same setup as above, using the AcmeExtension
and AcmeConfiguration
classes, we simply
need to create a configuration file, and register a FileConfigurationProvider
with your Application
.
In your project's root directory, create config.yml
. Add the contents:
acme:
my_class: Acme\SomeServiceClass
Next, in your front controller web/index.php
, add:
<?php
use Nice\Application;
use Nice\DependencyInjection\ConfigurationProvider\FileConfigurationProvider;
use Acme\AcmeExtension;
$app = new Application();
$app->appendExtension(new AcmeExtension());
$app->setConfigurationProvider(new FileConfigurationProvider($app->getRootDir().'/config.yml'));
// ...
$app->run();
Your application's extensions can now be configured via file!
Powered by Codex 1.1.0