Symfony 5.1



Most of the time, you’ll be working in src/, templates/ or config/.
When you install new packages, new directories will be created automatically when needed.

  • config/ Contains… configuration!. You will configure routes, services and packages.
  • src/ All your PHP code lives here.
  • templates/ All your Twig templates live here.
  • bin/ The famous bin/console file lives here (and other, less important executable files).
  • var/ This is where automatically-created files are stored, like cache files (var/cache/) and logs (var/log/).
  • vendor/ Third-party (i.e. “vendor”) libraries live here! These are downloaded via the Composer package manager.
  • public/ This is the document root for your project: you put any publicly accessible files here.

An autoloader is a tool that makes it possible to start using PHP classes without explicitly including the file containing the class.

Git


Ignorer un fichier

git update-index --assume-unchanged app/config/parameters.yml

Récupérer l’état d'un répertoire après le dernier commit

git reset --hard HEAD

Restaurer un fichier

git checkout HEAD fichier

Créer un nouveau commit qui annule les changements d’un commit précédent

git revert HEAD

Composer


Version

php composer.phar --version

Update

php composer.phar self-update

Rollback

php composer.phar self-update --rollback

Mettre à jour les dépendances

php composer.phar update

Update avec augmentation du memory_limit

php -d memory_limit=-1 composer.phar update

Symfony


Démarrer un nouveau projet Symfony

# run this if you are building a traditional web application
composer create-project symfony/website-skeleton my_project_name
# run this if you are building a microservice, console application or API
composer create-project symfony/skeleton my_project_name
# The Symfony Demo application
symfony new my_project_name --demo

Démarrer ou stoper l'application

symfony server:start
symfony server:stop
# ou
Ctrl+C dans la console

Vérifier les dépendances

symfony check:requirements

Vérifier les alertes de sécurité

symfony check:security

Liste des commandes de debug

php bin/console

Affiche les informations du projet

php bin/console about

Vider le cache

php bin/console cache:clear

Lister les routes

php bin/console debug:router

Affiche le détail d'une route

php bin/console debug:router app_lucky_number

Vérifier qu'une URL existe

php bin/console router:match /lucky/number/8

Lister les services

php bin/console debug:autowiring

Symfony Maker


Generate a new controller class

php bin/console make:controller BrandNewController

Generate an entire CRUD from a Doctrine entity

php bin/console make:crud Product

Twig


check all the application templates

php bin/console lint:twig

you can also check directories and individual templates

php bin/console lint:twig templates/email/
php bin/console lint:twig templates/article/recent_list.html.twig

you can also show the deprecated features used in your templates

php bin/console lint:twig --show-deprecations templates/email/

list general information

php bin/console debug:twig

filter output by any keyword

php bin/console debug:twig --filter=date

pass a template path to show the physical file which will be loaded

php bin/console debug:twig @Twig/Exception/error.html.twig

Symfony Flex


Flex executed a recipe, which is a set of automated instructions that tell Symfony how to integrate an external package. Flex recipes exist for many packages and have the ability to do a lot, like adding configuration files, creating directories, updating .gitignore and adding new config to your .env file. Flex automates the installation of packages so you can get back to coding.

Installing Packages

A common practice when developing Symfony applications is to install packages (Symfony calls them bundles) that provide ready-to-use features. Packages usually require some setup before using them (editing some file to enable the bundle, creating some file to add some initial config, etc.)
Most of the time this setup can be automated and that’s why Symfony includes Symfony Flex, a tool to simplify the installation/removal of packages in Symfony applications. Technically speaking, Symfony Flex is a Composer plugin that is installed by default when creating a new Symfony application and which automates the most common tasks of Symfony applications.

Symfony Flex modifies the behavior of the require, update, and remove Composer commands to provide advanced features. Consider the following example:

cd my-project/ composer require logger
If you execute that command in a Symfony application which doesn’t use Flex, you’ll see a Composer error explaining that logger is not a valid package name. However, if the application has Symfony Flex installed, that command installs and enables all the packages needed to use the official Symfony logger.

Symfony Packs

