Skip to content

Commit 9794c0b

Browse files
committed
Updated more contents
1 parent b997c1e commit 9794c0b

File tree

7 files changed

+113
-116
lines changed

7 files changed

+113
-116
lines changed

best_practices/business-logic.rst

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,24 @@ 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 add the following
36-
``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.
39+
It reads the type-hints on your constructor (or other methods) and automatically
40+
passes you the correct services. It can also add :doc:`service tags </service_container/tags>`
41+
to the services needed them, such as Twig extensions, event subscribers, etc.
42+
43+
The blog application needs a utility that can transform a post title (e.g.
44+
"Hello World") into a slug (e.g. "hello-world") to include it as part of the
45+
post URL. Let's create a new ``Slugger`` class inside ``src/Utils/``:
3746

3847
.. code-block:: php
3948
@@ -42,11 +51,9 @@ Let's create a new ``Slugger`` class inside ``src/Utils/`` and add the following
4251
4352
class Slugger
4453
{
45-
public function slugify($string)
54+
public function slugify(string $value): string
4655
{
47-
return preg_replace(
48-
'/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string)))
49-
);
56+
// ...
5057
}
5158
}
5259
@@ -71,14 +78,10 @@ such as the ``AdminController``:
7178
7279
use App\Utils\Slugger;
7380
74-
public function createAction(Request $request, Slugger $slugger)
81+
public function create(Request $request, Slugger $slugger)
7582
{
7683
// ...
7784
78-
// you can also fetch a public service like this
79-
// but fetching services in this way is not considered a best practice
80-
// $slugger = $this->get(Slugger::class);
81-
8285
if ($form->isSubmitted() && $form->isValid()) {
8386
$slug = $slugger->slugify($post->getTitle());
8487
$post->setSlug($slug);

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
}

best_practices/creating-the-project.rst

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@ Installing Symfony
66

77
.. best-practice::
88

9-
Use the `Symfony Skeleton`_ and `Composer`_ to create new Symfony-based projects.
9+
Use Composer and Symfony Flex to create and manage Symfony applications.
1010

11-
The **Symfony Skeleton** is a minimal and empty Symfony project which you can
11+
`Composer`_ is the package manager used by modern PHP application to manage their
12+
dependencies. `Symfony Flex`_ is a Composer plugin designed to automatize some
13+
of the most common tasks performed in Symfony applications. Using Flex is optional
14+
but recommended because it improves your productivity significantly.
15+
16+
.. best-practice::
17+
18+
Use the Symfony Skeleton to create new Symfony-based projects.
19+
20+
The `Symfony Skeleton`_ is a minimal and empty Symfony project which you can
1221
base your new projects on. Unlike past Symfony versions, this skeleton installs
1322
the absolute bare minimum amount of dependencies to make a fully working Symfony
1423
project. Read the :doc:`/setup` article to learn more about installing Symfony.

best_practices/forms.rst

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ PHP class::
4242

4343
public function configureOptions(OptionsResolver $resolver)
4444
{
45-
$resolver->setDefaults(array(
45+
$resolver->setDefaults([
4646
'data_class' => Post::class,
47-
));
47+
]);
4848
}
4949
}
5050

@@ -59,7 +59,7 @@ To use the class, use ``createForm()`` and pass the fully qualified class name::
5959
use App\Form\PostType;
6060

