Après ce premier article, Préparer à la Certification Symfony 4, il est temps de partager avec vous mes propres notes par rapport à ce sujet de certification.

Sans beaucoup parler, on rentre directement sur le sujet, ça va être sous forme de point avec des exemples de code. un mélange entre Français et Anglais.

Les exemples données concernent Symfony4.

Setup

  • make sure you are using php 7.1 or higher 
  • composer create-project symfony/website-skeleton my-project
  • for development, it’s convenient to use the Symfony PHP web server
  • Symfony code is released under the MIT licence

Auto-installing Recipes with Symfony Flex

  • when you ran composer require annotations, two special things happened, thanks to a composer plugin called flex
  • Symfony Flex automates the most common tasks of Symfony applications, like installing and removing bundles and other Composer dependencies. Symfony Flex works for Symfony 3.3 and higher. Starting from Symfony 4.0, Flex should be used by default, but it is still optional.
  • Symfony Flex is a composer plugin that modifies the behavior of the require, update, and remove commands. When installing or removing dependencies in a Flex-enabled application, Symfony can perform tasks before and after the execution of Composer tasks.
  • Symfony Flex asks about the mailer package and the Symfony Flex server detects that mailer is in fact an alias for SwiftmailerBundle and returns the « recipe » for it.
  • Using Symfony Flex is optional, even in Symfony 4, where Flex is used by default. However, Flex is so convenient and improves your productivity so much that it’s strongly recommended to upgrade your existing applications to it.
  • annotations is not a real package name : it’s an alias (shortcut) that Flex resolves to sensio/framework-extra-bundles
  • after this package was downloaded, 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 packagees

Commandes à savoir

  • php bin/console debug:router =>  Displays current routes for an application
  • php bin/console debug:event-dispatcher => Displays configured listeners for an application (for all event)
  • php bin/console debug:container => Displays current services for an application
  • php bin/console debug:config => Dumps the current config for an extension (bundle)
  • php bin/console debug:autowiring =>Lists classes/interfaces you can use for autowiring 

Container de services

  • Le composant Dependency Injection est fourni avec un container de services. 
  • Chaque service est enregistré dans un container ainsi que les étapes nécessaires à sa construction : dépendances, méthodes et arguments à appeler
  • Puisque un service est présent dans le container de services, on peut l’injecter dans nos classes. C’est grâce à l’une des fonctionnalités les plus utiles du container de services. l’autowiring
  • In Symfony each services lives inside a very special object called the service container. The container allows you to centralize the way objects are created 
    • If you never ask for the servce , it’s never constructed 
    • the service is only created once : the same instance is returned each time you ask for it  => (shared = true)
  • By putting the bind key under _defaults, you can specify the value of any argument for any service defined in this file. You can bind arguemnts by name or by type(\Psr\Log\LoggerInterface)
  • The id of each service is its FQCN. You can override any service that’s imported by using its id
    • If you override a service, none of the options are inherited from the import
  • Private services are special because they allow the container to optimize whether and how they are instantiated
    • This increases the container’s performance
    • It gives you better errors if you try to reference a non-existent service
  • Anonymous services are only supported by the XML and YML configuration formats
  • Aliases are used by the core bundles to allow services to be autowired
    • MonologBunlde creates a service whose id is logger. But it also adds alias : Psr\Log\LoggerInterface that points to the  logger service
  • Autowiring is not magic, it is simply looks for a service whose id matches the type-hint. 
  • Compiler passes give you an opportunity to manipulate other service definitions that have been registred with the service container
    • Compiler passes are registred in the build() method of the application Kernel
    • One of the most common use-cases of compiller passes is to work with tagged services.  In those cases, instead of creating a compiler passe, you can make the Kernel implement CompilerPassInterface and process the services inside the process() method
  • Symfony’s Service Container provides a powerful way of controlling the creation of objects, allowing you to specify arguments passed to the constructor as calling methods and settings parameters
    • you can use a factory to create the object and tell the service containerto call a method on the factory rather than directly instantiating the class
  • The service container is built using a single configuration resource (config/services.yaml by default). This gives you absolute flexibility over the services in your application.
    • External service configurationcan be imported intwo different ways.
      • The first method, commonly used to import other resources, is via the imports directive.
      • The second method, using dependency injection extensions
  • You can only set a parameter before the container is compiled: not at run-time.
  • The service container allows you to extend parent services in order to avoid duplicated service definitions
    • All attributes on the parent service are shared with the child except for sharedabstract and tags. These are not inherited from the parent.
  • Whenever you need to access the current request in a service, you can either add it as an argument to the methods that need the request or inject the request_stack service and access the Request by calling the getCurrentRequest() method
  • In the service container, all services are shared by default.
    • This means that each time you retrieve the service, you’ll get the same instance. This is usually the behavior you want, but in some cases, you might want to always get a new instance.
  • Service tags are a way to tell Symfony or other third-party bundles that your service should be registered in some special way. 
  • In order to run the compiler pass when the container is compiled, you have to add the compiler pass to the container in a bundle extension or from your kernel

