The Symfony Framework Best Practices¶

This article describes the best practices for developing web applications with Symfony that fit the philosophy envisioned by the original Symfony creators.

If you don’t agree with some of these recommendations, they might be a good starting point that you can then extend and fit to your specific needs. You can even ignore them completely and continue using your own best practices and methodologies. Symfony is flexible enough to adapt to your needs.

This article assumes that you already have experience developing Symfony applications. If you don’t, read first the Getting Started section of the documentation.

Tip Symfony provides a sample application called Symfony Demo that follows all these best practices, so you can experience them in practice.

Creating the Project¶ Use the Symfony Binary to Create Symfony Applications¶ The Symfony binary is an executable command created in your machine when you download Symfony. It provides multiple utilities, including the simplest way to create new Symfony applications: 1 $ symfony new my_project_name Under the hood, this Symfony binary command executes the needed Composer command to create a new Symfony application based on the current stable version. Use the Default Directory Structure¶ Unless your project follows a development practice that imposes a certain directory structure, follow the default Symfony directory structure. It’s flat, self-explanatory and not coupled to Symfony: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 your_project/ ├─ assets/ ├─ bin/ │ └─ console ├─ config/ │ ├─ packages/ │ └─ services.yaml ├─ migrations/ ├─ public/ │ ├─ build/ │ └─ index.php ├─ src/ │ ├─ Kernel.php │ ├─ Command/ │ ├─ Controller/ │ ├─ DataFixtures/ │ ├─ Entity/ │ ├─ EventSubscriber/ │ ├─ Form/ │ ├─ Repository/ │ ├─ Security/ │ └─ Twig/ ├─ templates/ ├─ tests/ ├─ translations/ ├─ var/ │ ├─ cache/ │ └─ log/ └─ vendor/

Configuration¶ Use Environment Variables for Infrastructure Configuration¶ These are the options that change from one machine to another (e.g. from your development machine to the production server) but which don’t change the application behavior. Use env vars in your project to define these options and create multiple .env files to configure env vars per environment. Use Secret for Sensitive Information¶ When your application has sensitive configuration - like an API key - you should store those securely via secrets. Use Parameters for Application Configuration¶ These are the options used to modify the application behavior, such as the sender of email notifications, or the enabled feature toggles. Their value doesn’t change per machine, so don’t define them as environment variables. Define these options as parameters in the config/services.yaml file. You can override these options per environment in the config/services_dev.yaml and config/services_prod.yaml files. Use Short and Prefixed Parameter Names¶ Consider using app. as the prefix of your parameters to avoid collisions with Symfony and third-party bundles/libraries parameters. Then, use just one or two words to describe the purpose of the parameter: 1 2 3 4 5 6 7 8 9 10 # config/services.yaml parameters : # don't do this: 'dir' is too generic and it doesn't convey any meaning app.dir : '...' # do this: short but easy to understand names app.contents_dir : '...' # it's OK to use dots, underscores, dashes or nothing, but always # be consistent and use the same format for all the parameters app.dir.contents : '...' app.contents-dir : '...' Use Constants to Define Options that Rarely Change¶ Configuration options like the number of items to display in some listing rarely change. Instead of defining them as service container parameters, define them as PHP constants in the related classes. Example: // src/Entity/Post.php namespace App\Entity ; class Post { public const NUMBER_OF_ITEMS = 10 ; // ... } The main advantage of constants is that you can use them everywhere, including Twig templates and Doctrine entities, whereas parameters are only available from places with access to the service container. The only notable disadvantage of using constants for this kind of configuration values is that it’s complicated to redefine their values in your tests.

Business Logic¶ Don’t Create any Bundle to Organize your Application Logic¶ When Symfony 2.0 was released, applications used bundles to divide their code into logical features: UserBundle, ProductBundle, InvoiceBundle, etc. However, a bundle is meant to be something that can be reused as a stand-alone piece of software. If you need to reuse some feature in your projects, create a bundle for it (in a private repository, to not make it publicly available). For the rest of your application code, use PHP namespaces to organize code instead of bundles. Use Autowiring to Automate the Configuration of Application Services¶ Service autowiring is a feature that reads the type-hints on your constructor (or other methods) and automatically passes the correct services to each method, making unnecessary to configure services explicitly and simplifying the application maintenance. Use it in combination with service autoconfiguration to also add service tags to the services needing them, such as Twig extensions, event subscribers, etc. Services Should be Private Whenever Possible¶ Make services private to prevent you from accessing those services via $container->get() . Instead, you will need to use proper dependency injection. Use the YAML Format to Configure your Own Services¶ If you use the default services.yaml configuration, most services will be configured automatically. However, in some edge cases you’ll need to configure services (or parts of them) manually. YAML is the format recommended to configure services because it’s friendly to newcomers and concise, but Symfony also supports XML and PHP configuration. Use Annotations to Define the Doctrine Entity Mapping¶ Doctrine entities are plain PHP objects that you store in some “database”. Doctrine only knows about your entities through the mapping metadata configured for your model classes. Doctrine supports several metadata formats, but it’s recommended to use annotations because they are by far the most convenient and agile way of setting up and looking for mapping information.

