8000 feature #8599 Updated the Best Practices for Symfony 4 and Flex (javi… · yceruto/symfony-docs@eb775bd · GitHub
[go: up one dir, main page]

Skip to content
10000

Commit eb775bd

Browse files
committed
feature symfony#8599 Updated the Best Practices for Symfony 4 and Flex (javiereguiluz)
This PR was squashed before being merged into the master branch (closes symfony#8599). Discussion ---------- Updated the Best Practices for Symfony 4 and Flex Still WIP. Some controllers have not been reviewed yet. Commits ------- 335e461 Fixed after Fabien's review 68b7a18 Fixed some missing references 9794c0b Updated more contents b997c1e Updated the Best Practices for Symfony 4 and Flex
2 parents ab9d232 + 335e461 commit eb775bd

10 files changed

+233
-479
lines changed

best_practices/business-logic.rst

Lines changed: 25 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,25 @@ Inside here, you can create whatever directories you want to organize things:
2525
├─ var/
2626
└─ vendor/
2727
28-
Services: Naming and Format
29-
---------------------------
28+
.. _services-naming-and-format:
3029

31-
The blog application needs a utility that can transform a post title (e.g.
32-
"Hello World") into a slug (e.g. "hello-world"). The slug will be used as
33-
part of the post URL.
30+
Services: Naming and Configuration
31+
----------------------------------
32+
33+
.. best-practice::
34+
35+
Use autowiring to automate the configuration of application services.
3436

35-
Let's create a new ``Slugger`` class inside ``src/Utils/`` and
36-
add the following ``slugify()`` method:
37+
:doc:`Service autowiring </service_container/autowiring>` is a feature provided
38+
by Symfony's Service Container to manage services with minimal configuration. It
39+
reads the type-hints on your constructor (or other methods) and automatically
40+
passes the correct services to each method. It can also add
41+
:doc:`service tags </service_container/tags>` to the services needed them, such
42+
as Twig extensions, event subscribers, etc.
43+
44+
The blog application needs a utility that can transform a post title (e.g.
45+
"Hello World") into a slug (e.g. "hello-world") to include it as part of the
46+
post URL. Let's create a new ``Slugger`` class inside ``src/Utils/``:
3747

3848
.. code-block:: php
3949
@@ -42,56 +52,33 @@ add the following ``slugify()`` method:
4252
4353
class Slugger
4454
{
45-
public function slugify($string)
55+
public function slugify(string $value): string
4656
{
47-
return preg_replace(
48-
'/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string)))
49-
);
57+
// ...
5058
}
5159
}
5260
53-
Next, define a new service for that class.
54-
55-
.. code-block:: yaml
56-
57-
# config/services.yaml
58-
services:
59-
# ...
60-
61-
# use the fully-qualified class name as the service id
62-
App\Utils\Slugger:
63-
public: false
64-
65-
.. note::
66-
67-
If you're using the :ref:`default services.yml configuration <service-container-services-load-example>`,
68-
the class is auto-registered as a service.
69-
70-
Traditionally, the naming convention for a service was a short, but unique
71-
snake case key - e.g. ``app.utils.slugger``. But for most services, you should now
72-
use the class name.
61+
If you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`,
62+
this class is auto-registered as a service whose ID is ``App\Utils\Slugger`` (or
63+
simply ``Slugger::class`` if the class is already imported in your code).
7364

7465
.. best-practice::
7566

7667
The id of your application's services should be equal to their class name,
7768
except when you have multiple services configured for the same class (in that
7869
case, use a snake case id).
7970

80-
Now you can use the custom slugger in any controller class, such as the
81-
``AdminController``:
71+
Now you can use the custom slugger in any other service or controller class,
72+
such as the ``AdminController``:
8273

8374
.. code-block:: php
8475
8576
use App\Utils\Slugger;
8677
87-
public function createAction(Request $request, Slugger $slugger)
78+
public function create(Request $request, Slugger $slugger)
8879
{
8980
// ...
9081
91-
// you can also fetch a public service like this
92-
// but fetching services in this way is not considered a best practice
93-
// $slugger = $this->get(Slugger::class);
94-
9582
if ($form->isSubmitted() && $form->isValid()) {
9683
$slug = $slugger->slugify($post->getTitle());
9784
$post->setSlug($slug);
@@ -127,36 +114,6 @@ personal taste.
127114
We recommend YAML because it's friendly to newcomers and concise. You can
128115
of course use whatever format you like.
129116

130-
Service: No Class Parameter
131-
---------------------------
132-
133-
You may have noticed that the previous service definition doesn't configure
134-
the class namespace as a parameter:
135-
136-
.. code-block:: yaml
137-
138-
# config/services.yaml
139-
140-
# service definition with class namespace as parameter
141-
parameters:
142-
slugger.class: App\Utils\Slugger
143-
144-
services:
145-
app.slugger:
146-
class: '%slugger.class%'
147-
148-
This practice is cumbersome and completely unnecessary for your own services.
149-
150-
.. best-practice::
151-
152-
Don't define parameters for the classes of your services.
153-
154-
This practice was wrongly adopted from third-party bundles. When Symfony
155-
introduced its service container, some developers used this technique to easily
156-
allow overriding services. However, overriding a service by just changing its
157-
class name is a very rare use case because, frequently, the new service has
158-
different constructor arguments.
159-
160117
Using a Persistence Layer
161118
-------------------------
162119

best_practices/controllers.rst

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ Symfony follows the philosophy of *"thin controllers and fat models"*. This
55
means that controllers should hold just the thin layer of *glue-code*
66
needed to coordinate the different parts of the application.
77

8-
As a rule of thumb, you should follow the 5-10-20 rule, where controllers should
9-
only define 5 variables or less, contain 10 actions or less and include 20 lines
10-
of code or less in each action. This isn't an exact science, but it should
11-
help you realize when code should be refactored out of the controller and
12-
into a service.
8+
Your controller methods should just call to other services, trigger some events
9+
if needed and then return a response, but they should not contain any actual
10+
business logic. If they do, refactor it out of the controller and into a service.
1311

1412
.. best-practice::
1513

16-
Make your controller extend the FrameworkBundle base controller and use
17-
annotations to configure routing, caching and security whenever possible.
14+
Make your controller extend the ``AbstractController`` base controller
15+
provided by Symfony and use annotations to configure routing, caching and
16+
security whenever possible.
1817

1918
Coupling the controllers to the underlying framework allows you to leverage
2019
all of its features and increases your productivity.
@@ -33,6 +32,18 @@ Overall, this means you should aggressively decouple your business logic
3332
from the framework while, at the same time, aggressively coupling your controllers
3433
and routing *to* the framework in order to get the most out of it.
3534

35+
Controller Action Naming
36+
------------------------
37+
38+
.. best-practice::
39+
40+
Don't add the ``Action`` suffix to the methods of the controller actions.
41+
42+
The first Symfony versions required that controller method names ended in
43+
``Action`` (e.g. ``newAction()``, ``showAction()``). This suffix became optional
44+
when annotations were introduced for controllers. In modern Symfony applications
45+
this suffix is neither required nor recommended, so you can safely remove it.
46+
3647
Routing Configuration
3748
---------------------
3849

@@ -94,32 +105,32 @@ for the homepage of our app:
94105
namespace App\Controller;
95106
96107
use App\Entity\Post;
97-
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
108+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
98109
use Symfony\Component\Routing\Annotation\Route;
99110
100-
class DefaultController extends Controller
111+
class DefaultController extends AbstractController
101112
{
102113
/**
103114
* @Route("/", name="homepage")
104115
*/
105-
public function indexAction()
116+
public function index()
106117
{
107118
$posts = $this->getDoctrine()
108119
->getRepository(Post::class)
109120
->findLatest();
110121
111-
return $this->render('default/index.html.twig', array(
122+
return $this->render('default/index.html.twig', [
112123
'posts' => $posts,
113-
));
124+
]);
114125
}
115126
}
116127
117128
Fetching Services
118129
-----------------
119130

120-
If you extend the base ``Controller`` class, you can access services directly from
121-
the container via ``$this->container->get()`` or ``$this->get()``. But instead, you
122-
should use dependency injection to fetch services: most easily done by
131+
If you extend the base ``AbstractController`` class, you can't access services
132+
directly from the container via ``$this->container->get()`` or ``$this->get()``.
133+
Instead, you must use dependency injection to fetch services: most easily done by
123134
:ref:`type-hinting action method arguments <controller-accessing-services>`:
124135

125136
.. best-practice::
@@ -153,40 +164,41 @@ For example:
153164
/**
154165
* @Route("/{id}", name="admin_post_show")
155166
*/
156-
public function showAction(Post $post)
167+
public function show(Post $post)
157168
{
158169
$deleteForm = $this->createDeleteForm($post);
159170
160-
return $this->render('admin/post/show.html.twig', array(
171+
return $this->render('admin/post/show.html.twig', [
161172
'post' => $post,
162173
'delete_form' => $deleteForm->createView(),
163-
));
174+
]);
164175
}
165176
166-
Normally, you'd expect a ``$id`` argument to ``showAction()``. Instead, by
167-
creating a new argument (``$post``) and type-hinting it with the ``Post``
168-
class (which is a Doctrine entity), the ParamConverter automatically queries
169-
for an object whose ``$id`` property matches the ``{id}`` value. It will
170-
also show a 404 page if no ``Post`` can be found.
177+
Normally, you'd expect a ``$id`` argument to ``show()``. Instead, by creating a
178+
new argument (``$post``) and type-hinting it with the ``Post`` class (which is a
179+
Doctrine entity), the ParamConverter automatically queries for an object whose
180+
``$id`` property matches the ``{id}`` value. It will also show a 404 page if no
181+
``Post`` can be found.
171182

172183
When Things Get More Advanced
173184
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
174185

175-
The above example works without any configuration because the wildcard name ``{id}`` matches
176-
the name of the property on the entity. If this isn't true, or if you have
177-
even more complex logic, the easiest thing to do is just query for the entity
178-
manually. In our application, we have this situation in ``CommentController``:
186+
The above example works without any configuration because the wildcard name
187+
``{id}`` matches the name of the property on the entity. If this isn't true, or
188+
if you have even more complex logic, the easiest thing to do is just query for
189+
the entity manually. In our application, we have this situation in
190+
``CommentController``:
179191

180192
.. code-block:: php
181193
182194
/**
183195
* @Route("/comment/{postSlug}/new", name = "comment_new")
184196
*/
185-
public function newAction(Request $request, $postSlug)
197+
public function new(Request $request, $postSlug)
186198
{
187199
$post = $this->getDoctrine()
188200
->getRepository(Post::class)
189-
->findOneBy(array('slug' => $postSlug));
201+
->findOneBy(['slug' => $postSlug]);
190202
191203
if (!$post) {
192204
throw $this->createNotFoundException();
@@ -209,7 +221,7 @@ flexible:
209221
* @Route("/comment/{postSlug}/new", name = "comment_new")
210222
* @ParamConverter("post", options={"mapping": {"postSlug": "slug"}})
211223
*/
212-
public function newAction(Request $request, Post $post)
224+
public function new(Request $request, Post $post)
213225
{
214226
// ...
215227
}

0 commit comments

Comments
 (0)
0