EventDispatcher

  • EventSubscriber est mieux adapté pour écouter  multiples événements et il contient la liste des événements à écouter 
  • Un Listener,  pour qu’il soit considéré comme écouteur par l’event Dispatcher, nous devons l’enregistrer avec le tag : kernel.event_listener
  • Des objets qui peuvent être des écouteurs (listeners) ou des souscripteurs d’événement (Event Subscriber) peuvent écouter ces events et exécuter des fonctions à partir de données qui sont transmises par l’événement 
  • Une application Symfony dispose d’un eventDispatcher qui va envoyer une série d’event natifs et métiers
  • La classe doit impérativement implémenter l’interface EventSubscriberInterface.
    • Par ce que la classe implémente une interface particulière du framewok Symfony, Le container de service a automatiquement ajouté le tag « kernel.event_subscriber » et déclaré la classe en tant que eventSubscriber => autoconfigure 
  • Un événement métier doit implémenter la classe « Event » de Symfony, ensuite il suffit de faire appel à l’EventDispatcher  pour envoyer l’info à tous les écouteurs 
    • il faut appeler la méthode dispatch de l’interface EventDispatcherInterface($event)

Cycle de vie d’une application Symfony

  • kernel.request => envoyé avant que le contrôleur ne soit déterminé 
  • kernel.controller => envoyé après la détermination de contrôleur 
  • kernel.response => envoyé après que le contrôleur retourne un objet Response 
  • kernel.terminate => envoyé après que la réponse est ennoyé à l’utilisateur 
  • kernel.exception => envoyé si une exception est lancée par l’application

Je vous invite d’aller voir cet article vite fait. Click here

Web Profiler

  • Par défaut Symfony fournit 3 environnements (prod/dev et test) et vous pouvez en créer autant 
  • une configuration spécifique de l’application sera chargée en fonction de l’environnement
    • Tous les fichiers disponibles à la racine du dossier packages sont chargés    
    • Ensuite, tous les fichiers disponibles dans le dossier packages/{environement} sont chargés
    • Troisièmement, le fichier services sera chargé
    • et pour conclure, le fichier service_{environneemnt} (s’il existe) sera chargé
  • Sachant que si une configuration est déjà écrite dans un fichier précédent, elle sera surchargée
  • A data collector is a PHP class that implements the Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface. For convenience, your data collectors can also extend from the Symfony\Component\HttpKernel\DataCollector\DataCollector class, which implements the interface and provides some utilities and the $this->data property to store the collected information.  tag => data_collector

Twig

  • Il est possible d’étendre Twig avec 3 types d’extensions différentes : les fonctions, les filtres et les macros
  • Pour ajouter vos propres filtres et fonctions, il faudra créer une extension Twig
  • Une extensions Twig est une classe qui permet de définir ses propres filtres et fonctions et qui implémente Twig_ExtensionInterface => Twig\Extension\ExtensionInterface, mais souvent on étend de la classe abstraite AbstractExtension
    • Grâce à l’autoconfiguration des services, Symfony va automatiquement reconnaître qu’il s’agit d’une extension Twig et rendre disponibles les filtres et les fonctions
    • If you’re using the default services.yaml configuration, this will already work! Otherwise, create a service for this class and tag your service with twig.runtime
  • Une macro comme étant une petite routine informatique pour automatiser une tâche répétitive
  • Twig is fast in prod because each template is compiled to a native PHP class and cached
  • If you need to refer to a template that lives in a bundle, Symfony uses namespaces syntax (@BundleName/directory/filename.html.twig)
  • By default, any Symfony template can be written in either Twig or PHP. 
  • All of the variables available in list.html.twig are also available in artciles_details.html.twig (unless you set with_context to false)
    • path() generates a RELATIVE url 
    • url() generates an ABSOLUTE ur
    • asset() generates URL for images/css/js/etc.  absolute_url(asset())
  • OutPut Escaping when rendering any content in order to protect you from Cross Site Scripting (XSS) attacks
  • During each Request, Symfony will set a global template variable app. The app variable is a AppVariable instance which will give you access to some application specific variables 
  • Twig must initialize all extensions before rendering any template, even if the template does not use an extension 
    • If extensions don’t define dependencies (if you don’t inject services in them), performance is not affected 
    • However if extensions define lots of complex dependencies, the performance loss can be signifcant
      • That’s why Twig allows to decouple the extension definition from its implementation

