Skip to content

3.3 Best Practices for DI Changes #7933

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions best_practices/business-logic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,42 @@ Next, define a new service for that class.

# app/config/services.yml
services:
# keep your service names short
app.slugger:
class: AppBundle\Utils\Slugger
# ...

# use the fully-qualified class name as the service id
AppBundle\Utils\Slugger:
public: false

.. note::

Traditionally, the naming convention for a service involved following the
class name and location to avoid name collisions. Thus, the service
*would have been* called ``app.utils.slugger``. But by using short service names,
your code will be easier to read and use.
If you're using the :ref:`default services.yml configuration <service-container-services-load-example>`,
the class is auto-registered as a service.

Traditionally, the naming convention for a service was a short, but unique
snake case key - e.g. ``app.utils.slugger``. But for most services, you should now
use the class name.

.. best-practice::

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

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

.. code-block:: php

public function createAction(Request $request)
use AppBundle\Utils\Slugger;

public function createAction(Request $request, Slugger $slugger)
{
// ...

// you can also fetch a public service like this
// but fetching services in this way is not considered a best practice
// $slugger = $this->get('app.slugger');

if ($form->isSubmitted() && $form->isValid()) {
$slug = $this->get('app.slugger')->slugify($post->getTitle());
$post->setSlug($slug);
Expand All @@ -116,6 +128,16 @@ Now you can use the custom slugger in any controller class, such as the
}
}

Services can also be :ref:`public or private <container-public>`. If you use the
:ref:`default services.yml configuration <service-container-services-load-example>`,
all services are private by default.

.. best-practice::

Services should be ``private`` whenever possible. This will prevent you from
accessing that service via ``$container->get()``. Instead, you will need to use
dependency injection.

Service Format: YAML
--------------------

Expand Down
22 changes: 19 additions & 3 deletions best_practices/controllers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,16 @@ for the homepage of our app:

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Doctrine\ORM\EntityManagerInterface;

class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
*/
public function indexAction()
public function indexAction(EntityManagerInterface $em)
{
$posts = $this->getDoctrine()
->getRepository('AppBundle:Post')
$posts = $em->getRepository('AppBundle:Post')
->findLatest();

return $this->render('default/index.html.twig', array(
Expand All @@ -115,6 +115,22 @@ for the homepage of our app:
}
}

Fetching Services
-----------------

If you extend the base ``Controller`` class, you can access services directly from
the container via ``$this->container->get()`` or ``$this->get()``. But instead, you
should use dependency injection to fetch services: most easily done by
:ref:`type-hinting action method arguments <controller-accessing-services>`:

.. best-practice::

Don't use ``$this->get()`` or ``$this->container->get()`` to fetch services
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in the previous paragraph we don't explain why you should do that. Should we add something or link to some other article in the docs?

from the container. Instead, use dependency injection.

By not fetching services directly from the container, you can make your services
*private*, which has :ref:`several advantages <services-why-private>`.

.. _best-practices-paramconverter:

Using the ParamConverter
Expand Down
7 changes: 4 additions & 3 deletions best_practices/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,9 @@ more advanced use-case, you can always do the same security check in PHP:
/**
* @Route("/{id}/edit", name="admin_post_edit")
*/
public function editAction($id)
public function editAction($id, EntityManagerInterface $em)
{
$post = $this->getDoctrine()->getRepository('AppBundle:Post')
$post = $em->getRepository('AppBundle:Post')
->find($id);

if (!$post) {
Expand Down Expand Up @@ -328,7 +328,8 @@ the same ``getAuthorEmail()`` logic you used above:
}
}

Your application will :ref:`autoconfigure <services-autoconfigure>` your security
If you're using the :ref:`default services.yml configuration <service-container-services-load-example>`,
your application will :ref:`autoconfigure <services-autoconfigure>` your security
voter and inject a ``AccessDecisionManagerInterface`` instance in it thanks to
:doc:`autowiring </service_container/autowiring>`.

Expand Down
7 changes: 6 additions & 1 deletion service_container/alias_private.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,13 @@ You can also control the ``public`` option on a service-by-service basis:
$container->register(Foo::class)
->setPublic(false);

.. _services-why-private:

Private services are special because they allow the container to optimize whether
and how they are instantiated. This increases the container's performance.
and how they are instantiated. This increases the container's performance. It also
gives you better errors: if you try to reference a non-existent service, you will
get a clear error when you refresh *any* page, even if the problematic code would
not have run on that page.

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