Sometimes a single feature requires installing several packages and bundles. Instead of installing them individually, Symfony provides packs, which are Composer metapackages that include several dependencies. For example, to add debugging features in your application, you can run the
composer require --dev debug
command. This installs the symfony/debug-pack, which in turn installs several packages like symfony/debug-bundle, symfony/monolog-bundle, symfony/var-dumper, etc. By default, when installing Symfony packs, your composer.json file shows the pack dependency (e.g. "symfony/debug-pack": "^1.0") instead of the actual packages installed. To show the packages, add the --unpack option when installing a pack:
composer require debug --dev --unpack
Or run this command to unpack the already installed packs:
composer unpack PACK_NAME
composer unpack debug
A controller is the PHP function you write that builds the page. You take the incoming request information and use it to create a Symfony Response object, which can hold HTML content, a JSON string or even a binary file like an image or PDF.

A controller is a PHP function you create that reads information from the Request object and creates and returns a Response object. The response could be an HTML page, JSON, XML, a file download, a redirect, a 404 error or anything else. The controller executes whatever arbitrary logic your application needs to render the content of a page.

In Symfony, a controller is usually a class method which is used to accept requests, and return a Response object. When mapped with a URL, a controller becomes accessible and its response can be viewed.
To facilitate the development of controllers, Symfony provides an AbstractController. It can be used to extend the controller class allowing access to some frequently used utilities such as render() and redirectToRoute(). The AbstractController also provides the createNotFoundException() utility which is used to return a page not found response.

But be careful not to confuse the terms front controller and controller. Your app will usually have just one front controller, which boots your code. You will have many controller functions: one for each page.

Generating URLs

# The generateUrl() method is just a helper method that generates the URL for a given route:
$url = $this->generateUrl('app_lucky_number', ['max' => 10]);

Redirecting

# If you want to redirect the user to another page, use the redirectToRoute() and redirect() methods:

use Symfony\Component\HttpFoundation\RedirectResponse;

// ... public function index() {
    // redirects to the "homepage" route
    return $this->redirectToRoute('homepage');

    // redirectToRoute is a shortcut for:
    // return new RedirectResponse($this->generateUrl('homepage'));

    // does a permanent - 301 redirect
    return $this->redirectToRoute('homepage', [], 301);

    // redirect to a route with parameters
    return $this->redirectToRoute('app_lucky_number', ['max' => 10]);

    // redirects to a route and maintains the original query string parameters
    return $this->redirectToRoute('blog_show', $request->query->all());

    // redirects externally
    return $this->redirect('http://symfony.com/doc');
}

Rendering Templates

# If you’re serving HTML, you’ll want to render a template. The render() method renders a template and puts that content into a Response object for you:

// renders templates/lucky/number.html.twig
return $this->render('lucky/number.html.twig', ['number' => $number]);

Fetching Services

# Symfony comes packed with a lot of useful classes and functionalities, called services. These are used for rendering templates, sending emails, querying the database and any other “work” you can think of.
# If you need a service in a controller, type-hint an argument with its class (or interface) name. Symfony will automatically pass you the service you need:

use Psr\Log\LoggerInterface;
// ...
/**
* @Route("/lucky/number/{max}")
*/
public function number($max, LoggerInterface $logger) {
    $logger->info('We are logging!');
    // ...
}

The Request object as a Controller Argument

# What if you need to read query parameters, grab a request header or get access to an uploaded file? That information is stored in Symfony’s Request object. To access it in your controller, add it as an argument and type-hint it with the Request class:

use Symfony\Component\HttpFoundation\Request;

public function index(Request $request, $firstName, $lastName) {
    $page = $request->query->get('page', 1);
    // ...
}

Managing the Session

# Symfony provides a session service that you can use to store information about the user between requests. Session is enabled by default, but will only be started if you read or write from it.
Session storage and other configuration can be controlled under the framework.session configuration in config/packages/framework.yaml.
To get the session, add an argument and type-hint it with Symfony\Component\HttpFoundation\Session\SessionInterface:

use Symfony\Component\HttpFoundation\Session\SessionInterface;

public function index(SessionInterface $session) {
    // stores an attribute for reuse during a later user request
    $session->set('foo', 'bar');

    // gets the attribute set by another controller in another request
    $foobar = $session->get('foobar');

    // uses a default value if the attribute doesn't exist
    $filters = $session->get('filters', []);
}

The Request and Response Object

