Skip to content

Commit 60e0eec

Browse files
committed
feature #7933 3.3 Best Practices for DI Changes (weaverryan)
This PR was squashed before being merged into the 3.3 branch (closes #7933). Discussion ---------- 3.3 Best Practices for DI Changes Hi guys! This updates the best practices for the 3.3 DI changes. The specific changes are: A) Use class names as service ids (except when a class has multiple services, then use normal snake-case) B) Do not use the container as a service locator - use DI (also, make services private) Thanks! Commits ------- 33647a6 Tweaks after review! adadb5a updating best practices for DI 3.3 changes
2 parents 6570553 + 33647a6 commit 60e0eec

File tree

4 files changed

+62
-18
lines changed

4 files changed

+62
-18
lines changed

best_practices/business-logic.rst

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,30 +84,42 @@ Next, define a new service for that class.
8484
8585
# app/config/services.yml
8686
services:
87-
# keep your service names short
88-
app.slugger:
89-
class: AppBundle\Utils\Slugger
87+
# ...
88+
89+
# use the fully-qualified class name as the service id
90+
AppBundle\Utils\Slugger:
91+
public: false
92+
93+
.. note::
9094

91-
Traditionally, the naming convention for a service involved following the
92-
class name and location to avoid name collisions. Thus, the service
93-
*would have been* called ``app.utils.slugger``. But by using short service names,
94-
your code will be easier to read and use.
95+
If you're using the :ref:`default services.yml configuration <service-container-services-load-example>`,
96+
the class is auto-registered as a service.
97+
98+
Traditionally, the naming convention for a service was a short, but unique
99+
snake case key - e.g. ``app.utils.slugger``. But for most services, you should now
100+
use the class name.
95101

96102
.. best-practice::
97103

98-
The name of your application's services should be as short as possible,
99-
but unique enough that you can search your project for the service if
100-
you ever need to.
104+
The id of your application's services should be equal to their class name,
105+
except when you have multiple services configured for the same class (in that
106+
case, use a snake case id).
101107

102108
Now you can use the custom slugger in any controller class, such as the
103109
``AdminController``:
104110

105111
.. code-block:: php
106112
107-
public function createAction(Request $request)
113+
use AppBundle\Utils\Slugger;
114+
115+
public function createAction(Request $request, Slugger $slugger)
108116
{
109117
// ...
110118
119+
// you can also fetch a public service like this
120+
// but fetching services in this way is not considered a best practice
121+
// $slugger = $this->get('app.slugger');
122+
111123
if ($form->isSubmitted() && $form->isValid()) {
112124
$slug = $this->get('app.slugger')->slugify($post->getTitle());
113125
$post->setSlug($slug);
@@ -116,6 +128,16 @@ Now you can use the custom slugger in any controller class, such as the
116128
}
117129
}
118130
131+
Services can also be :ref:`public or private <container-public>`. If you use the
132+
:ref:`default services.yml configuration <service-container-services-load-example>`,
133+
all services are private by default.
134+
135+
.. best-practice::
136+
137+
Services should be ``private`` whenever possible. This will prevent you from
138+
accessing that service via ``$container->get()``. Instead, you will need to use
139+
dependency injection.
140+
119141
Service Format: YAML
120142
--------------------
121143

best_practices/controllers.rst

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,16 @@ for the homepage of our app:
9797
9898
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
9999
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
100+
use Doctrine\ORM\EntityManagerInterface;
100101
101102
class DefaultController extends Controller
102103
{
103104
/**
104105
* @Route("/", name="homepage")
105106
*/
106-
public function indexAction()
107+
public function indexAction(EntityManagerInterface $em)
107108
{
108-
$posts = $this->getDoctrine()
109-
->getRepository('AppBundle:Post')
109+
$posts = $em->getRepository('AppBundle:Post')
110110
->findLatest();
111111
112112
return $this->render('default/index.html.twig', array(
@@ -115,6 +115,22 @@ for the homepage of our app:
115115
}
116116
}
117117
118+
Fetching Services
119+
-----------------
120+
121+
If you extend the base ``Controller`` class, you can access services directly from
122+
the container via ``$this->container->get()`` or ``$this->get()``. But instead, you
123+
should use dependency injection to fetch services: most easily done by
124+
:ref:`type-hinting action method arguments <controller-accessing-services>`:
125+
126+
.. best-practice::
127+
128+
Don't use ``$this->get()`` or ``$this->container->get()`` to fetch services
129+
from the container. Instead, use dependency injection.
130+
131+
By not fetching services directly from the container, you can make your services
132+
*private*, which has :ref:`several advantages <services-why-private>`.
133+
118134
.. _best-practices-paramconverter:
119135

120136
Using the ParamConverter

best_practices/security.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,9 @@ more advanced use-case, you can always do the same security check in PHP:
224224
/**
225225
* @Route("/{id}/edit", name="admin_post_edit")
226226
*/
227-
public function editAction($id)
227+
public function editAction($id, EntityManagerInterface $em)
228228
{
229-
$post = $this->getDoctrine()->getRepository('AppBundle:Post')
229+
$post = $em->getRepository('AppBundle:Post')
230230
->find($id);
231231
232232
if (!$post) {
@@ -328,7 +328,8 @@ the same ``getAuthorEmail()`` logic you used above:
328328
}
329329
}
330330
331-
Your application will :ref:`autoconfigure <services-autoconfigure>` your security
331+
If you're using the :ref:`default services.yml configuration <service-container-services-load-example>`,
332+
your application will :ref:`autoconfigure <services-autoconfigure>` your security
332333
voter and inject a ``AccessDecisionManagerInterface`` instance in it thanks to
333334
:doc:`autowiring </service_container/autowiring>`.
334335

service_container/alias_private.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,13 @@ You can also control the ``public`` option on a service-by-service basis:
5757
$container->register(Foo::class)
5858
->setPublic(false);
5959
60+
.. _services-why-private:
61+
6062
Private services are special because they allow the container to optimize whether
61-
and how they are instantiated. This increases the container's performance.
63+
and how they are instantiated. This increases the container's performance. It also
64+
gives you better errors: if you try to reference a non-existent service, you will
65+
get a clear error when you refresh *any* page, even if the problematic code would
66+
not have run on that page.
6267

6368
Now that the service is private, you *should not* fetch the service directly
6469
from the container::

0 commit comments

Comments
 (0)