Symfony best practices (official recommendation)
Web agency » Digital news » Symfony best practices (official recommendation)

Symfony best practices (official recommendation)

A few days ago, Fabien Potencier via Sensio, unveiled an e-book bringing together best practices and recommendations for developing a Symfony project. Compiled from recommendations posted by the community, the purpose of this document is to give a set of advice related to philoSophia from Symfony. I will try to briefly summarize the 31 tips described in English in this post.

Introduction

This document begins with some advice and a few sentences intended to inspire us with the document.

"This document is not a tutorial"

This document was written with the intention of being read by all Symfony developers. Both experts and neophytes.

"Don't refactor your apps"

From the beginning of the document, it is specifically explained that following this reading, we should not update and refactor our applications in order for it to fit perfectly with Sensio's multiple recommendations. We can therefore immediately make the link with the first ideas developed in the document: these good practices should allow us to simplify our lives, as well as simplify code review. To support my remarks, three reasons are advanced:

  • Your existing applications are not wrong, they just follow another set of guidelines;
  • A full codebase refactoring is prone to introduce errors in your applications;
  • The amount of work spent on this could be better dedicated to improving your tests or adding features that provide real value to the end users.

Create a project

In chapter 2 which deals with how to declare and create a Symfony project, a first “Best Practice” is given:

_Always use Composer. _Composer introduced the basics of modern PHP through its dependency resolution. Thanks to him, the dependencies registered in the file composer.json will be resolved and repatriated in your project so that it is usable. In addition, easy updates can be made.

In this way, you can respect the structural organization of a Symfony project. (see page 8).

