service container
Modern PHP programs are all objects. One object can be responsible for sending the email, and another object allows you to persist the information to the database. In the program, you can create an object to manage product inventory, or use another object to process data from a third-party API. The conclusion is that modern programs can do many things, and programs are composed of many objects organized together to handle their own tasks.
This chapter talks about a special PHP object in Symfony, which helps you instantiate, organize and retrieve many objects in the program. This object, called a "service container," allows you to standardize and centralize the organization of objects in your program. Containers make things simple, they are very fast, and they emphasize architecturally improving code reusability while reducing coupling. Since all Symfony classes use containers, you will learn how to extend, configure, and use objects in Symfony. From a large perspective, service containers are the biggest contributor to Symfony's speed and scalability.
Finally, configuring and using the service container is simple. After studying this chapter, you can easily create your own objects through the service container, and you can also customize any object in the third-party bundle. You'll start writing reusable, testable, loosely coupled code because service containers make it easy to write good code.
If you want to know more after reading this chapter, please refer to Dependency Injection Component.
What is a service ¶
Simply put, a service (Service) can be any object that performs "global" tasks. It is a proper noun in computer science, used to describe an object created "to complete a certain task (such as sending an email)." In the program, when you need a specific function provided by a service, you can access the service at any time. You don't need to do anything special when creating a service: just write a PHP class that performs a task. Congratulations, you have created a service!
As a principle, if a PHP object wants to become a service, it must be available in the global scope of the program. An independent Mailer
service is used "globally" to send email messages. However, the Message
information objects it transmits are not not services. Similarly, a Product
object is not a service, but an object that can persist products into the database is a service.
This shows what? The advantage of thinking about the problem from a "service" perspective is that you already want to separate each function in the program into a series of services. Since each service only does one thing, you can easily access the service whenever you need it. Testing and configuring each service becomes easier because they are separated from other functionality in your application. This concept is called Service-Oriented Architecture and is not exclusive to Symfony or PHP. "Structuring" your program through a set of independent service classes is a time-tested and well-known best practice in object-oriented programming. This skill is the key to becoming a good developer in any language.
What is a service container? ¶
The service container (Service Container/dependency injection container) is a PHP object that manages the instantiation of services (i.e. objects).
For example, suppose you have a simple class for sending email messages. Without a service container, you would have to manually create objects when needed.
use Acme\HelloBundle\Mailer; $mailer = new Mailer('sendmail'); $mailer->send('ryan@example.com', ...);
It's easy. A fictitious Mailer
mail service class that allows you to configure the method of sending mail (such as sendmail
, or smtp
, etc.). But what if you want to use your mail service elsewhere? You certainly don't want to reconfigure the Mailer
object every time. What if you want to change the way mail is transmitted and change all sendmail
in the entire program to smtp
? You have to find all the places where Mailer
is created and update it manually.
Creating and configuring services in containers ¶
The best answer to the above question is to let the service container create the Mailer object for you. In order for the container to work properly, you first need to teach it how to create the Mailer service. This is achieved through configuration, using YAML, XML or PHP:
PHP:// app/config/services.phpuse Symfony\Component\DependencyInjection\Definition; $container->setDefinition('app.mailer', new Definition( 'AppBundle\Mailer', array('sendmail')));
XML:<!-- app/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="app.mailer" class="AppBundle\Mailer"> <argument>sendmail</argument> </service> </services></container>
YAML:# app/config/services.ymlservices: app.mailer: class: AppBundle\Mailer arguments: [sendmail]
When Symfony initializes, it establishes services based on the configuration information Container (the default configuration file is app/config/config.yml
). The correct configuration file to be loaded is indicated by the AppKernel::registerContainerConfiguration()
method, which loads a "specific environment" configuration file (such as config_dev.yml is for the dev development environment, and config_prod.yml
is for the production environment).
An instance of the Acme\HelloBundle\Mailer
object is already available through the service container. Containers can be obtained directly from any standard Symfony controller through the get() shortcut method.
class HelloController extends Controller{ // ... public function sendEmailAction() { // ... $mailer = $this->get('app.mailer'); $mailer->send('ryan@foobar.net', ...); }}
When you request the app.mailer
service from the container, the container constructs the object and returns it (after instantiation). This is another benefit of using service containers. That is, a service is not constructed unless needed. If you define a service but it is never used during a request, the service will not be created. This saves memory and makes programs run faster. This also means that there is rarely a performance hit when defining a large number of services. Services that are never used will never be constructed.
By the way, the Mailer service is only created once, and the same instance is returned every time you request it. This should meet most of your needs (the behavior is flexible and powerful), but later you will need to learn how to configure a service with multiple instances, see How to define a non-shared service article.
In this example, the controller inherits Symfony's Controller base class, giving you an opportunity to use the service container. You can find and remove it from the container through the get() methodapp.mailer
Service.
Service Parameters ¶
The process of creating a new service through a container is very simple and straightforward. Parameters can make the definition of services more flexible and orderly:
PHP:// app/config/services.phpuse Symfony\Component\DependencyInjection\Definition; $container->setParameter('app.mailer.transport', 'sendmail'); $container->setDefinition('app.mailer', new Definition( 'AppBundle\Mailer', array('%app.mailer.transport%')));
XML:<!-- app/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="app.mailer.transport">sendmail</parameter> </parameters> <services> <service id="app.mailer" class="AppBundle\Mailer"> <argument>%app.mailer.transport%</argument> </service> </services></container>
YAML:# app/config/services.ymlparameters: app.mailer.transport: sendmailservices: app.mailer: class: AppBundle\Mailer arguments: ['%app.mailer.transport%']
The result is the same as before. The difference lies in how you define the service. By surrounding app.mailer.transport
with %
percent signs, the container knows to look for the parameter corresponding to that name. When the container itself is generated, the value of each parameter will be restored to the service definition.
If you want to use a string starting with @
as a parameter value in a YAML file (such as a very secure email password), you You need to add another @
symbol for escaping (this situation is only applicable in YAML format configuration files)
# app/config/parameters.ymlparameters: # This will be parsed as string '@securepass' mailer_password: '@@securepass'
Configuration parameters The percent sign in (parameter) or method parameter (argument) must also be escaped with another %:
##1 | <argument type="string">http://symfony.com/?foo=%%s&bar=%%d</argument> |