Form & Validator

  • La mise à jour de formulaire à l’aide des informations reçues de l’utilisateur se fait via la fonction handleRequest()
    • $form->handleRequest($request);
  • La validation se déclare à l’aide d’annotations en associant des contraintes de validation à des propriétés ou des méthodes publiques de nos objets
  • Lorsque nous soumettons notre formulaire et que nous appelons la fonction isValid(), les contraintes de validation déclarées dans la classe Article sont appliquées sur l’objet $article qui a été retrouvé à partir des données de la requête utilisateur
  • Un thème de formulaire est un template qui contient toutes les définitions, tous les templates pour chaque type de formulaire (radio, checkbox,…)
  • mapped => false
    • that’s means this field is not associated to any entity property
  • Each field type has a number of options that can be used to configure it. For example, the dueDate field is currently being rendered as 3 select boxes. However, the DateType can be configured to be rendered as a single text box (where the user would enter the date as a string in the box):
    • Each field type has a number of different options that can be passed to it. Many of these are specific to the field type and details can be found in the documentation for each type.
    • The most common option is the required option, which can be applied to any field. By default, the required option is set to true
  • Every form needs to know the name of the class that holds the underlying data,  it’s generally a good idea to explicitly specify the data_class option by adding the following to your form type class
  • When building forms, keep in mind that
    • the first goal of a form is to translate data from an object(Task) to an HTML form so that the user can modify that data.
    • the second goal of a form is to take the data submitted by the user and to re-apply it to the object.
  • By default, a form will be submitted via an HTTP POST request to the same URL under which the form was rendered.
  • When using a form type class, you can pass the action and method as form options
    • $form = $this->createForm(TaskType::class, $task, array('action' => $this->generateUrl('target_route'),'method' => 'GET'));
    • {{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }}
  • if the form’s method is not GET or POST, but PUT, PATCH or DELETE, Symfony will insert a hidden field with the name _method that stores this method. The form will be submitted in a normal POST request, but Symfony’s router is capable of detecting the _method parameter and will interpret it as a PUT, PATCH or DELETE request.
  • Symfony provides several ways of integrating Bootstrap into your application. The most straight forward way is to just add the required <link> and <script> elements in your templates (usually you only include them in the main layout template which other templates extend from)
  • When you use the Bootstrap form themes and render the fields manually, calling form_label() for a checkbox/radio field doesn’t render anything. Due to Bootstrap internals, the label is already rendered by form_widget().
  • http://127.0.0.1:8000/home?foo=ALL  =>
    • $request->getPathInfo() ; => « /home »
    • $request->getRequestUri();    => « /home?foo=all »
  • Symfony comes with a bunch of core field types available for building forms. However there are situations where you may want to create a custom form field type for a specific purpose.
    • Each field type is rendered by a template fragment, which is determined in part by the class name of your type. The first part of the prefix (e.g. shipping) comes from the class name (ShippingType -> shipping). This can be controlled by overriding getBlockPrefix() in ShippingType. 
    • When a custom field extends from Type filed already exist, you don’t need to do any work as the custom field type will automatically be rendered like a ChoiceType.
  • Form type extensions are incredibly powerful: they allow you to modify any existing form field types across the entire system. They have 2 main use-cases :
    • You want to add a specific feature to a single form type (such as adding a « download » feature to the FileType field type);
    • You want to add a generic feature to several types (such as adding a « help » text to every « input text« -like type).
      • class ImageTypeExtension extends AbstractTypeExtension    =>   getExtendedType
    • In the same way, since most form types natively available in Symfony inherit from the FormType form type, a form type extension applying to FormType would apply to all of these (notable exceptions are the ButtonType form types). Also keep in mind that if you created (or are using) a custom form type, it’s possible that it does not extend FormType, and so your form type extension may not be applied to it.
    • What is the tag to use to create a custom form type extension ? ===> form.type_extension
  • Data transformers are used to translate the data for a field idata_clanto a format that can be displayed in a form (and back on submit). They’re already used internally for many field types. For example, the DateType field can be rendered as a yyyy-MM-dd-formatted input textbox. Internally, a data transformer converts the starting DateTime value of the field into the yyyy-MM-dd string to render the form, and then back into a DateTime object on submit.
    •  When a form field has the inherit_data option set,
      • Data Transformers won’t be applied to that field.
      • it may allow to reduce duplicated fields in different forms
      • When a form has the inherit_data option set to true, it does not use the data mapper and lets its parent map inner values.
    •  The CallbackTransformer takes two callback functions as arguments.
      • The first transforms the original value into a format that’ll be used to render the field.
      • The second does the reverse: it transforms the submitted value back into the format you’ll use in your code.
    • a transformer has two directions. The transform() method is responsible for converting the data used in your code to a format that can be rendered in your form (e.g. an Issue object to its id, a string). The reverseTransform() method does the reverse: it converts the submitted value back into the format you want (e.g. convert the id back to the Issue object).
  • The majority of the work is done by the form_row() helper, which renders the label, errors and HTML form widget of each field inside a div tag by default.
  • Some field types have additional rendering options that can be passed to the widget. These options are documented with each type, but one common option is attr, which allows you to modify attributes on the form element. The following would add the task_field class to the rendered input text field:
    • {{ form_widget(form.task, {'attr': {'class': 'task_field'}}) }}
  • If you need to render form fields “by hand” then you can access individual values for fields such as the id, name and label. For example to get the id:
    • {{ dump(form.description.vars.id) }} 
    • {{ dump(form.description.vars.name) }}
  • Using form events, you may modify information or fields at different steps of the workflow, from the population of the form to the submission of the data from the request.
    • Two events are dispatched during pre-population of a form, when Form::setData() is called: 
      • FormEvents::PRE_SET_DATA (dispatched at the beginning of the Form::setData())
        • Modify the data given during pre-population;
        • Adding or removing fields dynamically
      • FormEvents::POST_SET_DATA (dispatched at the end of the Form::setData())
        • For reading data after having pre-populated the form
    • Three events are dispatched when Form::handleRequest() or Form::submit() are called: 
      • FormEvents::PRE_SUBMIT ( dispatched at the beginning of the Form::submit() method.)
        • Change data from the request, before submitting the data to the form;
        • Add or remove form fields, before submitting the data to the form.
      • FormEvents::SUBMIT ( dispatched just before the Form::submit() method transforms back the normalized data to the model and view data)
        • It can be used to change data from the normalized representation of the data.
      • FormEvents::POST_SUBMIT. (dispatched after the Form::submit() once the model and view data have been denormalized)
        • It can be used to fetch data after denormalization.

