diff --git a/_exts b/_exts index 03bc1c601..e58edd22d 160000 --- a/_exts +++ b/_exts @@ -1 +1 @@ -Subproject commit 03bc1c60172a280619e3476f22b111b4a187895d +Subproject commit e58edd22d16cb247267025d557410dcbfa5fa959 diff --git a/best_practices/business-logic.rst b/best_practices/business-logic.rst new file mode 100644 index 000000000..fe7364ec2 --- /dev/null +++ b/best_practices/business-logic.rst @@ -0,0 +1,344 @@ +Organizing Your Business Logic +============================== + +In computer software, **business logic** or domain logic is "the part of the +program that encodes the real-world business rules that determine how data can +be created, displayed, stored, and changed" (read `full definition`_). + +In Symfony applications, business logic is all the custom code you write for +your app that's not specific to the framework (e.g. routing and controllers). +Domain classes, Doctrine entities and regular PHP classes that are used as +services are good examples of business logic. + +For most projects, you should store everything inside the ``AppBundle``. +Inside here, you can create whatever directories you want to organize things: + +.. code-block:: text + + symfoy2-project/ + ├─ app/ + ├─ src/ + │ └─ AppBundle/ + │ └─ Utils/ + │ └─ MyClass.php + ├─ vendor/ + └─ web/ + +Storing Classes Outside of the Bundle? +-------------------------------------- + +But there's no technical reason for putting business logic inside of a bundle. +If you like, you can create your own namespace inside the ``src/`` directory +and put things there: + +.. code-block:: text + + symfoy2-project/ + ├─ app/ + ├─ src/ + │ ├─ Acme/ + │ │ └─ Utils/ + │ │ └─ MyClass.php + │ └─ AppBundle/ + ├─ vendor/ + └─ web/ + +.. tip:: + + The recommended approach of using the ``AppBundle`` directory is for + simplicity. If you're advanced enough to know what needs to live in + a bundle and what can live outside of one, then feel free to do that. + +Services: Naming and Format +--------------------------- + +The blog application needs a utility that can transform a post title (e.g. +"Hello World") into a slug (e.g. "hello-world"). The slug will be used as +part of the post URL. + +Let's, create a new ``Slugger`` class inside ``src/AppBundle/Utils/`` and +add the following ``slugify()`` method: + +.. code-block:: php + + // src/AppBundle/Utils/Slugger.php + namespace AppBundle\Utils; + + class Slugger + { + public function slugify($string) + { + return preg_replace( + '/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string))) + ); + } + } + +Next, define a new service for that class. + +.. code-block:: yaml + + # app/config/services.yml + services: + # keep your service names short + slugger: + class: AppBundle\Utils\Slugger + +Traditionally, the naming convention for a service involved following the +class name and location to avoid name collisions. Thus, the service +*would have been* called ``app.utils.slugger``. But by using short service names, +your code will be easier to read and use. + +.. best-practice:: + + The name of your application's services should be as short as possible, + ideally just one simple word. + +Now you can use the custom slugger in any controller class, such as the +``AdminController``: + +.. code-block:: php + + public function createAction(Request $request) + { + // ... + + if ($form->isSubmitted() && $form->isValid()) { + $slug = $this->get('slugger')->slugify($post->getTitle())); + $post->setSlug($slug); + + // ... + } + } + +Service Format: YAML +-------------------- + +In the previous section, YAML was used to define the service. + +.. best-practice:: + + Use the YAML format to define your own services. + +This is controversial, and in our experience, YAML and XML usage is evenly +distributed among developers, with a slight preference towards YAML. +Both formats have the same performance, so this is ultimately a matter of +personal taste. + +We recommend YAML because it's friendly to newcomers and concise. You can +of course use whatever format you like. + +Service: No Class Parameter +--------------------------- + +You may have noticed that the previous service definition doesn't configure +the class namespace as a parameter: + +.. code-block:: yaml + + # app/config/services.yml + + # service definition with class namespace as parameter + parameters: + slugger.class: AppBundle\Utils\Slugger + + services: + slugger: + class: "%slugger.class%" + +This practice is cumbersome and completely unnecessary for your own services: + +.. best-practice:: + + Don't define parameters for the classes of your services. + +This practice was wrongly adopted from third-party bundles. When Symfony +introduced its service container, some developers used this technique to easily +allow overriding services. However, overriding a service by just changing its +class name is a very rare use case because, frequently, the new service has +different constructor arguments. + +Using a Persistence Layer +------------------------- + +Symfony is an HTTP framework that only cares about generating an HTTP response +for each HTTP request. That's why Symfony doesn't provide a way to talk to +a persistence layer (e.g. database, external API). You can choose whatever +library of strategy you want for this. + +In practice, many Symfony applications rely on the independent +`Doctrine project`_ to define their model using entities and repositories. +Just like with business logic, we recommend storing Doctrine entities in +the ``AppBundle`` + +The three entities defined by our sample blog application are a good example: + +.. code-block:: text + + symfony2-project/ + ├─ ... + └─ src/ + └─ AppBundle/ + └─ Entity/ + ├─ Comment.php + ├─ Post.php + └─ User.php + +.. tip:: + + If you're more advanced, you can of course store them under your own + namespace in ``src/``. + +Doctrine Mapping Information +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Doctrine Entities are plain PHP objects that you store in some "database". +Doctrine only knows about your entities through the mapping metadata configured +for your model classes. Doctrine supports four metadata formats: YAML, XML, +PHP and annotations. + +.. best-practice:: + + Use annotations to define the mapping information of the Doctrine entities. + +Annotations are by far the most convenient and agile way of setting up and +looking for mapping information: + +.. code-block:: php + + namespace AppBundle\Entity; + + use Doctrine\ORM\Mapping as ORM; + use Doctrine\Common\Collections\ArrayCollection; + + /** + * @ORM\Entity + */ + class Post + { + const NUM_ITEMS = 10; + + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="string") + */ + private $title; + + /** + * @ORM\Column(type="string") + */ + private $slug; + + /** + * @ORM\Column(type="text") + */ + private $content; + + /** + * @ORM\Column(type="string") + */ + private $authorEmail; + + /** + * @ORM\Column(type="datetime") + */ + private $publishedAt; + + /** + * @ORM\OneToMany( + * targetEntity="Comment", + * mappedBy="post", + * orphanRemoval=true + * ) + * @ORM\OrderBy({"publishedAt" = "ASC"}) + */ + private $comments; + + public function __construct() + { + $this->publishedAt = new \DateTime(); + $this->comments = new ArrayCollection(); + } + + // getters and setters ... + } + +All formats have the same performance, so this is once again ultimately a +matter of taste. + +Data Fixtures +~~~~~~~~~~~~~ + +As fixtures support is not enabled by default in Symfony, you should execute +the following command to install the Doctrine fixtures bundle: + +.. code-block:: bash + + $ composer require "doctrine/doctrine-fixtures-bundle" + +Then, enable the bundle in ``AppKernel.php``, but only for the ``dev`` and +``test`` environments: + +.. code-block:: php + + use Symfony\Component\HttpKernel\Kernel; + + class AppKernel extends Kernel + { + public function registerBundles() + { + $bundles = array( + // ... + ); + + if (in_array($this->getEnvironment(), array('dev', 'test'))) { + // ... + $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(), + } + + return $bundles; + } + + // ... + } + +We recommend creating just *one* `fixture class`_ for simplicity, though +you're welcome to have more if that class gets quite large. + +Assuming you have at least one fixtures class and that the database access +is configured properly, you can load your fixtures by executing the following +command: + +.. code-block:: bash + + $ php app/console doctrine:fixtures:load + + Careful, database will be purged. Do you want to continue Y/N ? Y + > purging database + > loading AppBundle\DataFixtures\ORM\LoadFixtures + +Coding Standards +---------------- + +The Symfony source code follows the `PSR-1`_ and `PSR-2`_ coding standards that +were defined by the PHP community. You can learn more about +`the Symfony Code Standards`_ and even use the `PHP-CS-Fixer`_, which is +a command-line utility that can fix the coding standards of an entire codebase +in a matter of seconds. + +.. _`full definition`: http://en.wikipedia.org/wiki/Business_logic +.. _`Toran Proxy`: https://toranproxy.com/ +.. _`Composer`: https://getcomposer.org/ +.. _`MVC architecture`: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller +.. _`Doctrine project`: http://www.doctrine-project.org/ +.. _`fixture class`: http://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html#writing-simple-fixtures +.. _`PSR-1`: http://www.php-fig.org/psr/psr-1/ +.. _`PSR-2`: http://www.php-fig.org/psr/psr-2/ +.. _`the Symfony Code Standards`: http://symfony.com/doc/current/contributing/code/standards.html +.. _`PHP-CS-Fixer`: https://github.com/fabpot/PHP-CS-Fixer diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst new file mode 100644 index 000000000..ab340bbe7 --- /dev/null +++ b/best_practices/configuration.rst @@ -0,0 +1,187 @@ +Configuration +============= + +La configuration implique généralement différentes parties de l'application (comme +l'infrastructure et la sécurité) et différents environnements (développement, production). +C'est pourquoi Symfony recommande de diviser la configuration de l'application en trois +parties. + +Configuration liée à l'infrastructure +------------------------------------- + +.. best-practice:: + + Définissez les options de configuration liée à l'infrastructure dans + le fichier ``app/config/parameters.yml``. + +Le fichier par défaut ``parameters.yml`` suit cette recommandation et défini les +options relatives à la base de données et au serveur de mail : + +.. code-block:: yaml + + # app/config/parameters.yml + parameters: + database_driver: pdo_mysql + database_host: 127.0.0.1 + database_port: ~ + database_name: symfony + database_user: root + database_password: ~ + + mailer_transport: smtp + mailer_host: 127.0.0.1 + mailer_user: ~ + mailer_password: ~ + + # ... + +Ces options ne sont pas définies dans le fichier ``app/config/config.yml`` car +elles n'ont pas de rapport avec le comportement de l'application. En d'autres termes, +votre application ne se soucie pas de l'emplacement de la base de données ou des +droits permettant d'y avoir accès, tant que la base de données est bien configurée. + +Paramètres standards +~~~~~~~~~~~~~~~~~~~~ + +.. best-practice:: + + Définissez tous les paramètres de votre application dans le fichier + ``app/config/parameters.yml.dist``. + +Depuis la version 2.3, Symfony inclus un fichier de configuration appelé +``parameters.yml.dist``, qui stocke la liste des paramètres de configuration +standard de votre application. + +Chaque fois qu'un nouveau paramètre de configuration est défini pour votre application, +vous devriez également l'ajouter à ce fichier et envoyer cette modification à votre +gestionnaire de source. Ensuite, à chaque fois qu'un développeur met à jour le projet +ou le déploie sur un serveur, Symfony vérifiera s'il y a des différences entre le fichier +standard ``parameters.yml.dist`` et votre fichier local ``parameters.yml``. S'il existe +une différence, Symfony vous demandera d'indiquer une valeur pour le nouveau paramètre +et l'ajoutera à votre fichier local ``parameters.yml``. + +Configuration liée à l'application +---------------------------------- + +.. best-practice:: + + Définissez les options de configuration liées au comportement de + l'application dans le fichier ``app/config/config.yml``. + +Le fichier ``config.yml`` contient les options utilisées par l'application pour +modifier son comportement, comme l'expéditeur des notifications par email ou +l'activation de `fonctionnalitées conditionnelles`_. Définir ces valeurs +dans le fichier ``parameters.yml`` serait ajouter une couche supplémentaire de +configuration qui ne serait pas nécessaire car vous ne voulez pas ou n'avez pas +besoin de modifier ces valeurs de configuration sur chaque serveur. + +Les options de configuration définies dans le fichier ``config.yml`` varient +généralement d'un `environnement d'exécution`_ à l'autre. C'est pourquoi Symfony +inclut déjà les fichiers ``app/config/config_dev.yml`` et ``app/config/config_prod.yml`` +de sorte que vous puissiez indiquer des valeurs spécifiques à chaque environnement. + +Constantes vs Options de Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +L'une des erreurs les plus commune lors de la définition de la configuration +de l'application est de créer de nouvelles options pour les valeurs qui ne +change jamais, comme le nombre d'éléments pour des résultats paginés. + +.. best-practice:: + + Utilisez des constantes pour définir les options de configuration ne changeant que rarement. + +L'approche traditionnelle pour définir les options de configuration a impliqué +que beaucoup d'application Symfony ont inclut une option comme ce qui suit, qui +serait utilisée pour gérer le nombre de messages à afficher sur la page d'accueil +d'un blog : + +.. code-block:: yaml + + # app/config/config.yml + parameters: + homepage.num_items: 10 + +Si vous vous demandez quand est-ce la dernière fois que vous avez changé la valeur +d'une option de ce type, il y a de fortes chances pour que ce soit *jamais*. Créer une +option de configuration pour une valeur que vous ne configurerez jamais n'est pas +nécessaire. Notre recommandation est de définir ces valeurs en tant que constantes +dans votre application. Vous pourriez, par exemple, définir une constante ``NUM_ITEMS`` +dans l'entité ``Post`` : + +.. code-block:: php + + // src/AppBundle/Entity/Post.php + namespace AppBundle\Entity; + + class Post + { + const NUM_ITEMS = 10; + + // ... + } + +Le principal avantage à définir des constantes est que vous pouvez utiliser leur +valeur partout dans votre application. Lorsque vous utilisez des paramètres, ils +ne sont disponibles que lorsque vous avez accès au container Symfony. + +Les constantes peuvent être utilisées par exemple dans vos templates Twig grâce +à la fonction ``constant()`` : + +.. code-block:: html+jinja + +
+ Displaying the {{ constant('NUM_ITEMS', post) }} most recent results. +
+ +Et les entités et repositories Doctrine peuvent accéder facilement à ces valeurs, +alors qu'elles n'ont pas accès aux paramètres du container : + +.. code-block:: php + + namespace AppBundle\Repository; + + use Doctrine\ORM\EntityRepository; + use AppBundle\Entity\Post; + + class PostRepository extends EntityRepository + { + public function findLatest($limit = Post::NUM_ITEMS) + { + // ... + } + } + +Le seul inconvénient notable de l'utilisation des constantes pour ce type de +configuration est que vous ne pouvez pas les redéfinir facilement dans vos tests. + +Configuration Sémantique: Ne le faites pas +------------------------------------------ + +.. best-practice:: + + Ne définissez pas une configuration sémantique d'injection de dépendance pour vos bundle. + +Comme expliqué dans l'article `Comment exposer une configuration sémantique pour un Bundle`_, +les bundles Symfony ont deux possibilités concernant la gestion de la configuration : la +configuration normale des serveur via le fichier ``services.yml`` et la configuration +sémantique via une classe spécifique ``*Extension``. + +Bien que la configuration sémantique soit beaucoup plus puissante et fournisse des +fonctionnalités intéressante comme la validation, la charge de travail nécessaire +pour définir cette configution n'est pas valable pour vos bundles qui ne sont pas +destinés à être partagés en tant que bundle réutilisable. + +Déplacez les options sensibles entièrement en dehors de Symfony +--------------------------------------------------------------- + +Lorsque vous manipulez des options sensibles, comme des accès à une base de données, nous +vous recommendons de les stocker en dehors du projet Symfony et de les rendre disponible +au travers des variables d'environnement. Apprenez comme faire en suivant cet article : +`Comment configurer les paramètres externes dans le conteneur de services`_ + +.. _`fonctionnalitées conditionnelles`: http://en.wikipedia.org/wiki/Feature_toggle +.. _`environnement d'exécution`: http://symfony.com/doc/current/cookbook/configuration/environments.html +.. _`constant() function`: http://twig.sensiolabs.org/doc/functions/constant.html +.. _`Comment exposer une configuration sémantique pour un Bundle`: http://symfony.com/fr/doc/current/cookbook/bundles/extension.html +.. _`Comment configurer les paramètres externes dans le conteneur de services`: http://symfony.com/fr/doc/current/cookbook/configuration/external_parameters.html diff --git a/best_practices/controllers.rst b/best_practices/controllers.rst new file mode 100644 index 000000000..05cd7a1af --- /dev/null +++ b/best_practices/controllers.rst @@ -0,0 +1,212 @@ +Controllers +=========== + +Symfony follows the philosophy of *"thin controllers and fat models"*. This +means that controllers should hold just the thin layer of *glue-code* +needed to coordinate the different parts of the application. + +As a rule of thumb, you should follow the 5-10-20 rule, where controllers should +only define 5 variables or less, contain 10 actions or less and include 20 lines +of code or less in each action. This isn't an exact science, but it should +help you realize when code should be refactored out of the controller and +into a service. + +.. best-practice:: + + Make your controller extend the ``FrameworkBundle`` base Controller and + use annotations to configure routing, caching and security whenever possible. + +Coupling the controllers to the underlying framework allows you to leverage +all of its features and increases your productivity. + +And since your controllers should be thin and contain nothing more than a +few lines of *glue-code*, spending hours trying to decouple them from your +framework doesn't benefit you in the long run. The amount of time *wasted* +isn't worth the benefit. + +In addition, using annotations for routing, caching and security simplifies +configuration. You don't need to browse tens of files created with different +formats (YAML, XML, PHP): all the configuration is just where you need it +and it only uses one format. + +Overall, this means you should aggressively decouple your business logic +from the framework while, at the same time, aggressively coupling your controllers +and routing *to* the framework in order to get the most out of it. + +Routing Configuration +--------------------- + +To load routes defined as annotations in your controllers, add the following +configuration to the main routing configuration file: + +.. code-block:: yaml + + # app/config/routing.yml + app: + resource: "@AppBundle/Controller/" + type: annotation + +This configuration will load annotations from any controller stored inside the +``src/AppBundle/Controller/`` directory and even from its subdirectories. +So if your application defines lots of controllers, it's perfectly ok to +reorganize them into subdirectories: + +.. code-block:: text + +