Controllers¶ Make your Controller Extend the AbstractController Base Controller¶ Symfony provides a base controller which includes shortcuts for the most common needs such as rendering templates or checking security permissions. Extending your controllers from this base controller couples your application to Symfony. Coupling is generally wrong, but it may be OK in this case because controllers shouldn’t contain any business logic. Controllers should contain nothing more than a few lines of glue-code, so you are not coupling the important parts of your application. Use Annotations to Configure Routing, Caching and Security¶ Using annotations for routing, caching and security simplifies configuration. You don’t need to browse several files created with different formats (YAML, XML, PHP): all the configuration is just where you need it and it only uses one format. Don’t Use Annotations to Configure the Controller Template¶ The @Template annotation is useful, but also involves some magic. Moreover, most of the time @Template is used without any parameters, which makes it more difficult to know which template is being rendered. It also hides the fact that a controller should always return a Response object. Use Dependency Injection to Get Services¶ If you extend the base AbstractController , you can only access to the most common services (e.g twig , router , doctrine , etc.), directly from the container via $this->container->get() or $this->get() . Instead, you must use dependency injection to fetch services by type-hinting action method arguments or constructor arguments. Use ParamConverters If They Are Convenient¶ If you’re using Doctrine, then you can optionally use the ParamConverter to automatically query for an entity and pass it as an argument to your controller. It will also show a 404 page if no entity can be found. If the logic to get an entity from a route variable is more complex, instead of configuring the ParamConverter, it’s better to make the Doctrine query inside the controller (e.g. by calling to a Doctrine repository method).

Templates¶ Use Snake Case for Template Names and Variables¶ Use lowercased snake_case for template names, directories and variables (e.g. user_profile instead of userProfile and product/edit_form.html.twig instead of Product/EditForm.html.twig ). Prefix Template Fragments with an Underscore¶ Template fragments, also called “partial templates”, allow to reuse template contents. Prefix their names with an underscore to better differentiate them from complete templates (e.g. _user_metadata.html.twig or _caution_message.html.twig ).

Forms¶ Define your Forms as PHP Classes¶ Creating forms in classes allows to reuse them in different parts of the application. Besides, not creating forms in controllers simplify the code and maintenance of the controllers. Add Form Buttons in Templates¶ Form classes should be agnostic to where they will be used. For example, the button of a form used to both create and edit items should change from “Add new” to “Save changes” depending on where it’s used. Instead of adding buttons in form classes or the controllers, it’s recommended to add buttons in the templates. This also improves the separation of concerns, because the button styling (CSS class and other attributes) is defined in the template instead of in a PHP class. However, if you create a form with multiple submit buttons you should define them in the controller instead of the template. Otherwise, you won’t be able to check which button was clicked when handling the form in the controller. Define Validation Constraints on the Underlying Object¶ Attaching validation constraints to form fields instead of to the mapped object prevents the validation from being reused in other forms or other places where the object is used. Use a Single Action to Render and Process the Form¶ Rendering forms and processing forms are two of the main tasks when handling forms. Both are too similar (most of the times, almost identical), so it’s much simpler to let a single controller action handle everything.

Internationalization¶ Use the XLIFF Format for Your Translation Files¶ Of all the translation formats supported by Symfony (PHP, Qt, .po , .mo , JSON, CSV, INI, etc.) XLIFF and gettext have the best support in the tools used by professional translators. And since it’s based on XML, you can validate XLIFF file contents as you write them. Symfony also supports notes in XLIFF files, making them more user-friendly for translators. At the end, good translations are all about context, and these XLIFF notes allow you to define that context. Use Keys for Translations Instead of Content Strings¶ Using keys simplifies the management of the translation files because you can change the original contents in templates, controllers and services without having to update all of the translation files. Keys should always describe their purpose and not their location. For example, if a form has a field with the label “Username”, then a nice key would be label.username , not edit_form.label.username .

Security¶ Define a Single Firewall¶ Unless you have two legitimately different authentication systems and users (e.g. form login for the main site and a token system for your API only), it’s recommended to have only one firewall to keep things simple. Additionally, you should use the anonymous key under your firewall. If you require users to be logged in for different sections of your site, use the access_control option. Use the auto Password Hasher¶ The auto password hasher automatically selects the best possible encoder/hasher depending on your PHP installation. Currently, it tries to use sodium by default and falls back to bcrypt . Use Voters to Implement Fine-grained Security Restrictions¶ If your security logic is complex, you should create custom security voters instead of defining long expressions inside the @Security annotation.

Web Assets¶ Use Webpack Encore to Process Web Assets¶ Web assets are things like CSS, JavaScript and image files that make the frontend of your site look and work great. Webpack is the leading JavaScript module bundler that compiles, transforms and packages assets for usage in a browser. Webpack Encore is a JavaScript library that gets rid of most of Webpack complexity without hiding any of its features or distorting its usage and philosophy. It was originally created for Symfony applications, but it works for any application using any technology.