Validator

  • The Validator component is based on the JSR303    
    • Symfony’s validator uses PHP reflection
  • The golad of validation is to tell you if the data of an object is valid. For this to work you will configure a list of rules => called constraints
  • Constraints can be applied to a class property (e.g. name), a public getter method (e.g. getFullName()) or an entire class.
    • Property constraints are the most common and easy to use. => Symfony allows you to validate private, protected or public properties
    • Getter constraints allow you to specify more complex validation rules.  => Symfony allows you to add a constraint to any public method whose name starts with « get« , « is » or « has« 
    • Finally, class constraints are intended for scenarios where you want to validate a class as a whole.
  • Some constraints apply to the entire class being validated. For example, the Callback constraint is a generic constraint that’s applied to the class itself. When that class is validated, methods specified by that constraint are simply executed so that each can provide more custom validation.
  • If your constraint (custom constraint) contains options, then they should be public properties on the custom Constraint class
  • Tag => validator.constraint_validator
  • By default, when validating an object all constraints of this class will be checked whether or not they actually pass.
    • To do this, you can organize each constraint into one or more « validation groups » and then apply validation against just one group of constraints.
    • In other words, the Default group and the class name group (e.g. User) are identical, except when the class is embedded in another object that’s actually the one being validated.
  • In some cases, you want to validate your groups by steps. To do this, you can use the GroupSequence feature. In this case, an object defines a group sequence, which determines the order groups should be validated.
  • the Default group and the group containing the class name (e.g. User) were identical. However, when using Group Sequences, they are no longer identical. The Default group will now reference the group sequence, instead of all constraints that do not belong to any group.