# As mentioned earlier, Symfony will pass the Request object to any controller argument that is type-hinted with the Request class:

use Symfony\Component\HttpFoundation\Request;

public function index(Request $request) {
    $request->isXmlHttpRequest();
    // is it an Ajax request?
    $request->getPreferredLanguage(['en', 'fr']);

    // retrieves GET and POST variables respectively
    $request->query->get('page');
    $request->request->get('page');

    // retrieves SERVER variables
    $request->server->get('HTTP_HOST');

    // retrieves an instance of UploadedFile identified by foo
    $request->files->get('foo');

    // retrieves a COOKIE value
    $request->cookies->get('PHPSESSID');

    // retrieves an HTTP request header, with normalized, lowercase keys
    $request->headers->get('host');
    $request->headers->get('content-type');
}

Returning JSON Response

# To return JSON from a controller, use the json() helper method. This returns a JsonResponse object that encodes the data automatically:

// ... public function index() {
    // returns '{"username":"jane.doe"}' and sets the proper Content-Type header
    return $this->json(['username' => 'jane.doe']);

    // the shortcut defines three optional arguments
    return $this->json($data, $status = 200, $headers = [], $context = []);
}

# If the serializer service is enabled in your application, it will be used to serialize the data to JSON. Otherwise, the json_encode function is used.

Streaming File Responses

# You can use the file() helper to serve a file from inside a controller:

public function download() {
    // send the file contents and force the browser to download it
    return $this->file('/path/to/some_file.pdf');
}

# The file() helper provides some arguments to configure its behavior:

use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;

public function download() {
    // load the file from the filesystem $file = new File('/path/to/some_file.pdf');
    return $this->file($file);

    // rename the downloaded file
    return $this->file($file, 'custom_name.pdf');

    // display the file contents in the browser instead of downloading it
    return $this->file('invoice_3241.pdf', 'my_invoice.pdf', ResponseHeaderBag::DISPOSITION_INLINE);
}
Routes can be configured in YAML, XML, PHP or using annotations.
All formats provide the same features and performance, so choose your favorite.
Symfony recommends annotations because it’s convenient to put the route and controller in the same place.
Each route name must be unique in the application.

Routing