Examples:

  • app/cache will store the cache;
  • app/logs store logs;
  • app/Resources will store the templates as well as the global translations for your application;
  • src / store your application bundles;
  • vendor / store your project's dependencies;
  • web / will store the assets (css, js, images, etc.) as well as the front file (app.php) allowing you to subsequently redirect your requests to your controllers.
    As a result, a new recommendation on bundles. Without doing my own analysis which could call into question the philosophie from Symfony (which I don't want), here is the recommendation:

Create a single AppBundle bundle for an application. This is due to the way Symfony was designed. Each bundle must be _stand-alone. _They should therefore be able to live autonomously and independently.

I leave you to meditate on this recommendation…

Configuration

The third chapter gives us advice to configure its application. Some configuration items may vary from a development system to a production system.

As the recommendation indicates, you must therefore define the configuration relating to your infrastructure in the file app/config/parameters.yml.

Conversely, the configuration specific to your project, which will be static, will be declared in app/config/config.yml.

Additionally, the file parameters.yml should not be versioned. This is the file parameters.yml.dist who should be. It is this file that will give you basic options for configuring your application. (recommendation page 12).

As a result, the file _app/config/config.yml _itself serves as a recommendation.

Set configuration related to your application in app/config/config.yml
Following this recommendation, however, it is advisable to generate a file config.yml, config_dev.yml and a file config_prod.yml. These files are intended to store the constants specific to the development environment and the production environment.
We must consider a value as constant when its value changes very little.

Dependency Injection

Here we are ! In the third chapter, a recommendation is detailed on the use of dependencies. I advise you to read this recommendation if my explanation seems vague to you.

This recommendation is about injecting services into services. Sensio recommends declaring them in a file src/AppBundle/Resources/config/services.yml.

I also invite you to read my article on dependency injection and how to limit the use of the super service container.

Declare internal logic

Chapter 4 covers organizing your application. Depending on your needs, the organization will differ. For global and non-business functions, Sensio recommends placing them in a folder Utils or simply take them out of your bundle and put it in a separate folder. Following this, it is strongly recommended that you declare your class as a service. A new recommendation is given to help us declare them.

The name of your service should be as short as possible, ideally a single word.

Choosing the file format

Being particularly attached to the Yaml format within Symfony, I imagine that this point will raise discord. Sensio uncompromisingly recommends using the Yaml format within applications. The bundles developed are shared between two formats: XML and Yaml.

Sensio decided to recommend the latter simply because it is more " user-friendly ". Using another format would not change the way your application works.

Do not define a variable for the declaration of your services

This is a practice that you will see a lot in certain bundles and yet Sensio warns you. Here is the example given in the cookbook :

1
2
3
4
5
6
7
8
#app/config/services.yml
# service definition with class namespace as parameter
parameters:
slugger.class: AppBundleUtilsSlugger
services:
slugger:
class:
“%slugger.class%”

Here is the example of a useless code. Declaring the class to use for a service is a mistake, since this variable could possibly be used elsewhere in the code. In addition, and this is the reason given by Sensio, it weighs down the creation of the service.

The choice of ORM

Sensio recommends the use of the Doctrine ORM within Symfony. Being a Doctrine user, its integration into Symfony is very advanced. This results in a recommendation which relaunches the logical organization of our projects. Entity declaration must keep logic inside a bundle. See example on page 18.

Declaration of the mapping

The declaration of the Doctrine mapping should preferably be carried out thanks to the annotations which are recommended within the framework of Doctrine but also for other applications.

Use annotations to declare entity mapping.
An example is given on page 19.

Installation of fixtures

Sensio advises without putting it forward, to set up fixtures. For the uninitiated, these are datasets that will be used by default to launch the application before even putting it into production.

Coding standards (end of chapter 4)

This is a part that Sensio has strangely almost eclipsed. I say strangely since it is an important part for me.

Symfony is coded respecting the PSR1 and PSR2 standards. These standards must be promoted within the community of Symfony developers but also with PHP developers. Symfony has posted an article to collect and highlight the " Coding Standards ". The leader of Sensio, Fabien Potencier has also put a tool on GitHub to perform checks on compliance with standards.

Controllers

When it comes to controllers, Symfony's philosophie “thin controllers and fat models”. That means controllers should stay lightweight. Each method (action) that will be accessed via a route represents an action. The code for each of these methods must be "light" and call and coordinate actions. These shares must be in services.

This model presents a controller as using application codes/parts.

A controller must follow a few rules

  • A controller must have a maximum of 5 variables.
  • A controller has a maximum of 10 actions.
  • Each action must contain a maximum of 20 lines.
    • A method can contain more than 20 lines, provided that the code used cannot be factorized.

      A controller should inherit Sensio's FrameworkBundle base controller, and use annotations to manage routes, caching, and security.

      The use of annotations is recommended, but the use of XML, YAML or PHP is also valid. The application should only use a single format.

Don't use the @Template

This is a recommendation for the less unexpected and yet the explanation is particularly powerful.

Do not use the @Template() annotation to configure the template used by the controller
The explanation is simple, the @Template uses a TemplateListener which is called when the event kernel.view is thrown.
During the production of this document, a test was carried out. The use of @Template takes 26ms before launching the generation whereas for this same page which would have been generated using " $this->render(…) », the waiting time of 5 milliseconds.

Use ParamConverter annotation

The use of this annotation makes it possible to automate the hydration of an entity.

Using the ParamConverter trick is good when it's simple and convenient.

templates

Chapter 6 details the template generation. It is without _fuss _what the document gives us in the wake of several recommendations.

  • Use the Twig template engine
    Sensio recommends Twig for many reasons. Besides being a engine widely used by the PHP community, both by PHP developers from-scratch, only by Symfony developers and other frameworks. Simplicity is emphasized and the promotion of their product is clearly visible. Finally, Sensio guarantees support for Symfony up to its version 3. You can also find the first transitions to make to prepare your code to migrate to version 3.
  • Store application templates in app/Resources/views.
    By default, developers have become accustomed to storing their templates in the folder Resources of each bundle. This is not a bad thing but Fabien Potencier recommends storing the global templates for your application in the folder mentioned above.
  • Define your Twig extensions in AppBundle/Twig and configure the services in app/config/services.yml.
    Twig extensions suffer from poor visibility. These extensions, however very useful, are sometimes used too systematically. Sensio presents this part that I find essential, as a way to inject business code into its templates. I recommend you to read how to declare a Twig extension.

Forms

Chapter 7 gives us instructions on using forms.

  • Define your forms in PHP classes.
    Symfony gives the possibility to generate PHP classes allowing the automated generation of forms for given entities. THE Forms as they are called, offers complete integration. The modification of entities is assisted. In addition, using Forms makes it possible to deport the management of forms from templates.
  • Do not declare a button to send its form.
    This is a surprising point, since this feature was introduced in Symfony 2.5. At the time of writing these lines, Symfony 2.6 is in the process of being accepted and has therefore not yet been officially released. We can therefore raise some questions about the usefulness of this functionality, it should not be used!Add buttons in templates rather than in Forms, why?
    In its explanation, the Sensio team explains that although this feature remains “user-friendly” and that it simplifies the implementation of forms, certain functionalities remain restricted.
    Example, if you add a button of type Submit with a "Create" label, this form cannot be reused for an edit page. You will then have to declare your form as a service to perform an inheritance, which is not recommended a little further down.
  • Don't use Twig functions form() and _formstart().
    A part dedicated to the rendering of templates has been set up. She teaches us that the use of Twig functions form() And _ form_start()_ is deprecated. Once again, this is an error for me since these functions have only recently been available.
    In Symfony, there are several ways to render a form. Of the different ways to render, the best way is one that offers maximum flexibility.
    Using the Twig functions listed above brings little benefit. Sensio talks about increased readability when using native HTML tags. Despite this argument, this represents a small benefit… Very small!

Declare your forms as services

Forms are PHP classes which means they can be declared as a service. You will be able to see this method in the bundles of FriendsOfSymfony. This method is not recommended by Sensio except in certain cases that I quote below:

  • If you want to reuse existing forms. Setting up the inheritance between two forms makes it possible to limit the rewriting of all the attributes.
  • If we want to embed collections of entities.
    The use of forms as services in the case of an add or edit form is not recommended because the use of a service loads the @container. Moreover, it will be difficult to understand that this service is used by a controller.

Attach (binder) its forms

Recently, Sensio began a constant migration of Symfony2 code to future Symfony3 code. Among the list of modifications that can be made (you can find them all in UPGRADE-3.md), is the new solution for binder the request sent by a form with the here which was created. The new methodology is detailed on page 21 of the Best practices.

Voice the extract presented:

1
2
3
4
5
6
7
8
9
10
11
12
13
public insurance function newAction(Request $request)
{
// build your form
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $ this->container->get(“doctrine.orm.default_entity_manager”);
$em->persist($post);
$em->flush();
return $ this->redirect($ this->generateUrl(“admin_post_show”), array(“id” => $post->getId()));
}
}

