This article demonstrates how to create a simple website, a blog, using the pattern Dependency Injection . The approach to the introduction of dependencies to all possible components Symfony: controllers, doctrine-repository form.
To simplify the article reduce the number of pages to two:
The final architecture of the application will look like this:
DI as part of Symfony is considered to Habré, and is described in the documentation . Therefore, we will begin to create their own services and dependencies. This can be done in three ways: task dependencies in the code bundle, through configuration files (YAML, XML, PHP) and using annotations (with bundles JMSDiExtraBundle , included in the standard Symfony). Each method has its pros and cons. We will use the annotation visibility and reduce the amount of code. We start with a class that implements the business logic. Let this be PostManager, handles add a new post:
@ DI \ Service - turns the class into service. In the parameters of the annotation specifies the name of the service (app.manager.post) and its attributes.
public = false - this attribute indicates that the service can not be created called directly from DIC ($ container-> get ('app.manager.post') will fail.) Created service can only use services that depend on it directly (on the example of the controller, it becomes more clear.)
@ DI \ Inject - an indication of services on which the service is created. Use of this annotation is only possible with variables of type public. For private / protected variables dependencies, you can use @ DI \ InjectParams for the designer or other ways to create services.
So, we have created a service app.manager.post, depending on doctrine.orm.entity_manager:
Graphical display of servers and connections is available in a convenient web interface with installation JMSDebuggingBundle .
By default, Symfony controllers are not services, but the documentation is a note , allowing them to do so. Create a PostController, using a previously created PostManager:
scope = «request» - the attribute detail is described in the documentation
@ Route (service = «app.controller.post») - According to a routing system that the controller is used as a service. In this case, the string values to change with transfer rules 'AppBundle: Post: add' on 'app.controller.post: addAction'.
Use depending @ DI \ Inject («service_container») requires parent controller class Symfony \ Bundle \ FrameworkBundle \ Controller \ Controller. As controller permits any class, not necessarily derived from the standard controller - in this case the dependence of DIC can be excluded.
So we created a service app.controller.post, depending on service_container and app.manager.post:
Service access to service_container means that it has access directly to all public-services project (via $ this-> container-> get ('...')). This facilitates the use of the framework, but to trace the connection between services with such an approach is nearly impossible. Therefore, for the services of the application should use the attribute public = false and follow the rule:
This step is optional, and is likely to demonstrate the capabilities and consolidate the material. But in large projects that use a large number of forms, it may be useful to monitor connections.
The generated controller we used a form PostType, try to define it as a service:
We use to create a service in the controller:
We can trace the relationship of form and the controller:
Doctrine-use repositories as services is complicated by the fact that they are not part of Symfony, and are part of the Doctrine. But the opportunity is still there, a factory creation of services. Unfortunately, at the moment it is not supported by annotations, so you have to use the configs.
The file Doctrine-fact show the way to the repository:
PostRepository create a paginated list to receive posts:
Upon by the class as a service app.repository.post:
Repository and add a page listing the controller:
This method uses the pattern Adapter . Standard Doctrine-repository is included in our own service repository. In contrast to the first method, everything is achievable annotations.
The file Doctrine-essentially remove indication repository:
We need the parent class Repository, dependent doctrine.orm.entity_manager and implements the necessary features repository. For this we use the inheritance of services :
The service definition will now be in the class repository wrapper:
Note: when using annotations, specifying parent = «app.repository» is optional. JMSDiExtraBundle inserts it automatically, based on the parent class.
Both methods implement the same functionality and are interchangeable. Therefore, the code of the controller does not change:
As a result of the dependency graph application takes the following form:
To control connections with this approach, follow the rule:
As you may have noticed, all the dependent services are its variables are created together with the creation of this service. In turn, if you create a dependency created their dependencies, so are all the dependencies of the subtree, including unused. On app.controller.post example, we see that the function uses addAction app.manager.post and app.form.post, and listAction - app.repository.post. But all variables are created when you create a controller, so what would we function is not called, some of the variables are bound to be unusable: If this addAction app.repository.post, if listAction - app.manager.post and app.form.post. This class as it is composed of two independent parts, in this case, we say that it has low connectivity . Than with a lot of variables, the method works, the better the connection of this method with his class. The class in which each variable is used by each method has maximum connectivity. Creating classes with a maximum connection is not always possible, but in our case, this is easily achieved by dividing app.controller.post into two separate classes:
Dependency injection in all classes in the system increases the quality of architecture, but it increases the complexity and development time. Therefore, this approach is justified only in large projects. When creating small projects such as established in article site, it stands abandoned, however, as the use of Symfony (in favor of lightweight frameworks).
To simplify the article reduce the number of pages to two:
- Adding a New Post (/ add)
- Displays a list of all the posts (/ list)
The final architecture of the application will look like this:
Step 1. Creating a service
DI as part of Symfony is considered to Habré, and is described in the documentation . Therefore, we will begin to create their own services and dependencies. This can be done in three ways: task dependencies in the code bundle, through configuration files (YAML, XML, PHP) and using annotations (with bundles JMSDiExtraBundle , included in the standard Symfony). Each method has its pros and cons. We will use the annotation visibility and reduce the amount of code. We start with a class that implements the business logic. Let this be PostManager, handles add a new post:
/ Src / AppBundle / Manager / PostManager.php
@ DI \ Service - turns the class into service. In the parameters of the annotation specifies the name of the service (app.manager.post) and its attributes.
public = false - this attribute indicates that the service can not be created called directly from DIC ($ container-> get ('app.manager.post') will fail.) Created service can only use services that depend on it directly (on the example of the controller, it becomes more clear.)
@ DI \ Inject - an indication of services on which the service is created. Use of this annotation is only possible with variables of type public. For private / protected variables dependencies, you can use @ DI \ InjectParams for the designer or other ways to create services.
So, we have created a service app.manager.post, depending on doctrine.orm.entity_manager:
Graphical display of servers and connections is available in a convenient web interface with installation JMSDebuggingBundle .
Step 2. Creating a Controller
By default, Symfony controllers are not services, but the documentation is a note , allowing them to do so. Create a PostController, using a previously created PostManager:
/ Src / AppBundle / Controller / PostController.php
scope = «request» - the attribute detail is described in the documentation
@ Route (service = «app.controller.post») - According to a routing system that the controller is used as a service. In this case, the string values to change with transfer rules 'AppBundle: Post: add' on 'app.controller.post: addAction'.
Use depending @ DI \ Inject («service_container») requires parent controller class Symfony \ Bundle \ FrameworkBundle \ Controller \ Controller. As controller permits any class, not necessarily derived from the standard controller - in this case the dependence of DIC can be excluded.
So we created a service app.controller.post, depending on service_container and app.manager.post:
Service access to service_container means that it has access directly to all public-services project (via $ this-> container-> get ('...')). This facilitates the use of the framework, but to trace the connection between services with such an approach is nearly impossible. Therefore, for the services of the application should use the attribute public = false and follow the rule:
Step 3. Create a form
This step is optional, and is likely to demonstrate the capabilities and consolidate the material. But in large projects that use a large number of forms, it may be useful to monitor connections.
The generated controller we used a form PostType, try to define it as a service:
/ Src / AppBundle / Form / PostType.php
We use to create a service in the controller:
/ Src / AppBundle / Controller / PostController.php
We can trace the relationship of form and the controller:
Step 4. Creating a repository
The first way: factory creation
Doctrine-use repositories as services is complicated by the fact that they are not part of Symfony, and are part of the Doctrine. But the opportunity is still there, a factory creation of services. Unfortunately, at the moment it is not supported by annotations, so you have to use the configs.
The file Doctrine-fact show the way to the repository:
/ Src / AppBundle / Entity / Post.php
PostRepository create a paginated list to receive posts:
/ Src / AppBundle / Repository / PostRepository.php
Upon by the class as a service app.repository.post:
/ Src / AppBundle / Resources / config / services.yml
Repository and add a page listing the controller:
/ Src / AppBundle / Controller / PostController.php
The second method is the creation of repositories wrappers
This method uses the pattern Adapter . Standard Doctrine-repository is included in our own service repository. In contrast to the first method, everything is achievable annotations.
The file Doctrine-essentially remove indication repository:
src / AppBundle / Entity / Post.php
We need the parent class Repository, dependent doctrine.orm.entity_manager and implements the necessary features repository. For this we use the inheritance of services :
/ Src / AppBundle / Repository / Repository.php
The service definition will now be in the class repository wrapper:
/ Src / AppBundle / Repository / PostRepository.php
Note: when using annotations, specifying parent = «app.repository» is optional. JMSDiExtraBundle inserts it automatically, based on the parent class.
Both methods implement the same functionality and are interchangeable. Therefore, the code of the controller does not change:
/ Src / AppBundle / Controller / PostController.php
As a result of the dependency graph application takes the following form:
To control connections with this approach, follow the rule:
Step 5. Optimization
As you may have noticed, all the dependent services are its variables are created together with the creation of this service. In turn, if you create a dependency created their dependencies, so are all the dependencies of the subtree, including unused. On app.controller.post example, we see that the function uses addAction app.manager.post and app.form.post, and listAction - app.repository.post. But all variables are created when you create a controller, so what would we function is not called, some of the variables are bound to be unusable: If this addAction app.repository.post, if listAction - app.manager.post and app.form.post. This class as it is composed of two independent parts, in this case, we say that it has low connectivity . Than with a lot of variables, the method works, the better the connection of this method with his class. The class in which each variable is used by each method has maximum connectivity. Creating classes with a maximum connection is not always possible, but in our case, this is easily achieved by dividing app.controller.post into two separate classes:
Conclusion
Dependency injection in all classes in the system increases the quality of architecture, but it increases the complexity and development time. Therefore, this approach is justified only in large projects. When creating small projects such as established in article site, it stands abandoned, however, as the use of Symfony (in favor of lightweight frameworks).
0 commentaires:
Enregistrer un commentaire