/** * @Route("/blog", name="blog_list") */ public function list() { // ... }

By default, routes match any HTTP verb (GET, POST, PUT, etc.) Use the methods option to restrict the verbs each route should respond to:

/** * @Route("/api/posts/{id}", methods={"GET","HEAD"}) */
public function show(int $id) {
    // ... return a JSON response with the post
}

/** * @Route("/api/posts/{id}", methods={"PUT"}) */
public function edit(int $id) {
    // ... edit a post
}

Route Parameters

In Symfony routes, variable parts are wrapped in { ... } and they must have a unique name. For example, the route to display the blog post contents is defined as /blog/{slug}

/**
* @Route("/blog/{slug}", name="blog_show")
*/
public function show(string $slug) {
    // $slug will equal the dynamic part of the URL
    // e.g. at /blog/yay-routing, then $slug='yay-routing'
    // ...
}

Routes can define any number of parameters, but each of them can only be used once on each route:

/blog/posts-about-{category}/page/{pageNumber}

Parameters Validation

Imagine that your application has a blog_show route (URL: /blog/{slug}) and a blog_list route (URL: /blog/{page}). Given that route parameters accept any value, there’s no way to differentiate both routes.
If the user requests /blog/my-first-post, both routes will match and Symfony will use the route which was defined first. To fix this, add some validation to the {page} parameter using the requirements option:

/** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */
public function list(int $page) {
    // ...
}

/** * @Route("/blog/{slug}", name="blog_show") */
public function show($slug) {
    // ...
}

If you prefer, requirements can be inlined in each parameter using the syntax {parameter_name<requirements>}.
This feature makes configuration more concise, but it can decrease route readability when requirements are complex:

/** * @Route("/blog/{page<\d+>}", name="blog_list") */ public function list(int $page) { // ... }

In the previous example, the URL of blog_list is /blog/{page}. If users visit /blog/1, it will match. But if they visit /blog, it will not match. As soon as you add a parameter to a route, it must have a value. You can make blog_list once again match when the user visits /blog by adding a default value for the {page} parameter. When using annotations, default values are defined in the arguments of the controller action. In the other configuration formats they are defined with the defaults option:

/** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */ public function list(int $page = 1) { // ... }

You can have more than one optional parameter (e.g. /blog/{slug}/{page}), but everything after an optional parameter must be optional.
For example, /{page}/blog is a valid path, but page will always be required (i.e. /blog will not match this route).

To give a null default value to any parameter, add nothing after the ? character (e.g. /blog/{page?}).
default values can also be inlined in each parameter using the syntax {parameter_name?default_value}. This feature is compatible with inlined requirements, so you can inline both in a single parameter:

/** * @Route("/blog/{page<\d+>?1}", name="blog_list") */ public function list(int $page) { // ... }

Priority Parameter

New in version 5.1: The priority parameter was introduced in Symfony 5.1
When defining a greedy pattern that matches many routes, this may be at the beginning of your routing collection and prevents any route defined after to be matched.
A priority optional parameter is available in order to let you choose the order of your routes, and it is only available when using annotations.

/** * This route has a greedy pattern and is defined first. * * @Route("/blog/{slug}", name="blog_show") */ public function show(string $slug) { // ... }

/** * This route could not be matched without defining a higher priority than 0. * * @Route("/blog/list", name="blog_list", priority=2) */ public function list() { // ... }

The priority parameter expects an integer value. Routes with higher priority are sorted before routes with lower priority. The default value when it is not defined is 0.

Parameter Conversion

A common routing need is to convert the value stored in some parameter (e.g. an integer acting as the user ID) into another value (e.g. the object that represents the user). This feature is called “param converter” and is only available when using annotations to define routes.
Now, keep the previous route configuration, but change the arguments of the controller action. Instead of string $slug, add BlogPost $post:

/** * @Route("/blog/{slug}", name="blog_show") */ public function show(BlogPost $post) { // $post is the object whose slug matches the routing parameter // ... }

If your controller arguments include type-hints for objects (BlogPost in this case), the “param converter” makes a database request to find the object using the request parameters (slug in this case). If no object is found, Symfony generates a 404 response automatically.

Extra Parameters

In the defaults option of a route you can optionally define parameters not included in the route configuration. This is useful to pass extra arguments to the controllers of the routes:

/** * @Route("/blog/{page}", name="blog_index", defaults={"page": 1, "title": "Hello world!"}) */ public function index(int $page, string $title) { // ... }

Slash Characters in Route Parameters

Route parameters can contain any values except the / slash character, because that’s the character used to separate the different parts of the URLs. For example, if the token value in the /share/{token} route contains a / character, this route won’t match.
A possible solution is to change the parameter requirements to be more permissive:

/** * @Route("/share/{token}", name="share", requirements={"token"=".+"}) */ public function share($token) { // ... }

Special Parameters

In addition to your own parameters, routes can include any of the following special parameters created by Symfony:

  • _controllerThis parameter is used to determine which controller and action is executed when the route is matched.
  • _formatThe matched value is used to set the “request format” of the Request object. This is used for such things as setting the Content-Type of the response (e.g. a json format translates into a Content-Type of application/json).
  • _fragmentUsed to set the fragment identifier, which is the optional last part of a URL that starts with a # character and is used to identify a portion of a document.
  • _localeUsed to set the locale on the request.

You can include these attributes (except _fragment) both in individual routes and in route imports.
Symfony defines some special attributes with the same name (except for the leading underscore) so you can define them easier:

/** * @Route( * "/articles/{_locale}/search.{_format}", * locale="en", * format="html", * requirements={ * "_locale": "en|fr", * "_format": "html|xml", * } * ) */ public function search() { }

Route Groups and Prefixes

It’s common for a group of routes to share some options (e.g. all routes related to the blog start with /blog) That’s why Symfony includes a feature to share route configuration.

/** * @Route("/blog", requirements={"_locale": "en|es|fr"}, name="blog_") */ class BlogController { /** * @Route("/{_locale}", name="index") */ public function index() { // ... } /** * @Route("/{_locale}/posts/{slug}", name="show") */ public function show(Post $post) { // ... } }

In this example, the route of the index() action will be called blog_index and its URL will be /blog/. The route of the show() action will be called blog_show and its URL will be /blog/{_locale}/posts/{slug}. Both routes will also validate that the _locale parameter matches the regular expression defined in the class annotation.

Getting the Route Name and Parameters

The Request object created by Symfony stores all the route configuration (such as the name and parameters) in the “request attributes”.
You can get this information in a controller via the Request object:

public function list(Request $request) { // ... $routeName = $request->attributes->get('_route'); $routeParameters = $request->attributes->get('_route_params'); // use this to get all the available attributes (not only routing ones): $allAttributes = $request->attributes->all(); }

In templates, use the Twig global app variable to get the request and its attributes:

{% set route_name = app.request.attributes.get('_route') %} {% set route_parameters = app.request.attributes.get('_route_params') %} {# use this to get all the available attributes (not only routing ones) #} {% set all_attributes = app.request.attributes.all %}

Generating URLs

Routing systems are bidirectional: 1) they associate URLs with controllers (as explained in the previous sections); 2) they generate URLs for a given route. Generating URLs from routes allows you to not write the <a href="..."> values manually in your HTML templates. Also, if the URL of some route changes, you only have to update the route configuration and all links will be updated.
To generate a URL, you need to specify the name of the route (e.g. blog_show) and the values of the parameters defined by the route (e.g. slug = my-blog-post).
For that reason each route has an internal name that must be unique in the application. If you don’t set the route name explicitly with the name option, Symfony generates an automatic name based on the controller and action.

Generating URLs in Controllers

If your controller extends from the AbstractController, use the generateUrl() helper:

// generate a URL with no route arguments
$signUpPage = $this->generateUrl('sign_up');

// generate a URL with route arguments
$userProfilePage = $this->generateUrl('user_profile', [ 'username' => $user->getUsername(), ]);

// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);

// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->generateUrl('sign_up', ['_locale' => 'nl']);

If you pass to the generateUrl() method some parameters that are not part of the route definition, they are included in the generated URL as a query string::

$this->generateUrl('blog', ['page' => 2, 'category' => 'Symfony']);
// the 'blog' route only defines the 'page' parameter; the generated URL is: /blog/2?category=Symfony

Generating URLs in JavaScript

If your JavaScript code is included in a Twig template, you can use the path() and url() Twig functions to generate the URLs and store them in JavaScript variables.
The escape() function is needed to escape any non-JavaScript-safe values:

<script> const route = "{{ path('blog_show', {slug: 'my-blog-post'})|escape('js') }}"; </script>

Security

Créer une gestion de compte

composer create-project symfony/website-skeleton my_project_name
composer require symfonycasts/verify-email-bundle
composer require symfony/google-mailer
composer require symfonycasts/reset-password-bundle

php bin/console make:user

php bin/console make:auth
php bin/console make:registration-form
php bin/console make:reset-password

php bin/console make:migration
# ajouter la version de MariaDB au .env: ?serverVersion=mariadb-10.4.13
php bin/console doctrine:migrations:sync-metadata-storage
php bin/console doctrine:migrations:migrate

.env
Update la ligne:
DATABASE_URL=mysql://DofusElevage:ES6yKA5RS5qg0ljy@127.0.0.1:3306/dofuselevage?serverVersion=mariadb-10.4.13

Décommenter & update la ligne:
MAILER_DSN=smtp://localhost

Denying Access, Roles and other Authorization

The process of authorization has two different sides:

  1. The user receives a specific set of roles when logging in (e.g. ROLE_ADMIN).
  2. You add code so that a resource (e.g. URL, controller) requires a specific “attribute” (most commonly a role like ROLE_ADMIN) in order to be accessed.

Every role must start with ROLE_ (otherwise, things won’t work as expected)
Other than the above rule, a role is just a string and you can invent what you need (e.g. ROLE_PRODUCT_ADMIN).

Add Code to Deny Access

There are two ways to deny access to something:

  1. access_control in security.yaml allows you to protect URL patterns (e.g. /admin/*). Simpler, but less flexible;
  2. in your controller (or other code).

Securing URL patterns (access_control)

The most basic way to secure part of your app is to secure an entire URL pattern in security.yaml. For example, to require ROLE_ADMIN for all URLs that start with /admin, you can:

access_control:
# require ROLE_ADMIN for /admin*
- { path: '^/admin', roles: ROLE_ADMIN }

# or require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin*
- { path: '^/admin', roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }

# the 'path' value can be any valid regular expression
# (this one will match URLs like /api/post/7298 and /api/comment/528491)
- { path: ^/api/(post|comment)/\d+$, roles: ROLE_USER }

Securing Controllers and other Code

public function adminDashboard()
{
    $this->denyAccessUnlessGranted('ROLE_ADMIN');

    // or add an optional message - seen by developers
    $this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'User tried to access a page without having ROLE_ADMIN');
}
  1. If the user isn’t logged in yet, they will be asked to log in (e.g. redirected to the login page).
  2. If the user is logged in, but does not have the ROLE_ADMIN role, they’ll be shown the 403 access denied page (which you can customize).

Thanks to the SensioFrameworkExtraBundle, you can also secure your controller using annotations:

use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

/**
 * Require ROLE_ADMIN for *every* controller method in this class.
 *
 * @IsGranted("ROLE_ADMIN")
 */
class AdminController extends AbstractController
{
    /**
     * Require ROLE_ADMIN for only this controller method.
     *
     * @IsGranted("ROLE_ADMIN")
     */
    public function adminDashboard()
    {
        // ...
    }
}

Access Control in Templates

{% if is_granted('ROLE_ADMIN') %}
    <a href="...">Delete</a>
{% endif %}

Checking to see if a User is Logged In (IS_AUTHENTICATED_FULLY)

If you only want to check if a user is logged in (you don’t care about roles), you have two options. First, if you’ve given every user ROLE_USER, you can just check for that role. Otherwise, you can use a special “attribute” in place of a role:

public function adminDashboard()
{
    $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

    // ...
}
  • IS_AUTHENTICATED_REMEMBERED All logged in users have this, even if they are logged in because of a “remember me cookie”. Even if you don’t use the remember me functionality, you can use this to check if the user is logged in.
  • IS_AUTHENTICATED_FULLY This is similar to IS_AUTHENTICATED_REMEMBERED, but stronger. Users who are logged in only because of a “remember me cookie” will have IS_AUTHENTICATED_REMEMBERED but will not have IS_AUTHENTICATED_FULLY.
  • IS_AUTHENTICATED_ANONYMOUSLY All users (even anonymous ones) have this - this is useful when whitelisting URLs to guarantee access - some details are in How Does the Security access_control Work?.
  • IS_ANONYMOUS Only anonymous users are matched by this attribute.
  • IS_REMEMBERED Only users authenticated using the remember me functionality, (i.e. a remember-me cookie).
  • IS_IMPERSONATOR When the current user is impersonating another user in this session, this attribute will match.

Vérifier que le user dispose d'un ROLE

// GOOD - use of the normal security methods
$hasAccess = $this->isGranted('ROLE_ADMIN');
$this->denyAccessUnlessGranted('ROLE_ADMIN');

Creating and Using Templates

A template is the best way to organize and render HTML from inside your application, whether you need to render HTML from a controller or generate the contents of an email. Templates in Symfony are created with Twig: a flexible, fast, and secure template engine.

Twig syntax is based on these three constructs:

  • {{ ... }}, used to display the content of a variable or the result of evaluating an expression;
  • {% ... %}, used to run some logic, such as a conditional or a loop;
  • {# ... #}, used to add comments to the template (unlike HTML comments, these comments are not included in the rendered page).
// the template path is the relative file path from `templates/`
return $this->render('user/notifications.html.twig', [
    // this array defines the variables passed to the template,
    // where the key is the variable name and the value is the variable value
    // (Twig recommends using snake_case variable names: 'foo_bar' instead of 'fooBar')
    'user_first_name' => $userFirstName,
    'notifications' => $userNotifications,
]);

Template Naming

Symfony recommends the following for template names:
Use snake case for filenames and directories (e.g. blog_posts.twig, admin/default_theme/blog/index.twig, etc.);
Define two extensions for filenames (e.g. index.html.twig or blog_posts.xml.twig) being the first extension (html, xml, etc.) the final format that the template will generate.
Although templates usually generate HTML contents, they can generate any text-based format. That’s why the two-extension convention simplifies the way templates are created and rendered for multiple formats.

Template Location

Templates are stored by default in the templates/ directory. When a service or controller renders the product/index.html.twig template, they are actually referring to the /templates/product/index.html.twig file.
Twig provides quick access to complex PHP variables. Consider the following template:

{{ user.name }} added this comment on {{ comment.publishedAt|date }}

The user.name notation means that you want to display some information (name) stored in a variable (user). Is user an array or an object? Is name a property or a method? In Twig this doesn’t matter. When using the foo.bar notation, Twig tries to get the value of the variable in the following order:

  1. $foo['bar'] (array and element);
  2. $foo->bar (object and public property);
  3. $foo->bar() (object and public method);
  4. $foo->getBar() (object and getter method);
  5. $foo->isBar() (object and isser method);
  6. $foo->hasBar() (object and hasser method);

If none of the above exists, use null.
This allows to evolve your application code without having to change the template code (you can start with array variables for the application proof of concept, then move to objects with methods, etc.)

Linking to Pages

Instead of writing the link URLs by hand, use the path() function to generate URLs based on the routing configuration.

/** * @Route("/", name="blog_index") */ public function index() { // ... }
<a href="{{ path('blog_index') }}">Homepage</a>

The path() function generates relative URLs. If you need to generate absolute URLs (for example when rendering templates for emails or RSS feeds), use the url() function, which takes the same arguments as path() (e.g. <a href="{{ url('blog_index') }}"> ... </a>).

Linking to CSS, JavaScript and Image Assets

{# the image lives at "public/images/logo.png" #}
<img src="{{ asset('images/logo.png') }}" alt="Symfony!"/>

{# the CSS file lives at "public/css/blog.css" #}
<link href="{{ asset('css/blog.css') }}" rel="stylesheet"/>

{# the JS file lives at "public/bundles/acme/js/loader.js" #}
<script src="{{ asset('bundles/acme/js/loader.js') }}"></script>

If you’d like help packaging, versioning and minifying your JavaScript and CSS assets in a modern way, read about Symfony’s Webpack Encore.

If you need absolute URLs for assets, use the absolute_url() Twig function as follows:

<img src="{{ absolute_url(asset('images/logo.png')) }}" alt="Symfony!"/>

<link rel="shortcut icon" href="{{ absolute_url('favicon.png') }}">

The App Global Variable

Symfony creates a context object that is injected into every Twig template automatically as a variable called app. It provides access to some application information:

Username: {{ app.user.username ?? 'Anonymous user' }}

{% if app.debug %} <p>Request method: {{ app.request.method }}</p> <p>Application Environment: {{ app.environment }}</p> {% endif %}

The app variable (which is an instance of Symfony\Bridge\Twig\AppVariable) gives you access to these variables:

  • app.user The current user object or null if the user is not authenticated.
  • app.request The Symfony\Component\HttpFoundation\Request object that stores the current request data (depending on your application, this can be a sub-request or a regular request).
  • app.session The Symfony\Component\HttpFoundation\Session\Session object that represents the current user’s session or null if there is none.
  • app.flashes An array of all the flash messages stored in the session. You can also get only the messages of some type (e.g. app.flashes('notice')).
  • app.environment The name of the current configuration environment (dev, prod, etc).
  • app.debug True if in debug mode. False otherwise.
  • app.token A Symfony\Component\Security\Core\Authentication\Token\TokenInterface object representing the security token.

In addition to the global app variable injected by Symfony, you can also inject variables automatically to all Twig templates.

Rendering a Template in Controllers

If your controller extends from the AbstractController, use the render() helper:

// src/Controller/ProductController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class ProductController extends AbstractController
{
    public function index()
    {
        // ...

        // the `render()` method returns a `Response` object with the
        // contents created by the template
        return $this->render('product/index.html.twig', [
            'category' => '...',
            'promotions' => ['...', '...'],
        ]);

        // the `renderView()` method only returns the contents created by the
        // template, so you can use those contents later in a `Response` object
        $contents = $this->renderView('product/index.html.twig', [
            'category' => '...',
            'promotions' => ['...', '...'],
        ]);

        return new Response($contents);
    }
}

Checking if a Template Exists

Templates are loaded in the application using a Twig template loader, which also provides a method to check for template existence. First, get the loader:

// in a controller extending from AbstractController
$loader = $this->get('twig')->getLoader();

Then, pass the path of the Twig template to the exists() method of the loader:

if ($loader->exists('theme/layout_responsive.html.twig')) {
    // the template exists, do something
    // ...
}

Including Templates

First, create a new Twig template called blog/_user_profile.html.twig (the _ prefix is optional, but it’s a convention used to better differentiate between full templates and template fragments).
Then, remove that content from the original blog/index.html.twig template and add the following to include the template fragment:

{# templates/blog/index.html.twig #}

{# ... #}
{{ include('blog/_user_profile.html.twig') }}

You can also pass variables to the included template. This is useful for example to rename variables. Imagine that your template stores the user information in a variable called blog_post.author instead of the user variable that the template fragment expects. Use the following to rename the variable:

{# templates/blog/index.html.twig #}

{# ... #}
{{ include('blog/_user_profile.html.twig', {user: blog_post.author}) }}

Embedding Controllers

You can call to this controller from any template to embed its result:

{# templates/base.html.twig #}

{# ... #}
<div id="sidebar">
    {# if the controller is associated with a route, use the path() or url() functions #}
    {{ render(path('latest_articles', {max: 3})) }}
    {{ render(url('latest_articles', {max: 3})) }}

    {# if you don't want to expose the controller with a public URL,
       use the controller() function to define the controller to execute #}
    {{ render(controller(
        'App\\Controller\\BlogController::recentArticles', {max: 3}
    )) }}
</div>

Embedding controllers requires making requests to those controllers and rendering some templates as result. This can have a significant impact on the application performance if you embed lots of controllers. If possible, cache the template fragment.

Template Inheritance and Layouts

As your application grows you’ll find more and more repeated elements between pages, such as headers, footers, sidebars, etc. Including templates and embedding controllers can help, but when pages share a common structure, it’s better to use inheritance.

Symfony recommends the following three-level template inheritance for medium and complex applications:

  • templates/base.html.twig, defines the common elements of all application templates, such as <head>, <header>, <footer>, etc.;
  • templates/layout.html.twig, extends from base.html.twig and defines the content structure used in all or most of the pages, such as a two-column content + sidebar layout. Some sections of the application can define their own layouts (e.g. templates/blog/layout.html.twig);
  • templates/*.html.twig, the application pages which extend from the main layout.html.twig template or any other section layout.

Output Escaping

If you are rendering a variable that is trusted and contains HTML contents, use the Twig raw filter to disable the output escaping for that variable:

<h1>{{ product.title|raw }}</h1>

Template Namespaces

# config/packages/twig.yaml
twig:
    # ...
    paths:
        'email/default/templates': 'email'
        'backend/templates': 'admin'

Now, if you render the layout.html.twig template, Symfony will render the templates/layout.html.twig file. Use the special syntax @ + namespace to refer to the other namespaced templates (e.g. @email/layout.html.twig and @admin/layout.html.twig).

Basic Usage

Symfony provides a session service that is injected in your services and controllers if you type-hint an argument with Symfony\Component\HttpFoundation\Session\SessionInterface:

use Symfony\Component\HttpFoundation\Session\SessionInterface;

class SomeService
{
    private $session;

    public function __construct(SessionInterface $session)
    {
        $this->session = $session;
    }

    public function someMethod()
    {
        // stores an attribute in the session for later reuse
        $this->session->set('attribute-name', 'attribute-value');

        // gets an attribute by name
        $foo = $this->session->get('foo');

        // the second argument is the value returned when the attribute doesn't exist
        $filters = $this->session->get('filters', []);

        // ...
    }
}

Avoid Starting Sessions for Anonymous Users

Sessions are automatically started whenever you read, write or even check for the existence of data in the session. This may hurt your application performance because all users will receive a session cookie. In order to prevent that, you must completely avoid accessing the session.

For example, if your templates include some code to display the flash messages, sessions will start even if the user is not logged in and even if you haven’t created any flash messages. To avoid this behavior, add a check before trying to access the flash messages:

{# this check prevents starting a session when there are no flash messages #}
{% if app.request.hasPreviousSession %}
    {% for message in app.flashes('notice') %}
        <div class="flash-notice">
            {{ message }}
        </div>
    {% endfor %}
{% endif %}