Skip to content

Commit 7dd787b

Browse files
committed
Moved "Best Practices" to a dedicated article
1 parent 4d0bafa commit 7dd787b

File tree

12 files changed

+321
-923
lines changed

12 files changed

+321
-923
lines changed

_build/redirection_map

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,15 @@
460460
/testing/doctrine /testing/database
461461
/doctrine/lifecycle_callbacks /doctrine/events
462462
/doctrine/event_listeners_subscribers /doctrine/events
463+
/best_practices/index /best_practices
464+
/best_practices/introduction /best_practices
465+
/best_practices/creating-the-project /best_practices
466+
/best_practices/configuration /best_practices
467+
/best_practices/business-logic /best_practices
468+
/best_practices/controllers /best_practices
469+
/best_practices/templates /best_practices
470+
/best_practices/forms /best_practices
471+
/best_practices/i18n /best_practices
472+
/best_practices/security /best_practices
473+
/best_practices/web-assets /best_practices
474+
/best_practices/tests /best_practices

best_practices.rst

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
The Symfony Framework Best Practices
2+
====================================
3+
4+
The Symfony Framework is well-known for being *really* flexible and is used to
5+
build from micro-sites to enterprise applications. This guide describes the
6+
**best practices for developing web applications with Symfony** that fit the
7+
philosophy envisioned by the original Symfony creators. You should only read
8+
this guide if you already have experience developing Symfony applications.
9+
10+
If you don't agree with some of these recommendations, this guide might be a good
11+
**starting point** that you can then **extend and fit to your specific needs**.
12+
You can even ignore this guide completely and continue using your own best
13+
practices and methodologies. Symfony is flexible enough to adapt to your needs.
14+
15+
.. tip::
16+
17+
Symfony provides a sample application called `Symfony Demo`_ that follows
18+
all these best practices, so you can experience them in practice.
19+
20+
Creating the Project
21+
--------------------
22+
23+
24+
25+
26+
27+
28+
Configuration
29+
-------------
30+
31+
Use Environment Variables for Infrastructure Configuration
32+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33+
34+
These are the options that change from one machine to another (e.g. from your
35+
development machine to the production server) but which don't change the
36+
application behavior.
37+
38+
:ref:`Use env vars in your project <config-env-vars>` to define these options
39+
and create multiple ``.env`` files to :ref:`configure env vars per environment <config-dot-env>`.
40+
41+
Use Parameters for Application Configuration
42+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43+
44+
These are the options used to modify the application behavior, such as the sender
45+
of email notifications, or the enabled `feature toggles`_. Their value doesn't
46+
change per machine, so it's not worth it to define them as environment variables.
47+
48+
Define these options as :ref:`parameters <configuration-parameters>` in the
49+
``config/services.yaml`` file. You can override these options per
50+
:ref:`environment <configuration-environments>` in the ``config/services_dev.yaml``
51+
and ``config/services_prod.yaml`` files.
52+
53+
Use Short and Prefixed Parameter Names
54+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55+
56+
Consider using ``app.`` as the prefix of your :ref:`parameters <configuration-parameters>`
57+
to avoid collisions with Symfony and third-party bundles/libraries parameters.
58+
Then, use just one or two words to describe the purpose of the parameter:
59+
60+
.. code-block:: yaml
61+
62+
# config/services.yaml
63+
parameters:
64+
# don't do this: 'dir' is too generic and it doesn't convey any meaning
65+
app.dir: '...'
66+
# do this: short but easy to understand names
67+
app.contents_dir: '...'
68+
# it's OK to use dots, underscores, dashes or nothing, but always
69+
# be consistent and use the same format for all the parameters
70+
app.dir.contents: '...'
71+
app.contents-dir: '...'
72+
73+
Use Constants to Define Options that Rarely Change
74+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
75+
76+
Configuration options like the number of items to display in some listing rarely
77+
change. Instead of defining them as :ref:`service container parameters <configuration-parameters>`,
78+
define them as PHP constants in the related classes. Example::
79+
80+
// src/Entity/Post.php
81+
namespace App\Entity;
82+
83+
class Post
84+
{
85+
public const NUMBER_OF_ITEMS = 10;
86+
87+
// ...
88+
}
89+
90+
The main advantage of constants is that you can use them everywhere, including
91+
Twig templates and Doctrine entities, whereas parameters are only available
92+
from places with access to the :doc:`service container </service_container>`.
93+
94+
The only notable disadvantage of using constants for this kind of configuration
95+
values is that it's complicated to redefine their values in your tests.
96+
97+
Business Logic
98+
--------------
99+
100+
101+
102+
103+
104+
105+
106+
107+
Controllers
108+
-----------
109+
110+
Make your Controller Extend the ``AbstractController`` Base Controller
111+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
112+
113+
Symfony provides a :ref:`base controller <the-base-controller-classes-services>`
114+
which includes shortcuts for the most common needs such as rendering templates
115+
or checking security permissions.
116+
117+
Extending your controllers from this base controller couples your application
118+
to Symfony. Coupling is generally wrong, but it may be OK in this case because
119+
controllers shouldn't contain any business logic. Controllers should contain
120+
nothing more than a few lines of *glue-code*, so you are not coupling the
121+
important parts of your application.
122+
123+
Use Annotations to Configure Routing, Caching and Security
124+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125+
126+
Using annotations for routing, caching and security simplifies configuration.
127+
You don't need to browse tens of files created with different formats (YAML, XML,
128+
PHP): all the configuration is just where you need it and it only uses one format.
129+
130+
The only exception is when the annotations are too complex, such as when using
131+
long expressions in the ``@Security`` annotation or lots of variables and
132+
requirements in the ``@Route`` annotation.
133+
134+
Don't Use Annotations to Configure the Controller Template
135+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
136+
137+
The ``@Template`` annotation is useful, but also involves some *magic*.
138+
Moreover, most of the time ``@Template`` is used without any parameters, which
139+
makes it more difficult to know which template is being rendered. It also hides
140+
the fact that a controller should always return a ``Response`` object.
141+
142+
Use Dependency Injection to Get Services
143+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
144+
145+
If you extend the base ``AbstractController``, you can't access services
146+
directly from the container via ``$this->container->get()`` or ``$this->get()``.
147+
Instead, you must use dependency injection to fetch services by
148+
:ref:`type-hinting action method arguments <controller-accessing-services>` or
149+
constructor arguments.
150+
151+
Use ParamConverters If They Are Convenient
152+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
153+
154+
If you're using :doc:`Doctrine </doctrine>`, then you can *optionally* use the
155+
`ParamConverter`_ to automatically query for an entity and pass it as an argument
156+
to your controller. It will also show a 404 page if no entity can be found.
157+
158+
If the logic to get an entity from a route variable is more complex, instead of
159+
configuring the ParamConverter, it's better to make the Doctrine query inside
160+
the controller (e.g. by calling to a :ref:`Doctrine repository method </doctrine>`).
161+
162+
Templates
163+
---------
164+
165+
Use Twig to Create your Templates
166+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
167+
168+
:ref:`Twig <twig-language>` is more concise, readable and safe than PHP. Its
169+
performance is comparable because Symfony compiles Twig templates to PHP and
170+
caches the result.
171+
172+
Use Snake Case for Template Names and Variables
173+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
174+
175+
Use lowercased snake_case for template names, directories and variables (e.g.
176+
``user_profile`` instead of ``userProfile`` and ``product/edit_form.html.twig``
177+
instead of ``Product/EditForm.html.twig``).
178+
179+
Prefix Template Fragments with an Underscore
180+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
181+
182+
Template fragments, also called *"partial templates"*, allow to
183+
:ref:`reuse template contents <templates-reuse-contents>`. Prefix their names
184+
with an underscore to better differentiate them from complete templates (e.g.
185+
``_user_metadata.html.twig`` or ``_caution_message.html.twig``).
186+
187+
Forms
188+
-----
189+
190+
191+
192+
193+
194+
195+
196+
197+
Internationalization
198+
--------------------
199+
200+
Use the XLIFF Format for Your Translation Files
201+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
202+
203+
Of all the translation formats supported by Symfony (PHP, Qt, ``.po``, ``.mo``,
204+
JSON, CSV, INI, etc.) XLIFF and gettext have the best support in the tools used
205+
by professional translators. And since it's based on XML, you can validate XLIFF
206+
file contents as you write them.
207+
208+
Symfony also supports notes in XLIFF files, making them more user-friendly for
209+
translators. At the end, good translations are all about context, and these
210+
XLIFF notes allow you to define that context.
211+
212+
Use Keys for Translations Instead of Content Strings
213+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
214+
215+
Using keys simplifies the management of the translation files because you can
216+
change the original contents in templates, controllers and services without
217+
having to update all of the translation files.
218+
219+
Keys should always describe their *purpose* and *not* their location. For
220+
example, if a form has a field with the label "Username", then a nice key
221+
would be ``label.username``, *not* ``edit_form.label.username``.
222+
223+
Security
224+
--------
225+
226+
227+
228+
229+
230+
231+
232+
Web Assets
233+
----------
234+
235+
Use Webpack Encore to Process Web Assets
236+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
237+
238+
Web assets are things like CSS, JavaScript and image files that make the
239+
frontend of your site look and work great. `Webpack`_ is the leading JavaScript
240+
module bundler that compiles, transforms and packages assets for usage in a browser.
241+
242+
:doc:`Webpack Encore </frontend>` is a JavaScript library that gets rid of most
243+
of Webpack complexity without hiding any of its features or distorting its usage
244+
and philosophy. It was originally created for Symfony applications, but it works
245+
for any application using any technology.
246+
247+
Tests
248+
-----
249+
250+
Smoke Test your URLs
251+
~~~~~~~~~~~~~~~~~~~~
252+
253+
In software engineering, `smoke testing`_ consists of *"preliminary testing to
254+
reveal simple failures severe enough to reject a prospective software release"*.
255+
Using :ref:`PHPUnit data providers <testing-data-providers>` you can define a
256+
functional test that checks that all application URLs load successfully::
257+
258+
// tests/ApplicationAvailabilityFunctionalTest.php
259+
namespace App\Tests;
260+
261+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
262+
263+
class ApplicationAvailabilityFunctionalTest extends WebTestCase
264+
{
265+
/**
266+
* @dataProvider urlProvider
267+
*/
268+
public function testPageIsSuccessful($url)
269+
{
270+
$client = self::createClient();
271+
$client->request('GET', $url);
272+
273+
$this->assertResponseIsSuccessful();
274+
}
275+
276+
public function urlProvider()
277+
{
278+
yield ['/'];
279+
yield ['/posts'];
280+
yield ['/post/fixture-post-1'];
281+
yield ['/blog/category/fixture-category'];
282+
yield ['/archives'];
283+
// ...
284+
}
285+
}
286+
287+
Add this test while creating your application because it requires little effort
288+
and checks that none of your pages returns an error. Later you'll add more
289+
specific tests for each page.
290+
291+
Hardcode URLs in a Functional Test
292+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
293+
294+
In Symfony applications it's recommended to :ref:`generate URLs <routing-generating-urls>`
295+
using routes to automatically update all links when a URL changes. However, if a
296+
URL changes, your public website will break unless you set up a redirection to
297+
the new URL.
298+
299+
That's why it's recommended to use raw URLs in tests instead of generating them
300+
from routes. Whenever a route changes, tests will break and you'll know that
301+
you must set up a redirection.
302+
303+
.. _`Symfony Demo`: https://github.com/symfony/demo
304+
.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
305+
.. _`feature toggles`: https://en.wikipedia.org/wiki/Feature_toggle
306+
.. _`smoke testing`: https://en.wikipedia.org/wiki/Smoke_testing_(software)
307+
.. _`Webpack`: https://webpack.js.org/

0 commit comments

Comments
 (0)