Security

  • The most important is the User.php file itself. The class User must implement UserInterface 
  • You need also a « User Provider »: a class that helps with a few things, like reloading the User data from the session and some optional features
  • A « firewall » is your authentication system: the configuration defines how your users will be able to authenticate (e.g. login form, API token, etc).
    • Only one firewall is active on each request
    • firewall can have many modes of authentication, in other words many ways to ask the question « Who are you?« . Often, the user is unknown (i.e. not logged in) 
  • An Authentication provider  => some code that runs automatically before your controller is called.
  • The process of authorization has two different sides:
    • The user receives a specific set of roles when logging in (e.g. ROLE_ADMIN).
    • 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.
  • Roles
    • when a user logs in, Symfony calls the getRoles() method on your User object to determine which roles this user has. The roles are an array that’s stored in the database, and every user is always given at least one role: ROLE_USER:
    • Every role must start with ROLE_ (otherwise, things won’t work as expected)
    • There are two ways to deny access to something:
    • $this->denyAccessUnlessGranted('ROLE_ADMIN',null, 'Unable to access this page!');
    • use Symfony\Component\Security\Core\Exception\AccessDeniedException;
    • public function hello($name, AuthorizationCheckerInterface $authChecker){      if (false === $authChecker->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException('Unable to access this page!'); => 403 HTTP }}
  • Each access_control can also match on IP address, hostname and HTTP methods. It can also be used to redirect a user to the https version of a URL pattern
    • Thanks to the SensioFrameworkExtraBundle, you can also secure your controller using annotations
    • As routing is done before security, 404 error pages are not covered by any firewall. This means you can’t check for security or even access the user object on these pages.
  • Authentication Managers and Providers
    • When a request points to a secured area, and one of the listeners from the firewall map is able to extract the user’s credentials from the current Request object, it should create a token, containing these credentials. The next thing the listener should do is ask the authentication manager (authenticate method) to validate the given token, and return an authenticated token if the supplied credentials were found to be valid. The listener should then store the authenticated token using the token storage
    • The default authentication manager is an instance of AuthenticationProviderManager:
    • Each provider (since it implements AuthenticationProviderInterface) has a method supports() by which the AuthenticationProviderManager can determine if it supports the given token. If this is the case, the manager then calls the provider’s method authenticate(). This method should return an authenticated token or throw an AuthenticationException (or any other exception extending it).
    • When a provider authenticates the user, a security.authentication.success event is dispatched. But beware – this event will fire, for example, on every request if you have session-based authentication. See security.interactive_login below if you need to do something when a user actually logs in.
    • When a provider attempts authentication but fails (i.e. throws an AuthenticationException), a security.authentication.failure event is dispatched. You could listen on the security.authentication.failure event, for example, in order to log failed login attempts.
  • The security.interactive_login event is triggered after a user has actively logged into your website.
  • The security.switch_user event is triggered every time you activate the switch_user firewall listener.
    • The firewall dispatches the security.switch_user event right after the impersonation is completed. The SwitchUserEvent is passed to the listener, and you can use this to get the user that you are now impersonating.
  • As routing is done before security, 404 error pages are not covered by any firewall
  • At the end of each request, the User object is serialized to the session. On the next request, it’s unserialized. To help PHP do this correctly, you need to implement Serializable. But you don’t need to serialize everything:
  • Do you need to use a Salt property?
    • If you use bcrypt or argon2i, no. Otherwise, yes. All passwords must be hashed with a salt, but bcrypt and argon2i do this internally. Since this tutorial does use bcrypt, the getSalt() method in User can just return null (it’s not used). If you use a different algorithm, you’ll need to uncomment the salt lines in the User entity and add a persisted salt property. 
  • The native entity provider is only able to handle querying via a single property on the user
    • To do this, make your UserRepository implement a special UserLoaderInterface. This interface only requires one method: loadUserByUsername($username):
  • it’s useful to be able to switch from one user to another without having to log out and log in agai
    • http://example.com/somewhere?_switch_user=thomas
    • http://example.com/somewhere?_switch_user=_exit (to switch back to the original user
  • During impersonation, the user is provided with a special role called ROLE_PREVIOUS_ADMIN
  • How to Authenticate Users with API Keys
  • Authenticating a user based on the Request information should be done via a pre-authentication mechanism
    • The Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface impl SimpleAuthenticatorInterface
    • createToken() / supportsToken and authenticateToken()
  • The authentication process has several steps, and your implementation will probably differ
    • createToken => Early in the request cycle, Symfony calls createToken(). Your job here is to create a token object that contains all of the information from the request that you need to authenticate the user (e.g. the apikey query parameter).
    •  supportsToken => After Symfony calls createToken(), it will then call supportsToken() on your class (and any other authentication listeners) to figure out who should handle the token.   
    • authenticateToken =>  If supportsToken() returns true, Symfony will now call authenticateToken(). One key part is the $userProvider, which is an external class that helps you load information about the user     
  • The stateless configuration parameter prevents Symfony from trying to store the authentication information in the session, which isn’t necessary since the client will send the apikey on each request.
    • firewalls: 
      • api:     
        • pattern: ^/api     
        • stateless: true   
        • simple_preauth:   
          • authenticator: App\Security\ApiKeyAuthenticator   
        • provider: api_key_user_provider
  • How to use Voters 
    • Security voters are the most granular way of checking permissions (e.g. “can this specific user edit the given item?”).
    • All voters are called each time you use the isGranted() method on Symfony’s authorization checker or call denyAccessUnlessGranted() in a controller (which uses the authorization checker).
    • To inject the voter into the security layer, you must declare it as a service and tag it with security.voter.
    • A custom voter needs to implement Symfony\Component\Security\Core\Authorization\Voter\VoterInterface or extend Symfony\Component\Security\Core\Authorization\Voter\Voter, which makes creating a voter even easier:
      • Voter::supports($attribute, $subject) => is to determine if your voter should vote on the attribute/subject combination. If you return true, voteOnAttribute() will be called
      • Voter::voteOnAttribute($attribute, $subject, TokenInterface $token) => Your job is simple: return true to allow access and false to deny access. The $token can be used to find the current user object (if any).
    • security.access.decision_manager  => tag AccessDecisionManagerInterface
    • security.authorization_checker => tag AuthorizationCheckerInterface
    • Normally, only one voter will vote at any given time (the rest will “abstain”, which means they return false from supports()). But in theory, you could make multiple voters vote for one action and object
      • There are three strategies available
        • affirmative (default) => This grants access as soon as there is one voter granting access;
        • consensus => This grants access if there are more voters granting access than denying;
        • unanimous  => This only grants access if there is no voter denying access. If all voters abstained from voting, the decision is based on the allow_if_all_abstain config option (which defaults to false). (Cela accorde l’accès que si aucun Voters ne refuse l’accès
        • security
          • access_decision_manager:
            • strategy: unanimous
            • allow_if_all_abstrain: false
  • How to Build a Json Authentication Endpoint
    • firewalls:
      • main:
        • anonymous: tilda
        • json_login:
          • check_path: /login
          • username_path: security.credentials.login
          • password_path: security.credentials.password 
    • When you submit a POST request to the /login URL with the following JSON document as the body, the security system intercepts the requests. It takes care of authenticating the user with the submitted username and password or triggers an error in case the authentication process fails 
    • If the JSON document has a different structure, you can specify the path to access the username and password properties using the username_path and password_path keys (they default respectively to username and password). For example, if the JSON document has the following structure:
  • How to Create a Custom Authentication System with Guard
    • Whether you need to build a traditional login form, an API token authentication system or you need to integrate with some proprietary single-sign-on system, the Guard component can make it easy… and fun!
    • For Custom Message on Exception => Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException
    • If you’re building a login form, use the => Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator as your base class – it implements a few methods for you.
  • Creating a custom Password Encoder 
    • There are many built-in password encoders. But if you need to create your own, it needs to follow these rules:
      • The class must implement Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface (you can also extend Symfony\Component\Security\Core\Encoder\BasePasswordEncoder);
      • The implementations of encodePassword() and isPasswordValid() must first of all make sure the password is not too long, i.e. the password length is no longer than 4096 characters. This is for security reasons (see CVE-2013-5750), and you can use the isPasswordTooLong() method for this check

Translate

  • The Symfony Translation component supports lots of different translation formats: PHP, Qt, .po, .mo, JSON, CSV, INI, etc
    • Use the XLIFF format for your translation files.
    • Always use keys for translations instead of content strings.
    • The term “internationalization” (often abbreviated i18n) refers to the process of abstracting strings and other locale-specific pieces out of your application into a layer where they can be translated and converted based on the user’s locale 
    • The term locale refers roughly to the user’s language and country. (fr_FR) ) => ISO 639-1
    • default_local =====> _locale (routing/controller)
    • When this code is executed, Symfony will attempt to translate the message “Symfony is great” based on the locale of the user
  • Setting the locale using $request->setLocale() in the controller is too late to affect the translator. Either set the locale via a listener (like above), the URL (see next) or call setLocale() directly on the translator service.
  • The Translation Process
    • The locale of the current user, which is stored on the request is determined; … ; ….
    • When using the trans() method, Symfony looks for the exact string inside the appropriate message catalog and returns it (if it exists).
  • Message Placeholders
    • $translated=$translator->trans('Hello '.$name);
    • $translated=$translator->trans('Hello %name%',array('%name%'=>$name));
    • in file of translation => ‘Hello%name%’:Bonjour %name%
  • Pluralization
    • To translate pluralized messages, use the transChoice() method:
    • $translated = $translator->transChoice(‘There is one apple|There are %count% apples’, 10);
      • Based on the given number, the translator chooses the right plural form. In English, most words have a singular form when there is exactly one object and a plural form for all other numbers (0, 2, 3…). So, if count is 1, the translator will use the first string (There is one apple) as the translation. Otherwise it will use There are %count% apples.  
      • ‘Il y a %count% pomme|Il y a %count% pommes’
    • The easiest way to pluralize a messageis to let the Translator use internal logic to choose which string to use based on a given number. Sometimes, you’ll need more control or want a different translation for specific cases (for 0, or when the count is negative, for example). For such cases, you can use explicit math intervals :
    • ‘{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf[ There are many apples’   => IS0-31-11
    • You can also mix explicit math rules and standard rules. In this case, if the count is not matched by a specific interval, the standard rules take effect after removing the explicit rules:
  • Twig Template
    • Symfony provides specialized Twig tags (trans and transchoice) to help with message translation of static blocks of text
    • If you need to use the percent character (%) in a string, escape it by doubling it: {% trans %}Percent: %percent%%%{% endtrans %}
    • You can set the translation domain for an entire Twig template with a single tag: => {%trans_default_domain’app’%}
      • Note that this only influences the current template, not any “included” template (in order to avoid side effects).
  • Translation Ressource/Files Names and Locations 
    • Symfony looks for message files (i.e. translations) in the following default locations:
      • the translations/ directory (at the root of the project);
      • the src/Resources/<bundle name>/translations/ directory
      • the Resources/translations/ directory inside of any bundle.
    • The locations are listed here with the highest priority first. That is, you can override the translation messages of a bundle in any of the top two directories.
      • The override mechanism works at a key level:only the overridden keys need to be listed in a higher priority message file. When a key is not found in a message file, the translator will automatically fall back to the lower priority message files.
      • The filename of the translation files is also important: each message file must be named according to the following path: domain.locale.loader:
        • domain => messages / app /….
        • locale => fr_FR / fr
        • loader => xlf / php / yaml   (recommended)
    • You can also store translations in a database, or any other storage by providing a custom class implementing the
    • Imagine that the user’s locale is fr_FR and that you’re translating the key Symfony is great. To find the French translation, Symfony actually checks translation resources for several locales:
      • First, Symfony looks for the translation in a fr_FR translation resource (e.g. messages.fr_FR.xlf);
      • If it wasn’t found, Symfony looks for the translation in a fr translation resource (e.g. messages.fr.xlf);
      • If the translation still isn’t found, Symfony uses the fallbacks configuration parameter, which defaults to en 

CONFIG

  • The Component config provides several classes to help you find, load, combine, autofill and validate configuration values of any kind
  • The  config/packages directory stores dthe configuration of every package in your application
  • There is not difference between formats. In fact Symfony transforms and caches all of theme into PHP before runing the application
  • The .env file is special, because it defines the values that usually change on each server. That’s why this file is not committed to the shared repositor
  • In order to select the configuration file to load each environnement.
    •  Symfony executes the configureContainer() method of the Kernel class
  • Configuration files can import files defined with any other built-in configuration format (yaml/yml, php, xml, ini).
    • If you use any other configuration format, you have to define your own loader class extending it from FileLoader
    • When the configuration values are dynamic, you can use the PHP configuration file ….
  • In Production, it is recommended to configure the environment variables in your web server configuration
  • An environment is nothing more than a string that corresponds to a set of configuration 
  • The front controller is a design pattern, it is a section of code  that all requests served by an application run through 
    • create an instance of the Kernel 
    • make it handle the Request and return the Response 
  • The Kernel is the core of Symfony.
    • It is responsible for setting up all the bundles used by your application and  providing them with the application’s configuration.
    • It then creates the service container before serving requests in its handle method
    • The Kernel extends from Kernel and uses the MicroKernelTrait The Kernel class leaves some methods from KernelInterface .. so you must implement them all
      • registerBundles()
      • configureRoutes() => it adds routes to the application 
      • configureContainer() => it loads the application configuration from config or using the loadFromExtension() and can register new container parameters and services
  • The bin/console  script used to run Symfony commands always uses the default Kernel

SESSION & CONTROLLER

  • 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
  • To facilate the developpement, of controllers, Symfony provides an AbstractController. It can be used to extend the controller class allowing access to some frequently used utilities 
    • The AbstractController implements  the Interface ==> Symfony\Contracts\Service\ServiceSubscriberInterface;
    • The AbstractController uses the Trait ==> ControllerTrait  (has() / get() / generateUrl() / forward() / redirect() / redirectToRoute() / json() /file():BinaryFileResponse / addFlash() / isGranted() /denyAccessUnlessGranted() / renderView():String / render():Response / stream() /createNotFoundException() / createAccessDeniedException() / createForm() / createFormBuilder() / getDoctrine()  / getUser() / isCsrfTokenValid() / dispatchMessage() / addLink()
  • In Symfony, a controller does not need to be registered as a service. But by default (services.yml), your controllers are alerady registered as services. This means you can use dependency injection like any other normal service 
    • What’s the difference between Controller or AbstractController?
      • Not much: both are identical, except that AbstractController is more restrictive: it does not allow you to access services directly via $this->get() or $this->container->get(). This forces you to write more robust code to access services. But if you do need direct access to the container, using Controller is fine. (The Controller implements the ContainerAwareInterface
  • It is possible to forward Requests to another Controller. This is thought via forward() method, provided by the AbstractController class.
    • this makes an internal sub request and calls the defined controller. The method return a Response object
  • In Symfony apps, all errors are considered as exceptions. Error pages for the production environnement can be customized in 3 ways
  • If you just want to change the contents and styles => override the default error templates
    • errorxxx.html/json.twig
    • error.html/json/xml.twig
    • error.html.twig  
  • If you also want to tweak the logic used by Symfony => override the default exception controlle
    • create a new controller anywhere in your app and set the twig.exception_controller config
  • If you need total control of exception handling  => use the kernel.exception event 
  • When the error page loads, an internal ExceptionController is used to render a Twig template to show the user
  • The exception pages shown in dev env can be customized in the same way as error pages (prod env)
  • While you are in the dev env, Symfony show the big exception page instead of your customized error pages

CONSOLE

  • The configure() method is called automatically at the end of the command constructor. If your command defines its own constructor, set the properties first and then call to the parent constructor
  • Symfony commands must be registred as services and tagged with the console.command
    • The execute() method has access to the output stream to write messages to the console
    • The regular console output can be divided into multiple independent regions called « output sections » 
  • Your Command is already registred as a service, you can normal dependency injection.
  • Commands have 3 lifecycle methods that are invoked when running the command
    • initialize() optional 
      •  Initializes the command after the input has been bound and before the input is validated.
    • interact() optional  
      • Its purpose to check if some of the options/arguments are missing and interactively ask the user for those values.
      • This method is executed before the InputDefinition is validated.
    • execute() (required)   
      • Its contains the logic you want the command to execute 
  • Symfony provides several tools to help you test your commands. The most useful one is the CommandTester class. It uses special input and output classes to ease testing without a real console
  • If a command depends on another one being run before it, instead of asking the user to remember the order of execution, you can call it directly yourself. This is also useful if you want to create a « meta » command that just runs a bunch of other commands 
  • If your class extends ContainerAwareCommand, you can access public services via $this->getContainer()->get(‘SERVICE_ID’).
  • If your command is not lazy, try to avoid doing any work (e.g. making database queries), as that code will be run, even if you’re using the console to execute a different command.
  • To make your command lazily loaded, either define its $defaultName static property
  • Calling the list command will instantiate all commands, including lazy commands.
    • Hidden commands behave the same as normal commands but they are no longer displayed in command listings, so end-users are not aware of their existence
  • Arguments of Command
    • Are the strings – separated by spaces 
    • That come after the command name itself.
    • They are ordered, and can be optional or required.
  • It is also possible to let an argument take a list of values (imagine you want to greet all your friends). Only the last argument can be a list 
  • Unlike arguments, options are not ordered (meaning you can specify them in any order) and are specified with two dashes (e.g. –yell). 
  • A simple but effective way to prevent multiple executions of the same command in a single server is to use locks
  • The command line context does not know about your VirtualHost or domain name.
  • This means that if you generate absolute URLs within a console command you’ll probably end up with something like http://localhost/foo/bar which is not very useful.
    • To fix this, you need to configure the « request context« ,
      • There are two ways of configuring the request context:
        • At the application leve
        • per Command.
  • Console commands have different verbosity levels,which determine the messages displayed in their output. By default, commands display only the most useful messages, but you can control their verbosity with the -q and -v options
  • In your command, instantiate the SymfonyStyle class and pass the $input and $output variables as its arguments. Then, you can start using any of its helpers, such as title(), which displays the title of the command:
  • The Console component comes with some useful helpers. These helpers contain function to ease some common tasks.
    • Formatter / Process / Progress bar / Question / Table / Debug
  • The Application class of the Console component allows you to optionally hook into the lifecycle of a console application via events
    • ConsoleEvents::COMMAND event Listeners receive a ConsoleCommandEvent event
      • before any command is run
      • you can disable a command inside a listener. The application will then not execute the command, but instead will return the code 113 (
    • ConsoleEvents::ERROR => ConsoleErrorEvent event
      •  Handle exceptions thrown during the execution of a command.
    • ConsoleEvents::TERMINATE=> ConsoleTerminateEvent event
      • To perform some cleanup actions after the command has been executed.
    • console.command / console.error / console.terminate 

Routing

  • the routing configuration defines which action to run for each incomming URL. It also provides other useful featutes like generating SEO urls
  • Symfony recommends annotation because it’s convenient to put the route and controller in the same place
  • By default, routes match any HTTP verb( GET, POST, PUT, etc ..) use the methods option to restrict the verb
  • Param converter is only available when using annotations to define routes
  • In addition to your own params, routes can include any of the following special parameters created by Syfmony (_controller/_format/_fragment/_locale)

HTTP_Cache

  • With HTTP Caching, you cache the full output of a page (i.e. the response). 
  • With Edge Side Includes (ESI), you can use the power of HTTP caching on only fragments of your site.
  • When caching with HTTP, the cache is separated from your application entirely and sits between your application and the client making the request.
  • The job of the cache is to accept requests from the client and pass them back to your application. The cache will also receive responses back from your application and forward them on to the client. The cache is the « middle-man » of the request-response communication between the client and your application.
    • This type of cache is known as an HTTP gateway cache and many exist such as Varnish, Squid in reverse proxy mode, and the Symfony reverse proxy.
  • The cache kernel has a special getLog() method that returns a string representation of what happened in the cache layer. In the development environment, use it to debug and validate your cache strategy
  • The Symfony reverse proxy is a great tool to use when developing your website or when you deploy your website to a shared host where you cannot install anything beyond PHP code. But being written in PHP, it cannot be as fast as a proxy written in C.!!
  • Once you’ve added a reverse proxy cache (e.g. like the Symfony reverse proxy or Varnish), you’re ready to cache your responses. To do that, you need to communicate to your cache which responses are cacheable and for how long. This is done by setting HTTP cache headers on the response.
    •     Cache-Control / Expires / ETag/ Last-Modified 
  • These four headers are used to help cache your responses via two different models:
    • Expiration Caching Used to cache your entire response for a specific amount of time (e.g. 24 hours). Simple, but cache invalidation is more difficult.
    • Validation Caching More complex: used to cache your response, but allows you to dynamically invalidate it as soon as your content changes.
  • The URI of the request is used as the cache key 
  • HTTP caching only works for « safe » HTTP methods (like GET / HEAD / OPTION / TRACE)
  • If one content corresponds to one URL, the PURGE model works well. You send a request to the cache proxy with the HTTP method PURGE (using the word « PURGE » is a convention, technically this can be any string) instead of GET and make the cache proxy detect this and remove the data from the cache instead of going to the application to get a response
  • By default, HTTP caching is done by using the URI of the resource as the cache key. If two people request the same URI of a cacheable resource, the second person will receive the cached version.
  • if you compress pages when the client supports it, any given URI has two representations: one when the client supports compression, and one when it does not.
    • This determination is done by the value of the Accept-Encoding request header.
    • In this case, you need the cache to store both a compressed and uncompressed version of the response for the particular URI and return them based on the request’s Accept-Encoding value. This is done by using the Vary response header, which is a comma-separated list of different headers whose values trigger a different representation of the requested resource
  • Gateway caches are a great way to make your website perform better. But they have one limitation: they can only cache whole pages
  • The ESI specification describes tags you can embed in your pages to communicate with the gateway cache. Only one tag is implemented in Symfony, include
    • When a request is handled, the gateway cache fetches the entire page from its cache or requests it from the backend application. If the response contains one or more ESI tags, these are processed in the same way. In other words, the gateway cache either retrieves the included page fragment from its cache or requests the page fragment from the backend application again. When all the ESI tags have been resolved, the gateway cache merges each into the main page and sends the final content to the client.
  • When using the default render() function (or setting the renderer to inline), Symfony merges the included page content into the main one before sending the response to the client. 
    • But if you use the esi renderer (i.e. call render_esi()and if Symfony detects that it’s talking to a gateway cache that supports ESI, it generates an ESI include tag. But if there is no gateway cache or if it does not support ESI, Symfony will just merge the included page content within the main one as it would have done if you had used render().
  • Once you start using ESI, remember to always use the s-maxage directive instead of max-age. As the browser only ever receives the aggregated resource, it is not aware of the sub-components, and so it will obey the max-age directive and cache the entire page. And you don’t want that.
  • The expiration model can be accomplished using one of two, nearly identical, HTTP headersExpires or Cache-Control.
  • CSRF tokens are meant to be different for every user. This is why you need to be cautious if you try to cache pages with forms including them.
  • many reverse proxies (like Varnish) will refuse to cache a page with a CSRF token. This is because a cookie is sent in order to preserve the PHP session open and Varnish’s default behavior is to not cache HTTP requests with cookies.
  • With cache validation, for each request, the cache asks the application if the cached response is still valid or if it needs to be regenerated. If the cache is still valid, your application should return a 304 status code and no content. This tells the cache that it’s ok to return the cached response.
    • Like with expiration, there are two different HTTP headers that can be used to implement the validation modelETag and Last-Modified.
      • The cache sets the If-None-Match header on the request to the ETag of the original cached response before sending the request back to the app. This is how the cache and server communicate with each other and decide whether or not the resource has been updated since it was cached.
      • The Last-Modified header response is the second form of validation. According to the HTTP specification, « The Last-Modified header field indicates the date and time at which the origin server believes the representation was last modified. » In other words, the application decides whether or not the cached content has been updated based on whether or not it’s been updated since the response was cached.

Testing

  • Each test – whether it’s a unit test or a functional test – is a PHP class that should live in the tests/ directory of your application
    • use PHPUnit\Framework\TestCase;
  • Functional tests check the integration of the different layers of an application (from the routing to the views). They are no different from unit tests as far as PHPUnit is concerned, but they have a very specific workflow
    • use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
    • Make a request;
    • Click on a link or submit a form;
    • Test the response;
  • To run your functional tests, the WebTestCase class needs to know which is the application kernel to bootstrap it. The kernel class is usually defined in the KERNEL_CLASS environment variable (included in the default phpunit.xml.dist file provided by Symfony)
    • <env name="KERNEL_CLASS" value="App\Kernel" />
  • It’s common to have to execute the same test against different sets of data to check the multiple conditions code must handle. This is solved with PHPUnit’s data providers,
  • It’s highly recommended that a functional test only tests the response. But under certain very rare circumstances, you might want to access some internal objects to write assertions. In such cases, you can access the Dependency Injection Container
    •     $container = $client->getContainer();
  • A Crawler instance is returned each time you make a request with the Client. It allows you to traverse HTML documents, select nodes, find links and forms.
    • Like jQuery, the Crawler has methods to traverse the DOM of an HTML/XML document

Voilà voilà. J’espère que ça vous aide dans votre préparation. Avec quelques projets en Symfony. Normalement tout serait bon.

Je précise qu’il s’agit d’une prise de notes pour la version 4 de Symfony.