6161
// ...
62-
public function newAction(Request $request)
62+
public function new(Request $request)
6363
{
6464
$post = new Post();
6565
$form = $this->createForm(PostType::class, $post);
@@ -113,14 +113,14 @@ some developers configure form buttons in the controller::
113113
{
114114
// ...
115115

116-
public function newAction(Request $request)
116+
public function new(Request $request)
117117
{
118118
$post = new Post();
119119
$form = $this->createForm(PostType::class, $post);
120-
$form->add('submit', SubmitType::class, array(
120+
$form->add('submit', SubmitType::class, [
121121
'label' => 'Create',
122122
'attr' => ['class' => 'btn btn-default pull-right'],
123-
));
123+
]);
124124

125125
// ...
126126
}
@@ -168,7 +168,7 @@ Handling a form submit usually follows a similar template:
168168

169169
.. code-block:: php
170170
171-
public function newAction(Request $request)
171+
public function new(Request $request)
172172
{
173173
// build the form ...
174174
@@ -179,17 +179,16 @@ Handling a form submit usually follows a similar template:
179179
$em->persist($post);
180180
$em->flush();
181181
182-
return $this->redirect($this->generateUrl(
183-
'admin_post_show',
184-
array('id' => $post->getId())
185-
));
182+
return $this->redirectToRoute('admin_post_show', [
183+
'id' => $post->getId()
184+
]);
186185
}
187186
188187
// render the template
189188
}
190189
191190
We recommend that you use a single action for both rendering the form and
192-
handling the form submit. For example, you *could* have a ``newAction()`` that
193-
*only* renders the form and a ``createAction()`` that *only* processes the form
191+
handling the form submit. For example, you *could* have a ``new()`` action that
192+
*only* renders the form and a ``create()`` action that *only* processes the form
194193
submit. Both those actions will be almost identical. So it's much simpler to let
195-
``newAction()`` handle everything.
194+
``new()`` handle everything.

best_practices/security.rst

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Using ``@Security``, this looks like:
116116
* @Route("/new", name="admin_post_new")
117117
* @Security("has_role('ROLE_ADMIN')")
118118
*/
119-
public function newAction()
119+
public function new()
120120
{
121121
// ...
122122
}
@@ -139,7 +139,7 @@ method on the ``Post`` object:
139139
* @Route("/{id}/edit", name="admin_post_edit")
140140
* @Security("user.getEmail() == post.getAuthorEmail()")
141141
*/
142-
public function editAction(Post $post)
142+
public function edit(Post $post)
143143
{
144144
// ...
145145
}
@@ -194,7 +194,7 @@ Now you can reuse this method both in the template and in the security expressio
194194
* @Route("/{id}/edit", name="admin_post_edit")
195195
* @Security("post.isAuthor(user)")
196196
*/
197-
public function editAction(Post $post)
197+
public function edit(Post $post)
198198
{
199199
// ...
200200
}
@@ -222,7 +222,7 @@ more advanced use-case, you can always do the same security check in PHP:
222222
/**
223223
* @Route("/{id}/edit", name="admin_post_edit")
224224
*/
225-
public function editAction($id)
225+
public function edit($id)
226226
{
227227
$post = $this->getDoctrine()
228228
->getRepository(Post::class)
@@ -282,7 +282,7 @@ the same ``getAuthorEmail()`` logic you used above:
282282
283283
protected function supports($attribute, $subject)
284284
{
285-
if (!in_array($attribute, array(self::CREATE, self::EDIT))) {
285+
if (!in_array($attribute, [self::CREATE, self::EDIT])) {
286286
return false;
287287
}
288288
@@ -306,7 +306,7 @@ the same ``getAuthorEmail()`` logic you used above:
306306
switch ($attribute) {
307307
// if the user is an admin, allow them to create new posts
308308
case self::CREATE:
309-
if ($this->decisionManager->decide($token, array('ROLE_ADMIN'))) {
309+
if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
310310
return true;
311311
}
312312
@@ -338,7 +338,7 @@ Now, you can use the voter with the ``@Security`` annotation:
338338
* @Route("/{id}/edit", name="admin_post_edit")
339339
* @Security("is_granted('edit', post)")
340340
*/
341-
public function editAction(Post $post)
341+
public function edit(Post $post)
342342
{
343343
// ...
344344
}
@@ -351,7 +351,7 @@ via the even easier shortcut in a controller:
351351
/**
352352
* @Route("/{id}/edit", name="admin_post_edit")
353353
*/
354-
public function editAction($id)
354+
public function edit($id)
355355
{
356356
$post = ...; // query for the post
357357

0 commit comments

Comments
 (0)