发布于 2015-08-27 16:55:28 | 156 次阅读 | 评论: 0 | 来源: 网络整理
Making a class’s dependencies explicit and requiring that they be injected into it is a good way of making a class more reusable, testable and decoupled from others.
There are several ways that the dependencies can be injected. Each injection point has advantages and disadvantages to consider, as well as different ways of working with them when using the service container.
The most common way to inject dependencies is via a class’s constructor. To do this you need to add an argument to the constructor signature to accept the dependency:
class NewsletterManager
{
protected $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}
You can specify what service you would like to inject into this in the service container configuration:
services:
my_mailer:
# ...
newsletter_manager:
class: NewsletterManager
arguments: ["@my_mailer"]
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="my_mailer">
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<argument type="service" id="my_mailer"/>
</service>
</services>
</container>
use SymfonyComponentDependencyInjectionDefinition;
use SymfonyComponentDependencyInjectionReference;
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager',
array(new Reference('my_mailer'))
));
小技巧
Type hinting the injected object means that you can be sure that a suitable dependency has been injected. By type-hinting, you’ll get a clear error immediately if an unsuitable dependency is injected. By type hinting using an interface rather than a class you can make the choice of dependency more flexible. And assuming you only use methods defined in the interface, you can gain that flexibility and still safely use the object.
There are several advantages to using constructor injection:
These advantages do mean that constructor injection is not suitable for working with optional dependencies. It is also more difficult to use in combination with class hierarchies: if a class uses constructor injection then extending it and overriding the constructor becomes problematic.
Another possible injection point into a class is by adding a setter method that accepts the dependency:
class NewsletterManager
{
protected $mailer;
public function setMailer(Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}
services:
my_mailer:
# ...
newsletter_manager:
class: NewsletterManager
calls:
- [setMailer, ["@my_mailer"]]
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="my_mailer">
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<call method="setMailer">
<argument type="service" id="my_mailer" />
</call>
</service>
</services>
</container>
use SymfonyComponentDependencyInjectionDefinition;
use SymfonyComponentDependencyInjectionReference;
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager'
))->addMethodCall('setMailer', array(new Reference('my_mailer')));
This time the advantages are:
The disadvantages of setter injection are:
Another possibility is just setting public fields of the class directly:
class NewsletterManager
{
public $mailer;
// ...
}
services:
my_mailer:
# ...
newsletter_manager:
class: NewsletterManager
properties:
mailer: "@my_mailer"
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="my_mailer">
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<property name="mailer" type="service" id="my_mailer" />
</service>
</services>
</container>
use SymfonyComponentDependencyInjectionDefinition;
use SymfonyComponentDependencyInjectionReference;
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager'
))->setProperty('mailer', new Reference('my_mailer'));
There are mainly only disadvantages to using property injection, it is similar to setter injection but with these additional important problems:
But, it is useful to know that this can be done with the service container, especially if you are working with code that is out of your control, such as in a third party library, which uses public properties for its dependencies.