发布于 2015-08-27 16:33:50 | 238 次阅读 | 评论: 0 | 来源: 网络整理
A modern PHP application is full of objects. One object may facilitate the delivery of email messages while another may allow you to persist information into a database. In your application, you may create an object that manages your product inventory, or another object that processes data from a third-party API. The point is that a modern application does many things and is organized into many objects that handle each task.
This chapter is about a special PHP object in Symfony that helps you instantiate, organize and retrieve the many objects of your application. This object, called a service container, will allow you to standardize and centralize the way objects are constructed in your application. The container makes your life easier, is super fast, and emphasizes an architecture that promotes reusable and decoupled code. Since all core Symfony classes use the container, you’ll learn how to extend, configure and use any object in Symfony. In large part, the service container is the biggest contributor to the speed and extensibility of Symfony.
Finally, configuring and using the service container is easy. By the end of this chapter, you’ll be comfortable creating your own objects via the container and customizing objects from any third-party bundle. You’ll begin writing code that is more reusable, testable and decoupled, simply because the service container makes writing good code so easy.
小技巧
If you want to know a lot more after reading this chapter, check out the DependencyInjection component documentation.
Put simply, a Service is any PHP object that performs some sort of “global” task. It’s a purposefully-generic name used in computer science to describe an object that’s created for a specific purpose (e.g. delivering emails). Each service is used throughout your application whenever you need the specific functionality it provides. You don’t have to do anything special to make a service: simply write a PHP class with some code that accomplishes a specific task. Congratulations, you’ve just created a service!
注解
As a rule, a PHP object is a service if it is used globally in your application. A single Mailer
service is used globally to send email messages whereas the many Message
objects that it delivers are not services. Similarly, a Product
object is not a service, but an object that persists Product
objects to a database is a service.
So what’s the big deal then? The advantage of thinking about “services” is that you begin to think about separating each piece of functionality in your application into a series of services. Since each service does just one job, you can easily access each service and use its functionality wherever you need it. Each service can also be more easily tested and configured since it’s separated from the other functionality in your application. This idea is called service-oriented architecture and is not unique to Symfony or even PHP. Structuring your application around a set of independent service classes is a well-known and trusted object-oriented best-practice. These skills are key to being a good developer in almost any language.
A Service Container (or dependency injection container) is simply a PHP object that manages the instantiation of services (i.e. objects).
For example, suppose you have a simple PHP class that delivers email messages. Without a service container, you must manually create the object whenever you need it:
use AcmeHelloBundleMailer; $mailer = new Mailer('sendmail'); $mailer->send('ryan@example.com', ...);
This is easy enough. The imaginary Mailer
class allows you to configure the method used to deliver the email messages (e.g. sendmail
, smtp
, etc). But what if you wanted to use the mailer service somewhere else? You certainly don’t want to repeat the mailer configuration every time you need to use the Mailer
object. What if you needed to change the transport
from sendmail
to smtp
everywhere in the application? You’d need to hunt down every place you create a Mailer
service and change it.
A better answer is to let the service container create the Mailer
object for you. In order for this to work, you must teach the container how to create the Mailer
service. This is done via configuration, which can be specified in YAML, XML or PHP:
# app/config/config.yml services: my_mailer: class: AcmeHelloBundleMailer arguments: [sendmail]
<!-- app/config/config.xml --> <?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" class="AcmeHelloBundleMailer"> <argument>sendmail</argument> </service> </services> </container>
// app/config/config.php use SymfonyComponentDependencyInjectionDefinition; $container->setDefinition('my_mailer', new Definition( 'AcmeHelloBundleMailer', array('sendmail') ));
注解
When Symfony initializes, it builds the service container using the application configuration (app/config/config.yml
by default). The exact file that’s loaded is dictated by the AppKernel::registerContainerConfiguration()
method, which loads an environment-specific configuration file (e.g. config_dev.yml
for the dev
environment or config_prod.yml
for prod
).
An instance of the AcmeHelloBundleMailer
object is now available via the service container. The container is available in any traditional Symfony controller where you can access the services of the container via the get()
shortcut method:
class HelloController extends Controller { // ... public function sendEmailAction() { // ... $mailer = $this->get('my_mailer'); $mailer->send('ryan@foobar.net', ...); } }
When you ask for the my_mailer
service from the container, the container constructs the object and returns it. This is another major advantage of using the service container. Namely, a service is never constructed until it’s needed. If you define a service and never use it on a request, the service is never created. This saves memory and increases the speed of your application. This also means that there’s very little or no performance hit for defining lots of services. Services that are never used are never constructed.
As a bonus, the Mailer
service is only created once and the same instance is returned each time you ask for the service. This is almost always the behavior you’ll need (it’s more flexible and powerful), but you’ll learn later how you can configure a service that has multiple instances in the “How to Work with Scopes” cookbook article.
注解
In this example, the controller extends Symfony’s base Controller, which gives you access to the service container itself. You can then use the get
method to locate and retrieve the my_mailer
service from the service container. You can also define your controllers as services. This is a bit more advanced and not necessary, but it allows you to inject only the services you need into your controller.
The creation of new services (i.e. objects) via the container is pretty straightforward. Parameters make defining services more organized and flexible:
# app/config/config.yml parameters: my_mailer.transport: sendmail services: my_mailer: class: AcmeHelloBundleMailer arguments: ["%my_mailer.transport%"]
<!-- app/config/config.xml --> <?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"> <parameters> <parameter key="my_mailer.transport">sendmail</parameter> </parameters> <services> <service id="my_mailer" class="AcmeHelloBundleMailer"> <argument>%my_mailer.transport%</argument> </service> </services> </container>
// app/config/config.php use SymfonyComponentDependencyInjectionDefinition; $container->setParameter('my_mailer.transport', 'sendmail'); $container->setDefinition('my_mailer', new Definition( 'AcmeHelloBundleMailer', array('%my_mailer.transport%') ));
The end result is exactly the same as before - the difference is only in how you defined the service. By surrounding the my_mailer.transport
string in percent (%
) signs, the container knows to look for a parameter with that name. When the container is built, it looks up the value of each parameter and uses it in the service definition.
注解
If you want to use a string that starts with an @
sign as a parameter value (e.g. a very safe mailer password) in a YAML file, you need to escape it by adding another @
sign (this only applies to the YAML format):
# app/config/parameters.yml parameters: # This will be parsed as string "@securepass" mailer_password: "@@securepass"
注解
The percent sign inside a parameter or argument, as part of the string, must be escaped with another percent sign:
<argument type="string">http://symfony.com/?foo=%%s&bar=%%d</argument>
The purpose of parameters is to feed information into services. Of course there was nothing wrong with defining the service without using any parameters. Parameters, however, have several advantages:
parameters
key;The choice of using or not using parameters is up to you. High-quality third-party bundles will always use parameters as they make the service stored in the container more configurable. For the services in your application, however, you may not need the flexibility of parameters.
Parameters can also contain array values. See Array Parameters.
小技巧
In this section, service configuration files are referred to as resources. This is to highlight the fact that, while most configuration resources will be files (e.g. YAML, XML, PHP), Symfony is so flexible that configuration could be loaded from anywhere (e.g. a database or even via an external web service).
The service container is built using a single configuration resource (app/config/config.yml
by default). All other service configuration (including the core Symfony and third-party bundle configuration) must be imported from inside this file in one way or another. This gives you absolute flexibility over the services in your application.
External service configuration can be imported in two different ways. The first - and most common method - is via the imports
directive. Later, you’ll learn about the second method, which is the flexible and preferred method for importing service configuration from third-party bundles.
imports
导入¶So far, you’ve placed your my_mailer
service container definition directly in the application configuration file (e.g. app/config/config.yml
). Of course, since the Mailer
class itself lives inside the AcmeHelloBundle, it makes more sense to put the my_mailer
container definition inside the bundle as well.
First, move the my_mailer
container definition into a new container resource file inside AcmeHelloBundle. If the Resources
or Resources/config
directories don’t exist, create them.
# src/Acme/HelloBundle/Resources/config/services.yml parameters: my_mailer.transport: sendmail services: my_mailer: class: AcmeHelloBundleMailer arguments: ["%my_mailer.transport%"]
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <?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"> <parameters> <parameter key="my_mailer.transport">sendmail</parameter> </parameters> <services> <service id="my_mailer" class="AcmeHelloBundleMailer"> <argument>%my_mailer.transport%</argument> </service> </services> </container>
// src/Acme/HelloBundle/Resources/config/services.php use SymfonyComponentDependencyInjectionDefinition; $container->setParameter('my_mailer.transport', 'sendmail'); $container->setDefinition('my_mailer', new Definition( 'AcmeHelloBundleMailer', array('%my_mailer.transport%') ));
The definition itself hasn’t changed, only its location. Of course the service container doesn’t know about the new resource file. Fortunately, you can easily import the resource file using the imports
key in the application configuration.
# app/config/config.yml imports: - { resource: "@AcmeHelloBundle/Resources/config/services.yml" }
<!-- app/config/config.xml --> <?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"> <imports> <import resource="@AcmeHelloBundle/Resources/config/services.xml"/> </imports> </container>
// app/config/config.php $loader->import('@AcmeHelloBundle/Resources/config/services.php');
注解
Due to the way in which parameters are resolved, you cannot use them to build paths in imports dynamically. This means that something like the following doesn’t work:
# app/config/config.yml imports: - { resource: "%kernel.root_dir%/parameters.yml" }
<!-- app/config/config.xml --> <?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"> <imports> <import resource="%kernel.root_dir%/parameters.yml" /> </imports> </container>
// app/config/config.php $loader->import('%kernel.root_dir%/parameters.yml');
The imports
directive allows your application to include service container configuration resources from any other location (most commonly from bundles). The resource
location, for files, is the absolute path to the resource file. The special @AcmeHelloBundle
syntax resolves the directory path of the AcmeHelloBundle bundle. This helps you specify the path to the resource without worrying later if you move the AcmeHelloBundle to a different directory.
When developing in Symfony, you’ll most commonly use the imports
directive to import container configuration from the bundles you’ve created specifically for your application. Third-party bundle container configuration, including Symfony core services, are usually loaded using another method that’s more flexible and easy to configure in your application.
Here’s how it works. Internally, each bundle defines its services very much like you’ve seen so far. Namely, a bundle uses one or more configuration resource files (usually XML) to specify the parameters and services for that bundle. However, instead of importing each of these resources directly from your application configuration using the imports
directive, you can simply invoke a service container extension inside the bundle that does the work for you. A service container extension is a PHP class created by the bundle author to accomplish two things:
In other words, a service container extension configures the services for a bundle on your behalf. And as you’ll see in a moment, the extension provides a sensible, high-level interface for configuring the bundle.
Take the FrameworkBundle - the core Symfony framework bundle - as an example. The presence of the following code in your application configuration invokes the service container extension inside the FrameworkBundle:
# app/config/config.yml framework: secret: xxxxxxxxxx form: true csrf_protection: true router: { resource: "%kernel.root_dir%/config/routing.yml" } # ...
<!-- app/config/config.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config secret="xxxxxxxxxx"> <framework:form /> <framework:csrf-protection /> <framework:router resource="%kernel.root_dir%/config/routing.xml" /> <!-- ... --> </framework> </container>
// app/config/config.php $container->loadFromExtension('framework', array( 'secret' => 'xxxxxxxxxx', 'form' => array(), 'csrf-protection' => array(), 'router' => array( 'resource' => '%kernel.root_dir%/config/routing.php', ), // ... ));
When the configuration is parsed, the container looks for an extension that can handle the framework
configuration directive. The extension in question, which lives in the FrameworkBundle, is invoked and the service configuration for the FrameworkBundle is loaded. If you remove the framework
key from your application configuration file entirely, the core Symfony services won’t be loaded. The point is that you’re in control: the Symfony framework doesn’t contain any magic or perform any actions that you don’t have control over.
Of course you can do much more than simply “activate” the service container extension of the FrameworkBundle. Each extension allows you to easily customize the bundle, without worrying about how the internal services are defined.
In this case, the extension allows you to customize the error_handler
, csrf_protection
, router
configuration and much more. Internally, the FrameworkBundle uses the options specified here to define and configure the services specific to it. The bundle takes care of creating all the necessary parameters
and services
for the service container, while still allowing much of the configuration to be easily customized. As a bonus, most service container extensions are also smart enough to perform validation - notifying you of options that are missing or the wrong data type.
When installing or configuring a bundle, see the bundle’s documentation for how the services for the bundle should be installed and configured. The options available for the core bundles can be found inside the Reference Guide.
注解
Natively, the service container only recognizes the parameters
, services
, and imports
directives. Any other directives are handled by a service container extension.
If you want to expose user friendly configuration in your own bundles, read the “如何在Bundle内部载入Service配置” cookbook recipe.
So far, the original my_mailer
service is simple: it takes just one argument in its constructor, which is easily configurable. As you’ll see, the real power of the container is realized when you need to create a service that depends on one or more other services in the container.
As an example, suppose you have a new service, NewsletterManager
, that helps to manage the preparation and delivery of an email message to a collection of addresses. Of course the my_mailer
service is already really good at delivering email messages, so you’ll use it inside NewsletterManager
to handle the actual delivery of the messages. This pretend class might look something like this:
// src/Acme/HelloBundle/Newsletter/NewsletterManager.php namespace AcmeHelloBundleNewsletter; use AcmeHelloBundleMailer; class NewsletterManager { protected $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } // ... }
Without using the service container, you can create a new NewsletterManager
fairly easily from inside a controller:
use AcmeHelloBundleNewsletterNewsletterManager; // ... public function sendNewsletterAction() { $mailer = $this->get('my_mailer'); $newsletter = new NewsletterManager($mailer); // ... }
This approach is fine, but what if you decide later that the NewsletterManager
class needs a second or third constructor argument? What if you decide to refactor your code and rename the class? In both cases, you’d need to find every place where the NewsletterManager
is instantiated and modify it. Of course, the service container gives you a much more appealing option:
# src/Acme/HelloBundle/Resources/config/services.yml services: my_mailer: # ... newsletter_manager: class: AcmeHelloBundleNewsletterNewsletterManager arguments: ["@my_mailer"]
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <?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="AcmeHelloBundleNewsletterNewsletterManager"> <argument type="service" id="my_mailer"/> </service> </services> </container>
// src/Acme/HelloBundle/Resources/config/services.php use SymfonyComponentDependencyInjectionDefinition; use SymfonyComponentDependencyInjectionReference; $container->setDefinition('my_mailer', ...); $container->setDefinition('newsletter_manager', new Definition( 'AcmeHelloBundleNewsletterNewsletterManager', array(new Reference('my_mailer')) ));
In YAML, the special @my_mailer
syntax tells the container to look for a service named my_mailer
and to pass that object into the constructor of NewsletterManager
. In this case, however, the specified service my_mailer
must exist. If it does not, an exception will be thrown. You can mark your dependencies as optional - this will be discussed in the next section.
Using references is a very powerful tool that allows you to create independent service classes with well-defined dependencies. In this example, the newsletter_manager
service needs the my_mailer
service in order to function. When you define this dependency in the service container, the container takes care of all the work of instantiating the classes.
The service container also supports an “expression” that allows you to inject very specific values into a service.
For example, suppose you have a third service (not shown here), called mailer_configuration
, which has a getMailerMethod()
method on it, which will return a string like sendmail
based on some configuration. Remember that the first argument to the my_mailer
service is the simple string sendmail
:
# app/config/config.yml services: my_mailer: class: AcmeHelloBundleMailer arguments: [sendmail]
<!-- app/config/config.xml --> <?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" class="AcmeHelloBundleMailer"> <argument>sendmail</argument> </service> </services> </container>
// app/config/config.php use SymfonyComponentDependencyInjectionDefinition; $container->setDefinition('my_mailer', new Definition( 'AcmeHelloBundleMailer', array('sendmail') ));
But instead of hardcoding this, how could we get this value from the getMailerMethod()
of the new mailer_configuration
service? One way is to use an expression:
# app/config/config.yml services: my_mailer: class: AcmeHelloBundleMailer arguments: ["@=service('mailer_configuration').getMailerMethod()"]
<!-- app/config/config.xml --> <?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" class="AcmeHelloBundleMailer"> <argument type="expression">service('mailer_configuration').getMailerMethod()</argument> </service> </services> </container>
// app/config/config.php use SymfonyComponentDependencyInjectionDefinition; use SymfonyComponentExpressionLanguageExpression; $container->setDefinition('my_mailer', new Definition( 'AcmeHelloBundleMailer', array(new Expression('service("mailer_configuration").getMailerMethod()')) ));
To learn more about the expression language syntax, see The Expression Syntax.
In this context, you have access to 2 functions:
service
parameter
service
).You also have access to the ContainerBuilder
via a container
variable. Here’s another example:
services: my_mailer: class: AcmeHelloBundleMailer arguments: ["@=container.hasParameter('some_param') ? parameter('some_param') : 'default_value'"]
<?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" class="AcmeHelloBundleMailer"> <argument type="expression">container.hasParameter('some_param') ? parameter('some_param') : 'default_value'</argument> </service> </services> </container>
use SymfonyComponentDependencyInjectionDefinition; use SymfonyComponentExpressionLanguageExpression; $container->setDefinition('my_mailer', new Definition( 'AcmeHelloBundleMailer', array(new Expression( "container.hasParameter('some_param') ? parameter('some_param') : 'default_value'" )) ));
Expressions can be used in arguments
, properties
, as arguments with configurator
and as arguments to calls
(method calls).
Injecting dependencies into the constructor in this manner is an excellent way of ensuring that the dependency is available to use. If you have optional dependencies for a class, then “setter injection” may be a better option. This means injecting the dependency using a method call rather than through the constructor. The class would look like this:
namespace AcmeHelloBundleNewsletter; use AcmeHelloBundleMailer; class NewsletterManager { protected $mailer; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; } // ... }
Injecting the dependency by the setter method just needs a change of syntax:
# src/Acme/HelloBundle/Resources/config/services.yml services: my_mailer: # ... newsletter_manager: class: AcmeHelloBundleNewsletterNewsletterManager calls: - [setMailer, ["@my_mailer"]]
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <?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="AcmeHelloBundleNewsletterNewsletterManager"> <call method="setMailer"> <argument type="service" id="my_mailer" /> </call> </service> </services> </container>
// src/Acme/HelloBundle/Resources/config/services.php use SymfonyComponentDependencyInjectionDefinition; use SymfonyComponentDependencyInjectionReference; $container->setDefinition('my_mailer', ...); $container->setDefinition('newsletter_manager', new Definition( 'AcmeHelloBundleNewsletterNewsletterManager' ))->addMethodCall('setMailer', array( new Reference('my_mailer'), ));
注解
The approaches presented in this section are called “constructor injection” and “setter injection”. The Symfony service container also supports “property injection”.
As of Symfony 2.4, instead of injecting the request
service, you should inject the request_stack
service and access the Request
by calling the getCurrentRequest()
method:
namespace AcmeHelloBundleNewsletter; use SymfonyComponentHttpFoundationRequestStack; class NewsletterManager { protected $requestStack; public function __construct(RequestStack $requestStack) { $this->requestStack = $requestStack; } public function anyMethod() { $request = $this->requestStack->getCurrentRequest(); // ... do something with the request } // ... }
Now, just inject the request_stack
, which behaves like any normal service:
# src/Acme/HelloBundle/Resources/config/services.yml services: newsletter_manager: class: AcmeHelloBundleNewsletterNewsletterManager arguments: ["@request_stack"]
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <?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="newsletter_manager" class="AcmeHelloBundleNewsletterNewsletterManager" > <argument type="service" id="request_stack"/> </service> </services> </container>
// src/Acme/HelloBundle/Resources/config/services.php use SymfonyComponentDependencyInjectionDefinition; use SymfonyComponentDependencyInjectionReference; // ... $container->setDefinition('newsletter_manager', new Definition( 'AcmeHelloBundleNewsletterNewsletterManager', array(new Reference('request_stack')) ));
为什么不直接注入 request
Service?
Almost all Symfony2 built-in services behave in the same way: a single instance is created by the container which it returns whenever you get it or when it is injected into another service. There is one exception in a standard Symfony2 application: the request
service.
If you try to inject the request
into a service, you will probably receive a ScopeWideningInjectionException
exception. That’s because the request
can change during the life-time of a container (when a sub-request is created for instance).
小技巧
If you define a controller as a service then you can get the Request
object without injecting the container by having it passed in as an argument of your action method. See 使用 Request 对象作为Controller参数 for details.
Sometimes, one of your services may have an optional dependency, meaning that the dependency is not required for your service to work properly. In the example above, the my_mailer
service must exist, otherwise an exception will be thrown. By modifying the newsletter_manager
service definition, you can make this reference optional. The container will then inject it if it exists and do nothing if it doesn’t:
# src/Acme/HelloBundle/Resources/config/services.yml services: newsletter_manager: class: AcmeHelloBundleNewsletterNewsletterManager arguments: ["@?my_mailer"]
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <?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="AcmeHelloBundleNewsletterNewsletterManager"> <argument type="service" id="my_mailer" on-invalid="ignore" /> </service> </services> </container>
// src/Acme/HelloBundle/Resources/config/services.php use SymfonyComponentDependencyInjectionDefinition; use SymfonyComponentDependencyInjectionReference; use SymfonyComponentDependencyInjectionContainerInterface; $container->setDefinition('my_mailer', ...); $container->setDefinition('newsletter_manager', new Definition( 'AcmeHelloBundleNewsletterNewsletterManager', array( new Reference( 'my_mailer', ContainerInterface::IGNORE_ON_INVALID_REFERENCE ) ) ));
In YAML, the special @?
syntax tells the service container that the dependency is optional. Of course, the NewsletterManager
must also be rewritten to allow for an optional dependency:
public function __construct(Mailer $mailer = null) { // ... }
Since Symfony and all third-party bundles configure and retrieve their services via the container, you can easily access them or even use them in your own services. To keep things simple, Symfony by default does not require that controllers be defined as services. Furthermore, Symfony injects the entire service container into your controller. For example, to handle the storage of information on a user’s session, Symfony provides a session
service, which you can access inside a standard controller as follows:
public function indexAction($bar) { $session = $this->get('session'); $session->set('foo', $bar); // ... }
In Symfony, you’ll constantly use services provided by the Symfony core or other third-party bundles to perform tasks such as rendering templates (templating
), sending emails (mailer
), or accessing information on the request (request
).
You can take this a step further by using these services inside services that you’ve created for your application. Beginning by modifying the NewsletterManager
to use the real Symfony mailer
service (instead of the pretend my_mailer
). Also pass the templating engine service to the NewsletterManager
so that it can generate the email content via a template:
namespace AcmeHelloBundleNewsletter; use SymfonyComponentTemplatingEngineInterface; class NewsletterManager { protected $mailer; protected $templating; public function __construct( Swift_Mailer $mailer, EngineInterface $templating ) { $this->mailer = $mailer; $this->templating = $templating; } // ... }
Configuring the service container is easy:
# src/Acme/HelloBundle/Resources/config/services.yml services: newsletter_manager: class: AcmeHelloBundleNewsletterNewsletterManager arguments: ["@mailer", "@templating"]
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <?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"> <service id="newsletter_manager" class="AcmeHelloBundleNewsletterNewsletterManager"> <argument type="service" id="mailer"/> <argument type="service" id="templating"/> </service> </container>
// src/Acme/HelloBundle/Resources/config/services.php $container->setDefinition('newsletter_manager', new Definition( 'AcmeHelloBundleNewsletterNewsletterManager', array( new Reference('mailer'), new Reference('templating'), ) ));
The newsletter_manager
service now has access to the core mailer
and templating
services. This is a common way to create services specific to your application that leverage the power of different services within the framework.
小技巧
Be sure that the swiftmailer
entry appears in your application configuration. As was mentioned in 使用Container扩展导入配置, the swiftmailer
key invokes the service extension from the SwiftmailerBundle, which registers the mailer
service.
You can find out what services are registered with the container using the console. To show all services and the class for each service, run:
$ php app/console debug:container
2.6 新版功能: Prior to Symfony 2.6, this command was called container:debug
.
By default, only public services are shown, but you can also view private services:
$ php app/console debug:container --show-private
注解
If a private service is only used as an argument to just one other service, it won’t be displayed by the debug:container
command, even when using the --show-private
option. See Inline Private Services for more details.
You can get more detailed information about a particular service by specifying its id:
$ php app/console debug:container my_mailer