Internationalization

The purpose of the internationalization module is to translate the content from one language to another, depending on the region or the language of the user. More than recommendations data, this chapter has been written more to show the possibilities on this part which always remains sensitive.

In Chapter 8, the editors detail how to activate the translation.

what about the format

Symfony offers a multitude of translation formats:

  • PHP
  • Qt
  • .po
  • .mo
  • JSON
  • CSV
  • INI
  • and many others

    Use the XLIFF format for your translation files.
    This quote is obviously a Sensio recommendation. Yes but why ? A brief and precise answer is given to us.
    Among all the supported formats, only the XLIFF format and the gettext format are supported by professional translation tools. Since XLIFF is based on XML, it benefits from content validation.
    Symfony 2.6 brings a new feature allowing you to "comment" (add notes) in your XLIFF files. This is a big novelty, because the generation of translation files can cause problems for translators to understand the meaning of a sentence or the context in which it is used. In short, XLIFF is good!

Where to store our translation files?

This question introduces a Best Practice. Sensio recommends storing our files in app/Resources/translations.

Usually, we used to store our translations in each of our bundles. That wasn't a bad thing, but translations are generally considered global parts of our apps, just like global templates, that's why Sensio recommends that we store them in the app.

How to translate our applications?

Always use keys to translate your content.
So Best, I will not give my opinion since I have little opportunity to translate applications. Sensio advises that to translate a word like "Username", you should use a key such as label.username.

An example is given on page 35.

Security

Chapter 9 of the book, concerns a central part of Symfony; Security !

Symfony was designed to easily configure and authenticate users who would like to connect to our applications. This part is very complex and full of details, so it is a rough outline. You can find more documentation on the dedicated page.

Declare your firewalls

The configuration options are to be entered in the file security.yml which is in app/config.

Unless you have two different connections to your application (system and users), we recommend using only one entry firewall with the anonymous option activated.
Wow wow wow! Wait, I arrive on Symfony, I understood nothing!

What is a firewall?

You can find an article made in Wanadev, on how to declare and set up native authentication on Symfony. In this article, it is detailed what is a firewall.

A single firewall? Why a ?

If you have just read the article linked above, you may have noticed that several firewalls have been declared.

In the given article, the three names were giant, main et login. Despite this, the given rule is respected, only a firewall can be considered as a gateway.

  • giant : this firewall allows the debug-bar to display.
  • main : this firewall is our ENTRY POINT. Anonymous users will be able to log in as if to respect the rule. All routes beginning with / will use this entry.
  • login : it is a firewall. This will be our gateway in a single case: if we want to connect. This is essential to have access to a login form.
    The rule is therefore respected.

