diff --git a/best_practices/business-logic.rst b/best_practices/business-logic.rst index 21f066dbe49..cb3f6e5413d 100644 --- a/best_practices/business-logic.rst +++ b/best_practices/business-logic.rst @@ -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 `, + 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); @@ -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 `. If you use the +:ref:`default services.yml configuration `, +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 -------------------- diff --git a/best_practices/controllers.rst b/best_practices/controllers.rst index 98e55f4eef7..89f6a497493 100644 --- a/best_practices/controllers.rst +++ b/best_practices/controllers.rst @@ -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( @@ -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 `: + +.. best-practice:: + + Don't use ``$this->get()`` or ``$this->container->get()`` to fetch services + 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 `. + .. _best-practices-paramconverter: Using the ParamConverter diff --git a/best_practices/security.rst b/best_practices/security.rst index 5252686d7f7..dcc9497e33c 100644 --- a/best_practices/security.rst +++ b/best_practices/security.rst @@ -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) { @@ -328,7 +328,8 @@ the same ``getAuthorEmail()`` logic you used above: } } -Your application will :ref:`autoconfigure ` your security +If you're using the :ref:`default services.yml configuration `, +your application will :ref:`autoconfigure ` your security voter and inject a ``AccessDecisionManagerInterface`` instance in it thanks to :doc:`autowiring `. diff --git a/service_container/alias_private.rst b/service_container/alias_private.rst index b5fe9237dac..641b416861f 100644 --- a/service_container/alias_private.rst +++ b/service_container/alias_private.rst @@ -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::