Define an encoding

Encoding passwords is a key decision. Multiple algorithms exist, such as sha (sha1, sha256, sha512, md5...).

When declaring an encoding (see how to declare an encoding), three parameters can be given.

  • The encryption algorithm (default: sha512);
  • The number of iterations (by default: 5000);
  • The encoding is base64 of the encoded password (default: true).
    Despite these default values, which I invite you to modify to modify them for your applications. Sensio also recommends using the algorithm bcrypt.

    Use bcrypt encoding to encrypt user passwords.
    Sensio explains how to use bcrypt. This includes a salting value. This limits attacks and is more resistant to brute-force type attacks. You can find more details in the Wikipedia article above.

Set permissions

During a connection, Symfony detects which firewall should be used. Subsequently, and even before accessing the controller, an access check is performed. They are defined in the file security.yml,

Sensio recommends:

  1. protect our "edge URL patterns", understand our global patterns (example: /admin);
  2. Use annotations @Security as much as possible ;
  3. Check the rights for a user via the service security.context (since Symfony 2.6, the service has evolved, see here);
  4. Define voters to easily manage road safety;
  5. Use the ACL to manage object and user rights.

Use @Security annotations

Sensio recommends using the @Security annotation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SensioBundleFrameworkExtraBundleConfigurationSecurity;
//…
/ **
* Displays a form to create a new Post entity.
* @Road(“/new”, name=”admin_post_new”)
* @Security("has_role('ROLE_ADMIN')")
*/
public insurance function newAction()
{
//…
}

Use expressions to make restrictions more complex

Sensio explains to us that this annotation also makes it possible to generate more complex conditions, such as a comparison of two attributes between two objects. This annotation may require the use of ParamConverter.

You can find more information in the document:

  • Generate Expressions
  • Convert attributes to objects

Access security in Twig

If you want to compare the connected user with the user of an object, you can access from Twig the user In progress.

1
2
3
{% if app.user … %}
...
{% endif %}

Manage your security easily

Managing security is often a sensitive part, and organizing our code is an essential part of successfully securing our application. The use of Vote is highly recommended. Other ways to organize your code can be considered. For example, we can transfer a decision to a method of our entity.

You can view the Sensio example on page 40.

This part is very little detailed compared to the possibilities that Symfony offers. I advise you to read a little more documentation on this part if you are faced with a security task.

Web Assets

Assets allow us to manage resources such as Javascript, CSS, fos fonts, images… so that they are accessible from your pages.

Your assets must be stored in the web/ folder
This way you can load your resources into your templates like this:

1
2
<link rel="style sheet" href=« {{ asset(‘css/bootstrap.min.css’) }}«  />
<script src=« {{ asset(‘js/jquery.min.js’) }}« ></script>

Keep the public web folder and everything stored in it.

Use Assetic

Assetic has multiple interests, such as file compilation. For example, Less, Sass, TypeScript files… For this reason, the folder websites cannot contain files such as files .less.

Use Assetic to compile, combine and minify your assets unless you are using GruntJS.

Learn more about Assetic

Assetic is a complete tool despite some drawbacks. You can find documentation on this module using the links below:

  • Use Assetic
  • Minify CSS and JS
  • Compress images
  • See the official documentation

Set up tests

Testing is recognized by most developers as essential. Yet only a minority implement them.

We will see how Sensio advises us to carry out our tests.

Perform unit tests

Unit tests are used to perform functional tests that will in turn test the logic of your application. Symfony has not determined any particular tools to test your tests. The tools PHPUnit et PhpSpec are cited.

Perform functional tests

Creating good scenarios for your functional tests is essential, and yet developers quickly run into problems implementing them.

Define a functional test to test if your page has loaded correctly.

Attempt to use hardcoded URLs rather than generating them through the generator.

Test JavaScript functionality

Many tools exist like Mink (a PHPUnit library) and CasperJS.

Generate datasets

This is often a concern for developers, generating datasets.

Sensio recommends the use of libraries Faker et Alice.

Conclusion

This article is taken from Best Practices for symfony. While remaining simple, this article aims to dissect the 50 pages of advice in order to guide new Symfony developers.

★ ★ ★ ★ ★