<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Jérémy DECOOL (@jdecool), Ingénieur Etudes et Développement à Lyon</title>
        <description></description>
        <link>https://www.jdecool.fr</link>
        <atom:link href="https://www.jdecool.fr/feed/php.xml" rel="self" type="application/rss+xml" />
        
        
            
        
            
                235
                <item>
                    <title>Profilez vos tests PHPUnit avec OpenTelemetry</title>
                    <description>&lt;p&gt;Un projet de développement informatique qui grossit, c’est une base de code qui grossit par la même occasion et la batterie de tests qui évolue en conséquence. On ajoute des tests semaine après semaine, les temps d’exécution s’allongent. Et puis, à un moment, la CI s’arrête net: &lt;code&gt;Allowed memory size exhausted&lt;/code&gt;. Le premier réflexe est alors d’augmenter la mémoire allouée à PHP. Et ce cycle peut continuer un certain temps, jusqu’au moment où l’on atteint des seuils critiques.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Je l’ai rencontré dans plusieurs expériences professionnelles. Corriger des problèmes de mémoire est compliqué et l’outillage restreint. On est donc aveugle, on ne sait pas quels tests consomment de la mémoire, lesquels prennent du temps à s’exécuter. Monitorer une application en production est une bonne pratique, pourquoi n’en serait-il pas de même pour nos tests ?&lt;/p&gt;

&lt;p&gt;Pour l’observabilité de nos applications de production, un standard est en train de s’imposer: &lt;a href=&quot;https://opentelemetry.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;OpenTelemetry&lt;/a&gt;. Pourquoi, alors, ne pas utiliser ce dernier pour également suivre ce qu’il se passe sur nos tests: produire des traces et des métriques que nous pourrions analyser ?&lt;/p&gt;

&lt;p&gt;Il existe d’ailleurs une bibliothèque pour intégrer de l’observabilité dans nos tests PHPUnit avec OpenTelemetry: &lt;a href=&quot;https://packagist.org/packages/flow-php/phpunit-telemetry-bridge &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;flow-php/phpunit-telemetry-bridge&lt;/a&gt;. Il s’agit d’une extension PHPUnit qui collecte la télémétrie de votre suite de tests et l’exporte vers n’importe quel &lt;em&gt;backend&lt;/em&gt; compatible OTLP.&lt;/p&gt;

&lt;p&gt;Pour l’installer, rien de plus simple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;composer require --dev flow-php/phpunit-telemetry-bridge&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il restera ensuite à ajouter la configuration nécessaire dans PHPUnit:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;phpunit&amp;gt;
    &amp;lt;!-- ... --&amp;gt;
    &amp;lt;extensions&amp;gt;
        &amp;lt;bootstrap class=&amp;quot;Flow\Bridge\PHPUnit\Telemetry\TelemetryExtension&amp;quot;&amp;gt;
            &amp;lt;parameter name=&amp;quot;service_name&amp;quot; value=&amp;quot;phpunit-opentelemetry&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;transport&amp;quot; value=&amp;quot;curl&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;endpoint&amp;quot; value=&amp;quot;http://localhost:4318&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;emit_traces&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;emit_metrics&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;emit_test_spans&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;emit_test_case_spans&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;curl_connect_timeout_ms&amp;quot; value=&amp;quot;1000&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;curl_timeout_ms&amp;quot; value=&amp;quot;2000&amp;quot;/&amp;gt;
        &amp;lt;/bootstrap&amp;gt;
    &amp;lt;/extensions&amp;gt;
&amp;lt;/phpunit&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une fois configuré, il ne reste plus qu’à lancer vos tests, les données de télémétrie seront automatiquement envoyées, vous permettant ensuite de les visualiser dans des tableaux de bord.&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog/20260601-profilez-vos-tests-phpunit-avec-opentelemetry/phpunit-otel-overview.png&quot; alt=&quot;Vue d&apos;ensemble de la télémétrie d&apos;une suite de tests PHPUnit dans un tableau de bord&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;On y retrouvera chaque suite de tests avec le détail de chaque test: sa durée, son empreinte mémoire, son statut.&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog/20260601-profilez-vos-tests-phpunit-avec-opentelemetry/phpunit-otel-memory.png&quot; alt=&quot;Consommation mémoire des tests PHPUnit visualisée via OpenTelemetry&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Quelques points d’attention néanmoins. Instrumenter chaque test a un coût. Cela ne sera pas forcément intéressant sur une petite suite de test. C’est surtout lorsque le projet commence à grossir que ce type de donnée a de la valeur et que vous souhaitez arrêter de naviguer à l’aveugle.&lt;/p&gt;

&lt;p&gt;Et si vous désirez en savoir plus, n’hésitez pas à consulter &lt;a href=&quot;https://flow-php.com/documentation/components/bridges/phpunit-telemetry-bridge/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;la documentation de l’outil&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Mon, 01 Jun 2026 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2026/06/01/profilez-vos-tests-phpunit-avec-opentelemetry.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2026/06/01/profilez-vos-tests-phpunit-avec-opentelemetry.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                236
                <item>
                    <title>Faire des requêtes CTE avec Doctrine ORM en PHP</title>
                    <description>&lt;p&gt;J’ai déjà évoqué sur ce blog que &lt;a href=&quot;/blog/2026/02/18/boring-technology-les-bases-de-donnees-relationnelles.html&quot;&gt;le développement “moderne” avec les ORM masque les fonctionnalités avancées des SGBD&lt;/a&gt; au point que dorénavant les développeurs ne maitrisent et ne connaissent guère plus que le classique &lt;code&gt;SELECT ... FROM ... WHERE ...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Dans les mécanismes méconnus et qui pourtant, pourrait permettre de soulager certains traitements applicatifs, on retrouve les CTE (&lt;em&gt;Common Table Expressions&lt;/em&gt;). Voyons comment les utiliser avec Doctrine ORM en PHP.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Commençons par expliquer ce qu’est une CTE. Il s’agit d’une sous-requête utilisable comme une table temporaire. C’est une technique utile pour décomposer des requêtes complexes, éviter la duplication de sous-sélections ou écrire des requêtes récursives.&lt;/p&gt;

&lt;p&gt;En presque 20 ans d’expérience professionnelle, j’ai très rarement eu l’occasion d’en voir dans le code que je peux être amené à parcourir quotidiennement. Prenons par exemple, un blog où les articles sont rattachés à des catégories, ces dernières organisées sous forme arborescente sous la forme &lt;code&gt;Category(id, label, parent_id)&lt;/code&gt;. Je suis certain que, si l’on demande à un développeur de récupérer toutes les catégories parentes d’une catégorie précise, ce dernier fera le traitement en PHP avec un code ressemblant au suivant:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// src/Repository/CategoryRepository.php
class CategoryRepository extends ServiceEntityRepository
{
    // ...

    /**
     * @return list&amp;lt;Category&amp;gt;
     */
    function getCatagoriesWithParents(int $categoryId): array
    {
        $category = $this-&amp;gt;categoryRepository-&amp;gt;find($categoryId);

        $categories = [
            $category,
        ];

        while ($parent = $category-&amp;gt;getParent()) {
            $categories[] = $parent;
        }

        return $categories;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;L’inconvénient majeur ici est qu’en interne, Doctrine va faire une requête à chaque tour de boucle. C’est ce que l’on appelle &lt;a href=&quot;/blog/2015/12/17/le-probleme-n+1.html&quot;&gt;le problème N+1&lt;/a&gt;. Pour résoudre ce problème, via une requête SQL, il n’est pas possible de faire un simple &lt;code&gt;SELECT ... FROM ... WHERE&lt;/code&gt;, c’est exactement dans ce cas qu’une CTE récursive est utile. Cette dernière peut être écrite de la manière suivante:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;WITH RECURSIVE cte_category AS (
    -- point de départ
    SELECT id, label, parent_id, 0 AS depth
    FROM category
    WHERE id = :id

    UNION ALL

    -- récursivité
    SELECT c.id, c.label, c.parent_id, cp.depth + 1
    FROM category c
    INNER JOIN cte_category cp ON c.id = cp.parent_id
)
SELECT id, label, parent_id FROM cte_category
ORDER BY depth DESC&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;La requête commence par récupérer la catégorie identifiée par &lt;code&gt;:id&lt;/code&gt;, puis se joint récursivement à elle-même en remontant via &lt;code&gt;parent_id&lt;/code&gt; jusqu’à ce qu’il n’y ait plus de parent. La colonne &lt;code&gt;depth&lt;/code&gt; permet ensuite de trier du plus ancien ancêtre jusqu’à la catégorie cible.&lt;/p&gt;

&lt;p&gt;Le problème pour faire cette requête avec un ORM tel que Doctrine, c’est que ce dernier ne permet pas de gérer les CTE nativement au travers de son &lt;code&gt;QueryBuilder&lt;/code&gt; ni même en &lt;code&gt;DQL&lt;/code&gt;. Il est alors nécessaire de passer par une requête native où le résultat final sera mappé sur un objet.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// src/Repository/CategoryRepository.php
class CategoryRepository extends ServiceEntityRepository
{
    // ...

    public function findAllWithParents(int $categoryId)
    {
        $rsm = new ResultSetMappingBuilder($this-&amp;gt;getEntityManager());
        $rsm-&amp;gt;addRootEntityFromClassMetadata(Category::class, &amp;#39;c&amp;#39;);

        $sql = &amp;lt;&amp;lt;&amp;lt;SQL
            WITH RECURSIVE with_categories AS (
                SELECT id, label, parent_id, 0 AS depth
                FROM category
                WHERE id = :id

                UNION ALL

                SELECT c.id, c.label, c.parent_id, cp.depth + 1
                FROM category c
                INNER JOIN with_categories cp ON c.id = cp.parent_id
            )
            SELECT id, label, parent_id FROM with_categories
            ORDER BY depth DESC
        SQL;

        return $this-&amp;gt;getEntityManager()
            -&amp;gt;createNativeQuery($sql, $rsm)
            -&amp;gt;setParameter(&amp;#39;id&amp;#39;, $categoryId)
            -&amp;gt;getResult();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Dans le code précédent, le &lt;code&gt;ResultSetMappingBuilder&lt;/code&gt; se charge de faire correspondre les colonnes retournées avec les propriétés de l’entité &lt;code&gt;Category&lt;/code&gt;. On obtient alors en sortie une liste d’instances de &lt;code&gt;Category&lt;/code&gt; ordonnés de la racine jusqu’à la catégorie demandée.&lt;/p&gt;

&lt;p&gt;Avec ce type de requête, on peut faire énormément de choses, il est par exemple, possible de récupérer tous les articles appartenant à une catégorie enfant en réutilisant la requête précédente:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;WITH RECURSIVE category_path AS (
    SELECT id, label, parent_id
    FROM category
    WHERE id = :id

    UNION ALL

    SELECT c.id, c.label, c.parent_id
    FROM category c
    INNER JOIN category_path cp ON c.id = cp.parent_id
)
SELECT a.id, a.title, a.category_id
FROM article a
INNER JOIN category_path cp ON a.category_id = cp.id
ORDER BY a.published_date DESC&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;En une seule requête, on récupère tous les articles liés à la catégorie cible ou à n’importe lequel de ses ancêtres. Sans CTE récursive, il faudrait soit faire plusieurs requêtes successives pour parcourir la hiérarchie une approche bien moins efficace. Un point d’attention néanmoins, étant donné que l’on exécute une requête SQL native, il faudra faire attention à la portabilité de cette dernière, les CTE pouvant ne pas être disponible sur toutes les bases de données supportées par Doctrine.&lt;/p&gt;

&lt;p&gt;De plus, l’utilisation du &lt;code&gt;ResultSetMappingBuilder&lt;/code&gt; nécessite que le &lt;code&gt;SELECT&lt;/code&gt; de la requête contienne l’ensemble des colonnes nécessaire à l’alimentation de l’objet. À défaut, l’entité sera hydratée de manière incomplète sans qu’aucune erreur explicite ne soit levée.&lt;/p&gt;
</description>
                    <pubDate>Sat, 09 May 2026 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2026/05/09/faire-des-requetes-cte-avec-doctrine-orm-en-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2026/05/09/faire-des-requetes-cte-avec-doctrine-orm-en-php.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                237
                <item>
                    <title>Simplifier vos objets immuables avec PHP 8.5</title>
                    <description>&lt;p&gt;PHP 8.5 a été publié le 20 novembre 2025 et, dans les nouvelles fonctionnalités proposées par cette version, on trouve notamment la possibilité de mettre à jour des propriétés lors du clonage d’objets. Une amélioration qui va permettre de simplifier nos objets immuables.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;En programmation orientée objet, un objet immuable est un objet dont l’état ne peut pas être modifié après sa création. Ainsi, toute “modification” visant à changer l’état de ce dernier conduit à la création d’une nouvelle instance avec les valeurs mises à jour. Cela représente un réel avantage, car leur état ne changeant jamais, les effets de bord et bugs liés à des modifications inattendues de l’objet sont ainsi limités.&lt;/p&gt;

&lt;p&gt;Avant PHP 8.5, la modification d’un objet immuable pouvait s’avérer fastidieuse, car il était nécessaire de créer une nouvelle instance avec les nouvelles valeurs. Cela pouvait être d’autant plus contraignant si l’objet avait de nombreuses propriétés.&lt;/p&gt;

&lt;p&gt;Par exemple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;readonly class GPSLocation
{
    public function __construct(
        public float $latitude,
        public float $longitude,
        public float $altitude,
        public DateTimeImmutable $timestamp,
    ) {
    }

    public function withCoordinates(float $latitude, float $longitude): GPSLocation
    {
        return new GPSLocation(
            $latitude,
            $longitude,
            $this-&amp;gt;altitude,
            $this-&amp;gt;timestamp,
        );
    }

    public function withAltitude(float $altitude): GPSLocation
    {
        return new GPSLocation(
            $this-&amp;gt;latitude,
            $this-&amp;gt;longitude,
            $altitude,
            $this-&amp;gt;timestamp,
        );
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;PHP 8.5 nous permet de simplifier cette opération en permettant de cloner l’objet tout en autorisant de mettre à jour certaines propriétés lors du clonage. Seules les propriétés devant être modifiées doivent être précisées:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;readonly class GPSLocation
{
    public function __construct(
        public float $latitude,
        public float $longitude,
        public float $altitude,
        public DateTimeImmutable $timestamp,
    ) {
    }

    public function withCoordinates(float $latitude, float $longitude): GPSLocation
    {
        return clone($this, [
            &amp;#39;latitude&amp;#39; =&amp;gt; $latitude,
            &amp;#39;longitude&amp;#39; =&amp;gt; $longitude,
        ]);
    }

    public function withAltitude(float $altitude): GPSLocation
    {
        return clone($this, [
            &amp;#39;altitude&amp;#39; =&amp;gt; $altitude,
        ]);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Mon, 16 Feb 2026 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2026/02/16/simplifier-vos-objets-immuables-avec-php-8-5.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2026/02/16/simplifier-vos-objets-immuables-avec-php-8-5.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                238
                <item>
                    <title>Testez votre documentation OpenAPI (avec PHP)</title>
                    <description>&lt;p&gt;Il n’y a rien de plus frustrant que d’utiliser une API qui ne se comporte pas de la manière qui est décrite dans sa documentation. Nombreux sont les développeurs ayant déjà vécu cette situation. Et pour cause, maintenir une documentation précise et à jour peut s’avérer être une tâche complexe et laborieuse (et ce, même si cette dernière est générée automatiquement). Pour éviter ces désagréments, il est primordial de pouvoir comparer ce qui est décrit dans la documentation avec le comportement réel du code.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Dans le cadre d’une API, une partie de ce travail consiste à s’assurer de la conformité de la &lt;a href=&quot;https://swagger.io/specification &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;documentation OpenAPI&lt;/a&gt;. Une solution pouvant être mise en place consiste à intégrer la validation et la vérification de la documentation au sein de tests fonctionnels. L’objectif de cette approche est de vérifier que la requête HTTP effectuée, ainsi que la réponse retournée lors du test correspondent à ce qui est spécifié. Ainsi, si les tests s’exécutent à chaque modification du code, cela permettra de garantir que l’API se comporte bien tel que décrit dans la documentation OpenAPI tout au cours de la vie du projet.&lt;/p&gt;

&lt;p&gt;Pour mettre en place ces tests dans un projet PHP, j’utilise généralement la bibliothèque  &lt;a href=&quot;https://packagist.org/packages/league/openapi-psr7-validator &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;league/openapi-psr7-validator&lt;/code&gt;&lt;/a&gt; qui permet de valider des requêtes et réponses HTTP par rapport à une spécification OpenAPI.&lt;/p&gt;

&lt;p&gt;Pour faciliter leur intégration dans vos bases de code, je recommande généralement d’utiliser une classe abstraitre pour mutualiser le code de vérification nécessaire et qui sera dédié aux tests de l’API. Cela pourrait ressembler au code suivant:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// ...
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

abstract class ApiTestCase extends WebTestCase
{
    protected static KernelBrowser $client;

    protected function setUp(): void
    {
        parent::setUp();

        static::$client = static::createClient();
    }

    protected function makeRequest(
        string $method,
        string $uri,
        array $parameters = [],
        array $files = [],
        array $server = [],
        ?string $content = null,
        bool $changeHistory = true,
    ): Response {
        static::$client-&amp;gt;request($method, $uri, $parameters, $files, $server, $content, $changeHistory);
        $response = static::$client-&amp;gt;getResponse();

        // TODO: insérer le code de validation de la requête
        // TODO: insérer le code de validation de la réponse

        return $response;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le code précédent est basé sur l’utilisation du &lt;em&gt;framework&lt;/em&gt; Symfony, mais sa logique est universelle. L’idée étant d’avoir une méthode dans laquelle on puisse faire un appel HTTP, récupérer la requête HTTP effectuée ainsi que la réponse retournée afin de pouvoir effectuer des contrôles sur ces dernières.&lt;/p&gt;

&lt;p&gt;Ajoutons maintenant le code permettant d’effectuer la vérification des données avec la spécification OpenAPI.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// ...
use League\OpenAPIValidation\PSR7\OperationAddress;
use League\OpenAPIValidation\PSR7\ValidatorBuilder;
use Nyholm\Psr7\Request as Psr7Request;
use Nyholm\Psr7\Response as Psr7Response;

abstract class ApiTestCase extends WebTestCase
{
    private const OPENAPI_SPECIFICATION_FILE = &amp;#39;/path/to/specification.json&amp;#39;;

    private static ?ValidatorBuilder $validatorBuilder = null;

    // ...

    protected function makeRequest(
        // ...
        bool $enableOpenApiSpecificationRequestCheck = true,
        bool $enableOpenApiSpecificationResponseCheck = true,
    ): Response {
        // ...

        if ($enableOpenApiSpecificationRequestCheck) {
            $this-&amp;gt;validateOpenApiRequestSpecification($method, $uri, static::$client-&amp;gt;getRequest());
        }

        if ($enableOpenApiSpecificationResponseCheck) {
            $this-&amp;gt;validateOpenApiResponseSpecification($method, $uri, static::$client-&amp;gt;getResponse());
        }

        return $response;
    }

    private function validateOpenApiRequestSpecification(string $method, string $endpoint, Request $request): void
    {
        $validator = $this-&amp;gt;getValidatorBuilder()-&amp;gt;getRequestValidator();
        $validator-&amp;gt;validate(
            new Psr7Request(strtolower($method), $endpoint, $request-&amp;gt;headers-&amp;gt;all(), $request-&amp;gt;getContent()),
        );
    }

    private function validateOpenApiResponseSpecification(string $method, string $endpoint, Response $response): void
    {
        $validator = $this-&amp;gt;getValidatorBuilder()-&amp;gt;getResponseValidator();
        $validator-&amp;gt;validate(
            new OperationAddress($endpoint, strtolower($method)),
            new Psr7Response($response-&amp;gt;getStatusCode(), $response-&amp;gt;headers-&amp;gt;all(), (string) $response-&amp;gt;getContent()),
        );
    }

    private function getValidatorBuilder(): ValidatorBuilder
    {
        return self::$validatorBuilder ??= new ValidatorBuilder()-&amp;gt;fromJsonFile(self::OPENAPI_SPECIFICATION_FILE);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;De cette manière, à chaque appel à votre API, vous avez la possibilité de vérifier la documentation qui lui est associée. Vous remarquerez au passage qu’il est possible de désactiver la vérification soit de la requête soit de la réponse. Si la désactivation de la vérification de la réponse peut-être discutable, pouvoir désactiver la vérification de la requête est &lt;strong&gt;indispensable&lt;/strong&gt; pour pouvoir tester les cas d’erreurs avec des appels HTTP invalides (afin de pouvoir tester le comportement d’un client qui effectuerait un mauvais appel).&lt;/p&gt;

&lt;p&gt;L’intégration de la validation OpenAPI dans vos tests fonctionnels vous permettra de faire “d’une pierre deux coups”: vous aurez la possibilité d’écrire des tests fonctionnels pour tester le comportement de votre code tout en permettant d’assurer sa cohérence avec votre documentation. Cette approche renforcera la qualité de votre API et évitera des désagréments auprès de vos utilisateurs.  N’hésitez pas à adapter cette méthode à votre contexte et les différents &lt;em&gt;frameworks&lt;/em&gt; que vous utilisez.&lt;/p&gt;
</description>
                    <pubDate>Mon, 28 Jul 2025 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2025/07/28/testez-votre-documentation-openapi-avec-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2025/07/28/testez-votre-documentation-openapi-avec-php.html</guid>
                </item>
            
        
            
        
            
        
            
                239
                <item>
                    <title>Évaluez la qualité de vos tests avec les tests de mutation</title>
                    <description>&lt;p&gt;Très souvent, lorsque l’on souhaite mettre en place des indicateurs sur les tests d’un projet, la première mesure que l’on va mettre en place est celle de la couverture de code. Et pour beaucoup, il s’agit de la métrique principale permettant d’avoir une idée de qualité des tests d’un projet. Cette dernière sous-entend que plus la couverture est élevée et plus les tests assurent la sécurité du fonctionnement de notre code. Il s’agit pourtant d’un indicateur qui peut être trompeur.&lt;/p&gt;

&lt;p&gt;Il s’agit d’un &lt;a href=&quot;/blog/2023/01/03/a-propos-de-la-couverture-de-code.html&quot;&gt;sujet que j’ai déjà abordé dans ce blog&lt;/a&gt;, expliquant que ce n’est pas parce qu’un test exécute une ligne de code que la vérification du comportement associé est efficace et pertinente. C’est dans ce cadre-là, qu’il est possible de mettre en place des tests de mutation (du &lt;em&gt;mutation testing&lt;/em&gt; en anglais).&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Les tests de mutation introduisent volontairement des défauts dans le code source d’un projet dans le but de vérifier si les tests permettent de détecter les changements de comportement introduits. Ces “défauts” sont appelés des “mutants” (d’où le nom de tests de mutation) et correspondent à des modifications mineures du code tentant de simuler des erreurs de programmation courantes. À la suite de l’exécution de ces tests, un &lt;strong&gt;score de mutation&lt;/strong&gt; est retourné. Ce dernier représente le pourcentage de mutants tués. Plus le score est élevé, plus les tests sont efficaces pour détecter les erreurs.&lt;/p&gt;

&lt;p&gt;Parmi les mutations les plus courantes, on retrouve:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;le remplacement des opérateurs mathématiques: un &lt;code&gt;+&lt;/code&gt; qui devient un &lt;code&gt;-&lt;/code&gt;,&lt;/li&gt;
  &lt;li&gt;la modification des opérateurs de comparaison: une condition d’égalité &lt;code&gt;===&lt;/code&gt; qui devient une négation &lt;code&gt;!==&lt;/code&gt;,&lt;/li&gt;
  &lt;li&gt;la suppression de lignes de code,&lt;/li&gt;
  &lt;li&gt;la modification des valeurs booléennes: un &lt;code&gt;true&lt;/code&gt; qui devient &lt;code&gt;false&lt;/code&gt;,&lt;/li&gt;
  &lt;li&gt;la modification de valeurs de retour dans les fonctions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Les tests de mutation permettent alors d’évaluer la capacité des tests à détecter des erreurs et pas simplement à exécuter du code. Ils mettent en évidence les parties du code où les tests sont insuffisants ou inefficaces. Cela permet ainsi d’ajouter des tests aux endroits les plus pertinents. Au final, ils permettent d’améliorer la confiance envers la suite de tests.&lt;/p&gt;

&lt;p&gt;En PHP, l’outil le plus connu pour cela est certainement &lt;a href=&quot;https://infection.github.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Infection&lt;/a&gt;. Ce dernier supporte PHPUnit, PhpSpec et Codeception, nécessite PHP 7.4 au minimum avec au moins une des extensions suivantes: &lt;code&gt;Xdebug&lt;/code&gt;, &lt;code&gt;phpdbg&lt;/code&gt; ou &lt;code&gt;pcov&lt;/code&gt; installée. Son installation est extrêmement simple, puisqu’il suffira de faire un &lt;code&gt;composer require --dev infection/infection&lt;/code&gt;. Une configuration de base sera automatiquement créée au premier lancement d’Infection permettant un démarrage extrêmement rapide.&lt;/p&gt;

&lt;p&gt;Prenons un exemple simple pour illustrer le principe. Une classe &lt;code&gt;Calculator&lt;/code&gt; qui permet d’effectuer des opérations de calcul:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Calculator
{
    public function add(int $x, int $y): int
    {
        return $x + $y;
    }

    public function divide(int $x, int $y): float
    {
        if ($y === 0) {
            throw new \InvalidArgumentException(&amp;#39;Division by zero is not allowed.&amp;#39;);
        }

        return $x / $y;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cette classe est associée aux tests suivants:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;use PHPUnit\Framework\TestCase;

final class CalculatorTest extends TestCase
{
    public function testAdd(): void
    {
        $addition = new Calculator();

        $result = $addition-&amp;gt;add(2, 3);

        $this-&amp;gt;assertEquals(5, $result);
    }

    public function testDivide(): void
    {
        $addition = new Calculator();

        $result = $addition-&amp;gt;divide(6, 2);

        $this-&amp;gt;assertEquals(3, $result);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;En lançant les tests de mutation (via la commande &lt;code&gt;vendor/bin/infection&lt;/code&gt;), nous obtenons le rapport suivant:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Metrics:
   Mutation Score Indicator (MSI): 71%
   Mutation Code Coverage: 85%
   Covered Code MSI: 83%&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le &lt;code&gt;Mutation Score Indicator&lt;/code&gt; représente le pourcentage de mutants tués par rapport au nombre total de mutants générés. Un mutant tué correspond à au moins un test qui échoue après l’application d’un mutant. Elle constitue la métrique principale des tests de mutation. Elle qui permet de mesurer l’efficacité des tests à détecter des erreurs. Le score doit alors être le plus élevé possible.&lt;/p&gt;

&lt;p&gt;Le &lt;code&gt;Mutation Code Coverage&lt;/code&gt; correspond au pourcentage de code parcouru par les tests de mutation. Cette métrique est similaire à la couverture de code “classique”, mais du point de vue des mutations.&lt;/p&gt;

&lt;p&gt;La dernière métrique, le &lt;code&gt;Covered code MSI&lt;/code&gt; est un calcul effectué uniquement sur le code couvert par les tests. Elle permet notamment d’isoler l’efficacité des tests existants. Cet indicateur répond à la question: “Parmi le code que mes tests exécutent, quelle est leur efficacité à détecter des erreurs”. Cet indicateur permet de détecter si vos tests sont suffisamment présents (indicateur élevé) ou si ces derniers ne permettent pas de détecter les problèmes de manière efficace (indicateur bas).&lt;/p&gt;

&lt;p&gt;Pour améliorer le score des tests de mutation et par rebond la qualité de vos tests en général, il faudra &lt;strong&gt;tester tous les chemins d’exécution&lt;/strong&gt; de votre code (incluant les cas limites et les exceptions). De plus, il ne faut pas se contenter de tester les cas nominaux, il est important de &lt;strong&gt;tester les cas d’erreurs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Par exemple dans les tests que nous avons écrit précédemment, nous n’avons pas testé le cas d’erreur de la division par zéro. Modifions le code précédent, pour ajouter ce dernier:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;use PHPUnit\Framework\TestCase;

final class CalculatorTest extends TestCase
{
    public function testAdd(): void
    {
        $addition = new Calculator();

        $result = $addition-&amp;gt;add(2, 3);

        $this-&amp;gt;assertEquals(5, $result);
    }

    public function testDivide(): void
    {
        $addition = new Calculator();

        $result = $addition-&amp;gt;divide(6, 2);

        $this-&amp;gt;assertEquals(3, $result);
    }

    public function testDivideByZero(): void
    {
        $addition = new Calculator();

        $this-&amp;gt;expectException(\InvalidArgumentException::class);
        $this-&amp;gt;expectExceptionMessage(&amp;#39;Division by zero is not allowed.&amp;#39;);

        $addition-&amp;gt;divide(6, 0);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous obtenons alors le résultat ci-dessous:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Metrics:
   Mutation Score Indicator (MSI): 100%
   Mutation Code Coverage: 100%
   Covered Code MSI: 100%&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si mettre en place un calcul de la couverture de code de vos tests est un bon premier point, si vous souhaitez aller plus loin et avoir une métrique pertinente pour avoir une idée de la qualité de vos tests, les tests de mutation sont une très bonne approche. Vous aurez alors les outils pour identifier et combler les “trous” de vos tests. Et comme toute solution, il sera nécessaire de prêter attention à quelques points.&lt;/p&gt;

&lt;p&gt;Tout d’abord, il est important de noter que les tests de mutation sont des tests qui sont coûteux en termes de performance. La création et la mise en place de mutants sont consommatrices de ressources de calcul et de temps d’exécution. La génération de mutants peut ne pas être parfaite et peut ne pas changer le comportement observable du code. Dans ce dernier cas de figure, les mutants ne peuvent être tués, faussant ainsi le calcul final. On parle alors de “mutants équivalents”. Ces derniers entraînent alors de faux positifs, car ils ne changent pas réellement le comportement attendu.&lt;/p&gt;
</description>
                    <pubDate>Sun, 11 May 2025 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2025/05/11/evaluez-la-qualite-de-vos-tests-avec-les-tests-de-mutation.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2025/05/11/evaluez-la-qualite-de-vos-tests-avec-les-tests-de-mutation.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                240
                <item>
                    <title>Les constructeurs nommés comme alternative aux constructeurs multiples en PHP</title>
                    <description>&lt;p&gt;En PHP et, contrairement à d’autre langage, il n’est pas possible d’avoir plusieurs constructeurs dans une classe. Pouvoir définir plusieurs constructeurs peut-être intéressants dans de nombreux cas, comme par exemple, pouvoir construire un objet à partir de différents types de données. Si cela n’est pas possible en PHP, il est possible d’utiliser des &lt;em&gt;constructeurs nommés&lt;/em&gt; pour avoir un fonctionnement similaire.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Mais qu’est-ce qu’un constructeur nommé? Il s’agit d’une méthode statique que l’on va pouvoir appeler afin de construire une instance de classe. Ces méthodes ont l’avantage d’être potentiellement plus explicites que le constructeur de base parce qu’elles vont pouvoir ajouter une sémantique lors de la construction de notre objet.&lt;/p&gt;

&lt;p&gt;Prenons un premier exemple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;readonly class Color
{
    public function __construct(
        public int $red,
        public int $blue,
        public int $green,
    ) {}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le code précédent définit un objet permettant de stocker une couleur. Cette dernière est composant d’un niveau de rouge, de bleu et de vert. Le constructeur principal permet ainsi de renseigner les valeurs correspondantes. Mais dans certains cas d’utilisation, nous pourrions avoir envie de créer une couleur depuis sa valeur hexadécimale. Pour cela, nous pouvons imaginer introduire un constructeur nommé &lt;code&gt;fromHexCode&lt;/code&gt; pour réaliser cette tâche:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;readonly class Color
{
    public static function fromHexCode(string $code): self
    {
        $code = ltrim($code, &amp;#39;#&amp;#39;);

        $red = hexdec(substr($code, 0, 2));
        $blue = hexdec(substr($code, 2, 2));
        $green = hexdec(substr($code, 4, 2));

        return new self($red, $blue, $green);
    }

    public function __construct(
        public int $red,
        public int $blue,
        public int $green,
    ) {}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Les utilisations de constructeur nommé sont multiples et permettent de simplifier la création d’objets, de fournir des valeurs par défaut en fonction d’un contexte d’utilisation tout en encapsulant la logique de création potentiellement complexe au sein de l’objet lui-même. Cela améliore ainsi la lisibilité et la compréhension du code.&lt;/p&gt;

&lt;p&gt;C’est également une technique appréciée en &lt;em&gt;Domain Driven Design&lt;/em&gt; pour expliciter le processus métier qui a conduit à la création de la donnée. Prenons par exemple la création d’un utilisateur dans une application, nous pourrions imaginer la modélisation suivante:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class User
{
    public static function fromRegistration(string $name, string $email, string $password): self
    {
        $user = new self($name, $email, $password);

        // additionnal business logic related to registration

        return $user;
    }

    public static function fromSocialLogin(string $name, string $email): self
    {
        $user = new self($name, $email);

        // additionnal business logic related to social login

        return $user;
    }

    private function __construct(
        public string $name,
        public string $email,
        public ?string $password = null,
    ) {}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ce dernier exemple définit une classe &lt;code&gt;User&lt;/code&gt; avec un constructeur privé. Pour pouvoir créer une instance de notre objet, il sera nécessaire d’utiliser un des constructeurs nommés définis dans cette même classe. Le constructeur nommé à utiliser se fera en fonction du contexte métier dans lequel le traitement est effectué. Il y a ici un réel avantage à utiliser cette méthodologie, car elle permet de voir les éléments nécessaires à la création de notre utilisateur en fonction de l’action effectuée dans l’application.&lt;/p&gt;

&lt;p&gt;Car si un utilisateur se doit d’avoir un nom, un email et potentiellement un mot de passe, le code précédent rend visible le fait qu’il est obligatoire de renseigner toutes ces informations dans le cas d’un enregistrement de l’utilisateur depuis notre projet, alors que le mot de passe n’est pas nécessaire dans le cas d’une connexion d’un réseau social.&lt;/p&gt;

&lt;p&gt;Le concept de constructeur nommé est très puissant et peut se révéler extrêmement utile pour la maintenance et la compréhension d’une base de code. Le principe a été très brièvement introduit dans cet article et je vous recommande vivement de vous y intéresser plus en profondeur, ces derniers étant bien souvent sous-exploités.&lt;/p&gt;
</description>
                    <pubDate>Mon, 20 Jan 2025 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2025/01/20/les-constructeurs-nommes-comme-alternative-aux-constructeurs-multiples-en-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2025/01/20/les-constructeurs-nommes-comme-alternative-aux-constructeurs-multiples-en-php.html</guid>
                </item>
            
        
            
                241
                <item>
                    <title>Écrire une API idempotente (exemple en PHP avec Symfony)</title>
                    <description>&lt;p&gt;D’après la &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9110 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;RFC 9110&lt;/a&gt; qui spécifie la sémantique d’une requête HTTP, une requête est considérée comme &lt;em&gt;idempotente&lt;/em&gt; si elle peut être effectuée plusieurs fois et obtenir un résultat identique lors de chaque appel. Par exemple, si l’on tente d’accéder à une ressource REST &lt;code&gt;GET /resource/42&lt;/code&gt;, deux appels à cet endpoint retourneront toujours le même résultat. Les appels &lt;code&gt;GET&lt;/code&gt; à une API REST sont alors considérés comme idempotents (il en va de même pour des appels &lt;code&gt;PUT&lt;/code&gt; ou &lt;code&gt;DELETE&lt;/code&gt; par exemple). Mais ce n’est pas le cas d’un appel &lt;code&gt;POST /resource&lt;/code&gt; qui créera alors une nouvelle ressource à chaque appel. Nous allons voir dans ce billet comment rendre un appel &lt;code&gt;POST&lt;/code&gt; idempotent.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Pour rendre une API idempotente, il va falloir être en mesure d’identifier de manière unique un appel à une ressource. Généralement, le client API va générer une clé unique et l’ajouter à l’appel effectué. La clé est ensuite vérifiée par le serveur pour qu’il puisse savoir si la requête reçue a déjà été traitée ou non:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Si la clé n’est pas connue, le traitement va être effectué et le résultat sauvegardé avant de retourner la réponse HTTP.&lt;/li&gt;
  &lt;li&gt;Dans le cas contraire, le serveur va retourner la réponse précédemment enregistrée.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La plupart du temps, la clé d’idempotence est envoyée dans les en-têtes de l’appel HTTP afin d’éviter de polluer le corps de la requête.&lt;/p&gt;

&lt;p&gt;Prenons par exemple, le code source d’une API permettant de créer un billet de blog:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class BlogController extends AbstractController
{
    #[Route(&amp;#39;/api/articles&amp;#39;, methods: [&amp;#39;POST&amp;#39;])]
    public function create(Request $request): JsonResponse
    {
        $post = new stdClass();
        $post-&amp;gt;id = Uuid::v7();
        $post-&amp;gt;title = $request-&amp;gt;request-&amp;gt;get(&amp;#39;title&amp;#39;, &amp;#39;Default title&amp;#39;);
        $post-&amp;gt;createdAt = new DateTimeImmutable();
        // [...] enregistrement en base de données

        return new JsonResponse($post, JsonResponse::HTTP_CREATED);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Comme expliqué précédemment, cette API n’est pas idempotente. Car si une même requête est effectuée deux fois de suite, un même article &lt;code&gt;My awesome article&lt;/code&gt; sera ajouté en base de données.&lt;/p&gt;

&lt;p&gt;Nous allons maintenant ajouter à notre API la gestion d’une clé permettant de s’assurer de l’unicité du traitement d’une requête. Cette clé doit être envoyée par le client dans une en-tête que nous nommerons &lt;code&gt;X-Idempotent-Key&lt;/code&gt;. Cette clé sera stockée dans un système de cache (via le composant &lt;code&gt;symfony/cache&lt;/code&gt;) pendant une durée d’une heure. Si la clé n’existe pas, elle sera créée et dans le cas contraire, la réponse du traitement de la requête sera renvoyée une nouvelle fois depuis les données mises en cache.&lt;/p&gt;

&lt;p&gt;Cela pourrait conduire à l’implémentation suivante:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class BlogController extends AbstractController
{
    public function __construct(
        private readonly Psr\Cache\CacheItemPoolInterface $cache,
    ) {}

    #[Route(&amp;#39;/api/blog&amp;#39;, methods: [&amp;#39;POST&amp;#39;])]
    public function create(Request $request): JsonResponse
    {
        if (
            // si l&amp;#39;en-tête X-Idempotent-Key est présente
            ($idempotentKey = $request-&amp;gt;headers-&amp;gt;get(&amp;#39;X-Idempotent-Key&amp;#39;)) !== null

            // et que la donnée est en cache
            &amp;amp;&amp;amp; ($postCache = $cache-&amp;gt;getItem($idempotentKey))-&amp;gt;isHit()
        ) {
            // on retourne la réponse présente dans le cache
            return new JsonResponse($postCache-&amp;gt;get(), JsonResponse::HTTP_CREATED);
        }

        // dans le cas contraire, on effectue le traitement

        $post = new stdClass();
        $post-&amp;gt;id = Uuid::v7();
        $post-&amp;gt;title = $request-&amp;gt;request-&amp;gt;get(&amp;#39;title&amp;#39;, &amp;#39;Default title&amp;#39;);
        $post-&amp;gt;createdAt = new DateTimeImmutable();
        // [...] enregistrement en base de données

        // dans le cas où la clé d&amp;#39;idempotence est définie
        if ($idempotentKey !== null) {
            // on enregistre le résultat du traitement en cache
            $item = $cache-&amp;gt;getItem($idempotentKey);
            $item-&amp;gt;set($post);
            $item-&amp;gt;expiresAfter(3600)
            $cache-&amp;gt;save($item);
        }

        return new JsonResponse($post, JsonResponse::HTTP_CREATED);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;De cette manière notre API pourra être idempotente, améliorant ainsi sa fiabilité ainsi que la cohérence des données du service. L’idempotence permet de garantir que les requêtes identiques produisent toujours le même résultat, évitant ainsi les doublons involontaires qui pourraient survenir en cas de problème réseau par exemple.&lt;/p&gt;

&lt;p&gt;Faut-il mettre en place ce mécanisme partout ? Pas nécessairement. Focalisez les opérations les plus critiques de votre système et en particulier les opérations de modification de l’état de ce dernier.&lt;/p&gt;
</description>
                    <pubDate>Mon, 13 Jan 2025 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2025/01/13/ecrire-une-api-idempotente-en-php-avec-symfony.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2025/01/13/ecrire-une-api-idempotente-en-php-avec-symfony.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
                242
                <item>
                    <title>Le pattern Optional, le conteneur de valeur qui va remplacer vos données nullables</title>
                    <description>&lt;p&gt;Nous manipulons tous au quotidien des données &lt;em&gt;nullables&lt;/em&gt;, or manipuler des données qui peuvent être &lt;code&gt;null&lt;/code&gt; implique de devoir effectuer de nombreuses vérifications afin de savoir si la donnée que l’on manipule contient bien une valeur avant de l’utiliser. Auquel cas, nous aurons une erreur de type:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;PHP Warning:  Uncaught Error: Call to a member function method() on null in php shell code:1
Stack trace:
#0 {main}
  thrown in php shell code on line 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pour éviter l’utilisation de conditions de type &lt;code&gt;if ($var !== null)&lt;/code&gt;, il est possible d’utiliser le &lt;em&gt;pattern&lt;/em&gt; &lt;code&gt;Optional&lt;/code&gt; qui peut s’apparenter à la monade &lt;code&gt;Maybe&lt;/code&gt;.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Un &lt;code&gt;Optional&lt;/code&gt; est un conteneur permettant d’encapsuler une valeur qui peut être présente ou non. Ainsi, plutôt que d’utiliser une variable &lt;code&gt;null&lt;/code&gt; pour représenter une valeur absente, nous allons pouvoir manipuler systématiquement un objet et appeler des méthodes sur ce dernier qui effectuera des opérations en fonction que la valeur soit présente ou non.&lt;/p&gt;

&lt;p&gt;Prenons un exemple concret pour illustrer ce concept. Sur un site d’e-commerce, un événement est émis une fois un achat effectué par un utilisateur afin de lui envoyer un récapitulatif de sa commande. La gestion de l’envoi de l’email pourrait ressembler au code suivant:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Customer
{
    public ?string $email;
}

readonly class OrderConfirmed
{
    public Customer $customer;
}

readonly class OrderNotification
{
    public function __construct(private EmailSender $email) {}

    public function sendConfirmatinEmailOnOrderConfirmed(OrderConfirmed $event): void
    {
        if ($event-&amp;gt;getCustomer() == null) { // invite account
            return;
        }


        if ($event-&amp;gt;getCustomer()-&amp;gt;getEmail() == null) { // no email filled
            return;
        }

        $this-&amp;gt;email-&amp;gt;send($event-&amp;gt;getCustomer()-&amp;gt;getEmail(), &amp;#39;Email content&amp;#39;);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous voyons dans ce cas d’exemple, qu’il est nécessaire de faire deux vérifications avant de pouvoir envoyer notre e-mail de confirmation:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Dans un premier temps, il est nécessaire de vérifier que la commande n’a pas été effectuée en “mode invité”, ce qui impliquerait d’avoir une commande sans notion d’acheteur,&lt;/li&gt;
  &lt;li&gt;Il est ensuite nécessaire de vérifier que l’on a bien une adresse de renseignée pour pouvoir envoyer l’e-mail.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Voyons maintenant ce que pourrait donner ce même code avec l’utilisation du pattern &lt;code&gt;Optional&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Customer
{
    /** @var Optional&amp;lt;string&amp;gt; */
    public Optional $email;
}

readonly class OrderConfirmed
{
    /** @var Optional&amp;lt;Customer&amp;gt; */
    public Optional $customer;
}

readonly class OrderNotification
{
    public function __construct(private EmailSender $email) {}

    public function sendConfirmatinEmailOnOrderConfirmed(OrderConfirmed $event): void
    {
        $event-&amp;gt;customer
            -&amp;gt;flatMap(static fn (Customer $customer) =&amp;gt; $customer-&amp;gt;email)
            -&amp;gt;ifPresent(static fn (string $email) =&amp;gt; $this-&amp;gt;email-&amp;gt;send($event-&amp;gt;getCustomer()-&amp;gt;getEmail(), &amp;#39;Email content&amp;#39;));
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Dans cette seconde version, nous constatons que toutes nos vérifications (les &lt;code&gt;if&lt;/code&gt;) ont été supprimées. Le code s’en retrouve plus concis et surtout se concentre sur les opérations utiles au traitement de l’envoi de notre e-mail, ce qui le rend plus expressif. Il n’est plus question de devoir vérifier si la donnée est présente ou non, la structure de données &lt;code&gt;Optional&lt;/code&gt; va appliquer les transformations demandées uniquement si la valeur est présente et ne fait rien dans le cas contraire.&lt;/p&gt;

&lt;p&gt;Les monades gagnant en popularité ces dernières années, on commence à voir des implémentations du pattern &lt;code&gt;Optional&lt;/code&gt; directement dans les langages de programmation. Ce n’est (malheureusement) pas le cas en PHP. Mais on retrouve quelques implémentations sur &lt;a href=&quot;https://packagist.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Packagist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bien que son utilisation dans l’écosystème PHP reste marginale, à la vue des nombreux avantages qu’il représente, vous avez tout intérêt à regarder ça de plus près. Pour ma part, j’ai commencé à introduire cette notion en construisant ma propre implémentation qui se résume à quelques lignes de code.&lt;/p&gt;
</description>
                    <pubDate>Fri, 25 Oct 2024 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2024/10/25/le-pattern-optional-le-conteneur-de-valeur-qui-va-remplacer-vos-donnees-nullables.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2024/10/25/le-pattern-optional-le-conteneur-de-valeur-qui-va-remplacer-vos-donnees-nullables.html</guid>
                </item>
            
        
            
                243
                <item>
                    <title>Installer des extensions PHP facilement dans une image Docker</title>
                    <description>&lt;p&gt;Si vous avez déjà construit des images Docker pour des applications ou projet PHP, vous avez certainement utilisé l’outil &lt;code&gt;docker-php-ext-install&lt;/code&gt;. Ce dernier permet d’installer et configurer simplement des extensions PHP qui seront disponibles dans le conteneur. Néanmoins, cet outil se limite malheureusement aux extensions officielles fournies avec le langage.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Pour résoudre ce problème et permettre d’installer de nombreuses extensions PHP dans un conteneur Docker, il existe un outil disponible librement sur Github: &lt;a href=&quot;https://github.com/mlocati/docker-php-extension-installer &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;mlocati/docker-php-extension-installer&lt;/code&gt;&lt;/a&gt;. Ce dernier met à disposition un script nommé &lt;code&gt;install-php-extensions&lt;/code&gt; qui vous permettra d’installer et de configurer des extensions PHP au sein de votre image. À la différence du script officiel, il ne se limite pas aux seules extensions fournies directement avec PHP puisqu’il couvre un très large panel d’extensions parmi lesquelles: &lt;code&gt;amqp&lt;/code&gt;, &lt;code&gt;cassandra&lt;/code&gt;, &lt;code&gt;ioncube_loader&lt;/code&gt;, &lt;code&gt;jsonpath&lt;/code&gt;, &lt;code&gt;newrelic&lt;/code&gt;, &lt;code&gt;rdkafka&lt;/code&gt;, &lt;code&gt;xdebug&lt;/code&gt; et bien d’autres encore.&lt;/p&gt;

&lt;p&gt;Mais il permet d’aller encore plus loin en installer également les dépendances nécessaires à l’installation des extensions. Il installera par exemple la bibliothèque &lt;code&gt;rabbitmq-c&lt;/code&gt; nécessaire au bon fonctionnement de l’extension &lt;code&gt;amqp&lt;/code&gt; permettant entre autres de communiquer avec RabbitMQ.&lt;/p&gt;

&lt;p&gt;Fonctionnant avec les images basées sur Debian et Alpine, vous aurez la possibilité d’installer les extensions pour les versions de 7.1 à 8.4 de PHP, de quoi couvrir un grand nombre de besoins et cas d’utilisation.&lt;/p&gt;

&lt;p&gt;Et pour finir, voici un exemple d’utilisation:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot; data-lang=&quot;dockerfile&quot;&gt;FROM php:8.3-cli

ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/

RUN install-php-extensions amqp gd xdebug&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous êtes passé à côté de cet outil et que vous utilisez encore une combinaison des utilitaires officiels et/ou &lt;code&gt;pecl&lt;/code&gt;, je ne peux que vous encourager à tester cet outil.&lt;/p&gt;
</description>
                    <pubDate>Wed, 23 Oct 2024 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2024/10/23/installer-des-extensions-php-facilement-dans-une-image-docker.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2024/10/23/installer-des-extensions-php-facilement-dans-une-image-docker.html</guid>
                </item>
            
        
            
        
            
                244
                <item>
                    <title>Comment récupérer le nombre d&apos;erreurs ignorées dans une analyse PHPStan</title>
                    <description>&lt;p&gt;Je travaille au quotidien avec &lt;a href=&quot;https://phpstan.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHPStan&lt;/a&gt; que ce soit pour mes projets personnels, mais également professionnels. Pour des projets complexes et ayant plusieurs années d’existence, il n’est pas rare d’ajouter des règles d’analyses graduellement afin d’éviter un trop grand nombre d’erreurs à corriger (ou à ignorer) d’un seul coup. Aussi dans une optique d’amélioration continue, il peut être intéressant de suivre le nombre d’erreurs ignorées afin de pouvoir s’assurer que le chiffre diminue (ou n’augmente pas de manière disproportionnée) au fil du temps.&lt;/p&gt;

&lt;p&gt;Malheureusement, c’est une information que PHPStan ne permet pas de récupérer simplement.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;J’ai donc cherché une solution, car c’est une métrique que je cherche à suivre. L’idée étant d’avoir une idée de l’état du code et de détecter si une équipe a tendance à ignorer des erreurs relevées par l’analyse statique ou si au contraire, elle est dans une optique d’amélioration continue du code.&lt;/p&gt;

&lt;p&gt;Lors de mes recherches, j’ai trouvé le projet &lt;a href=&quot;https://github.com/staabm/phpstan-baseline-analysis &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;phpstan-baseline-analysis&lt;/a&gt; qui permet d’analyser une &lt;code&gt;baseline&lt;/code&gt; PHPStan pour fournir un résumé de la typologie des erreurs qui sont présentes dans le fichier. C’est un outil intéressant, dont l’auteur Markus Staab, &lt;a href=&quot;https://staabm.github.io/2022/07/04/phpstan-baseline-analysis.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;a publié un article pour présenter le projet&lt;/a&gt;. Bien qu’intéressant, il ne permet pas de déterminer le nombre d’erreurs réellement ignorées dans le projet. Se basant sur un fichier de configuration, le projet fait par exemple l’impasse sur les erreurs directement ignorées dans le code ou ne permet pas de compter avec précision les erreurs ignorées au travers d’une expression régulière.&lt;/p&gt;

&lt;p&gt;Je me suis alors intéressé plus en détail au fonctionnement de PHPStan et à son mécanisme d’analyse. J’ai notamment pu découvrir le fonctionnement du système de cache. L’essentiel des informations est stocké dans un fichier unique (&lt;code&gt;resultCache.php&lt;/code&gt;), lequel contient l’ensemble des erreurs détectées. Ainsi, en analysant le contenu du fichier, en lisant les informations telles que &lt;code&gt;errorsCallback&lt;/code&gt;, &lt;code&gt;locallyIgnoredErrorsCallback&lt;/code&gt; et &lt;code&gt;linesToIgnore&lt;/code&gt;, il est possible de récupérer avec détails et précision, l’ensemble des erreurs présentes dans la base de code (et ainsi de pouvoir en extraire des métriques).&lt;/p&gt;

&lt;p&gt;C’est ainsi qu’est né &lt;a href=&quot;https://github.com/jdecool/phpstan-report &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;phpstan-report&lt;/code&gt;&lt;/a&gt;, une surcouche à PHPStan, capable de lancer une analyse et de lire le fichier de cache afin d’en extraire des métriques permettant de tracer et suivre l’évolution des erreurs de l’analyse statique.&lt;/p&gt;
</description>
                    <pubDate>Wed, 04 Sep 2024 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2024/09/04/comment-recuperer-le-nombre-d-erreurs-ignorees-dans-une-analyse-phpstan.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2024/09/04/comment-recuperer-le-nombre-d-erreurs-ignorees-dans-une-analyse-phpstan.html</guid>
                </item>
            
        
            
                245
                <item>
                    <title>Tester un bundle avec plusieurs versions de Symfony</title>
                    <description>&lt;p&gt;Lorsque l’on travaille et maintient un bundle Symfony, il n’est pas rare de devoir gérer la compatibilité de ce dernier avec plusieurs versions du framework. Par exemple, au moment où je publie ces lignes, les versions 5.4, 6.4 et 7.1 sont officiellement maintenues. Exécuter sa batterie de tests automatisés sur plusieurs versions peut alors être fastidieux.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Le minimum que l’on puisse faire serait de tester le bundle en installant les dépendances Composer avec les options &lt;code&gt;--prefer-stable&lt;/code&gt; et &lt;code&gt;--prefer-lowest&lt;/code&gt; (pour récupérer les versions minimales des dépendances autorisées). Cela fonctionne, mais si une version du bundle supporte les trois versions courantes, ce dernier serait alors testé avec les versions 5.4 et 7.1.&lt;/p&gt;

&lt;p&gt;Pour remédier à ce problème, on pourrait alors à l’installation des dépendances utiliser la commande &lt;code&gt;composer require&lt;/code&gt; pour installer spécifiquement les versions désirées. Par exemple, si l’on souhaite installer une version 6.4, on pourrait utiliser la commande &lt;code&gt;composer require symfony/framework-bundle:6.4.*&lt;/code&gt;. Cela fonctionne bien, mais peut rapidement être fastidieux si le nombre de dépendances Symfony est important.&lt;/p&gt;

&lt;p&gt;Pour résoudre cette problématique, les équipes de Symfony ont intégré une fonctionnalité dans &lt;a href=&quot;https://github.com/symfony/flex &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Symfony Flex&lt;/a&gt; permettant d’installer une version spécifique pour toutes les dépendances Symfony en une seule opération. Flex est une extension Composer bien connue des développeurs Symfony. Ainsi, pour mettre à jour vos dépendances sur une version spécifique, il vous suffira d’utiliser la variable d’environnement &lt;code&gt;SYMFONY_REQUIRE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Par exemple, la commande ci-dessous mettre toutes vos dépendances &lt;code&gt;symfony/*&lt;/code&gt; en version 6.4:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ SYMFONY_REQUIRE=6.4 composer update
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;À noter que &lt;code&gt;symfony/flex&lt;/code&gt; peut être installé en tant que dépendance de votre projet, mais peut également être installé sur l’OS et donc en dehors de votre projet via la commande: &lt;code&gt;composer global require --no-progress --no-scripts --no-plugins symfony/flex&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cela vous permettra de grandement simplifier vos workflows d’intégration continue. Et pour conclure cet article, voici par exemple, une configuration Github Actions que vous pouvez utiliser sur vos projets:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Run tests
on: [ push ]
jobs:
    phpunit:
        runs-on: ubuntu-latest
        strategy:
            matrix:
                composer-prefs: [ &apos;--prefer-stable&apos;, &apos;--prefer-lowest&apos; ]
                php-version: [ &apos;8.2&apos;, &apos;8.3&apos; ]
                symfony-version: [ &apos;5.4.*&apos;, &apos;6.4.*&apos;, &apos;7.1.*&apos; ]
        name: &apos;PHPUnit - PHP/${{ matrix.php-version }} - SF/${{ matrix.symfony-version }} ${{ matrix.composer-prefs }}&apos;
        steps:
            -   name: Checkout
                uses: actions/checkout@v2
            -   name: Setup PHP
                uses: shivammathur/setup-php@v2
                with:
                    php-version: ${{ matrix.php-version }}
                    coverage: xdebug
            -   run: composer global config --no-plugins allow-plugins.symfony/flex true
            -   run: composer global require --no-progress --no-scripts --no-plugins symfony/flex
            -   run: composer update --prefer-dist --no-interaction ${{ matrix.composer-prefs }}
                env:
                    SYMFONY_REQUIRE: ${{ matrix.symfony-version }}
            -   name: PHPUnit
                run: vendor/bin/phpunit
&lt;/code&gt;&lt;/pre&gt;

</description>
                    <pubDate>Mon, 29 Jul 2024 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2024/07/29/tester-un-bundle-avec-plusieurs-versions-de-symfony.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2024/07/29/tester-un-bundle-avec-plusieurs-versions-de-symfony.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                246
                <item>
                    <title>Utiliser PHPUnit 10 avec Symfony</title>
                    <description>&lt;p&gt;Au moment où j’écris ce billet, PHPUnit 10 a été publié il y a près de 8 mois (le 3 février 2023). Et pourtant si on tente de l’utiliser dans un projet Symfony utilisant le bridge &lt;code&gt;symfony/phpunit-bridge&lt;/code&gt;, nous obtenons l’erreur suivante: &lt;code&gt;PHP Fatal error:  Uncaught Error: Class &quot;PHPUnit\TextUI\Command&lt;/code&gt;.&lt;/p&gt;

&lt;!--more--&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;PHP Fatal error:  Uncaught Error: Class &quot;PHPUnit\TextUI\Command&quot; not found in /home/jdecool/Workspace/sandbox/test/bin/phpunit:11
Stack trace:
#0 {main}
  thrown in /home/jdecool/Workspace/sandbox/test/bin/phpunit on line 11

Fatal error: Uncaught Error: Class &quot;PHPUnit\TextUI\Command&quot; not found in /home/jdecool/Workspace/sandbox/test/bin/phpunit on line 11

Error: Class &quot;PHPUnit\TextUI\Command&quot; not found in /home/jdecool/Workspace/sandbox/test/bin/phpunit on line 11

Call Stack:
    0.0001     396248   1. {main}() /home/jdecool/Workspace/sandbox/test/bin/phpunit:0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;La raison étant que le script fourni par Symfony utilise du code qui a été supprimé dans la dernière version majeure du framework de test.&lt;/p&gt;

&lt;p&gt;Il y a plusieurs propositions en cours pour corriger ce problème, mais aucune n’est actuellement mergée.&lt;/p&gt;

&lt;p&gt;Pour corriger cela, vous pouvez simplement modifier le fichier &lt;code&gt;bin/phpunit&lt;/code&gt; en remplaçant la ligne ci-dessous:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-patch&quot;&gt;-    PHPUnit\TextUI\Command::main();
+    exit((new PHPUnit\TextUI\Application)-&amp;gt;run($_SERVER[&apos;argv&apos;]));
&lt;/code&gt;&lt;/pre&gt;
</description>
                    <pubDate>Mon, 09 Oct 2023 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2023/10/09/utiliser-phpunit-10-avec-symfony.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2023/10/09/utiliser-phpunit-10-avec-symfony.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                247
                <item>
                    <title>Le pattern &quot;Parameter Object&quot;</title>
                    <description>&lt;p&gt;Après avoir publié mon billet concernant &lt;a href=&quot;/blog/2022/02/25/le-pattern-commande.html&quot;&gt;le pattern &lt;em&gt;Commande&lt;/em&gt;&lt;/a&gt; hier, on m’a demandé s’il était possible d’utiliser une commande pour remplacer un groupe de paramètre d’une fonction ou d’une méthode avec pour objectif de simplifier le code et sa lisibilité sans pour autant mettre en place un bus de commande.&lt;/p&gt;

&lt;p&gt;Pour répondre sans détour à cette question, oui il est tout à fait possible de remplacer des paramètres de fonction par un objet, cela correspond même à un autre patron de conception nommé le &lt;strong&gt;paramètre objet&lt;/strong&gt; (ou &lt;em&gt;Parameter Object&lt;/em&gt; en anglais).&lt;/p&gt;

&lt;p&gt;Ce &lt;em&gt;pattern&lt;/em&gt; permet de regrouper un ensemble de paramètres au sein d’un ou plusieurs objets qui seront transmis à votre fonction afin de &lt;strong&gt;rendre le code plus lisible&lt;/strong&gt; en simplifiant les signatures de méthodes.&lt;/p&gt;

&lt;p&gt;Il permet également de &lt;strong&gt;supprimer de la duplication de code&lt;/strong&gt; dans le cas ou différentes méthodes définissent systématiquement un même groupe de paramètre parce que ces derniers sont fonctionnellement liés.&lt;/p&gt;

&lt;p&gt;Par exemple, je travaille sur une application de calendrier dans laquelle j’ai énormément de méthodes qui travaillent avec des dates de début et des dates fins de période:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function filterEventByDate(DateTime $start, DateTime $end);
public function getEvents(DateTime $start, DateTime $end);
public function getAvailableSlot(DateTime $start, DateTime $end);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On remarque ici que de nombreuses méthodes partagent les paramètres &lt;code&gt;$start&lt;/code&gt; et &lt;code&gt;$end&lt;/code&gt; qui partage une même sémantique fonctionnelle. On pourra alors regrouper ces deux paramètres au sein d’un object &lt;code&gt;DateRange&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class DateRange
{
    public function __construct(
        public readonly DateTime $start,
        public readonly DateTime $end,
    ) {
    }
}
public function filterEventByDate(DateRange $dateRange);
public function getEvents(DateRange $dateRange);
public function getAvailableSlot(DateRange $dateRange);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si l’exemple précédent est relativement simpliste, dans des cas d’utilisation réels, on peut facilement imaginer que notre objet paramètre pourra regrouper bien plus de données.&lt;/p&gt;
</description>
                    <pubDate>Sat, 26 Feb 2022 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2022/02/26/la-pattern-parameter-object.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2022/02/26/la-pattern-parameter-object.html</guid>
                </item>
            
        
            
                248
                <item>
                    <title>Le pattern &quot;Commande&quot;</title>
                    <description>&lt;p&gt;Si je reprends &lt;a href=&quot;https://fr.wikipedia.org/wiki/Commande_(patron_de_conception) &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;la définition de wikipedia&lt;/a&gt;, il s’agit d’un patron de conception (ou &lt;em&gt;design pattern&lt;/em&gt; en anglais) qui permet d’encapsuler la notion d’&lt;em&gt;invocation&lt;/em&gt;. C’est un moyen très utile de rendre votre code modulaire et lisible en découpant votre code en différentes commandes, chacune ayant des responsabilités uniques et spécifiques.&lt;/p&gt;

&lt;p&gt;Un objet &lt;code&gt;Command&lt;/code&gt; est un DTO (&lt;code&gt;Data Transfer Object&lt;/code&gt;) qui permet d’encapsuler toutes les informations nécessaires pour réaliser une action. La commande n’a aucune logique, elle s’utilise au travers d’un bus (&lt;em&gt;command bus pattern&lt;/em&gt;) qui est chargé de recevoir &lt;a href=&quot;/blog/2020/04/04/coherence-des-donnees-dans-un-modele-oriente-objet.html&quot;&gt;une commande &lt;strong&gt;valide&lt;/strong&gt;&lt;/a&gt; et de déclencher les actions associées. Son unique responsabilité est de gérer les différents cas d’utilisation en déléguant l’exécution de la commande à une classe spécifique au traitement en question. L’objectif est de permettre au développeur d’&lt;strong&gt;émettre une intention&lt;/strong&gt; d’effectuer une action sans que ce dernier n’ait à se soucier de la manière dont elle va être traitée et/ou implémentée.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

class CreateCustomer {
    public function __construct(
        public readonly string $name,
        public readonly string $phonenumber,
    ) {
        // assert data validity
    }
}

$this-&amp;gt;bus-&amp;gt;execute(new CreateCustomer(/* ... */));&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il existe de &lt;a href=&quot;https://packagist.org/?query=command%20bus &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;nombreux composants PHP&lt;/a&gt; qui vous permettent d’implémenter ce pattern dans vos projets. Mais concevoir un bus de commande n’est fondamentalement pas compliqué. Dans sa version minimaliste, il s’agit de faire un lien entre une commande et son gestionnaire (ou &lt;em&gt;handler&lt;/em&gt; en anglais).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

class CommandBus {
    /** Handler[] */
    public function __construct(
        private readonly array $handlers,
    ) {
    }

    public function execute(object $command): void
    {
        $commandClass = get_class($command);
        $handler = $this-&amp;gt;handlers[$commandClass] ?? throw new RuntimeException(&amp;quot;No handler for $commandClass command.&amp;quot;);

        $handler($command);
    }
}

class MyCommand {}
class MyCommandHandler
{
    public function __invoke(MyCommand $command): void
    {
        echo &amp;quot;Hello World&amp;quot;, PHP_EOL;
    }
}

$bus = new CommandBus([
    MyCommand::class =&amp;gt; new MyCommandHandler(),
]);
$bus-&amp;gt;execute(new MyCommand()); // will display &amp;quot;Hello World&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si ce type d’architecture a été popularisé par la montée en puissance du &lt;a href=&quot;/blog/2015/03/30/la-conception-pilotee-par-le-domaine.html&quot;&gt;Domain Driven Design&lt;/a&gt; ou des architectures &lt;a href=&quot;https://en.wikipedia.org/wiki/Command%E2%80%93query_separation &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;CQRS&lt;/a&gt;, il est tout à fait possible de l’utiliser dans n’importe quel projet.&lt;/p&gt;

&lt;p&gt;J’en suis pour ma part très friand car il y a de nombreux avantages à utiliser ce paradigme. Il permet de bien &lt;strong&gt;découper le code&lt;/strong&gt;, d’avoir des petites classes avec &lt;strong&gt;des responsabilités uniques&lt;/strong&gt; et isolées, donc faciles à tester. Au-delà de ces avantages purement techniques, cela permet également de pouvoir dresser simplement la liste de cas d’utilisation qui sont gérés par notre projet.&lt;/p&gt;

&lt;p&gt;Un autre aspect que j’apprécie particulièrement est la facilité que l’on a d’étendre le comportement d’un bus de commande. Il est possible de décorer un bus afin de lui ajouter des comportements supplémentaires qui ne sont pas directement liéss à l’intention métier que l’on souhaite réaliser dans l’application. Il sera par exemple possible de créer un bus de commande qui gérera les transactions de base de données, de gérer des logs ou d’effectuer diverses tâches de monitoring, tout cela de manière complètement &lt;strong&gt;découplée&lt;/strong&gt; de votre code orienté métier.&lt;/p&gt;
</description>
                    <pubDate>Fri, 25 Feb 2022 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2022/02/25/le-pattern-commande.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2022/02/25/le-pattern-commande.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                249
                <item>
                    <title>Utiliser MinIO comme stockage de données objets en PHP</title>
                    <description>&lt;p&gt;Je travaille sur un projet où il est question de stocker un grand nombre de fichiers. Ce dernier devrait être hébergé dans le &lt;em&gt;Cloud&lt;/em&gt; et notamment sur des infrastructures orientées &lt;em&gt;Serverless&lt;/em&gt;. Il est donc impensable d’utiliser un système de fichiers “classique” pour stocker des fichiers. Je me suis donc tourné vers les systèmes de stockage objet (type Amazon S3). C’est alors qu’en effectuant quelques recherches, j’ai découvert &lt;a href=&quot;https://min.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MinIO&lt;/a&gt;. Il s’agit d’un serveur de stockage open source compatible Amazon S3.&lt;/p&gt;

&lt;p&gt;Pour utiliser MinIO avec PHP, vous pouvez simplement utiliser le &lt;a href=&quot;https://packagist.org/packages/aws/aws-sdk-php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;SDK d’AWS&lt;/a&gt;. Après avoir créé votre bucket dans le serveur MinIO, vous pouvez commencer à travailler avec le système de fichier comme vous le feriez avec S3:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$s3 = new Aws\S3\S3Client([
    &amp;#39;version&amp;#39; =&amp;gt; &amp;#39;latest&amp;#39;,
    &amp;#39;region&amp;#39;  =&amp;gt; &amp;#39;eu-east-1&amp;#39;, // cette configuration n&amp;#39;a pas d&amp;#39;importance pour MinIO
    &amp;#39;endpoint&amp;#39; =&amp;gt; &amp;#39;http://localhost:9000&amp;#39;,
    &amp;#39;use_path_style_endpoint&amp;#39; =&amp;gt; true, // ce paramètre doit obligatoirement être actif
    &amp;#39;credentials&amp;#39; =&amp;gt; [
        &amp;#39;key&amp;#39;    =&amp;gt; &amp;#39;ACCESSKEYID&amp;#39;,
        &amp;#39;secret&amp;#39; =&amp;gt; &amp;#39;SECRETACCESSKEY&amp;#39;,
    ],
]);

// enregistrer un fichier
$insert = $s3-&amp;gt;putObject([
     &amp;#39;Bucket&amp;#39; =&amp;gt; &amp;#39;bucket&amp;#39;,
     &amp;#39;Key&amp;#39;    =&amp;gt; &amp;#39;key&amp;#39;,
     &amp;#39;Body&amp;#39;   =&amp;gt; &amp;#39;Hello from MinIO!!&amp;#39;,
]);

// récupérer un fichier
$retrive = $s3-&amp;gt;getObject([
     &amp;#39;Bucket&amp;#39; =&amp;gt; &amp;#39;bucket&amp;#39;,
     &amp;#39;Key&amp;#39;    =&amp;gt; &amp;#39;key&amp;#39;,
     &amp;#39;SaveAs&amp;#39; =&amp;gt; &amp;#39;key_local&amp;#39;,
]);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Voila, ce n’est guère plus compliqué.&lt;/p&gt;

&lt;p&gt;Pour ma part, lorsque je travaille avec le système de fichiers, j’ai pour habitude d’utiliser &lt;a href=&quot;https://packagist.org/packages/league/flysystem &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;la bibliothèque Flysystem&lt;/a&gt;. Si vous ne connaissez pas &lt;em&gt;Flysystem&lt;/em&gt;, je vous recommande vivement de vous y intéresser. Il s’agit de composant permettant d’abstraire l’accès à tout système de fichiers. Cela vous permettra d’écrire un code qui sera automatiquement compatible avec votre système de fichiers local, un FTP, mais également un grand nombre de fournisseur Cloud tel que &lt;em&gt;Amazon S3&lt;/em&gt; ou &lt;em&gt;Azure Blob Storage&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Une fois le composant de base &lt;code&gt;league/flysystem&lt;/code&gt; et l’extension &lt;code&gt;league/flysystem-aws-s3-v3&lt;/code&gt; installé avec l’aide de Composer. Il vous suffit de créer l’adapteur qui va bien:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$s3 = new Aws\S3\S3Client([
    &amp;#39;version&amp;#39; =&amp;gt; &amp;#39;latest&amp;#39;,
    &amp;#39;region&amp;#39;  =&amp;gt; &amp;#39;eu-east-1&amp;#39;, // cette configuration n&amp;#39;a pas d&amp;#39;importance pour MinIO
    &amp;#39;endpoint&amp;#39; =&amp;gt; &amp;#39;http://localhost:9000&amp;#39;,
    &amp;#39;use_path_style_endpoint&amp;#39; =&amp;gt; true, // ce paramètre doit **obligatoirement** être actif
    &amp;#39;credentials&amp;#39; =&amp;gt; [
        &amp;#39;key&amp;#39;    =&amp;gt; &amp;#39;ACCESSKEYID&amp;#39;,
        &amp;#39;secret&amp;#39; =&amp;gt; &amp;#39;SECRETACCESSKEY&amp;#39;,
    ],
]);

$adapter = new League\Flysystem\AwsS3v3\AwsS3Adapter($client, &amp;#39;bucket&amp;#39;);
$filesystem = new League\Flysystem\Filesystem($adapter);

// enregistrer un fichier
$filesystem-&amp;gt;put(&amp;#39;my-file.txt&amp;#39;, &amp;#39;Hello from MinIO!!&amp;#39;);

// récupérer un fichier
$object = $filesystem-&amp;gt;get(&amp;#39;my-file.txt&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous utilisez Symfony, en ce qui me concerne j’utilise le bundle &lt;a href=&quot;https://packagist.org/packages/oneup/flysystem-bundle &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;oneup/flysystem-bundle&lt;/code&gt;&lt;/a&gt;. Pour Laravel le composant le plus complet semble être &lt;a href=&quot;https://packagist.org/packages/graham-campbell/flysystem &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;graham-campbell/flysystem&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tout ça pour dire, que si vous souhaitez utiliser un système de stockage objet et que vous souhaitez héberger vous même ce dernier, je ne peux que vous conseiller de jeter un coup d’oeil à &lt;a href=&quot;https://min.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MinIO&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Tue, 07 Jul 2020 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2020/07/07/utiliser-minio-comme-stockage-de-donnees-objets-en-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2020/07/07/utiliser-minio-comme-stockage-de-donnees-objets-en-php.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
                250
                <item>
                    <title>La gestion des enums en PHP</title>
                    <description>&lt;p&gt;Un enum, on parle également de type énuméré ou énumération en français, “est un type de donnée qui consiste en un ensemble de valeurs constantes” (&lt;a href=&quot;https://fr.wikipedia.org/wiki/Type_%C3%A9num%C3%A9r%C3%A9 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;source Wikipédia&lt;/a&gt;). Il s’agit d’une structure très pratique, mais qui n’existe malheureusement pas nativement dans PHP.&lt;/p&gt;

&lt;p&gt;Si l’on se réfère à la documentation officielle de PHP, il existe bien un type similaire au travers de la classe &lt;code&gt;SplEnum&lt;/code&gt;. Mais cette structure de données a le défaut de nécessiter l’installation d’une extension supplémentaire ce qui peut ne pas être une opération triviale pour une application de production si cela n’est pas prévu en avance.&lt;/p&gt;

&lt;p&gt;C’est pour cela qu’il existe de nombreux composants qui permettent de mettre en place un système qui se rapproche d’un &lt;em&gt;enum&lt;/em&gt;. Pour ma part, j’utilise régulièrement &lt;a href=&quot;https://github.com/myclabs/php-enum &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;myclabs/php-enum&lt;/code&gt;&lt;/a&gt;. Une bibliothèque qui m’a rendu de nombreux services à maintes reprises et qui fait très bien le travail.&lt;/p&gt;

&lt;p&gt;Elle a pourtant un défaut qui m’a posé un problème sur un projet récemment. La plupart des composants d’&lt;em&gt;enum&lt;/em&gt; en PHP fonctionnent suivant le même principe. Il s’agit d’une classe de base qui est étendue dans notre code et à laquelle on attache des constantes. Par exemple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class WeekDay extends Enum
{
    public const MONDAY = &amp;#39;monday&amp;#39;;
    public const TUEDAY = &amp;#39;tuedsay&amp;#39;;
    // ...
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il est ensuite possible dans notre code, d’utiliser la notation &lt;code&gt;WeekDay::MONDAY()&lt;/code&gt; pour récupérer une instance de notre &lt;em&gt;enum&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Le problème que j’ai par exemple rencontré avec &lt;code&gt;myclabs/php-enum&lt;/code&gt; est que chaque appel à &lt;code&gt;WeekDay::MONDAY()&lt;/code&gt; retourne une nouvelle instance de classe. Cela signifie que lorsque l’on récupère deux fois le même &lt;em&gt;enum&lt;/em&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;WeekDay::MONDAY() == WeekDay::MONDAY() // true
WeekDay::MONDAY() === WeekDay::MONDAY() // false&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cela peut-être problématique car dans mon cas, il m’arrive par exemple d’utiliser la classe &lt;a href=&quot;https://www.php.net/manual/en/class.splobjectstorage.php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;SplObjectStorage&lt;/code&gt;&lt;/a&gt; pour stocker des associations de données.&lt;/p&gt;

&lt;p&gt;Par exemple, si je conçois une application qui permet à des utilisateurs d’indiquer leur humeur de la journée, je souhaite par exemple pouvoir associer un jour de la semaine (via notre &lt;em&gt;enum&lt;/em&gt; &lt;code&gt;WeekDay&lt;/code&gt;) avec une humeur (qui serait également géré au travers d’un &lt;em&gt;enum&lt;/em&gt; &lt;code&gt;Mood&lt;/code&gt;). Or si chaque élément de mon énumération est systématiquement une instance d’objet différente, il est alors impossible de travailler avec le code suivant:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$userMood = new SplObjectStorage();
$userMood-&amp;gt;attach(WeekDay::MONDAY(), Mood::GOOD());

$userMood-&amp;gt;contains(WeekDay::MONDAY()); // false&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour remédier à cette problématique, j’ai découvert le composant &lt;a href=&quot;https://github.com/marc-mabe/php-enum &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;marc-mabe/php-enum&lt;/code&gt;&lt;/a&gt; qui permet également de gérer un système d’énumération. À la différence de &lt;code&gt;myclabs/php-enum&lt;/code&gt;, cette bibliothèque renverra toujours la même instance pour une même donnée de notre &lt;em&gt;enum&lt;/em&gt;. Ce qui en reprenant notre code précédent, donnerait la chose suivante:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;WeekDay::MONDAY() == WeekDay::MONDAY() // true
WeekDay::MONDAY() === WeekDay::MONDAY() // true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;À ce stade, vous vous dites certainement que mon choix est maintenant fait et que dorénavant j’utiliserai de manière systématique &lt;code&gt;marc-mabe/php-enum&lt;/code&gt;. Pourtant tout n’est pas aussi simple. Si on a vu un avantage d’une des bibliothèques par rapport à l’autre, il y a une fonctionnalité de &lt;code&gt;myclabs/php-enum&lt;/code&gt; que j’affectionne tout particulièrement: le fait de pouvoir mettre les constantes en privées.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class WeekDay extends Enum
{
    private const MONDAY = &amp;#39;monday&amp;#39;;
    private const TUEDAY = &amp;#39;tuedsay&amp;#39;;
    // ...
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;C’est pour moi un énorme avantage car lorsque j’utilise un &lt;em&gt;enum&lt;/em&gt;, je ne souhaite pas savoir comment il est construit à l’intérieur, comment il fonctionne. Je veux simplement l’utiliser. Or si les constantes sont publiques, cela signifie que la donnée est visible par le développeur et pire encore, qu’il est possible qu’il l’utilise en dehors de l’énumération.&lt;/p&gt;

&lt;p&gt;Comme toujours dans l’ingénierie logicielle, il vous faudra faire le bon choix selon votre problématique et le choix de votre implémentation technique.&lt;/p&gt;
</description>
                    <pubDate>Mon, 07 Oct 2019 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2019/10/07/la-gestion-des-enums-en-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2019/10/07/la-gestion-des-enums-en-php.html</guid>
                </item>
            
        
            
                251
                <item>
                    <title>Tout ce que vous devez savoir sur PHP 7.4</title>
                    <description>&lt;p&gt;La fin de l’année approche à grand et comme d’habitude, nous autres développeurs PHP auront sous le sapin une nouvelle version de PHP à notre disposition. La version 7.4 est une version que j’attends avec impatience notamment pour la possibilité de pouvoir typer les propriétés de classe. Ce changement s’inscrit dans la continuité du langage d’avoir un typage fort.&lt;/p&gt;

&lt;p&gt;Comme avec l’arrivée de chaque nouvelle version, les équipes de PHP ont publié &lt;a href=&quot;https://www.php.net/manual/en/migration74.php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;un guide de migration de la version 7.3 vers la version 7.4&lt;/a&gt; incluant notamment les nouvelles évolutions du langage, les nouvelles classes, interfaces fonction et constante à disposition des développeurs, mais aussi les fonctionnalités dépréciées, les changements non rétro-compatibles ainsi que les extensions supprimées.&lt;/p&gt;

&lt;p&gt;Je vous conseille fortement d’y jeter un coup d’oeil, mais attention le guide est tout frais et n’est actuellement disponible qu’en anglais !&lt;/p&gt;
</description>
                    <pubDate>Thu, 03 Oct 2019 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2019/10/03/tout-ce-que-vous-devez-savoir-sur-php-74.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2019/10/03/tout-ce-que-vous-devez-savoir-sur-php-74.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
                252
                <item>
                    <title>Déployer un projet PHP depuis un monorepo</title>
                    <description>&lt;p&gt;Je parlais dans un billet précédent de comment &lt;a href=&quot;/blog/2019/05/18/publier-des-dependances-php-sur-packagist-dans-un-projet-monorepo.html&quot;&gt;publier des composants PHP sur Packagist depuis un dépôt de code monolithique&lt;/a&gt;. Une autre question récurrente venant des équipes projet qui souhaitent mettre en place ce type de structure est: comment déployer un sous-projet du dépôt de manière indépendante ?&lt;/p&gt;

&lt;p&gt;En effet, il y a quelques années un formidable outil, &lt;a href=&quot;https://capistranorb.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Capistrano&lt;/a&gt;, a modifié la manière dont nous déployons nos applications. Capistrano est un outil Open Source développé en Ruby qui permet d’automatiser le processus de création et de publication d’une application sur un ou plusieurs serveurs Web (source &lt;a href=&quot;https://fr.wikipedia.org/wiki/Capistrano_(logiciel) &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Wikipédia&lt;/a&gt;). L’outil a eu un tel succès que différentes alternatives basées sur le même principe sont apparues. On peut par exemple citer &lt;a href=&quot;https://deployer.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Deployer&lt;/a&gt; en PHP ou &lt;a href=&quot;https://ansistrano.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Ansistrano&lt;/a&gt; une solution équivalente fonctionnant avec Ansible.&lt;/p&gt;

&lt;p&gt;Nous avons donc avec ces solutions pris l’habitude de déployer nos applications en clonant directement le dépôt de sources (que ce soit Git ou Subversion auparavant) afin de déployer le tag ou la branche de production de nos projets. Or lorsque l’on souhaite opter pour une approche &lt;em&gt;monorepo&lt;/em&gt;, cela signifie que notre déploiement va cloner tous les projets composant notre dépôt de code. Bien que cela puisse être envisagé dans certains cas, c’est loin d’être une solution optimale (notamment quand vous allez vouloir mettre vos applications sur différents serveurs).&lt;/p&gt;

&lt;p&gt;Cloner un dépôt de source n’est qu’une solution parmi tant d’autres pour déployer nos projets. C’est également omettre que les outils qui font ce travail pour nous sont un peu plus intelligents que cela et offrent de nombreuses autres possibilités. En plus de pouvoir cloner un dépôt Git, les solutions d’automatisations sont bien souvent capables de copier des fichiers localement sur un système de fichiers, par FTP ou encore via SSH. C’est donc une stratégie de copie qui peut ne pas remettre en cause votre processus de déploiement ni même la configuration (ou très peu) de ce dernier.&lt;/p&gt;

&lt;p&gt;Prenons par exemple une configuration de base générée par Deployer (le principe sera le même pour l’ensemble des outils, mais la syntaxe de configuration variera):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

namespace Deployer;

require &amp;#39;recipe/common.php&amp;#39;;

set(&amp;#39;application&amp;#39;, &amp;#39;my_project&amp;#39;);
set(&amp;#39;repository&amp;#39;, &amp;#39;https://github.com/foo/bar.git&amp;#39;);

set(&amp;#39;git_tty&amp;#39;, true);

set(&amp;#39;shared_files&amp;#39;, [&amp;#39;shared-files.php&amp;#39;]);
set(&amp;#39;shared_dirs&amp;#39;, [&amp;#39;var/log&amp;#39;]);
set(&amp;#39;writable_dirs&amp;#39;, [&amp;#39;data/uploads&amp;#39;]);

host(&amp;#39;project.com&amp;#39;)
    -&amp;gt;set(&amp;#39;deploy_path&amp;#39;, &amp;#39;~/&amp;#39;);

desc(&amp;#39;Deploy your project&amp;#39;);
task(&amp;#39;deploy&amp;#39;, [
    &amp;#39;deploy:info&amp;#39;,
    &amp;#39;deploy:prepare&amp;#39;,
    &amp;#39;deploy:lock&amp;#39;,
    &amp;#39;deploy:release&amp;#39;,
    &amp;#39;deploy:update_code&amp;#39;,
    &amp;#39;deploy:shared&amp;#39;,
    &amp;#39;deploy:writable&amp;#39;,
    &amp;#39;deploy:vendors&amp;#39;,
    &amp;#39;deploy:clear_paths&amp;#39;,
    &amp;#39;deploy:symlink&amp;#39;,
    &amp;#39;deploy:unlock&amp;#39;,
    &amp;#39;cleanup&amp;#39;,
    &amp;#39;success&amp;#39;
]);

after(&amp;#39;deploy:failed&amp;#39;, &amp;#39;deploy:unlock&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Comme je l’ai évoqué juste avant, par défaut la configuration se base sur un dépôt Git pour déployer (ici &lt;code&gt;https://github.com/foo/bar.git&lt;/code&gt;). Lors du processus de déploiement l’ensemble des tâches &lt;code&gt;deploy&lt;/code&gt; seront exécutées. C’est au niveau de la tâche &lt;code&gt;deploy:update_code&lt;/code&gt; que tout se joue. C’est cette dernière qui va mettre à jour le code sur notre serveur &lt;code&gt;project.com&lt;/code&gt; en clonant le dépôt spécifié dans la configuration &lt;code&gt;repository&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nous devons donc modifier cette tâche afin de déployer non plus via Git, mais en effectuant une copie de fichier en SSH (via &lt;code&gt;rsync&lt;/code&gt; dans notre cas). Pour cela, nous allons installer les recettes additionnelles de Deployer via Composer : &lt;code&gt;composer require --dev deployer/recipes&lt;/code&gt;. Nous pourront ensuite ajouter la configuration nécessaire au fonctionnement de cette dernière:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

namespace Deployer;

require &amp;#39;recipe/common.php&amp;#39;;
require &amp;#39;recipe/rsync.php&amp;#39;; // inclusion de la recette

set(&amp;#39;application&amp;#39;, &amp;#39;my_project&amp;#39;);
set(&amp;#39;repository&amp;#39;, &amp;#39;https://github.com/foo/bar.git&amp;#39;);

set(&amp;#39;git_tty&amp;#39;, true);

set(&amp;#39;shared_files&amp;#39;, [&amp;#39;shared-files.php&amp;#39;]);
set(&amp;#39;shared_dirs&amp;#39;, [&amp;#39;var/log&amp;#39;]);
set(&amp;#39;writable_dirs&amp;#39;, [&amp;#39;data/uploads&amp;#39;]);

// configuration de rsync
set(&amp;#39;rsync&amp;#39;,[
    &amp;#39;exclude&amp;#39;      =&amp;gt; [
        &amp;#39;.git&amp;#39;,
        &amp;#39;var&amp;#39;,
        &amp;#39;vendor&amp;#39;,
        &amp;#39;deploy.php&amp;#39;,
    ],
    &amp;#39;exclude-file&amp;#39; =&amp;gt; false,
    &amp;#39;include&amp;#39;      =&amp;gt; [],
    &amp;#39;include-file&amp;#39; =&amp;gt; false,
    &amp;#39;filter&amp;#39;       =&amp;gt; [],
    &amp;#39;filter-file&amp;#39;  =&amp;gt; false,
    &amp;#39;filter-perdir&amp;#39;=&amp;gt; false,
    &amp;#39;flags&amp;#39;        =&amp;gt; &amp;#39;rzcE&amp;#39;,
    &amp;#39;options&amp;#39;      =&amp;gt; [&amp;#39;delete&amp;#39;],
    &amp;#39;timeout&amp;#39;      =&amp;gt; 60,
]);

// configuration du répertoire devant être copié et sa destination
set(&amp;#39;rsync_src&amp;#39;, __DIR__);
set(&amp;#39;rsync_dest&amp;#39;, &amp;#39;&amp;#39;);

host(&amp;#39;project.com&amp;#39;)
    -&amp;gt;set(&amp;#39;deploy_path&amp;#39;, &amp;#39;~/&amp;#39;);

desc(&amp;#39;Deploy your project&amp;#39;);
task(&amp;#39;deploy&amp;#39;, [
    &amp;#39;deploy:info&amp;#39;,
    &amp;#39;deploy:prepare&amp;#39;,
    &amp;#39;deploy:lock&amp;#39;,
    &amp;#39;deploy:release&amp;#39;,
    &amp;#39;rsync&amp;#39;, // pour finir nous remplaçons la tâche de déploiement Git
    &amp;#39;deploy:shared&amp;#39;,
    &amp;#39;deploy:writable&amp;#39;,
    &amp;#39;deploy:vendors&amp;#39;,
    &amp;#39;deploy:clear_paths&amp;#39;,
    &amp;#39;deploy:symlink&amp;#39;,
    &amp;#39;deploy:unlock&amp;#39;,
    &amp;#39;cleanup&amp;#39;,
    &amp;#39;success&amp;#39;
]);

after(&amp;#39;deploy:failed&amp;#39;, &amp;#39;deploy:unlock&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et voilà ! Nous avons maintenant notre projet dont le fonctionnement est identique à la première version mis à part que la copie des fichiers ne se fait plus via un clone du dépôt Git, mais via une copie de fichier en SSH.&lt;/p&gt;

&lt;p&gt;Il se peut que vous ne souhaitiez pas modifier ce qui est fait par la tâche &lt;code&gt;deploy&lt;/code&gt;. C’est le cas, si par exemple vous déployez un projet Symfony en utilisant la recette fournie par Deployer. Dans ce cas, remplacer la tâche &lt;code&gt;update_code&lt;/code&gt; par &lt;code&gt;rsync&lt;/code&gt; vous obligerez à redéfinir l’ensemble des opérations de déploiement, ce qui peut-être gênant.&lt;/p&gt;

&lt;p&gt;Il est alors préférable de redéfinir uniquement le fonctionnement de la tâche &lt;code&gt;update_code&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

namespace Deployer;

require &amp;#39;recipe/symfony4.php&amp;#39;;
require &amp;#39;recipe/rsync.php&amp;#39;;

set(&amp;#39;application&amp;#39;, &amp;#39;my_symfony_project&amp;#39;);

add(&amp;#39;shared_files&amp;#39;, [&amp;#39;.env&amp;#39;]);
add(&amp;#39;shared_dirs&amp;#39;, [&amp;#39;var/log&amp;#39;]);
add(&amp;#39;writable_dirs&amp;#39;, [&amp;#39;var&amp;#39;]);

set(&amp;#39;rsync&amp;#39;,[
    /* configuration rsync */
]);

set(&amp;#39;rsync_src&amp;#39;, __DIR__);
set(&amp;#39;rsync_dest&amp;#39;, &amp;#39;&amp;#39;);

// surchage de la tâche `deploy:update_code`
task(&amp;#39;deploy:update_code&amp;#39;, function() {
    invoke(&amp;#39;rsync&amp;#39;); // appel de la tâche `rsync`
});

host(&amp;#39;symfony-project.com&amp;#39;)
    -&amp;gt;set(&amp;#39;deploy_path&amp;#39;, &amp;#39;~/&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Avec ce genre de configuration, vous avez donc la possibilité de déployer les projets présents dans votre &lt;em&gt;monorepo&lt;/em&gt; de manière complètement indépendante.&lt;/p&gt;

&lt;p&gt;J’ai dans ce billet, principalement évoqué comment configurer Deployer, mais comme je l’ai précisé au début de ce billet, les autres outils peuvent fonctionner avec le même principe. Par exemple, si vous utilisez &lt;code&gt;Ansistrano&lt;/code&gt;, vous devrez rajouter les configurations ci-dessous à votre playbook Ansible pour effectuer une copie de fichier &lt;code&gt;rsync&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;vars:
    # ...
    ansistrano_deploy_via: &amp;quot;rsync&amp;quot;
    ansistrano_rsync_extra_params: &amp;quot;&amp;quot;
    ansistrano_rsync_set_remote_user: yes
    ansistrano_rsync_path: &amp;quot;&amp;quot;
    ansistrano_rsync_use_ssh_args: no&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Sun, 19 May 2019 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2019/05/19/deployer-un-projet-php-depuis-un-monorepo.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2019/05/19/deployer-un-projet-php-depuis-un-monorepo.html</guid>
                </item>
            
        
            
                253
                <item>
                    <title>Publier des dépendances PHP sur Packagist dans un projet monorepo</title>
                    <description>&lt;p&gt;Les dépôts monolithiques (on parle également de dépôt &lt;em&gt;monorepo&lt;/em&gt; ou &lt;em&gt;monorepository&lt;/em&gt;) consistent tout simplement à avoir un dépôt de code unique regroupant plusieurs projets. Cela peut être des &lt;em&gt;applications distinctes&lt;/em&gt; (dans le cas de microservices), des composants d’un même projet (une API, avec son interface et éventuellement des bibliothèques de code autonomes) ou tout le code appartenant à une société.&lt;/p&gt;

&lt;p&gt;À la différence d’un projet de type monolithe, chaque projet gère de manière indépendante ses propres dépendances. Cela permet d’avoir une gestion plus fine des dépendances de chaque projet, mais également de pouvoir utiliser des technologies différentes si le besoin le nécessite. On peut par exemple imaginer: une UI de type SPA développée en Javascript fonctionnant avec un backend composé d’une API PHP et d’un service de gestion des utilisateurs développé en Go. Tout cela centralisé au sein d’un même dépôt.&lt;/p&gt;

&lt;p&gt;Je passerai sur les avantages et inconvénients de ce type de gestion du code. Si vous n’êtes pas à l’aise avec le sujet, je vous laisserai vous faire votre propre opinion en parcourant &lt;a href=&quot;https://github.com/korfuri/awesome-monorepo &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;cette liste de ressources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Si le sujet n’a rien de nouveau, je ne l’ai vu que très rarement utilisé. Je connais très peu de sociétés françaises utilisant ce modèle (je peux les compter sur les doigts de la main). S’il ne s’agit aucunement d’une &lt;em&gt;silver bullet&lt;/em&gt;, je suis convaincu que c’est une méthode efficace pour travailler sur certains projets. Par exemple, je travaille actuellement sur une plateforme orientée microservices (une dizaine), utilisée par 3 fronts et également composée de quelques bibliothèques de code indépendantes. Cette plateforme est gérée par une seule et même équipe, ce qui en fait un excellent candidat. Pourtant, nous gérons actuellement chaque brique au sein de son propre dépôt de code.&lt;/p&gt;

&lt;p&gt;Mais nous ne sommes pas là pour débattre autour de ce point, mais plutôt pour évoquer comment gérer les dépendances &lt;a href=&quot;https://getcomposer.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Composer&lt;/a&gt; dans un dépôt de code monolithique. C’est un sujet que j’ai déjà abordé il y a quelques années dans &lt;a href=&quot;/blog/2017/06/26/gerer-les-dependances-composer-dans-un-projet-monorepo.html&quot;&gt;un article de ce blog&lt;/a&gt;. Mais comment faire lorsque l’on souhaite partager une dépendance Composer sur &lt;a href=&quot;https://packagist.org/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Packagist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vous ne le savez peut-être pas, mais pour déclarer un composant PHP comme une bibliothèque de code pouvant être utilisée via Composer, il est nécessaire que votre code PHP soit présent dans un dépôt de code indépendant avec un fichier &lt;code&gt;composer.json&lt;/code&gt; à la racine. Ce mode de fonctionnement n’est donc pas compatible avec un dépôt monolithique.&lt;/p&gt;

&lt;p&gt;Pour obtenir ce dépôt de code indépendant, il est bien entendu hors de question de copier à la main les changements. Mais quelles solutions s’offrent à nous dans ce cas ?&lt;/p&gt;

&lt;p&gt;La première solution (en partant du principe que vous gérez votre code avec &lt;a href=&quot;https://git-scm.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Git&lt;/a&gt;) est d’utiliser les &lt;a href=&quot;https://git-scm.com/book/fr/v1/Utilitaires-Git-Fusion-de-sous-arborescences &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;em&gt;subtrees&lt;/em&gt;&lt;/a&gt;. Ces derniers vont permettre d’extraire une sous-arborescence de votre dépôt afin de le pousser code dans un autre dépôt.&lt;/p&gt;

&lt;p&gt;Supposons que notre dossier contienne l’arborescence ci-dessous :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;.git
/api
/backend
/ui
/packages
    /package-1
    /package-2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous souhaitons extraire le dossier &lt;code&gt;/packages/package-2&lt;/code&gt; dans son propre dépôt afin de publier ce dernier sur Packagist. Pour cela, nous allons commencer par créer le dépôt de code qui accueillera le projet:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ cd &amp;lt;chemin-nouveau-depot&amp;gt;
$ git init --bare&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous allons ensuite créer notre sous-arbre Git sur le dépôt original et indiquer un nom de branche :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ git subtree split --prefix=packages/package-2 -b split&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne nous reste plus qu’à pousser les modifications dans le dépôt du composant :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ git push &amp;lt;chemin-nouveau-depot&amp;gt; split:master&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous pouvez ensuite pousser le dépôt de code indépendant là où vous le souhaitez et déclarer ce dernier dans Package comme n’importe quel autre dépôt.&lt;/p&gt;

&lt;p&gt;Par la suite, vous n’aurez plus qu’à répéter les étapes 2 et 3 afin de mettre à jour le dépôt du composant avec les modifications du dépôt principal. Pour simplifier et automatiser ces mises à jour, je vous recommande vivement d’ajouter ces opérations dans votre intégration continue.&lt;/p&gt;

&lt;p&gt;L’utilisation de &lt;code&gt;git subtree split&lt;/code&gt; n’est en soi pas compliquée, mais elle est un peu verbeuse et nécessite de jongler entre différents dépôts de code. Il existe des outils pour simplifier cette opération.&lt;/p&gt;

&lt;p&gt;Le premier outil auquel je pense (même si je ne l’ai jamais utilisé) est &lt;a href=&quot;https://github.com/splitsh/lite &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;splitsh&lt;/code&gt;&lt;/a&gt;. Il s’agit de l’outil utilisé pour découper le projet monolithique de &lt;a href=&quot;https://github.com/symfony/symfony &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Symfony&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Le second outil est un composant PHP développé par &lt;a href=&quot;https://twitter.com/votrubaT &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Tomáš Votruba&lt;/a&gt;, membre reconnu de la communauté PHP. Tomáš a développé un composant qu’il a nommé &lt;a href=&quot;https://github.com/Symplify/MonorepoBuilder &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MonorepoBuilder&lt;/a&gt; et qui contient tout un ensemble d’outils pour aider les développeurs à gérer un &lt;em&gt;monorepo&lt;/em&gt;. Ce qui nous intéresse particulièrement est la commande de &lt;code&gt;split&lt;/code&gt;. Pour l’utiliser, vous devrez récupérer la dépendance via Composer au travers de la commande &lt;code&gt;composer require symplify/monorepo-builder --dev&lt;/code&gt;. Il faudra ensuite écrire un fichier de configuration &lt;code&gt;monorepo-builder.yml&lt;/code&gt; qui permettra de configurer quel dossier doit être extrait vers quel dépôt.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# monorepo-builder.yml
parameters:
    directories_to_repositories:
        packages/package-1: &amp;#39;git@github.com:Foo/Package1.git&amp;#39;
        packages/package-2: &amp;#39;git@github.com:Foo/Package2.git&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous n’avez ensuite plus qu’à lancer l’opération via la commande &lt;code&gt;vendor/bin/monorepo-builder split&lt;/code&gt; pour extraire le dossier dans son propre dépôt.&lt;/p&gt;
</description>
                    <pubDate>Sat, 18 May 2019 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2019/05/18/publier-des-dependances-php-sur-packagist-dans-un-projet-monorepo.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2019/05/18/publier-des-dependances-php-sur-packagist-dans-un-projet-monorepo.html</guid>
                </item>
            
        
            
                254
                <item>
                    <title>Tester une connexion SMTP avec SwiftMailer</title>
                    <description>&lt;p&gt;J’ai pour habitude de créer une page de statut dans les applications que je développe afin de tester que l’ensemble des services nécessaires au bon fonctionnement de cette dernière (base de données, serveur mail, API…) sont lancés et correctement configurés. Nous allons voir dans cet article comment tester une connexion SMTP au sein d’une application utilisant le composant &lt;a href=&quot;https://swiftmailer.symfony.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;SwiftMailer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Si l’on prend le cas d’usage simple proposé en introduction dans la documentation du composant :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// Create the Transport
$transport = (new Swift_SmtpTransport(&amp;#39;smtp.example.org&amp;#39;, 25))
  -&amp;gt;setUsername(&amp;#39;your username&amp;#39;)
  -&amp;gt;setPassword(&amp;#39;your password&amp;#39;)
;

// Create the Mailer using your created Transport
$mailer = new Swift_Mailer($transport);

// Create a message
$message = (new Swift_Message(&amp;#39;Wonderful Subject&amp;#39;))
  -&amp;gt;setFrom([&amp;#39;john@doe.com&amp;#39; =&amp;gt; &amp;#39;John Doe&amp;#39;])
  -&amp;gt;setTo([&amp;#39;receiver@domain.org&amp;#39;, &amp;#39;other@domain.org&amp;#39; =&amp;gt; &amp;#39;A name&amp;#39;])
  -&amp;gt;setBody(&amp;#39;Here is the message itself&amp;#39;)
  ;

// Send the message
$result = $mailer-&amp;gt;send($message);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le principe pour une connexion est simple, il faut tester que la couche &lt;em&gt;transport&lt;/em&gt; puisse se connecter au service. Pour cela, il faut démarrer la connexion vers le serveur SMTP et vérifier si une erreur s’est produite :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;try {
    $transport-&amp;gt;start();
    $isSmtpOk = true;
} catch (Swift_TransportException $e) {
    $isSmtpOk = false;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;L’exemple ci-dessus marche bien, mais dans la plupart des cas, nous envoyons rarement directement un mail. Dans une grande majorité des cas, nous allons utiliser un &lt;em&gt;spool&lt;/em&gt; de mail qui va stocker les mails que l’on souhaite envoyer pour par la suite effectuer un traitement en lot. C’est par exemple, le fonctionnement par défaut de &lt;em&gt;SwiftMailer&lt;/em&gt; dans Symfony.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$spool = new FileSpool(__DIR__.&amp;#39;/var/spool&amp;#39;);
$transport = new Swift_SpoolTransport($spool);
$mailer = new Swift_Mailer($transport);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Or dans ce cas, on ne peut pas réellement tester la couche de transport directement car notre instance de la classe &lt;code&gt;Swift_SpoolTransport&lt;/code&gt; ne gère pas réellement l’envoi des mails, mais gère la logique permettant de traiter le mail plus tard. De ce fait l’ &lt;a href=&quot;https://github.com/swiftmailer/swiftmailer/blob/master/lib/classes/Swift/Transport/SpoolTransport.php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;implémentation de la méthode &lt;code&gt;$transport-&amp;gt;start()&lt;/code&gt;&lt;/a&gt; est vide et ne fait donc aucune action.&lt;/p&gt;

&lt;p&gt;Il faudra donc dans ce cas, tester la méthode &lt;code&gt;start&lt;/code&gt; directement sur l’instance du spool, à savoir sur notre objet &lt;code&gt;$spool&lt;/code&gt;. Et si vous utilisez &lt;a href=&quot;https://symfony.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Symfony&lt;/a&gt;, le framework configure un service &lt;code&gt;swiftmailer.transport.real&lt;/code&gt; permettant d’accéder à l’instance de l’objet qui gérera réellement l’envoi des mails.&lt;/p&gt;
</description>
                    <pubDate>Mon, 13 May 2019 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2019/05/13/tester-une-connexion-smtp-avec-swiftmailer.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2019/05/13/tester-une-connexion-smtp-avec-swiftmailer.html</guid>
                </item>
            
        
            
        
            
                255
                <item>
                    <title>phpdaily le blog</title>
                    <description>&lt;p&gt;Si vous suivez sur ce blog ou les réseaux sociaux, vous n’êtes pas sans savoir que depuis le mois de février, je travaille sur un projet qui met à disposition des &lt;a href=&quot;/blog/2019/02/01/envie-de-tester-php-74.html&quot;&gt;images Docker pour tester les versions en cours de développement de PHP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;En plus de simplifier l’accès aux futures versions du langage, j’avais envie d’avoir un espace de publication pour communiquer les nouveautés de chaque version. C’est pour cela que j’ai démarré un blog (en anglais) rattaché au projet. Ce dernier servira à publier des articles pour décrire, expliquer et mettre en oeuvre au travers d’exemples les futures fonctionnalités du langage. Ce dernier est accessible à l’adresse &lt;a href=&quot;https://phpdaily.github.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://phpdaily.github.io&lt;/a&gt;. Un &lt;a href=&quot;https://phpdaily.github.io/feed.xml &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;flux RSS&lt;/a&gt; est disponible et un &lt;a href=&quot;https://twitter.com/phpdailybuilds &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;compte Twitter&lt;/a&gt; a été créé pour l’occasion.&lt;/p&gt;

&lt;p&gt;Le blog est actuellement hébergé sur &lt;a href=&quot;https://pages.github.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub Pages&lt;/a&gt; et le &lt;a href=&quot;https://github.com/phpdaily/phpdaily.github.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;code source est disponible&lt;/a&gt;. Aussi, je souhaite que ce blog soit communautaire. Donc si vous souhaitez écrire un billet (en anglais) à propos d’une prochaine évolution du langage, n’hésitez pas à faire une &lt;em&gt;pull request&lt;/em&gt; sur le projet.&lt;/p&gt;
</description>
                    <pubDate>Wed, 06 Mar 2019 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2019/03/06/phpdaily-le-blog.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2019/03/06/phpdaily-le-blog.html</guid>
                </item>
            
        
            
                256
                <item>
                    <title>Envie de tester PHP 7.4 ? Il y a une image Docker pour ça !</title>
                    <description>&lt;p&gt;La version 7.4 de PHP est prévue pour la fin de cette année 2019. Elle apportera un certain nombre de nouvelles fonctionnalités dont celle que j’attends avec la plus grande impatience: &lt;a href=&quot;https://wiki.php.net/rfc/typed_properties_v2 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;les propriétés typées&lt;/a&gt;. PHP est un langage dont le code source est librement disponible, il est alors possible de tester les nouveautés du langage au fil du développement de ce dernier.&lt;/p&gt;

&lt;p&gt;Compiler PHP peut sembler être une tâche complexe et nécessitant d’installer un certain nombre d’outils avec lequel nous (développeurs PHP) ne sommes pas habitués. Pour éviter ce travail et faciliter l’utilisation de la version de développement de PHP, j’ai décidé d’utiliser &lt;a href=&quot;https://docker.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Docker&lt;/a&gt;. En prenant comme base &lt;a href=&quot;https://hub.docker.com/_/php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;le dépôt des images officielles&lt;/a&gt;, j’ai adapté ces dernières afin de compiler la version en cours de développement de PHP.&lt;/p&gt;

&lt;p&gt;J’ai rendu ce &lt;a href=&quot;https://github.com/phpdaily/php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;travail disponible sur Github&lt;/a&gt; et les images sont librement accessibles &lt;a href=&quot;https://hub.docker.com/r/phpdaily/php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;sur le registre officiel de Docker&lt;/a&gt;. Ces dernières sont construites (presque) quotidiennement.&lt;/p&gt;

&lt;p&gt;Si vous souhaitez commencer à jouer simplement via le mode interactif de la CLI, rien de plus simple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ docker run --rm -it phpdaily/php:7.4.0-dev php -a&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous souhaitez tester un code existant, ce n’est guère plus compliqué:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ docker run --rm -it -v &amp;quot;$PWD:/src&amp;quot; -w /src phpdaily/php:7.4.0-dev php script.php&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il se peut que vous souhaitiez tester des fonctionnalités qui ne soient pas actives de base dans les images. C’est par exemple le cas de l’extension &lt;a href=&quot;https://wiki.php.net/rfc/ffi &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;FFI (Foreign Function Interface)&lt;/a&gt; qui nécessite des options de compilation spécifiques. Pour utiliser cette dernière, les images Docker mettent en place tout le nécessaire pour que vous puissiez facilement l’installer au sein d’une image personnalisée.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-docker&quot; data-lang=&quot;docker&quot;&gt;# phpdaily/php:7.4.0-dev est une image basée sur Linux Alpine
FROM phpdaily/php:7.4.0-dev

RUN apk add --no-cache --virtual .persistent-deps libffi-dev \
    &amp;amp;&amp;amp; docker-php-ext-configure ffi --with-ffi \
    &amp;amp;&amp;amp; docker-php-ext-install ffi&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Construisez ensuite l’image Docker correspondante au travers de la commande:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ docker build -t you/php-ffi .&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;L’extension sera alors utilisable dans les conteneurs qui seront basés sur votre image (vous pouvez vérifier cela en listant les extensions disponibles &lt;code&gt;docker run --rm -it you/php-ffi php -m&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Toutes les images &lt;code&gt;phpdaily/php&lt;/code&gt; étant basées sur les images officielles, vous avez accès aux mêmes possibilités d’utilisation et d’extension que sur ces dernières.&lt;/p&gt;

&lt;p&gt;C’est maintenant à vous de jouer !&lt;/p&gt;
</description>
                    <pubDate>Fri, 01 Feb 2019 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2019/02/01/envie-de-tester-php-74.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2019/02/01/envie-de-tester-php-74.html</guid>
                </item>
            
        
            
        
            
        
            
                257
                <item>
                    <title>Déploiement avec Deployer et Gitlab CI</title>
                    <description>&lt;p&gt;Chez &lt;a href=&quot;https://opera-energie.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Opéra Energie&lt;/a&gt;, la société dans laquelle je travaille, nous développons nos outils en PHP et utilisons &lt;a href=&quot;https://deployer.org/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Deployer&lt;/a&gt; pour le déploiement de nos différents projets. Notre code est hébergé sur une instance Gitlab et c’est tout naturellement que nous utilisons Gitlab CI pour notre intégration continue et le déploiement de nos applications.&lt;/p&gt;

&lt;p&gt;Si déployer du code depuis les instances de nos runners Gitlab CI ne posent pas de problème particulier (il est facile d’y déposer une clé SSH autorisant la connexion à nos serveurs de productions et de tests), nous utilisons également les instances des runners Gitlab. Avec ces derniers, il est alors impossible d’y déposer une clé SSH permettant de se connecter à nos serveurs.&lt;/p&gt;

&lt;p&gt;Les runners Gitlab que nous utilisons fonctionnent exclusivement sur Docker. Pour le déploiement, nous installons Deployer dans une image &lt;code&gt;php:7-alpine&lt;/code&gt; des plus classiques (à laquelle nous avons néanmoins ajouté le nécessaire pour démarrer une connexion SSH, à savoir le paquet &lt;code&gt;openssh-client&lt;/code&gt; dans le cas d’une distribution &lt;em&gt;Alpine&lt;/em&gt;). Afin de pouvoir s’assurer que nos conteneurs Docker puissent se connecter sur les serveurs qui hébergeront nos applications, nous avons commencé par créer des clés SSH par serveurs (afin de pouvoir avoir une gestion fine d’une éventuelle révocation de ces dernières). Nos clés SSH seront ensuite injectées dans le conteneur qui effectuera le déploiement lors de son démarrage.&lt;/p&gt;

&lt;p&gt;Il convient tout d’abord d’ajouter les clés publiques sur chacun des serveurs concernés. Ensuite, dans Gitlab, nous créons des &lt;a href=&quot;https://docs.gitlab.com/ce/ci/variables/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;variables Gitlab CI&lt;/a&gt; qui contiendront les clés privées que nous avons précédemment générées. Ces dernières sont automatiquement transmises aux runners lors de leurs exécutions.&lt;/p&gt;

&lt;p&gt;Une fois Gitlab correctement configuré, il est maintenant possible d’éditer le fichier &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; décrivant les actions qui seront réalisées par notre intégration continue. C’est dans ce dernier, que nous allons récupérer le contenu de nos variables d’environnement et ajouter dynamiquement nos clés SSH à l’agent pour ensuite effectuer le déploiement de notre projet :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;image: php:7-cli-alpine

stages:
  - deploy

before_script:
    - apk add --update git openssh-client
    - mkdir -p ~/.ssh &amp;amp;&amp;amp; echo -e &amp;quot;Host *\n\tStrictHostKeyChecking no\n\n&amp;quot; &amp;gt; ~/.ssh/config
    - eval $(ssh-agent -s)
    - echo &amp;quot;$SERVER_TEST1_KEY&amp;quot; | ssh-add -
    - echo &amp;quot;$SERVER_TEST2_KEY&amp;quot; | ssh-add -
    - echo &amp;quot;$SERVER_PROD1_KEY&amp;quot; | ssh-add -

deploy:app:
    stage: deploy
    script:
        - curl --show-error --silent https://getcomposer.org/installer | php
        - php composer.phar install
        - vendor/bin/dep deploy
    when: manual
    only:
        - master
    environment:
        name: prod
        url: https://my-app.domain.com&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous remarquerez que le déploiement s’effectuant dans un conteneur Docker et ne pouvant prévoir le serveur sur lequel la tâche sera lancée, nous désactivons un contrôle de la clé SSH via l’option &lt;code&gt;StrictHostKeyChecking no&lt;/code&gt;. Cette action n’est à effectuer qu’au sein d’un conteneur Docker et peut-être potentiellement dangereux, car elle permet à des attaques de type &lt;em&gt;man-in-the-middle&lt;/em&gt; de pouvoir être effectué.&lt;/p&gt;

&lt;p&gt;Et c’est ainsi, en injectant dynamiquement des clés SSH au sein d’un conteneur Docker, qu’il est possible de déployer une application PHP avec Deployer (au n’importe quel autre outil de déploiement en réalité) via des runners Gitlab.&lt;/p&gt;
</description>
                    <pubDate>Mon, 03 Sep 2018 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2018/09/03/deploiement-avec-deployer-et-gitlab-ci.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2018/09/03/deploiement-avec-deployer-et-gitlab-ci.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                258
                <item>
                    <title>Réalisez vos benchmarks de code PHP avec PHPBench</title>
                    <description>&lt;p&gt;Pour mes besoins personnels, je souhaitais tester du code PHP et obtenir certaines données sur l’exécution de ce dernier (temps d’exécution, mémoire consommée, …) sans pour autant sortir l’&lt;a href=&quot;/blog/2018/05/09/les-outils-de-profiling-php-open-source.html&quot;&gt;artillerie lourde&lt;/a&gt;. En effectuant quelques recherches, j’ai alors découvert &lt;a href=&quot;https://github.com/phpbench/phpbench &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHPBench&lt;/a&gt; qui comme son nom l’indique est un framework de benchmark PHP.&lt;/p&gt;

&lt;p&gt;PHPBench permet de réaliser simplement des benchmarks sur du code PHP. Il permet de standardiser l’écriture du code et génère des rapports permettant de comparer différents cas d’utilisation.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180514-realisez-vos-benchmarks-de-code-php-avec-phpbench/phpbench-report.png &quot; style=&quot;width: 100%; height: 100%;&quot; /&gt;&lt;/center&gt;

&lt;p&gt;Le framework vous propose d’écrire vos benchmarks un peu comme vous écririez un test PHPUnit. Vous allez ainsi commencer par écrire une classe contenant le code que vous souhaitez mesurer.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

namespace Acme;

class TimeConsumer
{
    public function consume()
    {
        usleep(100);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Puis le code permettant d’effectuer la mesure:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

use Acme\TimeConsumer;

class TimeConsumerBench
{
    public function benchConsume()
    {
       $consumer = new TimeConsumer();
       $consumer-&amp;gt;consume();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne restera plus qu’à exécuter le benchmark au travers de la commande : &lt;code&gt;vendor/bin/phpbench run benchmarks --report=default&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour plus d’informations et pour découvrir les nombreuses fonctionnalités offertes par l’outil, consultez la &lt;a href=&quot;http://phpbench.readthedocs.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;documentation officielle&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Mon, 14 May 2018 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2018/05/14/realisez-vos-benchmarks-de-code-php-avec-phpbench.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2018/05/14/realisez-vos-benchmarks-de-code-php-avec-phpbench.html</guid>
                </item>
            
        
            
                259
                <item>
                    <title>Améliorez vos applications avec l&apos;analyse statique de code</title>
                    <description>&lt;p&gt;Pour corriger des problèmes sur un projet, il est primordial de détecter ces derniers le plus rapidement possible. Effectivement, au plus tôt un problème est détecté et au moins il sera coûteux de résoudre ce dernier. Il est pour cela possible d’avoir recours à de l’analyse statique. Il s’agit d’une opération permettant de détecter automatiquement des erreurs de programmation sans avoir à exécuter de code. Les outils utilisés vont analyser le code source afin de trouver d’éventuelles erreurs et rechercher des modèles de code reconnu comme étant à risque.&lt;/p&gt;

&lt;div class=&quot;alert alert-notice&quot;&gt;
    Ce billet a été originalement publié sur &lt;a href=&quot;https://www.novaway.fr/blog/tech/ameliorez-vos-applications-avec-l-analyse-statique-de-code &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;le blog de la société Novaway&lt;/a&gt; dans le cadre de mon travail.
&lt;/div&gt;

&lt;p&gt;Dans sa forme la plus simple, le mode CLI de PHP propose une option permettant d’analyser un fichier PHP. Il ne s’agit en réalité pas d’une analyse statique, mais plutôt d’une analyse syntaxique (l’opération qui va vérifier que le programme ne contient pas d’erreur de syntaxe). Néanmoins, cela permet d’avoir un premier retour sur le code écrit. Pour effectuer cette opération, il faut utiliser l’argument “-l” de l’interpréteur.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180511-ameliorez-vos-applications-avec-l-analyse-statique-de-code/php-lint.png &quot; /&gt;&lt;/center&gt;

&lt;h2 id=&quot;evaluer-lenvergure-dun-projet&quot;&gt;Evaluer l’envergure d’un projet&lt;/h2&gt;

&lt;p&gt;Obtenir des informations quantifiées sur la taille d’un projet est une opération qui peut s’avérer particulièrement intéressante lors de la prise en main d’un nouveau projet. Un suivi des données recueilli peut également être utile pour mesurer la façon dont le code grossi.&lt;/p&gt;

&lt;p&gt;Dans ce cas, &lt;a href=&quot;https://github.com/sebastianbergmann/phploc &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHPLoc&lt;/a&gt; est l’outil idéal pour effectuer cette analyse. Ce dernier permet de mesurer rapidement la taille d’un projet PHP et d’en analyser la structure pour ensuite visualiser les résultats sous la forme de statistiques. Cet outil est particulièrement intéressant lors de la prise en main d’un nouveau projet car il permet de se rendre compte de l’envergure de ce dernier.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180511-ameliorez-vos-applications-avec-l-analyse-statique-de-code/phploc-size.png &quot; /&gt;&lt;/center&gt;

&lt;p&gt;Les informations fournies par PHPLoc sont exhaustives. Il est possible de connaître le nombre de fichiers composant le projet, le nombre de lignes de code et de commentaires ainsi que le nombre de classes et de méthodes existantes (découpés en fonction de leurs visibilités).&lt;/p&gt;

&lt;p&gt;En plus de ces données, PHPLoc fournit des indicateurs sur les dépendances décrites dans le code (utilisation de variables globales, accès direct à des attributs de classes ou l’utilisation de méthodes statiques). Il fournira également quelques informations sur la complexité cyclomatique du projet.&lt;/p&gt;

&lt;h2 id=&quot;analyser-la-complexité-du-code-dun-projet&quot;&gt;Analyser la complexité du code d’un projet&lt;/h2&gt;

&lt;p&gt;Il est primordial pour la pérennité d’un projet de pouvoir mesurer la complexité de sa base de code. Cette mesure permet d’avoir des informations sur la facilité de maintenabilité du projet. Des outils d’analyse permettent ainsi de détecter des éventuels problèmes au travers d’un ensemble de règles prédéfinies.&lt;/p&gt;

&lt;p&gt;Dans ce sens, &lt;a href=&quot;https://phpmd.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP Mess Detector&lt;/a&gt; (communément appelé PHPMD) vous aidera à identifier les fonctions, méthodes, paramètres ou variables inutilisés. Mais aussi de mettre en évidence des séquences de codes complexes ou des bugs potentiels. Au travers de ces différentes analyses PHPMD sera également capable de proposer des optimisations pour certaines portions de votre projet.&lt;/p&gt;

&lt;p&gt;Dans le même esprit, &lt;a href=&quot;https://pdepend.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP Depend&lt;/a&gt; (qui est le portage PHP de JDepend) est une application d’analyse de code et de mesure de la qualité. Les instructions de l’application sont examinées afin de déterminer l’extensibilité, la facilité de réutilisation et de maintenance de la base de code.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180511-ameliorez-vos-applications-avec-l-analyse-statique-de-code/phpdepend.png &quot; /&gt;&lt;/center&gt;

&lt;p&gt;Avec l’arrivée de PHP 7, de nouveaux outils ont également fait leur apparition. C’est le cas de Phan. Développé par Etsy, &lt;a href=&quot;https://github.com/etsy/phan &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Phan&lt;/a&gt; est un analyseur de code statique qui tente de minimiser au maximum les faux positifs. L’outil va se focaliser sur les problèmes les plus fréquents en détectant par exemple les types des paramètres passés aux fonctions et vérifier que ces derniers sont correctement définis.&lt;/p&gt;

&lt;h2 id=&quot;respecter-les-standards-de-code&quot;&gt;Respecter les standards de code&lt;/h2&gt;

&lt;p&gt;Les standards de code sont très importants pour le travail en équipe car ils permettent d’uniformiser la manière dont les développeurs écrivent du code et facilite ainsi la lecture, la compréhension et la prise en main du projet.&lt;/p&gt;

&lt;p&gt;Dans ce sens, plusieurs outils sont à la disposition du développeur. Le plus connu et plus ancien est certainement &lt;a href=&quot;https://pear.php.net/package/PHP_CodeSniffer &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP_CodeSniffer&lt;/a&gt;. Il s’agit d’un ensemble de scripts qui vont analyser votre code PHP (mais aussi Javascript et CSS) afin de relever les violations de vos standards. En plus de la détection des erreurs, il sera possible de corriger automatiquement les erreurs détectées.&lt;/p&gt;

&lt;p&gt;Dans le même esprit, il existe également &lt;a href=&quot;https://github.com/FriendsOfPHP/PHP-CS-Fixer &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP CS Fixer&lt;/a&gt;, un outil plus récent que le précédent et bénéficiant d’une gestion de la configuration plus « actuelle » afin d’éviter d’écrire de la configuration au format XML.&lt;/p&gt;

&lt;h2 id=&quot;tester-la-compatibilité-avec-une-version-de-php&quot;&gt;Tester la compatibilité avec une version de PHP&lt;/h2&gt;

&lt;p&gt;Proposé peu avant l’arrivée de PHP 7, &lt;a href=&quot;https://github.com/sstalle/php7cc &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;php7cc&lt;/a&gt; est l’analyseur de code statique qui vous permettra de faciliter la migration de vos projets PHP 5 vers PHP 7. Une analyse de php7cc vous permettra de détecter les portions de votre code qui ne sont pas compatibles avec les dernières versions du langage et facilitera ainsi la transition vers la dernière branche majeure du langage.&lt;/p&gt;

&lt;p&gt;Il existe également une solution nommée &lt;a href=&quot;https://github.com/llaville/php-compat-info &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP CompatInfo&lt;/a&gt; qui est une librairie PHP qui va rechercher la version minimum du langage avec laquelle votre code est compatible.&lt;/p&gt;

&lt;h2 id=&quot;les-solutions-complètes&quot;&gt;Les solutions complètes&lt;/h2&gt;

&lt;p&gt;Il existe également des solutions plus complètes et qui permettent de regrouper l’ensemble des analyses citées précédemment. Ma solution préférée est certainement &lt;a href=&quot;http://www.phpmetrics.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PhpMetrics&lt;/a&gt;, une des solution les plus récentes de ce billet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PhpMetric&lt;/strong&gt;, tout comme les outils précédents, permet d’analyser un projet pour obtenir des métriques sur la complexité et la maintenabilité du code. Le principal élément différenciateur de ce projet est qu’il se focalise sur les critères de qualité et fait un grand travail sur la présentation des résultats afin que ces derniers puissent être compris et interprétés par des développeurs de tout niveau au travers d’une interface agréable.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180511-ameliorez-vos-applications-avec-l-analyse-statique-de-code/phpmetrics.png &quot; /&gt;&lt;/center&gt;

&lt;p&gt;Un &lt;a href=&quot;http://www.phpmetrics.org/report/latest/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;rapport de démo&lt;/a&gt; est disponible sur le site de l’outil afin de se faire une idée des capacités de ce dernier.&lt;/p&gt;

&lt;p&gt;Notons également la présence de nombreuses autres solutions telles que SonarQube qui contrairement à l’ensemble des outils présentés jusqu’à maintenant, est un outil multilangage. Ce dernier est entre autres compatibles avec les langages tels que Java, C, C++, Objective-C, JavaScript, Python… Disponible dans une version open source, SonarSource l’éditeur de la solution propose également une version commerciale disponible en SaaS avec un support technique et proposant des analyses pour des langages tel que COBOL, Objective-C, Swift.&lt;/p&gt;

&lt;p&gt;Parmi les autres outils existants, on peut également citer :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://scrutinizer-ci.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Scrutinizer&lt;/a&gt;, une solution propriétaire massivement utilisée pour analyser la qualité des projets open source&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://insight.sensiolabs.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Sensiolabs Insight&lt;/a&gt;, le produit de la société Sensiolabs créatrice du framework Symfony&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.exakat.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Exakat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voilà qui conclut notre tour d’horizon des outils d’analyse de code statique. Notons que la plupart des outils évoqués s’intègrent très facilement dans les principaux IDE (PHPStorm, Eclipse, …) et éditeur de textes spécialisés (tels que SublimeText, Atom ou VSCode) de base ou au travers de leurs systèmes d’extensions.&lt;/p&gt;

&lt;p&gt;Les solutions d’analyse statique sont nombreuses et il est souvent simple de les mettre en place pour obtenir les premiers rapports et indicateurs qui permettront d’améliorer la qualité d’un projet. Il est important de signaler que les résultats obtenus ne sont intéressants et pertinents que dans le contexte où ils sont mis en place.&lt;/p&gt;

&lt;p&gt;Il est également intéressant de signaler, qu’il ne faut pas prendre les recommandations émises par ces outils comme étant une vérité absolue. Les analyses effectuées doivent être mises en place pour indiquer le chemin à suivre et fournir des indicateurs, elles ne remplacent en aucun cas l’expérience d’un développeur.&lt;/p&gt;
</description>
                    <pubDate>Fri, 11 May 2018 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2018/05/11/ameliorez-vos-applications-avec-l-analyse-statique-de-code.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2018/05/11/ameliorez-vos-applications-avec-l-analyse-statique-de-code.html</guid>
                </item>
            
        
            
                260
                <item>
                    <title>Les outils de profiling PHP open source</title>
                    <description>&lt;p&gt;L’activité de profiling consiste à collecter un certain nombre d’informations sur l’exécution d’un code PHP. Une telle opération est effectuée lorsque l’on souhaite par exemple analyser le code d’un projet pour améliorer sa scalabilité ou encore optimiser ce dernier pour corriger un problème de performance applicative.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180509-les-outils-de-profiling-php-open-source/xhprof.jpg &quot; /&gt;&lt;/center&gt;

&lt;div class=&quot;alert alert-notice&quot;&gt;
    Ce billet a été originalement publié sur &lt;a href=&quot;https://www.novaway.fr/blog/tech/les-outils-de-profiling-php-open-source-en-2017 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;le blog de la société Novaway&lt;/a&gt; dans le cadre de mon travail.
&lt;/div&gt;

&lt;p&gt;Lorsque Facebook a rendu public &lt;a href=&quot;https://pecl.php.net/package/xhprof &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;XHProf&lt;/a&gt;, son outil de profiling dédié à PHP en mars 2009, cela a été une petite révolution pour la communauté. Effectivement avant cet outil, il était nécessaire d’utiliser &lt;a href=&quot;https://xdebug.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;XDebug&lt;/a&gt;, mais ce dernier n’est pas recommandé pour un usage en production car l’activation du mode de profiling entraîne de gros ralentissement de l’application.&lt;/p&gt;

&lt;p&gt;Contrairement à XDebug, XHProf a été conçu pour pouvoir être utilisé en production et l’impact sur les performances de l’application est minimal. Un autre avantage et qu’il est possible de profiler uniquement une portion de code. Il sera donc possible de récupérer des données telles que la consommation CPU, la consommation mémoire, le nombre d’appels et le temps passé dans chaque fonction. L’analyse de ces données est facilité par le fait que XHProf fournit l’ensemble des scripts nécessaires à cette opération.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180509-les-outils-de-profiling-php-open-source/xhprof-gui-visualisation.png &quot; style=&quot;width: 100%; height: 100%;&quot; /&gt;&lt;/center&gt;

&lt;p&gt;Malheureusement, Facebook se concentre désormais sur le développement de sa machine virtuelle &lt;a href=&quot;http://hhvm.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;HHVM&lt;/a&gt; qui intègre nativement XHProf. De ce fait l’extension PHP n’est actuellement plus maintenue. Si l’extension reste compatible avec les versions 5.x de PHP, il n’est pas possible de l’utiliser avec les versions 7 du fait de changement interne dans la structure du moteur. On a alors vu ces dernières années se développer des solutions de profiling commerciales. La plus en vogue est certainement &lt;a href=&quot;https://blackfire.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Blackfire&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pour remédier à ce problème, les développeurs de la société Tideways ont décidé de forker le projet XHProf et de créer leur propre extension PHP. Cette dernière propose des fonctionnalités équivalentes à XHProf, mais est compatible avec toutes les versions de PHP depuis la version 5.3. Elle est également proposée en open source et disponible sur un dépôt &lt;a href=&quot;https://github.com/tideways/php-profiler-extension &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

include __DIR__ . &amp;#39;/common.php&amp;#39;;
include __DIR__ . &amp;#39;/tideways_symfony.php&amp;#39;;

tideways_enable();

$kernel = new \Symfony\Component\HttpKernel\Kernel();
$kernel-&amp;gt;boot();

$httpKernel = new \Symfony\Component\HttpKernel\HttpKernel();
$httpKernel-&amp;gt;handle(&amp;#39;indexAction&amp;#39;);
$httpKernel-&amp;gt;handle(&amp;#39;helloAction&amp;#39;);

print_spans(tideways_get_spans());

tideways_disable();&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Mais le profiling avec l’extension Tideways ne s’arrête pas à un simple fork iso-fonctionnel de XHProf, puisque les développeurs ont également ajouté des fonctionnalités supplémentaires telles que notamment la détection du framework exécutant le code “profilé” afin d’ajouter des informations essentielles et de diminuer “le bruit généré” par le code du framework. À l’heure de l’écriture de ce billet, l’extension supporte 13 frameworks.&lt;/p&gt;

&lt;p&gt;L’extension fait maintenant partie du cœur de la solution “Tideways Profiler Platform” une solution commerciale SaaS proposée par la société. Cette partie commerciale n’est en rien obligatoire, mais apporte notamment une interface de visualisation des informations remontées par l’application ainsi qu’un stockage de ces dernières directement sur les serveurs de la société.&lt;/p&gt;

&lt;p&gt;Au final, les solutions de profiling open source sont rares, mais existantes. Bien que XHProf ne soit aujourd’hui plus maintenu par Facebook pour PHP, l’extension fournie par Tideways est une excellente alternative aux diverses solutions commerciales existantes. Il sera cependant nécessaire de passer par une phase de configuration et de trouver l’outil nécessaire pour traiter les données de profiling. Cela reste certainement la meilleure solution pour des besoins ponctuels. Si par contre vous souhaitez avoir une solution fonctionnelle rapidement et avec un minimum d’effort, vous pourrez vous tourner vers les nombreuses solutions SaaS existantes.&lt;/p&gt;

&lt;p&gt;D’ailleurs si vous souhaitez tester le profiling avec Tideways, n’hésitez pas à télécharger les &lt;a href=&quot;https://github.com/novaway/tideways-profiler-stubs &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;stubs de l’extension&lt;/a&gt; afin d’avoir une auto-complétion dans votre IDE préféré.&lt;/p&gt;
</description>
                    <pubDate>Wed, 09 May 2018 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2018/05/09/les-outils-de-profiling-php-open-source.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2018/05/09/les-outils-de-profiling-php-open-source.html</guid>
                </item>
            
        
            
                261
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Partie 6: Aller plus loin avec le modèle</title>
                    <description>&lt;p&gt;Notre application Jobeet commence à devenir utilisable. Nous savons maintenant créer des pages, les afficher et naviguer entre elles en utilisant le framework Symfony via les différents composants qui sont à notre disposition. Attardons-nous un peu sur la couche &lt;a href=&quot;/blog/2017/09/20/tutorial-jobeet-symfony-4-partie-3a-le-modele-de-donnees.html&quot;&gt;modèle&lt;/a&gt; de notre projet.&lt;/p&gt;

&lt;p&gt;Cette dernière est actuellement composée de nos entités (les classes qui représentent les données stockées en base). Nous allons dans ce chapitre, travailler sur l’optimisation de notre code, ce qui vous permettra d’en apprendre un peu plus sur le sujet.&lt;/p&gt;

&lt;p&gt;Revenons sur nos différents &lt;a href=&quot;/blog/2017/09/19/tutorial-jobeet-symfony-4-partie-2-le-projet.html&quot;&gt;scénarios&lt;/a&gt; et plus précisément sur le scénario &lt;em&gt;F1&lt;/em&gt;: &lt;code&gt;En tant qu&apos;utilisateur, je vois les dernières offres actives sur la page d&apos;accueil&lt;/code&gt;. Car si vous avez bien suivi ce que nous avons réalisé, la page d’accueil liste actuellement toutes les offres d’emploi aussi bien celles qui sont actives que celles qui ne le sont pas.&lt;/p&gt;

&lt;p&gt;Un emploi est considéré actif s’il a été posté il y a moins de 30 jours. Commençons par modifier la requête effectuée dans la méthode &lt;code&gt;JobController::index&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/JobController.php

namespace App\Controller;

// ...
use DateTime;

class JobController extends AbstractController
{
    public function index(EntityManagerInterface $em): Response
    {
        $queryBuilder = $em-&amp;gt;getRepository(Job::class)-&amp;gt;createQueryBuilder(&amp;#39;j&amp;#39;);
        $queryBuilder-&amp;gt;andWhere(&amp;#39;j.createdAt &amp;gt; :date&amp;#39;);
        $queryBuilder-&amp;gt;setParameter(&amp;#39;date&amp;#39;, new DateTime(&amp;#39;-30 day&amp;#39;));
        $jobs = $queryBuilder-&amp;gt;getQuery()-&amp;gt;getResult();

        return $this-&amp;gt;render(&amp;#39;job/index.html.twig&amp;#39;, [
            &amp;#39;jobs&amp;#39; =&amp;gt; $jobs,
        ]);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pour écrire une requête “complexe”, nous utilisons l’objet &lt;a href=&quot;http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;QueryBuilder&lt;/code&gt;&lt;/a&gt; fourni par Doctrine et qui permet comme son nom l’indique de créer une requête compréhensible par l’ORM sans devoir écrire de code SQL. L’avantage est que Doctrine adaptera la requête au type de base de données avec lequel il communique (SQLite, MySQL, PostgresSQL, …). L’inconvénient est que cela masque complètement la requête qui est générée.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notre requête commence à devenir plus complexe et il devient intéressant de l’extraire de notre contrôleur pour que cette dernière puisse être réutilisée sans devoir dupliquer le code. Jusqu’à maintenant, la méthode &lt;code&gt;EntityManager::getRepository&lt;/code&gt; nous permettait d’obtenir un objet générique que Doctrine utilise pour fournir des méthodes de base permettant de faire des requêtes en base de données.&lt;/p&gt;

&lt;p&gt;Nous allons maintenant définir une classe de type &lt;code&gt;Repository&lt;/code&gt;. Les objets de types &lt;code&gt;Repository&lt;/code&gt; contiennent des méthodes permettant de récupérer des données en base. Définissons donc une classe &lt;code&gt;JobRepository&lt;/code&gt;, qui comme son nom l’indique, nous permettra de récupérer des données liées aux offres d’emploi.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Repository/JobRepository.php

namespace App\Repository;

use DateTime;
use Doctrine\ORM\EntityRepository;

class JobRepository extends EntityRepository
{
    public function findActive(DateTime $date)
    {
        return $this-&amp;gt;createQueryBuilder(&amp;#39;j&amp;#39;)
            -&amp;gt;andWhere(&amp;#39;j.createdAt &amp;gt; :date&amp;#39;)
            -&amp;gt;setParameter(&amp;#39;date&amp;#39;, $date)
            -&amp;gt;getQuery()
            -&amp;gt;getResult();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une fois notre classe créée, nous allons devoir modifier la configuration du mapping de l’entité &lt;code&gt;Job&lt;/code&gt; pour indiquer à Doctrine la classe que l’ORM devra utiliser pour accéder aux données. Cela se passe dans le fichier &lt;code&gt;config/doctrine/mapping/Job.orm.yml&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/doctrine/mapping/Job.orm.yml
App\Entity\Job:
    type: entity
    repositoryClass: App\Repository\JobRepository

    # ...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour finir, supprimons le code du contrôleur pour utiliser notre nouvelle classe :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/JobController.php

namespace App\Controller;

// ...

class JobController extends AbstractController
{
    public function index(EntityManagerInterface $em): Response
    {
        $jobs = $em-&amp;gt;getRepository(Job::class)-&amp;gt;findActive(new DateTime(&amp;#39;-30 day&amp;#39;));

        return $this-&amp;gt;render(&amp;#39;job/index.html.twig&amp;#39;, [
            &amp;#39;jobs&amp;#39; =&amp;gt; $jobs,
        ]);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et voilà, nous utilisons maintenant une classe pour la récupération des données de notre offre d’emploi, ce qui permet d’isoler le code dédié à la récupération des données et permet ainsi d’améliorer la maintenabilité de notre projet.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Il est possible de consulter la requête générée par Doctrine en consultant les logs générés par l’application. Par défaut, Symfony crée les logs sur la sortie standard et son donc consultable directement sur le terminal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;2018-02-10T19:32:20+01:00 [debug] SELECT j0_.id AS id_0, j0_.type AS type_1, j0_.company AS company_2, j0_.logo AS logo_3, j0_.url AS url_4, j0_.position AS position_5, j0_.location AS location_6, j0_.description AS description_7, j0_.how_to_apply AS how_to_apply_8, j0_.token AS token_9, j0_.is_public AS is_public_10, j0_.is_activated AS is_activated_11, j0_.email AS email_12, j0_.expires_at AS expires_at_13, j0_.created_at AS created_at_14, j0_.updated_at AS updated_at_15, j0_.category_id AS category_id_16 FROM job j0_ WHERE j0_.created_at &amp;gt; ?&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Un moyen plus simple d’accéder à ces informations est d’installer le composant &lt;code&gt;symfony/profiler-pack&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ composer require symfony/profiler-pack --dev&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ce dernier permet la mise en place d’une interface graphique qui affiche un certain nombre d’informations sur votre application. Chaque bundle peut ainsi y afficher des données. Cette interface ajoute une barre permettant d’avoir un résumé des informations disponibles :&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180224-tutorial-jobeet-symfony-4-partie-6-aller-plus-loin-avec-le-modele/profiler-bar.png &quot; style=&quot;width: 100%; height: 100%;&quot; /&gt;&lt;/center&gt;

&lt;p&gt;Il est également possible d’accéder à un détail des informations récupérées en cliquant sur l’icône du composant concerné :&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20180224-tutorial-jobeet-symfony-4-partie-6-aller-plus-loin-avec-le-modele/profiler-detail.png &quot; style=&quot;width: 100%; height: 100%;&quot; /&gt;&lt;/center&gt;

&lt;p&gt;Pour l’heure, notre code est encore loin d’être parfait. Pour récupérer toutes les offres actives, nous sommes systématiquement obligés de passer en paramètre la date à partir de laquelle les offres sont visibles. Cela revient à dupliquer le calcul de la date et serait source d’erreurs. Pour corriger ce problème, nous allons créer une constante qui nous permettra de masquer ce calcul. Et pour optimiser notre requête, nous allons utiliser le champ &lt;code&gt;expiresAt&lt;/code&gt; afin de stocker la date d’expiration d’une offre plutôt que de devoir la calculer.&lt;/p&gt;

&lt;p&gt;Tout comme pour les dates de création et de modification de nos offres d’emploi, nous allons utilisons le gestionnaire d’événement de Doctrine pour mettre à jour la valeur du champ automatiquement. Commençons par ajouter le code nécessaire à notre entité :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Entity/Job.php

namespace App\Entity;

// use ...

class Job
{
    public const OFFER_LIFETIME = 30; // durée de vie d&amp;#39;une offre en jours

    // ...

    public function setExpiresAtValue(LifecycleEventArgs $event): self
    {
        // nous remplissons automatiquement la date d&amp;#39;expiration si cette dernière n&amp;#39;a pas été saisie
        // manuellement
        if (!$this-&amp;gt;expiresAt) {
            $this-&amp;gt;expiresAt = new DateTime(&amp;#39;+&amp;#39;.self::OFFER_LIFETIME.&amp;#39; day&amp;#39;);
        }

        return $this;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;N’oublions pas d’ajouter la configuration liée à cette gestion d’événement.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/doctrine/mapping/Job.orm.yml
App\Entity\Job:
    # ...
    lifecycleCallbacks:
        prePersist: [ setCreatedAtValue, setExpiresAtValue ]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous pouvons maintenant mettre à jour notre requête :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Repository/JobRepository.php

namespace App\Repository;

use DateTime;
use Doctrine\ORM\EntityRepository;

class JobRepository extends EntityRepository
{
    public function findActive()
    {
        return $this-&amp;gt;createQueryBuilder(&amp;#39;j&amp;#39;)
            -&amp;gt;andWhere(&amp;#39;j.expiresAt &amp;gt;= :date&amp;#39;)
            -&amp;gt;setParameter(&amp;#39;date&amp;#39;, new DateTime())
            -&amp;gt;getQuery()
            -&amp;gt;getResult();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;N’oubliez pas d’enlever le paramètre dans l’appel de la méthode dans la classe &lt;code&gt;JobController::index&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le code de notre application est maintenant plus simple et plus maintenable, mais si nous voulons pouvoir tester que tout fonctionne correctement, encore faut-il mettre à jour nos données de test. Car dans les données actuelles, nous avons défini une date d’expiration des offres au 10/10/2012 et nous ne voyons donc maintenant plus aucune offre. Supprimons les dates d’expiration de notre jeu actuel et ajoutons une offre expirée (&lt;a href=&quot;https://github.com/jdecool/jobeet/blob/06-modele/src/DataFixtures/JobFixtures.php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;consultez directement ce fichier pour avoir le code correspondant&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;En rechargeant les fixtures au travers de la commande &lt;code&gt;bin/console doctrine:fixtures:load&lt;/code&gt;, l’affichage ne devrait pas avoir changé, mais si vous regardez les données en base, vous constaterez qu’il y a pourtant bien 3 offres d’emploi enregistrées.&lt;/p&gt;

&lt;p&gt;Si nous revenons à nos &lt;a href=&quot;/blog/2017/09/19/tutorial-jobeet-symfony-4-partie-2-le-projet.html&quot;&gt;scénarios utilisateurs&lt;/a&gt;, nous avons spécifié que les offres devaient être classées par catégories, ce qui n’est actuellement pas le cas. Pour répondre à ce besoin, nous allons créer une classe de type &lt;code&gt;Repository&lt;/code&gt; pour notre entité &lt;code&gt;Category&lt;/code&gt;. Cette dernière nous permettra de lister les catégories existantes avec les offres d’emploi correspondantes.&lt;/p&gt;

&lt;p&gt;Commençons par modifier le mapping Doctrine :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/doctrine/mapping/Job.orm.yml
App\Entity\Category:
    type: entity
    repositoryClass: App\Repository\CategoryRepository

    # ...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Créons maintenant la classe correspondante avec la nouvelle méthode de récupération des offres par catégorie :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Entity/CategoryRepository.php

declare(strict_types=1);

namespace App\Repository;

use DateTime;
use Doctrine\ORM\EntityRepository;

class CategoryRepository extends EntityRepository
{
    public function findCategoriesWithJobs()
    {
        return $this-&amp;gt;createQueryBuilder(&amp;#39;c&amp;#39;)
            -&amp;gt;join(&amp;#39;c.jobs&amp;#39;, &amp;#39;j&amp;#39;)
            -&amp;gt;where(&amp;#39;j.expiresAt &amp;gt;= :date&amp;#39;)
            -&amp;gt;setParameter(&amp;#39;date&amp;#39;, new DateTime())
            -&amp;gt;getQuery()
            -&amp;gt;getResult();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;La requête Doctrine a été construite via le &lt;code&gt;QueryBuilder&lt;/code&gt;. Doctrine implémente également son propre langage de requête appelé DQL (dérivé du SQL). Le &lt;code&gt;QueryBuilder&lt;/code&gt; tout comme le &lt;code&gt;DQL&lt;/code&gt; se base sur nos entités pour construire les requêtes effectuées en base de données, cela permet ensuite à Doctrine de créer les objets correspondants.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Utilisons maintenant cette dernière dans l’affichage de notre homepage :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/JobController.php

namespace App\Controller;

// ...

class JobController extends AbstractController
{
    public function index(EntityManagerInterface $em): Response
    {
        $categories = $em-&amp;gt;getRepository(Category::class)-&amp;gt;findCategoriesWithJobs();

        $jobsCategories = [];
        foreach ($categories as $category) {
            $jobsCategories[$category-&amp;gt;getName()] = $em-&amp;gt;getRepository(Job::class)-&amp;gt;findActiveByCategory($category);
        }

        return $this-&amp;gt;render(&amp;#39;job/index.html.twig&amp;#39;, [
            &amp;#39;categories&amp;#39; =&amp;gt; $jobsCategories,
        ]);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ajoutons la méthode permettant de récupérer les offres actives d’une catégorie :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Entity/JobRepository.php

declare(strict_types=1);

namespace App\Repository;

use DateTime;
use Doctrine\ORM\EntityRepository;

class JobRepository extends EntityRepository
{
    public function findActiveByCategory(Category $category)
    {
        return $this-&amp;gt;createQueryBuilder(&amp;#39;j&amp;#39;)
            -&amp;gt;where(&amp;#39;j.category = :category&amp;#39;)
            -&amp;gt;andWhere(&amp;#39;j.expiresAt &amp;gt;= :date&amp;#39;)
            -&amp;gt;setParameter(&amp;#39;category&amp;#39;, $category)
            -&amp;gt;setParameter(&amp;#39;date&amp;#39;, new DateTime())
            -&amp;gt;getQuery()
            -&amp;gt;getResult();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Modifions ensuite le template en conséquence :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;{# templates/job/index.html #}
{% extends &amp;quot;base.html.twig&amp;quot; %}

{% block body %}
    &amp;lt;h1 class=&amp;quot;my-4&amp;quot;&amp;gt;Liste des offres&amp;lt;/h1&amp;gt;

    {% for category, jobs in categories %}
        &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
            &amp;lt;h2 style=&amp;quot;font-weight: bold; margin: 2rem 0;&amp;quot;&amp;gt;{{ category }}&amp;lt;/h2&amp;gt;

            {% for job in jobs %}
                &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
                    &amp;lt;div class=&amp;quot;col-md-7&amp;quot;&amp;gt;
                        &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;
                            &amp;lt;img class=&amp;quot;img-fluid rounded mb-3 mb-md-0&amp;quot; src=&amp;quot;{{ asset(&amp;#39;images/&amp;#39; ~ job.logo) }}&amp;quot; alt=&amp;quot;{{ job.company }}&amp;quot;&amp;gt;
                        &amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class=&amp;quot;col-md-5&amp;quot;&amp;gt;
                        &amp;lt;h3&amp;gt;{{ job.position }}&amp;lt;/h3&amp;gt;
                        &amp;lt;p&amp;gt;{{ job.description }}&amp;lt;/p&amp;gt;
                        &amp;lt;p&amp;gt;Posted on {{ job.createdAt|date(&amp;quot;m/d/Y&amp;quot;) }}&amp;lt;/p&amp;gt;
                        &amp;lt;a class=&amp;quot;btn btn-primary&amp;quot; href=&amp;quot;{{ path(&amp;#39;job_show&amp;#39;, { &amp;#39;id&amp;#39;: job.id, &amp;#39;company&amp;#39;: job.companySlug, &amp;#39;location&amp;#39;: job.locationSlug, &amp;#39;position&amp;#39;: job.positionSlug }) }}&amp;quot;&amp;gt;See more&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;

                &amp;lt;hr&amp;gt;
            {% endfor %}
        &amp;lt;/div&amp;gt;
    {% endfor %}

    &amp;lt;ul class=&amp;quot;pagination justify-content-center&amp;quot;&amp;gt;
        &amp;lt;li class=&amp;quot;page-item disabled&amp;quot;&amp;gt;
            &amp;lt;a class=&amp;quot;page-link&amp;quot; href=&amp;quot;#&amp;quot; aria-label=&amp;quot;Previous&amp;quot;&amp;gt;
                &amp;lt;span aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;amp;laquo;&amp;lt;/span&amp;gt;
                &amp;lt;span class=&amp;quot;sr-only&amp;quot;&amp;gt;Previous&amp;lt;/span&amp;gt;
            &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li class=&amp;quot;page-item&amp;quot;&amp;gt;
            &amp;lt;a class=&amp;quot;page-link&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;1&amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li class=&amp;quot;page-item disabled&amp;quot;&amp;gt;
            &amp;lt;a class=&amp;quot;page-link&amp;quot; href=&amp;quot;#&amp;quot; aria-label=&amp;quot;Next&amp;quot;&amp;gt;
                &amp;lt;span aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;amp;raquo;&amp;lt;/span&amp;gt;
                &amp;lt;span class=&amp;quot;sr-only&amp;quot;&amp;gt;Next&amp;lt;/span&amp;gt;
            &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour finir, nous allons sécuriser la page de consultation des offres. Effectivement, si vous connaissez l’URL d’une offre, il est possible d’accéder à cette dernière, et ce, même si la date d’expiration est dépassée. Pour cela, rajouter un contrôle dans notre action d’affichage :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/JobController.php

namespace App\Controller;

// ...
use DateTime;

class JobController extends AbstractController
{
    public function show(EntityManagerInterface $em, int $id, string $company, string $location, string $position) : Response
    {
        // dans un projet réel, il sera nécessaire de faire une requête permettant de vérifier que tous les éléments
        // correspondent à une offre d&amp;#39;emploi valide
        $job = $em-&amp;gt;getRepository(Job::class)-&amp;gt;find($id);
        if (null === $job) {
            throw new NotFoundHttpException();
        }

        $currentDate = new DateTime();
        if ($job-&amp;gt;getExpiresAt() &amp;lt; $currentDate) {
            throw new NotFoundHttpException();
        }

        return $this-&amp;gt;render(&amp;#39;job/show.html.twig&amp;#39;, [
            &amp;#39;job&amp;#39; =&amp;gt; $job,
        ])
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Retrouvez tous les tutorials Jobeet disponibles depuis le &lt;a href=&quot;/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html&quot;&gt;billet d’introduction de la série&lt;/a&gt;. Le code source de cette application est également disponible sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/06-modele &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;. Vous trouverez une branche associée à l’état du projet après chaque chapitre.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Sat, 24 Feb 2018 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2018/02/24/tutorial-jobeet-symfony-4-partie-6-aller-plus-loin-avec-le-modele.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2018/02/24/tutorial-jobeet-symfony-4-partie-6-aller-plus-loin-avec-le-modele.html</guid>
                </item>
            
        
            
        
            
                262
                <item>
                    <title>Symfony 4 et les tests avec le logger par défaut</title>
                    <description>&lt;p&gt;Symfony 4 est sortie il y a quelques jours. Ce dernier est dorénavant fourni avec un minimum de dépendances. Monolog n’étant plus fourni par défaut, un logger proposant le strict nécessaire est inclus par défaut. Ce dernier, compatible PSR-3, va logger les informations par défaut sur la sortie standard. Ce fonctionnement peut ne pas être sans impact sur vos tests.&lt;/p&gt;

&lt;p&gt;Effectivement, certains frameworks de tests interpréteront les sorties sur la sortie standard comme un défaut de fonctionnement et considérons de ce fait votre test comme échoué.&lt;/p&gt;

&lt;p&gt;Pour cela, il sera peut-être nécessaire dans vos tests de remplacer le logger par défaut par le &lt;code&gt;Psr\Log\NullLogger&lt;/code&gt;.&lt;/p&gt;
</description>
                    <pubDate>Wed, 06 Dec 2017 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/12/06/symfony-4-tests-logger-par-defaut.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/12/06/symfony-4-tests-logger-par-defaut.html</guid>
                </item>
            
        
            
        
            
        
            
                263
                <item>
                    <title>PHP Meminfo, l&apos;extension qui vous fait voyager dans la mémoire PHP</title>
                    <description>&lt;p&gt;Savez-vous vraiment comment est consommé la mémoire de votre application PHP ? Si la réponse est non, alors devriez considérer l’utilisation de &lt;a href=&quot;https://github.com/BitOne/php-meminfo &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;PHP Meminfo&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;PHP Meminfo est une extension PHP vous permettant d’avoir un aperçu de l’utilisation de la mémoire de vos scripts PHP. Cette dernière a été créé dans le but de comprendre l’origine des fuites mémoires de vos applications et de mieux comprendre le comportement de cette dernière.&lt;/p&gt;

&lt;p&gt;L’extension vient tout récemment de sortir sa version 1.0.0 apportant notamment un support de PHP 7 et fournit une documentation plus complète.&lt;/p&gt;

&lt;p&gt;En bonus, si vous êtes sous macOS et que vous utilisez &lt;a href=&quot;https://brew.sh &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;Homebrew&lt;/code&gt;&lt;/a&gt; en tant que gestionnaire de paquet, vous aurez la possibilité d’installer très facilement l’extension sur votre système, &lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;http://formulae.brew.sh/search/meminfo &quot;&gt;un paquet étant disponible pour chaque version de PHP&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Thu, 23 Nov 2017 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/11/23/php-meminfo-l-extension-qui-vous-fait-voyager-dans-la-memoire-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/11/23/php-meminfo-l-extension-qui-vous-fait-voyager-dans-la-memoire-php.html</guid>
                </item>
            
        
            
                264
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Partie 5: Les routes</title>
                    <description>&lt;p&gt;Vous connaissez maintenant le principe d’une architecture MVC et comment cette dernière se met en place au sein d’un projet Symfony. Nous avons également rapidement évoqué le principe du routage (ou &lt;strong&gt;routing&lt;/strong&gt; en anglais). Ce chapitre sera entièrement consacré à ce dernier point.&lt;/p&gt;

&lt;p&gt;Dans un contexte web, une URL (&lt;em&gt;U&lt;/em&gt;niform &lt;em&gt;R&lt;/em&gt;esource &lt;em&gt;L&lt;/em&gt;ocator) désigne un contenu accessible. Lorsque vous accédez à une page au travers de son URL, vous demandez au navigateur d’aller cherche un contenu identifié. C’est cette information que nous allons devoir décrire dans notre projet.&lt;/p&gt;

&lt;p&gt;Dans la section consacrée au &lt;a href=&quot;/blog/2017/09/29/tutorial-jobeet-symfony-4-partie-4a-le-controleur-et-la-vue.html&quot;&gt;contrôleur et à la vue&lt;/a&gt;, nous avons commencé à modifier le fichier &lt;code&gt;config/routes.yaml&lt;/code&gt; qui permet de faire le lien entre l’URL courante du navigateur et l’action de notre contrôleur devant être exécutée. Nous ne sommes pas rentré dans les détails, mais une route est définie par un nom, un schéma d’URL ainsi que le contrôleur avec la méthode associée.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;C’est le composant &lt;a href=&quot;http://symfony.com/components/Routing &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;Routing&lt;/code&gt;&lt;/a&gt; de Symfony qui s’occupe d’établir la correspondance entre notre configuration et le code de notre projet. Signalons que si deux routes possèdent le même nom, la seconde route écrase la première. De ce fait la première configuration ne sera jamais prise en compte.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dans l’état actuel de notre projet, notre fichier &lt;code&gt;config/routes.yaml&lt;/code&gt; contient la configuration suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/routes.yaml
index:
    path: /
    defaults: { _controller: &amp;#39;App\Controller\JobController::index&amp;#39; }

# Depends on sensio/framework-extra-bundle, doctrine/annotations, and doctrine/cache
#   install with composer req sensio/framework-extra-bundle annot
#controllers:
#    resource: ../src/Controller/
#    type: annotation&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une seule route y est déclarée. Cette dernière correspond à la page d’accueil de notre application qui liste les offres d’emploi disponible. Ajoutons maintenant une route qui permettra d’afficher le détail d’une offre.&lt;/p&gt;

&lt;p&gt;Les offres d’emploi sont créées dynamiquement par un utilisateur. Pour accéder au détail d’une offre, nous allons faire référence à son identifiant unique de base de données (autrement dit, la propriété &lt;code&gt;$id&lt;/code&gt; de notre classe &lt;code&gt;Job&lt;/code&gt;). Nous allons créer une route qui contiendra un paramètre dynamique permettant de récupérer cette information et de la transmettre à notre action.&lt;/p&gt;

&lt;p&gt;Modifions le fichier de configuration en conséquence :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/routes.yaml
index:
    path: /
    defaults: { _controller: &amp;#39;App\Controller\JobController::index&amp;#39; }

job_show:
    path: /job/{id}
    defaults: { _controller: &amp;#39;App\Controller\JobController::show&amp;#39; }

# Depends on sensio/framework-extra-bundle, doctrine/annotations, and doctrine/cache
#   install with composer req sensio/framework-extra-bundle annot
#controllers:
#    resource: ../src/Controller/
#    type: annotation&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous venons donc de rajouter une nouvelle route nommée &lt;code&gt;job_show&lt;/code&gt; qui correspond à l’appel de la méthode &lt;code&gt;show&lt;/code&gt; de notre classe &lt;code&gt;JobController&lt;/code&gt;. Le paramètre de la route est indiqué entre crochet (&lt;code&gt;{id}&lt;/code&gt;), ce dernier correspond au nom de la variable qui sera automatiquement passé à la méthode du contrôleur.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Sur notre environnement de développement local, il sera possible d’accéder à une offre d’emploi au travers de l’URL &lt;code&gt;http://localhost:8000/job/1&lt;/code&gt; ou &lt;code&gt;http://localhost:8000/job/2&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ajoutons maintenant l’action correspondante dans notre contrôleur :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/JobController.php

namespace App\Controller;

use App\Entity\Job;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class JobController extends AbstractController
{
    // ...

    public function show(EntityManagerInterface $em, int $id): Response
    {
        $job = $em-&amp;gt;getRepository(Job::class)-&amp;gt;find($id);
        if (null === $job) {
            throw new NotFoundHttpException();
        }

        return $this-&amp;gt;render(&amp;#39;job/show.html.twig&amp;#39;, [
            &amp;#39;job&amp;#39; =&amp;gt; $job,
        ]);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;La vue affichée par cette action ne sera pas détaillée car elle ne présente pas d’intérêt pour le routing. Je vous invite à récupérer le contenu du fichier directement sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/05-les-routes/templates/job/show.html.twig &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;le dépôt du projet&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nous découvrons dans ce contrôleur comment décrire une route contenant un paramètre dynamique. Dans l’exemple précédent, le paramètre &lt;code&gt;$id&lt;/code&gt; est automatiquement extrait de l’URL et transmis à l’action de notre contrôleur. Symfony faisant correspondre le nom du paramètre de notre configuration avec le nom de la variable de présent dans la définition de notre action.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Notons au passage l’utilisation de la méthode &lt;code&gt;find&lt;/code&gt; de Doctrine permettant de récupérer l’objet via sa clé primaire de base de données.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Après avoir effectué notre requête en base de données avec Doctrine, il convient de contrôler que l’identifiant transmis correspond bien à une offre d’emploi. Si ce n’est pas le cas, nous générons une exception afin d’indiquer que l’utilisateur tente d’accéder à une ressource qui n’existe pas.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;La classe &lt;code&gt;Symfony\Component\HttpKernel\Exception\NotFoundHttpException&lt;/code&gt; est une exception fournie par le composant &lt;code&gt;HttpFundation&lt;/code&gt; de Symfony. Elle permet de lancer d’indiquer au framework que l’erreur rencontrée est de type “ressource introuvable”. Le framework générera ainsi automatiquement une réponse renvoyant le code HTTP 404 au navigateur.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nous avons créé une route qui permet de capter un paramètre correspondant à l’identifiant d’une offre d’emploi. Mais à ce stade, nous n’avons défini aucune restriction sur ce paramètre. Une donnée de type numérique est attendue, mais actuellement rien n’empêche un utilisateur d’entrer une URL du type &lt;code&gt;/job/mon-offre&lt;/code&gt;. Cette adresse est valide mais va provoquer une erreur car nous avons indiqué dans notre action que la paramètre &lt;code&gt;$id&lt;/code&gt; était de type &lt;code&gt;int&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour éviter ce problème, il est possible de définir des règles de validations des adresses pouvant être captées par nos routes. Ajoutons donc un prérequis sur le paramètre &lt;code&gt;{id}&lt;/code&gt; afin d’indiquer que ce dernier ne doit prendre en compte que les valeurs numériques entières.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/routes.yaml

# ...
job_show:
    path: /job/{id}
    defaults: { _controller: &amp;#39;App\Controller\JobController::show&amp;#39; }
    requirements:
        id: &amp;#39;\d+&amp;#39;

# ...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour définir une contrainte sur un paramètre de notre URL, nous utilisons le mot-clé &lt;code&gt;requirements&lt;/code&gt; qui permet de définir une liste de prérequis. Dans l’exemple précédent, nous spécifions que le paramètre &lt;code&gt;id&lt;/code&gt; doit être de valeur numérique.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Le format des validateurs est en réalité une expression régulière. Le format raccourci &lt;code&gt;\d+&lt;/code&gt; correspond en réalité à l’expression &lt;code&gt;[0-9]+&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si nous réfléchissons d’un point de vue SEO, nos URL de type &lt;code&gt;/job/1&lt;/code&gt; ne sont pas très explicites et pourraient pénaliser notre référencement dans les moteurs de recherche. Une URL du type &lt;code&gt;/job/sensio-labs/1/web-developer&lt;/code&gt; serait plus pertinente car elle décrit mieux la ressource à laquelle elle fait référence. Effectuons cette modification :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/routes.yaml

# ...
job_show:
    path: /job/{company}/{location}/{id}/{position}
    defaults: { _controller: &amp;#39;App\Controller\JobController::show&amp;#39; }
    requirements:
        id: &amp;#39;\d+&amp;#39;
        company: &amp;#39;[A-Za-z0-9\-]+&amp;#39;
        location: &amp;#39;[A-Za-z0-9\-]+&amp;#39;
        position: &amp;#39;[A-Za-z0-9\-]+&amp;#39;

# ...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et adaptons notre classe &lt;code&gt;JobController&lt;/code&gt; en conséquence :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/JobController.php

namespace App\Controller;

use App\Entity\Job;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class JobController extends AbstractController
{
    // ...

    public function show(EntityManagerInterface $em, int $id, string $company, string $position, string $location): Response
    {
        // Faire les contrôles sur les variables $company et $position
        // ou les inclures dans la requête SQL
        $job = $em-&amp;gt;getRepository(Job::class)-&amp;gt;find($id);
        if (null === $job) {
            throw new NotFoundHttpException();
        }

        return $this-&amp;gt;render(&amp;#39;job/show.html.twig&amp;#39;, [
            &amp;#39;job&amp;#39; =&amp;gt; $job,
        ]);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Nous n’implémenterons pas les requêtes ni les vérifications, ce n’est pas l’objectif de ce tutorial. Qui se concentre sur l’écriture d’une application avec Symfony.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maintenant que nous avons ajouté notre page permettant de visualiser le détail d’une offre d’emploi, nous allons devoir mettre à jour les liens permettant à un utilisateur de naviguer au sein de notre application. Pour faciliter la navigation entre les pages dans nos templates Twig, le module &lt;code&gt;TwigBridge&lt;/code&gt; met en place des fonctions permettant de faire référence à une page au travers du nom de la route associée. C’est notamment le cas de la fonction &lt;code&gt;path&lt;/code&gt;. Cette dernière prend en paramètre le nom de la route à laquelle nous allons faire référence ainsi que les différentes variables nécessaires à la génération de l’URL.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;!-- templates/job/index.html.twig --&amp;gt;
{% extends &amp;quot;base.html.twig&amp;quot; %}

{% block body %}
    &amp;lt;h1 class=&amp;quot;my-4&amp;quot;&amp;gt;Liste des offres&amp;lt;/h1&amp;gt;

    {% for job in jobs %}
        &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;col-md-7&amp;quot;&amp;gt;
                &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;
                    &amp;lt;img class=&amp;quot;img-fluid rounded mb-3 mb-md-0&amp;quot; src=&amp;quot;{{ asset(&amp;#39;images/&amp;#39; ~ job.logo) }}&amp;quot; alt=&amp;quot;{{ job.company }}&amp;quot;&amp;gt;
                &amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&amp;quot;col-md-5&amp;quot;&amp;gt;
                &amp;lt;h3&amp;gt;{{ job.position }}&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;{{ job.description }}&amp;lt;/p&amp;gt;
                &amp;lt;p&amp;gt;Posted on {{ job.createdAt|date(&amp;quot;m/d/Y&amp;quot;) }}&amp;lt;/p&amp;gt;
                &amp;lt;a class=&amp;quot;btn btn-primary&amp;quot; href=&amp;quot;{{ path(&amp;#39;job_show&amp;#39;, { &amp;#39;id&amp;#39;: job.id, &amp;#39;company&amp;#39;: job.companySlug, &amp;#39;location&amp;#39;: job.locationSlug, &amp;#39;position&amp;#39;: job.positionSlug }) }}&amp;quot;&amp;gt;See more&amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;hr&amp;gt;
    {% endfor %}

    &amp;lt;!-- ... --&amp;gt;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pour générer le lien vers une offre d’emploi, nous avons utilisé les propriétés &lt;code&gt;job.companySlug&lt;/code&gt;, &lt;code&gt;job.locationSlug&lt;/code&gt; et &lt;code&gt;job.positionSlug&lt;/code&gt; de notre classe &lt;code&gt;Job&lt;/code&gt;. Ces dernières correspondent en réalité à des appels de méthode (au besoin, je vous invite à relire le &lt;a href=&quot;/blog/2017/09/29/tutorial-jobeet-symfony-4-partie-4a-le-controleur-et-la-vue.html&quot;&gt;chapitre concernant Twig&lt;/a&gt; pour plus d’informations). L’implémentation des méthodes ne sera pas détaillée, consultez &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/05-les-routes/src/Entity/Job.php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;le code source de la classe&lt;/a&gt; pour plus de détails.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Au fur et à mesure que nous allons ajouter des fonctionnalités à notre application, cette dernière va grossir et contenir de plus en plus de code et de configuration. Il peut être parfois utile de lister l’ensemble des routes disponibles sans pour autant devoir parcourir les différents fichiers de configuration. Pour cela, Symfony met à disposition des développeurs une commande permettant d’afficher ces dernières :&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20171016-tutorial-jobeet-symfony-4-partie-5-les-routes/cmd-debug-router.png &quot; /&gt;&lt;/center&gt;

&lt;p&gt;Cette commande permet également d’obtenir des informations détaillées sur une route. Il suffit pour cela d’indiquer le nom de la route en question :&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20171016-tutorial-jobeet-symfony-4-partie-5-les-routes/cmd-debug-router-details.png &quot; /&gt;&lt;/center&gt;

&lt;p&gt;Ce chapitre a introduit la notion de route et explique comment créer des liens entre les pages de votre application. Vous avez ainsi appris à utiliser le composant &lt;code&gt;Routing&lt;/code&gt; de Symfony. Le prochain sera consacré à l’approfondissement du concept de modèle. Nous y expliquerons plus en détail comment structurer l’information et faire des requêtes complexes.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Retrouvez tous les tutorials Jobeet disponibles depuis le &lt;a href=&quot;/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html&quot;&gt;billet d’introduction de la série&lt;/a&gt;. Le code source de cette application est également disponible sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/05-les-routes &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;. Vous trouverez une branche associée à l’état du projet après chaque chapitre.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Mon, 16 Oct 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/10/16/tutorial-jobeet-symfony-4-partie-5-les-routes.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/10/16/tutorial-jobeet-symfony-4-partie-5-les-routes.html</guid>
                </item>
            
        
            
                265
                <item>
                    <title>Proxifier des requêtes HTTP en PHP</title>
                    <description>&lt;p&gt;Ce soir, je me suis demandé comment il était possible de “proxifier” une requête HTTP effectuée depuis un script PHP. Pour être plus précis, je souhaitais faire une requête sur le réseau &lt;a href=&quot;https://www.torproject.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;TOR&lt;/a&gt; (ou via n’importe quel proxy de manière générale) via une commande PHP.&lt;/p&gt;

&lt;p&gt;En fait cela est beaucoup plus simple que je ne le pensais car l’extension &lt;a href=&quot;http://php.net/manual/en/book.curl.php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;CURL de PHP&lt;/a&gt; contient tout le nécessaire pour réaliser cette tâche. Il suffit pour cela de passer les bonnes options à notre ressource :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, &amp;#39;https://check.torproject.org&amp;#39;);
curl_setopt($ch, CURLOPT_PROXY, &amp;#39;localhost:9150&amp;#39;); // l&amp;#39;URL de notre proxy
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); // le type du proxy
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_HEADER, 1);
$curl_scraped_page = curl_exec($ch);
curl_close($ch);

echo $curl_scraped_page;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous utilisez une bibliothèque &lt;a href=&quot;http://docs.guzzlephp.org/en/stable/request-options.html#proxy &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;type Guzzle&lt;/a&gt;, ces dernières fournissent généralement tout le nécessaire pour proxifier vos requêtes HTTP.&lt;/p&gt;

&lt;p&gt;D’ailleurs concernant cette dernière, j’ai trouvé un &lt;a href=&quot;https://github.com/megahertz/guzzle-tor &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;middleware &lt;code&gt;megahertz/guzzle-tor&lt;/code&gt;&lt;/a&gt; dédié à la mise en place d’une connexion via TOR.&lt;/p&gt;
</description>
                    <pubDate>Wed, 11 Oct 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/10/11/proxifier-des-requetes-http-en-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/10/11/proxifier-des-requetes-http-en-php.html</guid>
                </item>
            
        
            
        
            
        
            
                266
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Partie 4B: La gestion des assets avec Twig</title>
                    <description>&lt;p&gt;Dans la section précédente, nous avons commencé à afficher nos premières pages et avons défini une charte graphique. Nous avons donc inclus un certain nombre de fichiers CSS, Javascript et utilisé des images. Voyons avant de continuer quelques bonnes pratiques sur la gestion des assets.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Les assets sont des fichiers statiques qui sont utilisés par l’interface de notre site. Il peut s’agir de fichiers CSS (pour la mise en page et la charte graphique du site), Javascripts (pour gérer l’interactivité de notre interface avec l’utilisateur), d’images ou par exemple des fichiers pouvant être  téléchargées par l’utilisateur.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tout d’abord, revoyons notre template &lt;code&gt;base.html.twig&lt;/code&gt; qui inclut nos différentes feuilles de style :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;# templates/base.html.twig
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
        &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1, shrink-to-fit=no&amp;quot;&amp;gt;
        &amp;lt;title&amp;gt;{% block title %}Jobeet{% endblock %}&amp;lt;/title&amp;gt;
        &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/vendor/bootstrap/css/bootstrap.min.css&amp;quot;&amp;gt;
        &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/vendor/1-col-portfolio.css&amp;quot;&amp;gt;
        {% block stylesheets %}{% endblock %}
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;nav class=&amp;quot;navbar navbar-expand-lg navbar-dark bg-dark fixed-top&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;navbar-brand&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Jobeet&amp;lt;/a&amp;gt;
                &amp;lt;button class=&amp;quot;navbar-toggler&amp;quot; type=&amp;quot;button&amp;quot; data-toggle=&amp;quot;collapse&amp;quot; data-target=&amp;quot;#navbarResponsive&amp;quot; aria-controls=&amp;quot;navbarResponsive&amp;quot; aria-expanded=&amp;quot;false&amp;quot; aria-label=&amp;quot;Toggle navigation&amp;quot;&amp;gt;
                    &amp;lt;span class=&amp;quot;navbar-toggler-icon&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;
                &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/nav&amp;gt;

        &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
            {% block body %}{% endblock %}
        &amp;lt;/div&amp;gt;

        &amp;lt;script src=&amp;quot;/vendor/jquery/jquery-3.2.1.slim.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src=&amp;quot;/vendor/popper/popper.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src=&amp;quot;/vendor/bootstrap/js/bootstrap.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        {% block javascripts %}{% endblock %}
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour inclure nos différents fichiers, nous avons spécifié le chemin complet pour accéder à nos ressources. Cette pratique n’est pas recommandée car elle possède de nombreux désavantages :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Elle rend nos &lt;em&gt;templates verbeux&lt;/em&gt; en nous obligeant à écrire pour chaque fichier le chemin complet pour y accéder.&lt;/li&gt;
  &lt;li&gt;Le &lt;em&gt;versionnement des fichiers&lt;/em&gt; est compliqué car il nécessite d’être géré manuellement pour chaque ressource.&lt;/li&gt;
  &lt;li&gt;Le &lt;em&gt;déplacement des assets&lt;/em&gt; peut être source d’erreurs.&lt;/li&gt;
  &lt;li&gt;L’&lt;em&gt;utilisation d’un CDN&lt;/em&gt; est impossible avec cette gestion manuelle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Afin de résoudre toutes ces problématiques, il existe de nombreux composants PHP permettant de mettre en place une gestion d’asset simple, efficace et maintenable. Symfony propose également un composant pour ça, installons-le :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ composer require symfony/asset

Using version ^3.3 for symfony/asset
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing symfony/asset (v3.3.9): Downloading (100%)
Writing lock file
Generating autoload files
Executing script make cache-warmup [OK]
Executing script assets:install --symlink --relative public [OK]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une fois téléchargé, le composant est tout de suite prêt à être utilisé dans notre projet. Lors de notre découverte de Twig, nous avons rapidement évoqué le composant &lt;code&gt;TwigBridge&lt;/code&gt; comme étant un module permettant d’étendre le fonctionnement de Twig en ajoutant des fonctionnalités spécifiques aux différents modules de de Symfony. Le composant Asset en fait partie. &lt;code&gt;TwigBridge&lt;/code&gt; fournit ainsi une fonction &lt;code&gt;asset&lt;/code&gt; permettant d’accéder à une ressource.&lt;/p&gt;

&lt;p&gt;Modifions notre template d’origine :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;# templates/base.html.twig
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
        &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1, shrink-to-fit=no&amp;quot;&amp;gt;
        &amp;lt;title&amp;gt;{% block title %}Jobeet{% endblock %}&amp;lt;/title&amp;gt;
        &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;{{ asset(&amp;#39;vendor/bootstrap/css/bootstrap.min.css&amp;#39;) }}&amp;quot;&amp;gt;
        &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;{{ asset(&amp;#39;vendor/1-col-portfolio.css&amp;#39;) }}&amp;quot;&amp;gt;
        {% block stylesheets %}{% endblock %}
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;nav class=&amp;quot;navbar navbar-expand-lg navbar-dark bg-dark fixed-top&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;navbar-brand&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Jobeet&amp;lt;/a&amp;gt;
                &amp;lt;button class=&amp;quot;navbar-toggler&amp;quot; type=&amp;quot;button&amp;quot; data-toggle=&amp;quot;collapse&amp;quot; data-target=&amp;quot;#navbarResponsive&amp;quot; aria-controls=&amp;quot;navbarResponsive&amp;quot; aria-expanded=&amp;quot;false&amp;quot; aria-label=&amp;quot;Toggle navigation&amp;quot;&amp;gt;
                    &amp;lt;span class=&amp;quot;navbar-toggler-icon&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;
                &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/nav&amp;gt;

        &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
            {% block body %}{% endblock %}
        &amp;lt;/div&amp;gt;

        &amp;lt;script src=&amp;quot;{{ asset(&amp;#39;vendor/jquery/jquery-3.2.1.slim.min.js&amp;#39;) }}&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src=&amp;quot;{{ asset(&amp;#39;vendor/popper/popper.min.js&amp;#39;) }}&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src=&amp;quot;{{ asset(&amp;#39;vendor/bootstrap/js/bootstrap.min.js&amp;#39;) }}&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        {% block javascripts %}{% endblock %}
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Nous pouvons également modifier l’affichage de l’image d’une offre d’emploi dans le fichier &lt;a href=&quot;https://github.com/jdecool/jobeet/blob/04b-assets-twig/templates/job/index.html.twig#L10 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;templates/job/index.html.twig&lt;/code&gt;&lt;/a&gt;. Notons au passage l’utilisation de l’opérateur de concaténation de chaine de caractère Twig &lt;code&gt;~&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Peut-être que vous ne remarquez que très peu de différences. Pour le moment, nous avons juste utilisé notre fonction &lt;code&gt;asset&lt;/code&gt; mais le code reste le même et le chemin vers le fichier est toujours présent.&lt;/p&gt;

&lt;p&gt;S’il y a peu de différences, c’est tout d’abord parce que nous avons placé nos fichiers dans le répertoire &lt;code&gt;public&lt;/code&gt; (qui est le point d’entrée du serveur HTTP pour rechercher nos fichiers accessibles publiquement). C’est donc le répertoire où le composant Asset va rechercher nos fichiers par défaut.&lt;/p&gt;

&lt;p&gt;Imaginons que nous souhaitons changer le répertoire contenant nos ressources, pour par exemple le déplacer dans un sous-répertoire &lt;code&gt;public/assets&lt;/code&gt;. Il ne sera maintenant plus nécessaire de modifier tous les chemins des fichiers que nous avons utilisés. Il suffira de modification la configuration du framework afin de spécifier le répertoire racine qui contient nos assets comme dans l’exemple ci-dessous :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/packages/framework.yml
framework:
    # ...
    assets:
        base_path: &amp;#39;/assets&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Au travers de cet exemple, il est possible de commencer à voir les avantages d’utiliser un composant pour gérer nos ressources. Une autre des fonctionnalités majeures du module &lt;code&gt;Asset&lt;/code&gt; est la gestion du versionnement des fichiers. Pour plus d’informations sur le versionnement des assets, je vous propose de consulter la &lt;a href=&quot;http://symfony.com/doc/current/components/asset.html#versioned-assets &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;documentation officielle&lt;/a&gt; qui fournira tous les informations utiles.&lt;/p&gt;

&lt;p&gt;Si vous souhaitez spécifier une version d’asset dans notre projet, voici la configuration que vous allez devoir renseigner :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/packages/framework.yml
# app/config/config.yml
framework:
    # ...
    assets:
        version: &amp;#39;v2&amp;#39; # asset(&amp;#39;/images/logo.png&amp;#39;) ==&amp;gt; /images/logo.png?v2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Le versionnement des assets est très utile pour gérer la mise en cache de nos ressources. C’est une pratique courante pour optimiser les données devant être renvoyées par les serveurs HTTP et économiser des appels réseaux.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Avant de terminer sur la gestion des ressources, évoquons une dernière fonctionnalité essentielle : la gestion des CDN.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Un &lt;em&gt;Content&lt;/em&gt; &lt;em&gt;Delivery&lt;/em&gt; &lt;em&gt;Network&lt;/em&gt; (CDN ou réseau de diffusion de contenu en français) est une plate-forme de serveurs hautement distribuée optimisée pour diffuser du contenu. Les CDN ont l’avantage d’accélérer le chargement des pages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pour mettre en place la gestion d’un CDN, il suffit comme pour le versionnement des ressources, de spécifier les domaines qui vont être utilisés dans le fichier de configuration. Symfony sélectionnera un domaine (si vous n’en spécifiez pas un lors de l’utilisaton de la fonction &lt;code&gt;asset&lt;/code&gt;) à chaque génération d’un page.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/packages/framework.yml
framework:
    # ...
    assets:
        base_urls:
            - &amp;#39;http://cdn.example.com/&amp;#39; # asset(&amp;#39;/images/logo.png&amp;#39;) ==&amp;gt; http://cdn.example.com/images/logo.png&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Retrouvez tous les tutorials Jobeet disponibles depuis le &lt;a href=&quot;/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html&quot;&gt;billet d’introduction de la série&lt;/a&gt;. Le code source de cette application est également disponible sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/04b-assets-twig &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;. Vous trouverez une branche associée à l’état du projet après chaque chapitre.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Sat, 30 Sep 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/09/30/tutorial-jobeet-symfony-4-partie-4b-la-gestion-des-assets-avec-twig.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/09/30/tutorial-jobeet-symfony-4-partie-4b-la-gestion-des-assets-avec-twig.html</guid>
                </item>
            
        
            
                267
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Partie 4A: Le contrôleur et la vue</title>
                    <description>&lt;p&gt;Nous avons jusqu’à présent entrevu le fonctionnement de Doctrine en créant une base de données et en y insérant un jeu de données afin d’avoir des données initiales, nous évitant ainsi d’avoir une application vide. Nous allons  maintenant pouvoir commencer à afficher nos premières pages web.&lt;/p&gt;

&lt;p&gt;Un projet Symfony repose sur le pattern MVC (&lt;em&gt;M&lt;/em&gt;odel &lt;em&gt;V&lt;/em&gt;iew &lt;em&gt;C&lt;/em&gt;ontroller ou &lt;em&gt;M&lt;/em&gt;odel &lt;em&gt;V&lt;/em&gt;ue &lt;em&gt;C&lt;/em&gt;ontrôleur en français). Ce type d’architecture permet d’organiser le code en le séparant en trois couches :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;La couche &lt;code&gt;modèle&lt;/code&gt; contenant le traitement logique de vos données (on y retrouve les traitements métier, les accès à la base de données, …).&lt;/li&gt;
  &lt;li&gt;La &lt;code&gt;vue&lt;/code&gt; est la modélisation de l’IHM (Interface Homme Machine). Elle représente ce qui est rendu à l’utilisateur (sous la forme d’une page Web, d’une commande d’un terminal, de données JSON/XML, …).&lt;/li&gt;
  &lt;li&gt;Le &lt;code&gt;contrôleur&lt;/code&gt; correspond au code faisant le lien entre le &lt;code&gt;modèle&lt;/code&gt; et la &lt;code&gt;vue&lt;/code&gt;. Il récupère les données utilisateurs pour y appliquer les traitements et donner les résultats à la vue.
 Démarrons par ce dernier. Comme nous venons brièvement de le dire, le contrôleur est la couche qui va exécuter les traitements liés à notre application et transmettre les résultats à la vue pour que ces derniers puissent être affichés à l’utilisateur. Dans notre projet Web, cela va se matérialiser par des classes qui vont contenir des fonctions qui seront appelées en fonction d’une URL. Ces dernières renverront un objet de type &lt;code&gt;Symfony\Component\HttpFoundation\Response&lt;/code&gt; qui est l’abstraction d’une réponse HTTP dans Symfony.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ecrivons un premier contrôleur que nous allons nommer &lt;code&gt;DefaultController&lt;/code&gt; et que nous allons placer dans le répertoire &lt;code&gt;src/Controller&lt;/code&gt;. Ce dernier, contiendra une fonction qui permettra d’afficher un message dans le navigateur.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/DefaultController.php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;

class DefaultController
{
    public function index(): Response
    {
        return new Response(&amp;#39;Accueil Jobeet - Hello&amp;#39;);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour tester ce code, nous allons devoir indiquer au framework comment accéder à ce contrôleur. Pour cela nous allons éditer le fichier &lt;code&gt;config/routes.yaml&lt;/code&gt;. Sans rentrer dans les détails (car c’est le sujet du prochain chapitre), ce fichier permet d’indiquer à Symfony l’URL qui déclenchera l’appel de la méthode &lt;code&gt;index&lt;/code&gt; de notre contrôleur.&lt;/p&gt;

&lt;p&gt;Pour le moment, décommentons simplement les 3 premières lignes :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/routes.yaml

index:
    path: /
    defaults: { _controller: &amp;#39;App\Controller\DefaultController::index&amp;#39; }

# Depends on sensio/framework-extra-bundle, doctrine/annotations, and doctrine/cache
#   install with composer req sensio/framework-extra-bundle annot
#controllers:
#    resource: ../src/Controller/
#    type: annotation&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous pouvons maintenant démarrer notre serveur Web via l’exécution de la commande &lt;span style=&quot;text-decoration: line-through;&quot;&gt;&lt;code&gt;make serve&lt;/code&gt;&lt;/span&gt; &lt;code&gt;bin/console server:run&lt;/code&gt; dans un terminal et se rendre à l’adresse &lt;code&gt;http://localhost:8000&lt;/code&gt; avec son navigateur pour visualiser le résultat.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20170929-tutorial-jobeet-symfony-4-partie-4a-le-controleur-et-la-vue/browser.png &quot; /&gt;&lt;/center&gt;

&lt;p&gt;Pour pouvoir exécuter les différents traitements de notre application, le contrôleur doit pouvoir récupérer les données saisies par les utilisateurs. Dans un environnement Web, ces informations sont transmises par le navigateur au sein d’une requête HTTP.&lt;/p&gt;

&lt;p&gt;Symfony utilise un composant &lt;a href=&quot;https://symfony.com/components/HttpFoundation &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;HttpFoundation&lt;/code&gt;&lt;/a&gt;, dont l’objectif est de fournir une couche objet permettant de travailler avec le protocole HTTP. Nous avons, dans les exemples précédents, déjà utilisé un objet &lt;code&gt;Response&lt;/code&gt; issue de ce composant. Nous allons maintenant utiliser un objet &lt;code&gt;Symfony\Component\HttpFoundation\Request&lt;/code&gt; nous permettant d’accéder à toutes les informations d’une requête HTTP.&lt;/p&gt;

&lt;p&gt;Pour ce faire, Symfony passe automatiquement en paramètre une instance d’un objet &lt;code&gt;Request&lt;/code&gt; aux actions de nos contrôleurs si le paramètre est présent (Symfony détecte automatiquement la variable grâce à son typage).&lt;/p&gt;

&lt;p&gt;Complétons notre code précédent pour récupérer un paramètre &lt;code&gt;name&lt;/code&gt; et afficher le nom de la personne à saluer.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/DefaultController.php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class DefaultController
{
    public function index(Request $request): Response
    {
        return new Response(&amp;#39;Accueil Jobeet - Hello &amp;#39;.$request-&amp;gt;get(&amp;#39;name&amp;#39;, &amp;#39;World&amp;#39;));
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Dans cet exemple, nous utilisons la méthode &lt;code&gt;Request::get&lt;/code&gt; pour récupérer un paramètre de la requête. Ce paramètre peut être transmis via l’URL ou au travers d’une requête de type POST. S’il n’est présent dans aucun des cas, nous avons choisi ici de retourner la valeur par défaut “World”. La valeur par défaut est facultative, si rien n’est spécifié et que le paramètre n’est pas présent &lt;code&gt;NULL&lt;/code&gt; sera renvoyé.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Intéressons-nous maintenant à la couche vue. C’est cette dernière qui renvoie et met en forme les informations qui seront affichées à l’utilisateur. Le format de retour des données dépend de plusieurs paramètres dont le contexte d’utilisation. Dans le cas d’une navigation Web classique, l’application va retourner des données au format HTML, alors que dans le cas d’une API, les données pourraient être renvoyées au format JSON, XML ou n’importe quel autre format. Dans ce cas chapitre nous allons travailler exclusivement avec des pages HTML.&lt;/p&gt;

&lt;p&gt;Comme nous l’avons déjà évoqué auparavant, Symfony 4 laisse libre le développeur de choisir les outils qu’il souhaite utiliser. Le framework ne prend aucun parti pris. De ce fait et contrairement aux versions précédentes, Symfony n’est plus fourni avec un moteur de templating par défaut.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Les moteurs de templating simplifient le travail d’écriture HTML en améliorant la lisibilité du code, son organisation et sa maintenance. Ces derniers sont généralement fournis avec un ensemble de fonctions de haut niveau permettant entre autres d’afficher des variables PHP, de créer des macros, d’utiliser des structures de boucles et de contrôles, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dans ce tutorial, nous avons fait le choix d’utiliser &lt;a href=&quot;https://twig.symfony.com &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Twig&lt;/a&gt;. Twig a été créé par l’agence SensioLabs, les créateurs du framework Symfony. Ce moteur de templating était jusqu’à maintenant celui qui était fourni par défaut avec le framework. Sa simplicité et sa puissance en font le moteur le plus populaire PHP.&lt;/p&gt;

&lt;p&gt;Avant de pouvoir commencer à nous servir de Twig dans notre projet, nous allons devoir installer les dépendances nécessaires. Pour fonctionner dans notre projet Symfony, plusieurs composants sont requis :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;Twig&lt;/code&gt;: le moteur de template en lui-même. Ce dernier n’est pas couplé à Symfony et peut ainsi être réutilisé dans n’importe quel projet (avec ou sans framework).&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;TwigBridge&lt;/code&gt;: permet d’intégrer des nouvelles fonctionnalités au moteur de template qui sont liées aux différents composants du framework (formulaires, internationalisation, …).&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;TwigBundle&lt;/code&gt;: l’intégration du moteur Twig dans Symfony.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comme pour toute dépendance PHP, nous allons utiliser Composer (et Symfony Flex) pour installer Twig. Nous allons ainsi demander l’installation de &lt;code&gt;symfony/twig-bundle&lt;/code&gt;. Ce dernier ayant besoin des deux autres composants pour fonctionner, toutes les dépendances requises au fonctionnement de Twig dans notre projet seront mises en place.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ composer require symfony/twig-bundle

Using version ^3.3 for symfony/twig-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
  - Installing twig/twig (v2.4.3): Loading from cache
  - Installing symfony/twig-bridge (v3.3.9): Downloading (100%)
  - Installing symfony/twig-bundle (v3.3.9): Downloading (100%)
Writing lock file
Generating autoload files
Symfony operations: 1 recipe
  - Configuring symfony/twig-bundle (3.3): From github.com/symfony/recipes:master
Executing script make cache-warmup [OK]
Executing script assets:install --symlink --relative public [OK]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Il est également possible d’utiliser une notation raccourcie pour installer Twig au travers de la commande &lt;code&gt;composer require twig&lt;/code&gt;. Cela est possible grace à Symfony Flex qui permet de définir des noms alternatifs à des dépendances Composer. Dans le chapitre précédent, nous aurions pu utiliser la commande &lt;code&gt;composer require orm&lt;/code&gt; pour installer Doctrine, cette dernière commande étant un alias de la commande &lt;code&gt;composer require orm/pack&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maintenant que Twig est installé, nous allons pouvoir créer et afficher notre premier template. Pour cela, nous allons commencer par injecter le moteur de templating dans notre contrôleur. Nous pourrons ensuite, utiliser ce dernier depuis nos actions pour récupérer le rendu de nos templates.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/DefaultController.php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;

class DefaultController
{
    private $twig;

    public function __construct(Environment $twig)
    {
        $this-&amp;gt;twig = $twig;
    }

    public function index(Request $request): Response
    {
        return new Response($this-&amp;gt;twig-&amp;gt;render(&amp;#39;home.html.twig&amp;#39;, [
            &amp;#39;name&amp;#39; =&amp;gt; $request-&amp;gt;get(&amp;#39;name&amp;#39;, &amp;#39;World&amp;#39;)
        ]));
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Notez qu’il n’est pas nécessaire de faire quoi que ce soit pour injecter notre objet &lt;code&gt;Twig\Environment&lt;/code&gt; dans le constructeur de notre contrôleur. Le composant d’injection de dépendance de Symfony utilise l’&lt;a href=&quot;https://symfony.com/doc/current/service_container/autowiring.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;autowiring&lt;/a&gt; pour injecter notre dépendance automatiquement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Au moment de l’installation de Twig, Flex a préparé un dossier &lt;code&gt;template&lt;/code&gt; à la racine de notre projet et a automatiquement configuré Twig pour que ce dernier aille chercher nos vues dans ce répertoire. Nous allons donc y créer un fichier &lt;code&gt;home.html.twig&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;# templates/home.html.twig
Hello {{ name }}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pour afficher les variables que nous avons passé à notre template Twig, il faut utiliser la syntaxe &lt;code&gt;{{ variable }}&lt;/code&gt;.
 Pour afficher le contenu de notre template, nous appelons la méthode &lt;code&gt;render&lt;/code&gt; de Twig et passons le résultat à notre objet &lt;code&gt;Response&lt;/code&gt; pour que le résultat puisse être retourné à l’utilisateur.
 Pour afficher le résultat de notre vue, cette syntaxe est un peu longue. Effectivement, nous allons devoir injecter dans chacun de nos contrôleurs une instance de Twig, récupérer le contenu d’un template et créer la réponse associée.
 Heureusement, Symfony met à notre disposition des outils pour simplifier notre travail et surtout mutualiser ce code au travers de la classe &lt;code&gt;AbstractController&lt;/code&gt;. Cette dernière met à disposition une méthode &lt;code&gt;render&lt;/code&gt; qui récupérera automatiquement une instance de Twig et créera notre objet &lt;code&gt;Response&lt;/code&gt; en un appel de méthode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notre contrôleur peut ainsi être réécrit de la manière suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php  // src/Controller/DefaultController.php

namespace App\Controller;

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

class DefaultController extends AbstractController
{
    public function index(Request $request): Response
    {
        return $this-&amp;gt;render(&amp;#39;home.html.twig&amp;#39;, [
            &amp;#39;name&amp;#39; =&amp;gt; $request-&amp;gt;get(&amp;#39;name&amp;#39;, &amp;#39;World&amp;#39;)
        ]);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous connaissons à présent le fonctionnement des contrôleurs et comment ces derniers communiquent avec la vue pour afficher nos données à l’utilisateur. Nous allons maintenant pouvoir afficher les premières pages de notre projet Jobeet. Commençons par la page de listing des offres d’emploi.&lt;/p&gt;

&lt;p&gt;Créons pour cela un contrôleur dédié à la gestion des offres et ajouter une méthode pour récupérer les offres disponibles.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Controller/JobController.php

namespace App\Controller;

use App\Entity\Job;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class JobController extends AbstractController
{
    public function index(EntityManagerInterface $em)
    {
        $jobs = $em-&amp;gt;getRepository(Job::class)-&amp;gt;findAll();

        return $this-&amp;gt;render(&amp;#39;job/index.html.twig&amp;#39;, [
            &amp;#39;jobs&amp;#39; =&amp;gt; $jobs,
        ]);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous injectons ici un objet de type &lt;code&gt;EntityManagerInterface&lt;/code&gt; directement dans notre méthode grace à l’autowiring (Symfony va détecter et injecter l’instance de l’objet qui implémente l’interface).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;L’autowiring fonctionne avec une interface uniquement s’il n’existe qu’une seule classe qui implémente l’interface en question. Si deux classes implémentent la même interface, Symfony ne sera pas capable de savoir quelle instance injecter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L’&lt;code&gt;EntityManager&lt;/code&gt; est l’objet Doctrine qui permet de manipuler nos entités. Dans notre code, nous demandons une instance du repository (c’est-à-dire la classe qui permet de faire les requêtes en base de données) de notre entité &lt;code&gt;Job&lt;/code&gt; pour ensuite récupérer la liste des emplois disponibles.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Doctrine fournit un ensemble de méthodes par défaut permettant de récupérer nos objets persistés. Signalons entre autres les fonctions &lt;a href=&quot;https://goo.gl/EyWW5N &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;find&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://goo.gl/EyWW5N &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;findBy&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;findOneBy&lt;/code&gt; et &lt;code&gt;findAll&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ajoutons la vue qui va afficher nos résultats :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;!-- templates/job/index.html.twig --&amp;gt;

&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;
        &amp;lt;title&amp;gt;Jobeet - Liste des jobs&amp;lt;/title&amp;gt;
        &amp;lt;link rel=&amp;quot;icon&amp;quot; type=&amp;quot;image/x-icon&amp;quot; href=&amp;quot;{{ asset(&amp;#39;favicon.ico&amp;#39;) }}&amp;quot; /&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;ul&amp;gt;
            {% for job in jobs %}
                &amp;lt;li&amp;gt;{{ job.position }}&amp;lt;/li&amp;gt;
            {% endfor %}
        &amp;lt;/ul&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pour afficher la page, il sera nécessaire de modifier le fichier de configuration &lt;a href=&quot;https://github.com/jdecool/jobeet/blob/04-controleur-vue/config/routes.yaml &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;config/routes.yaml&lt;/code&gt;&lt;/a&gt; en modifiant le contrôleur devant être appelé.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L’exemple ci-dessus nous permet de découvrir de nouveaux éléments du langage de Twig. Tout d’abord, les instructions d’itérations qui nous permettent de parcourir des données de type &lt;code&gt;array&lt;/code&gt; ou plus généralement des données &lt;a href=&quot;http://php.net/manual/en/language.types.iterable.php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;iterable&lt;/code&gt;&lt;/a&gt; de PHP. Il s’agit d’une boucle &lt;a href=&quot;https://twig.symfony.com/doc/2.x/tags/for.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;for&lt;/code&gt;&lt;/a&gt; permettant de parcourir une liste d’éléments.&lt;/p&gt;

&lt;p&gt;Nous constatons également qu’il est possible de passer à notre template Twig des instances d’objets et d’accéder aux propriétés de ces derniers avec l’opérateur &lt;code&gt;.&lt;/code&gt; (cela fonctionnement également pour accéder à des tableaux).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bien que la propriété &lt;code&gt;position&lt;/code&gt; de notre objet &lt;code&gt;Job&lt;/code&gt; soit privée, Twig parvient à afficher cette dernière. Twig possède un mécanisme qui va automatiquement trouver la méthode &lt;code&gt;get&lt;/code&gt; associé à la propriété si cette dernière n’est pas accessible directement (si elle n’est pas &lt;code&gt;public&lt;/code&gt;). Il est toutefois possible d’appeler directement la méthode &lt;code&gt;getPosition()&lt;/code&gt; ou n’importe quelle autre méthode depuis notre template.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pour le moment nous avons copié la totalité de la structure HTML dans notre fichier. Pour éviter d’avoir à dupliquer de nombreuses lignes de code dans tous nos templates, nous allons mutualiser le code qui va être commun à toutes les pages.&lt;/p&gt;

&lt;p&gt;Si vous jetez un oeil aux fichiers qui ont été ajoutés dans le dossier &lt;code&gt;templates&lt;/code&gt; par Flex lors de l’installation de Twig, vous noterez la présence d’un fichier &lt;code&gt;base.html.twig&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;!-- templates/base.html.twig --&amp;gt;

&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;
        &amp;lt;title&amp;gt;{% block title %}Jobeet{% endblock %}&amp;lt;/title&amp;gt;
        {% block stylesheets %}{% endblock %}
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ce fichier correspond à un template qui a vocation de définir la mise en page globale de notre application. Twig fonctionne sur le principe d’héritage. Cela signifie que vous allez pouvoir définir des templates qui contiendront des éléments qui pourront ensuite être surchargés dans d’autres templates. Pour cela, Twig utilise un système de blocs (&lt;a href=&quot;https://twig.symfony.com/doc/2.x/tags/block.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;block&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Utilisons le fichier &lt;code&gt;templates/base.html.twig&lt;/code&gt; pour définir la mise en page de Jobeet. Pour commencer, nous allons étendre ce dernier dans notre template &lt;code&gt;templates/job/index.html.twig&lt;/code&gt; et surcharger le bloc &lt;code&gt;body&lt;/code&gt; pour y faire apparaître notre contenu :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;!-- templates/job/index.html.twig --&amp;gt;
{% extends &amp;quot;base.html&amp;quot; %}

{% block body %}
    &amp;lt;ul&amp;gt;
        {% for job in jobs %}
            &amp;lt;li&amp;gt;{{ job.position }}&amp;lt;/li&amp;gt;
        {% endfor %}
    &amp;lt;/ul&amp;gt;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous rechargez la page, le résultat devrait être identique, mais nous avons beaucoup moins de code dans notre template, qui se concentre maintenant uniquement sur une tâche bien précise : afficher notre liste d’offres d’emploi.&lt;/p&gt;

&lt;p&gt;Pour que nous interfaces soient un plus agréables à utiliser, nous allons y ajouter un peu de style avec du CSS. Pour gagner du temps, nous allons utiliser le framework &lt;a href=&quot;http://getbootstrap.com/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;boostrap&lt;/a&gt; au travers de ce &lt;a href=&quot;&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;thème https://startbootstrap.com/template-overviews/1-col-portfolio/ &lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Une fois les fichiers téléchargés, nous allons placer les différents fichiers CSS et Javascript dans le dossier &lt;code&gt;public&lt;/code&gt; (car ces derniers doivent être desservis par le serveur HTTP). Nous les organiserons dans un dossier &lt;code&gt;vendor&lt;/code&gt; comme vous pouvez le voir dans le &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/04-controleur-vue/public/vendor &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;dépôt Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Commençons ensuite par modifier notre mise en page globale :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;!-- templates/base.html.twig --&amp;gt;

&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
        &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1, shrink-to-fit=no&amp;quot;&amp;gt;
        &amp;lt;title&amp;gt;{% block title %}Jobeet{% endblock %}&amp;lt;/title&amp;gt;
        &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/vendor/bootstrap/css/bootstrap.min.css&amp;quot;&amp;gt;
        &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/vendor/1-col-portfolio.css&amp;quot;&amp;gt;
        {% block stylesheets %}{% endblock %}
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;nav class=&amp;quot;navbar navbar-expand-lg navbar-dark bg-dark fixed-top&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;navbar-brand&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Jobeet&amp;lt;/a&amp;gt;
                &amp;lt;button class=&amp;quot;navbar-toggler&amp;quot; type=&amp;quot;button&amp;quot; data-toggle=&amp;quot;collapse&amp;quot; data-target=&amp;quot;#navbarResponsive&amp;quot; aria-controls=&amp;quot;navbarResponsive&amp;quot; aria-expanded=&amp;quot;false&amp;quot; aria-label=&amp;quot;Toggle navigation&amp;quot;&amp;gt;
                    &amp;lt;span class=&amp;quot;navbar-toggler-icon&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;
                &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/nav&amp;gt;

        &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
            {% block body %}{% endblock %}
        &amp;lt;/div&amp;gt;

        &amp;lt;script src=&amp;quot;/vendor/jquery/jquery-3.2.1.slim.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src=&amp;quot;/vendor/popper/popper.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src=&amp;quot;/vendor/bootstrap/js/bootstrap.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        {% block javascripts %}{% endblock %}
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous conservons les blocs Twig &lt;code&gt;stylesheets&lt;/code&gt; et &lt;code&gt;javascripts&lt;/code&gt; même si nous ne les utiliserons pas pour le moment. C’est dernier seront utiles à l’avenir pour inclure des fichiers CSS ou Javascript spécifiques à certaines vues.&lt;/p&gt;

&lt;p&gt;Pour finir, mettons en forme la présentation de nos offres d’emploi :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;!-- templates/job/index.html.twig --&amp;gt;
{% extends &amp;quot;base.html.twig&amp;quot; %}

{% block body %}
    &amp;lt;h1 class=&amp;quot;my-4&amp;quot;&amp;gt;Liste des offres&amp;lt;/h1&amp;gt;

    {% for job in jobs %}
        &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;col-md-7&amp;quot;&amp;gt;
                &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;
                    &amp;lt;img class=&amp;quot;img-fluid rounded mb-3 mb-md-0&amp;quot; src=&amp;quot;/images/{{ job.logo }}&amp;quot; alt=&amp;quot;{{ job.company }}&amp;quot;&amp;gt;
                &amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&amp;quot;col-md-5&amp;quot;&amp;gt;
                &amp;lt;h3&amp;gt;{{ job.position }}&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;{{ job.description }}&amp;lt;/p&amp;gt;
                &amp;lt;p&amp;gt;Posted on {{ job.createdAt|date(&amp;quot;m/d/Y&amp;quot;) }}&amp;lt;/p&amp;gt;
                &amp;lt;a class=&amp;quot;btn btn-primary&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;See more&amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;hr&amp;gt;
    {% endfor %}

    &amp;lt;ul class=&amp;quot;pagination justify-content-center&amp;quot;&amp;gt;
        &amp;lt;li class=&amp;quot;page-item disabled&amp;quot;&amp;gt;
            &amp;lt;a class=&amp;quot;page-link&amp;quot; href=&amp;quot;#&amp;quot; aria-label=&amp;quot;Previous&amp;quot;&amp;gt;
                &amp;lt;span aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;amp;laquo;&amp;lt;/span&amp;gt;
                &amp;lt;span class=&amp;quot;sr-only&amp;quot;&amp;gt;Previous&amp;lt;/span&amp;gt;
            &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li class=&amp;quot;page-item&amp;quot;&amp;gt;
            &amp;lt;a class=&amp;quot;page-link&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;1&amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li class=&amp;quot;page-item disabled&amp;quot;&amp;gt;
            &amp;lt;a class=&amp;quot;page-link&amp;quot; href=&amp;quot;#&amp;quot; aria-label=&amp;quot;Next&amp;quot;&amp;gt;
                &amp;lt;span aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;amp;raquo;&amp;lt;/span&amp;gt;
                &amp;lt;span class=&amp;quot;sr-only&amp;quot;&amp;gt;Next&amp;lt;/span&amp;gt;
            &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Les images liées à notre jeu de données sont disponibles directement dans les &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/04-controleur-vue/public/images &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;sources de ce billet&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Et voilà le rendu final de notre application Jobeet :&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20170929-tutorial-jobeet-symfony-4-partie-4a-le-controleur-et-la-vue/final-render.png &quot; style=&quot;width: 100%; height: 100%;&quot; /&gt;&lt;/center&gt;

&lt;blockquote&gt;
  &lt;p&gt;Retrouvez tous les tutorials Jobeet disponibles depuis le &lt;a href=&quot;/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html&quot;&gt;billet d’introduction de la série&lt;/a&gt;. Le code source de cette application est également disponible sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/04-controleur-vue &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;. Vous trouverez une branche associée à l’état du projet après chaque chapitre.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Fri, 29 Sep 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/09/29/tutorial-jobeet-symfony-4-partie-4a-le-controleur-et-la-vue.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/09/29/tutorial-jobeet-symfony-4-partie-4a-le-controleur-et-la-vue.html</guid>
                </item>
            
        
            
        
            
                268
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Partie 3B: Les données initiales</title>
                    <description>&lt;p&gt;Nous avons donc créé notre base et données avec les tables qui permettront de stocker les informations que notre application va manipuler. Avant de commencer l’implémentation des fonctionnalités évoquées dans les billets précédents, nous allons commencer par insérer un jeu de données initiales (également appelées &lt;strong&gt;fixtures&lt;/strong&gt;) afin de ne pas démarrer avec un projet vide.&lt;/p&gt;

&lt;p&gt;Pour créer nos données, nous allons utiliser un bundle fourni par les équipes de Doctrine et qui propose un moyen de charger des données en base. Nous avions jusqu’à maintenant utilisé uniquement des modules officiels de Symfony. Le bundle que nous allons utiliser (&lt;code&gt;DoctrineFixturesBundle&lt;/code&gt;) est un module non officiel de Symfony. Or, par défaut, Symfony Flex ne permet de ne travailler qu’avec les bundles officiels de Symfony.&lt;/p&gt;

&lt;p&gt;Il est néanmoins possible d’utiliser Flex avec ces bundles, mais il sera nécessaire de le spécifier explicitement dans la configuration. Pour cela, nous allons modifier la valeur du paramètre &lt;code&gt;allow-contrib&lt;/code&gt; présent dans notre fichier &lt;code&gt;composer.json&lt;/code&gt; au travers de la commande suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ composer config extra.symfony.allow-contrib true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une fois effectué, il sera possible de télécharger la dépendance. Cette dernière n’étant utilisée qu’en développement, nous allons utiliser l’option &lt;code&gt;--dev&lt;/code&gt; de Composer afin de l’installer comme tel :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ composer require doctrine/doctrine-fixtures-bundle --dev

Using version ^2.4 for doctrine/doctrine-fixtures-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing doctrine/data-fixtures (v1.2.2): Loading from cache
  - Installing doctrine/doctrine-fixtures-bundle (v2.4.0): Loading from cache
Writing lock file
Generating autoload files
Symfony operations: 1 recipe
  - Configuring doctrine/doctrine-fixtures-bundle (2.4): From github.com/symfony/recipes-contrib:master
Executing script make cache-warmup [OK]
Executing script assets:install --symlink --relative public [OK]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;L’installation de cette dépendance va créer un dossier &lt;code&gt;src/DataFixtures/ORM&lt;/code&gt; qui contiendra nos déclarations de fixtures. Mais avant d’écrire ces dernières, vous avez peut-être remarqué, que lorsque nous avons écrit nos entités, nous avons déclaré les propriétés de nos objets comment étant &lt;code&gt;private&lt;/code&gt;. Nous allons donc devoir commencer par écrire les méthodes &lt;code&gt;get&lt;/code&gt; qui permettront d’accéder à nos propriétés ainsi que les méthodes &lt;code&gt;set&lt;/code&gt; qui permettront de modifier la valeur de nos propriétés.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;L’activation du paramètre &lt;code&gt;allow-contrib&lt;/code&gt; notamment permis la création du répertoire qui accueillera nos fixtures. Même si nous n’avions pas changé cette configuration, il aurait été possible d’installer le bundle, mais nous n’aurions pas profité des mécanismes de Flex qui permettent de créer une configuration du bundle par défaut.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si vous avez eu la curiosité de regarder la liste des commandes proposées par le bundle Doctrine, vous aurez peut-être remarqué qu’il existe une commande &lt;code&gt;doctrine:generate:entities&lt;/code&gt; dont le rôle est justement de remplir cette tâche.&lt;/p&gt;

&lt;p&gt;Notre projet suit la convention &lt;a href=&quot; http://www.php-fig.org/psr/psr-4 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PSR-4&lt;/a&gt; décrivant la norme sur le chargement des fichiers PHP en fonction de leur arborescence (appelée &lt;a href=&quot;http://php.net/autoload &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;autoloading&lt;/a&gt;). Malheureusement, le générateur de Doctrine n’est actuellement pas compatible avec cette norme (et ce n’est pas prévu à court ou moyen terme).&lt;/p&gt;

&lt;p&gt;Mais cela n’est pas un problème étant donné que la plupart des outils de développement (tel que PHPStorm, Netbeans, Eclipse, Sublime Text, Atom, VSCode, …) ont une fonctionnalité (ou des plugins) permettant de générer du code automatiquement et notamment nos getters et setters.&lt;/p&gt;

&lt;p&gt;Nous allons donc générer des méthodes &lt;code&gt;get&lt;/code&gt; pour l’ensemble des propriétés de nos entités ainsi que les méthodes &lt;code&gt;set&lt;/code&gt; associées, à l’exception des propriétés &lt;code&gt;$id&lt;/code&gt; (car une clé primaire ne doit pouvoir être modifié), &lt;code&gt;createdAt&lt;/code&gt; et &lt;code&gt;updateAt&lt;/code&gt; (car nous avons déjà ajouté les méthodes permettant de modifier ces données).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Si vous souhaitez gagner du temps, vous pouvez accéder au code source correspondant à ce billet sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/03b-donnees-initiales &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt; pour copier/coller les méthodes décrites ci-dessus.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nous pouvons maintenant commencer à écrire nos fixtures, c’est-à-dire les classes qui vont s’occuper de la création notre jeu de données initiales. Nous allons commencer par créer des catégories d’offre d’emploi en créant une classe &lt;code&gt;LoadCategoryData&lt;/code&gt; dans le dossier &lt;code&gt;src/DataFixtures/ORM&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php //src/DataFixtures/ORM/LoadCategoryData.php

namespace App\DataFixtures\ORM;

use App\Entity\Category;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;

class LoadCategoryData extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $design = new Category();
        $design-&amp;gt;setName(&amp;#39;Design&amp;#39;);
        $manager-&amp;gt;persist($design);

        $programming = new Category();
        $programming-&amp;gt;setName(&amp;#39;Programming&amp;#39;);
        $manager-&amp;gt;persist($programming);

        $managing = new Category();
        $managing-&amp;gt;setName(&amp;#39;Manager&amp;#39;);
        $manager-&amp;gt;persist($managing);

        $administrator = new Category();
        $administrator-&amp;gt;setName(&amp;#39;Administrator&amp;#39;);
        $manager-&amp;gt;persist($administrator);

        $manager-&amp;gt;flush();

        $this-&amp;gt;addReference(&amp;#39;category-design&amp;#39;, $design);
        $this-&amp;gt;addReference(&amp;#39;category-programming&amp;#39;, $programming);
        $this-&amp;gt;addReference(&amp;#39;category-managing&amp;#39;, $managing);
        $this-&amp;gt;addReference(&amp;#39;category-administrator&amp;#39;, $administrator);
    }

    public function getOrder()
    {
        return 1;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour charger des données, notre classe doit étendre de &lt;code&gt;AbstractFixture&lt;/code&gt; qui contient des fonctions de base de gestion de fixtures. Nous implémentons ensuite une interface &lt;code&gt;OrderedFixtureInterface&lt;/code&gt; permettant de spécifier un ordre d’ insertion des nos fixtures (dans le cas présent, nous ne pouvons enregistrer une offre d’emploi si nous n’avons pas auparavant créé des catégories) au travers de la méthode &lt;code&gt;getOrder()&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Mise à jour du 04/03/2018 :&lt;/strong&gt; Ce chapitre a été initialement rédigé sur la version 3.4 de Symfony. Depuis, la version installée de Doctrine a évolué. La classe &lt;code&gt;Doctrine\Common\DataFixtures\AbstractFixture&lt;/code&gt; est maintenant remplacée par &lt;code&gt;Doctrine\Bundle\FixturesBundle\Fixture&lt;/code&gt; et l’interface &lt;code&gt;Doctrine\Common\DataFixtures\OrderedFixtureInterface&lt;/code&gt; par &lt;code&gt;Doctrine\Common\DataFixtures\DependentFixtureInterface&lt;/code&gt;. Le fonctionnement pour charger des fixtures dépendantes les unes des autres a évolué, et il n’est plus nécessaire de définir l’ordre de chargement de ces dernières. L’interface &lt;code&gt;DependentFixtureInterface&lt;/code&gt; définit une méthode retournant un tableau contenant les classes dont dépend le jeu de données courant. Vous n’avez donc plus à déterminer l’ordre de chargement des &lt;em&gt;fixtures&lt;/em&gt;, Doctrine s’en charge dorénavant pour vous.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le chargement des données se fait au sein d’une méthode &lt;code&gt;load(ObjectManager $manager)&lt;/code&gt;. Il s’agit de la méthode appelée lorsque les données doivent être insérées. Cette méthode prend en paramètre un objet de type &lt;code&gt;ObjectManager&lt;/code&gt; qui est l’objet Doctrine qui permet de travailler avec la base de données.&lt;/p&gt;

&lt;p&gt;Ce dernier permet d’appeler la méthode &lt;code&gt;persist&lt;/code&gt; pour indiquer à Doctrine que nous souhaitons persister un objet en base. Pour que la donnée soit réellement écrite en base, on utilise un appel à la méthode &lt;code&gt;flush&lt;/code&gt;. Dans le cas où l’on fait plusieurs appels à la méthode &lt;code&gt;persist&lt;/code&gt; (comme c’est le cas ici) et qu’ensuite on &lt;code&gt;flush&lt;/code&gt;, les insertions en bases sont réalisés au sein d’une transaction.&lt;/p&gt;

&lt;p&gt;Une fois les données enregistrées, nous souhaitons les rendre accessibles depuis nos autres fixtures pour par exemple pouvoir affecter une catégorie à une offre d’emploi. Pour cela, nous allons définir explicitement les objets auxquels nous souhaitons accéder en appelant la méthode &lt;code&gt;addReference&lt;/code&gt; et en associant une clé à notre donnée.&lt;/p&gt;

&lt;p&gt;Nous pouvons ensuite passer à la fixture suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php  //src/DataFixtures/ORM/LoadJobData.php

namespace App\DataFixtures\ORM;

use App\Entity\Job;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;

class LoadJobData extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $categoryProgramming = $this-&amp;gt;getReference(&amp;#39;category-programming&amp;#39;);
        $categoryDesign = $this-&amp;gt;getReference(&amp;#39;category-design&amp;#39;);

        $jobSensioLabs = new Job();
        $jobSensioLabs-&amp;gt;setCategory($categoryProgramming);
        $jobSensioLabs-&amp;gt;setType(&amp;#39;full-time&amp;#39;);
        $jobSensioLabs-&amp;gt;setCompany(&amp;#39;Sensio Labs&amp;#39;);
        $jobSensioLabs-&amp;gt;setLogo(&amp;#39;sensio-labs.gif&amp;#39;);
        $jobSensioLabs-&amp;gt;setUrl(&amp;#39;http://www.sensiolabs.com/&amp;#39;);
        $jobSensioLabs-&amp;gt;setPosition(&amp;#39;Web Developer&amp;#39;);
        $jobSensioLabs-&amp;gt;setLocation(&amp;#39;Paris, France&amp;#39;);
        $jobSensioLabs-&amp;gt;setDescription(&amp;quot;You&amp;#39;ve already developed websites with symfony and you want to work with Open-Source technologies. You have a minimum of 3 years experience in web development with PHP or Java and you wish to participate to development of Web 2.0 sites using the best frameworks available.&amp;quot;);
        $jobSensioLabs-&amp;gt;setHowToApply(&amp;#39;Send your resume to fabien.potencier [at] sensio.com&amp;#39;);
        $jobSensioLabs-&amp;gt;setIsPublic(true);
        $jobSensioLabs-&amp;gt;setIsActivated(true);
        $jobSensioLabs-&amp;gt;setToken(&amp;#39;job_sensio_labs&amp;#39;);
        $jobSensioLabs-&amp;gt;setEmail(&amp;#39;job@example.com&amp;#39;);
        $jobSensioLabs-&amp;gt;setExpiresAt(new \DateTime(&amp;#39;2012-10-10&amp;#39;));
        $manager-&amp;gt;persist($jobSensioLabs);

        $jobExtremeSensio = new Job();
        $jobExtremeSensio-&amp;gt;setCategory($categoryDesign);
        $jobExtremeSensio-&amp;gt;setType(&amp;#39;part-time&amp;#39;);
        $jobExtremeSensio-&amp;gt;setCompany(&amp;#39;Extreme Sensio&amp;#39;);
        $jobExtremeSensio-&amp;gt;setLogo(&amp;#39;extreme-sensio.gif&amp;#39;);
        $jobExtremeSensio-&amp;gt;setUrl(&amp;#39;http://www.extreme-sensio.com/&amp;#39;);
        $jobExtremeSensio-&amp;gt;setPosition(&amp;#39;Web Designer&amp;#39;);
        $jobExtremeSensio-&amp;gt;setLocation(&amp;#39;Paris, France&amp;#39;);
        $jobExtremeSensio-&amp;gt;setDescription(&amp;#39;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in.&amp;#39;);
        $jobExtremeSensio-&amp;gt;setHowToApply(&amp;#39;Send your resume to fabien.potencier [at] sensio.com&amp;#39;);
        $jobExtremeSensio-&amp;gt;setIsPublic(true);
        $jobExtremeSensio-&amp;gt;setIsActivated(true);
        $jobExtremeSensio-&amp;gt;setToken(&amp;#39;job_extreme_sensio&amp;#39;);
        $jobExtremeSensio-&amp;gt;setEmail(&amp;#39;job@example.com&amp;#39;);
        $jobExtremeSensio-&amp;gt;setExpiresAt(new \DateTime(&amp;#39;2012-10-10&amp;#39;));
        $manager-&amp;gt;persist($jobExtremeSensio);

        $manager-&amp;gt;flush();
    }

    public function getOrder()
    {
        return 2;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Notez dans le code ci-dessus l’utilisation de la méthode &lt;code&gt;getReference&lt;/code&gt; nous permettant de récupérer une donnée qui a été créée dans la fixture précédente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il ne reste maintenant plus qu’à charger nos données au travers de la commande :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ bin/console doctrine:fixtures:load
Careful, database will be purged. Do you want to continue y/N ?y
  &amp;gt; purging database
  &amp;gt; loading [1] App\DataFixtures\ORM\LoadCategoryData
  &amp;gt; loading [2] App\DataFixtures\ORM\LoadJobData&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et voilà !&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Retrouvez tous les tutorials Jobeet disponibles depuis le &lt;a href=&quot;/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html&quot;&gt;billet d’introduction de la série&lt;/a&gt;. Le code source de cette application est également disponible sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/03b-donnees-initiales &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;. Vous trouverez une branche associée à l’état du projet après chaque chapitre.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Thu, 21 Sep 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/09/21/tutorial-jobeet-symfony-4-partie-3b-les-donnees-initiales.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/09/21/tutorial-jobeet-symfony-4-partie-3b-les-donnees-initiales.html</guid>
                </item>
            
        
            
                269
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Partie 3A: Le modèle de données</title>
                    <description>&lt;p&gt;Maintenant que l’aspect fonctionnel de notre projet a été défini, nous allons pouvoir créer le modèle de données associé à notre application, c’est-à-dire les classes qui permettront d’interagir avec la base de données. Nous allons pour cela avoir recours à un ORM. Ce sera également l’occasion de voir comment ajouter et configurer un module tier dans notre projet.&lt;/p&gt;

&lt;p&gt;D’après &lt;a href=&quot;/blog/2017/09/19/tutorial-jobeet-symfony-4-partie-2-le-projet.html&quot;&gt;les scénarios&lt;/a&gt;  que nous avons précédemment écrits, voici le schéma correspondant aux relations entre entités que l’on peut en déduire :&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20170920-tutorial-jobeet-symfony-4-partie-3a-le-modele-de-donnees/database.png &quot; /&gt;&lt;/center&gt;

&lt;p&gt;En plus des informations, nous avons également ajouté un champ &lt;code&gt;created_at&lt;/code&gt; et &lt;code&gt;updated_at&lt;/code&gt; à certaines tables afin de conserver une trace des dernières modifications des données que nous allons traiter.&lt;/p&gt;

&lt;p&gt;Pour stocker les informations de l’application, nous allons utiliser une base de données relationnelle. Symfony étant un framework orienté-objet, nous allons donc manipuler les informations sous la forme d’objet. Les informations de notre base de données doivent ainsi être mappées avec notre modèle objet et pour cela nous allons utiliser un ORM.&lt;/p&gt;

&lt;p&gt;Symfony 4 laisse le choix au développeur sur les outils qu’il souhaite utiliser. Contrairement aux versions précédentes, Symfony est livré “vide” et n’impose aucun choix par défaut. Pour ce tutorial, nous allons faire le choix d’utiliser l’ORM &lt;a href=&quot;http://www.doctrine-project.org/projects/orm.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Doctrine&lt;/a&gt; qui est l’ORM le plus répandu dans l’écosystème PHP. Mais il serait tout à fait possible d’utiliser une simple connexion &lt;a href=&quot;http://php.net/manual/fr/book.pdo.php &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PDO&lt;/a&gt; ou &lt;a href=&quot;http://www.doctrine-project.org/projects/dbal.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Doctrine DBAL&lt;/a&gt;, ou un autre ORM tel que &lt;a href=&quot;http://propelorm.org/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Propel&lt;/a&gt;, &lt;a href=&quot;https://laravel.com/docs/5.0/eloquent &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Eloquent&lt;/a&gt;, &lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;http://www.pomm-project.org/ &quot;&gt;Pomm&lt;/a&gt; ou n’importe quel autre outil.&lt;/p&gt;

&lt;p&gt;Avant de créer et configurer les classes de notre modèle de données, nous allons commencer par télécharger Doctrine. Pour intégrer ce dernier dans notre projet Symfony, nous devrons récupérer deux dépendances. La première, &lt;code&gt;doctrine/orm&lt;/code&gt; est l’ORM en tant que tel. La seconde dépendance consiste à intégrer l’ORM dans notre architecture Symfony. Symfony facilite le développement et la réutilisation de module que l’on peut partager entre plusieurs projets. Ces modules (des plugins en quelque sorte) sont appelés &lt;strong&gt;bundles&lt;/strong&gt; dans l’écosystème du framework. Il convient donc de télécharger la dépendance &lt;code&gt;doctrine/doctrine-bundle&lt;/code&gt; qui va intégrer l’ORM dans notre environnement Symfony.&lt;/p&gt;

&lt;p&gt;Afin d’éviter au développeur d’avoir à installer deux dépendances distinctes, les équipes de développement fournissent un méta-paquet Composer permettant d’installer les deux éléments d’un coup. Pour intégrer Doctrine à notre projet, il nous suffit donc de récupérer la dépendance &lt;code&gt;symfony/orm-pack&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ composer require symfony/orm-pack

Using version ^1.0 for symfony/orm-pack
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 20 installs, 0 updates, 0 removals
  - Installing ocramius/package-versions (1.1.3): Loading from cache
  - Installing zendframework/zend-eventmanager (3.2.0): Loading from cache
  - Installing zendframework/zend-code (3.3.0): Loading from cache
  - Installing ocramius/proxy-manager (2.1.1): Loading from cache
  - Installing doctrine/lexer (v1.0.1): Loading from cache
  - Installing doctrine/inflector (v1.2.0): Loading from cache
  - Installing doctrine/collections (v1.5.0): Loading from cache
  - Installing doctrine/cache (v1.7.1): Loading from cache
  - Installing doctrine/annotations (v1.5.0): Loading from cache
  - Installing doctrine/common (v2.8.1): Loading from cache
  - Installing doctrine/dbal (v2.6.3): Loading from cache
  - Installing doctrine/migrations (v1.6.1): Loading from cache
  - Installing symfony/doctrine-bridge (v4.0.0-RC1): Loading from cache
  - Installing doctrine/doctrine-cache-bundle (1.3.2): Loading from cache
  - Installing jdorn/sql-formatter (v1.2.17): Loading from cache
  - Installing doctrine/doctrine-bundle (1.8.0): Loading from cache
  - Installing doctrine/doctrine-migrations-bundle (v1.3.1): Loading from cache
  - Installing doctrine/instantiator (1.1.0): Loading from cache
  - Installing doctrine/orm (v2.5.12): Loading from cache
Writing lock file
Generating autoload files
Symfony operations: 4 recipes (9eb5d7e36646d167c1d8407c068032b9)
  - Configuring doctrine/annotations (1.0): From github.com/symfony/recipes:master
  - Configuring doctrine/doctrine-cache-bundle (1.3.2): From auto-generated recipe
  - Configuring doctrine/doctrine-bundle (1.6): From github.com/symfony/recipes:master
  - Configuring doctrine/doctrine-migrations-bundle (1.2): From github.com/symfony/recipes:master
ocramius/package-versions:  Generating version class...
ocramius/package-versions: ...done generating version class
Executing script cache:clear [OK]
Executing script assets:install --symlink --relative public [OK]


 Next: Configuration


  * Modify your DATABASE_URL config in .env

  * Configure the driver (mysql) and
    server_version&amp;lt;/fg&amp;gt; (5.7) in config/packages/doctrine.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour utiliser un bundle dans un projet, il est nécessaire d’activer ce dernier afin qu’il soit reconnu par le framework. Cette configuration s’effectue dans le fichier &lt;code&gt;config/bundles.php&lt;/code&gt;. Ce fichier retourne un tableau associatif où la clé des éléments correspond au namespace complet du bundle à activer et dont la valeur est également un tableau associatif indiquant les environnements pour lesquels le bundle est actif (ou non).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // config/bundles.php

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class =&amp;gt; [&amp;#39;all&amp;#39; =&amp;gt; true],
    Symfony\Bundle\WebServerBundle\WebServerBundle::class =&amp;gt; [&amp;#39;dev&amp;#39; =&amp;gt; true],
    Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class =&amp;gt; [&amp;#39;all&amp;#39; =&amp;gt; true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class =&amp;gt; [&amp;#39;all&amp;#39; =&amp;gt; true],
    Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class =&amp;gt; [&amp;#39;all&amp;#39; =&amp;gt; true],
];&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;En réalité, l’activation d’un bundle se fait rarement manuellement. Effectivement, &lt;a href=&quot;https://github.com/symfony/flex &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Symfony Flex&lt;/a&gt;, que nous avons évoqué brièvement lors de la mise en place du projet se chargera de cette action. Il sera néanmoins parfois nécessaire de corriger la configuration par défaut en activant ou désactivant le bundle pour certains environnements.&lt;/p&gt;

&lt;p&gt;Maintenant que Doctrine est installé et activé dans notre projet, nous allons pouvoir commencer à paramétrer notre application pour qu’elle puisse accéder à une base de données. Pour cela nous allons renseigner les paramètres nécessaires à l’établissement de la connexion. Cette dernière étant propre à l’environnement d’exécution de notre code, nous allons définir les paramètres dans le fichier &lt;code&gt;.env&lt;/code&gt;. Ce dernier a d’ailleurs été prérempli avec une configuration de base par Flex :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;# .env
# ...

###&amp;gt; doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: &amp;quot;sqlite:///%kernel.project_dir%/var/data.db&amp;quot;
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
###&amp;lt; doctrine/doctrine-bundle ###&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Vous avez certainement noté la présence des fichiers &lt;code&gt;.env&lt;/code&gt; et &lt;code&gt;.env.dist&lt;/code&gt;. Le fichier &lt;code&gt;.env&lt;/code&gt; est le fichier qui contient réellement la configuration de notre application. Contenant des informations pouvant être très sensibles (comme par exemple des mots de passe), il n’est pas versionné.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;C’est pour cela qu’un fichier &lt;code&gt;.env.dist&lt;/code&gt; est présent. Ce dernier qui lui est versionné sert de modèle pour que les développeurs qui vont être ammenés à travailler sur le projet puisse connaître les informations à renseigner.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L’exemple de configuration qui a été inséré par Symfony Flex permet de se connecter à une base de données MySQL. Pour les besoins de ce tutorial, ainsi que pour éviter l’installation d’un serveur, nous allons utiliser SQLite qui est un système de base de données ne nécessitant pas une architecture client-serveur. Il sera nécessaire de vérifier que le driver SQLite de PHP (&lt;code&gt;php-sqlite3&lt;/code&gt;) soit installé et activé. Nous allons ensuite modifier la variable d’environnement &lt;code&gt;DATABASE_URL&lt;/code&gt; comme suit :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;DATABASE_URL=sqlite:///%kernel.project_dir%/var/jobeet.db&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Dans cette configuration, nous faisons référence à un paramètre &lt;code&gt;kernel.project_dir&lt;/code&gt; définit par le framework (facilement reconnaissable car il est entouré du caractère &lt;code&gt;%&lt;/code&gt;). Ce dernier fait référence à la racine du dossier du projet et permet ainsi de définir facilement l’endroit où l’on souhaite enregistrer nos données.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;span style=&quot;text-decoration: line-through;&quot;&gt;&lt;em&gt;Mise à jour du 23/09/2017 :&lt;/em&gt; En parcourant le projet sur Github, j’ai trouvé l’&lt;a href=&quot;https://github.com/symfony/flex/issues/129 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;issue concernant ce problème&lt;/a&gt; ainsi que &lt;a href=&quot;https://github.com/symfony/symfony/pull/23901 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;sa résolution&lt;/a&gt;. Tout devrait rentrer dans l’ordre lors de la publication de la branche 3.4 du projet.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le dossier &lt;code&gt;var&lt;/code&gt; qui a été créé lors de la mise en place de Doctrine est, par convention, un dossier qui va contenir les fichiers écrits par notre application durant son fonctionnement (tel que des logs, des fichiers de cache, …). C’est donc tout naturellement dans ce dernier que nous allons stocker le fichier qui contiendra nos données SQLite.&lt;/p&gt;

&lt;p&gt;Nous allons maintenant pouvoir démarrer l’écriture des classes qui vont modéliser nos données. Par défaut Doctrine est configuré pour travailler avec des annotations. Personnellement, je préfère séparer le code de sa configuration, c’est pour cela quand dans ce tutorial, nous utiliserons une configuration en YAML (il est également possible d’avoir une configuration en XML). Pour cela, nous allons éditer le fichier de configuration &lt;code&gt;config/packages/doctrine.yaml&lt;/code&gt; pour y mettre le contenu ci-dessous :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;# config/packages/doctrine.yaml
parameters:
    env(DATABASE_URL): &amp;#39;sqlite:///%kernel.project_dir%/var/jobeet.db&amp;#39;

doctrine:
    dbal:
        driver: &amp;#39;pdo_sqlite&amp;#39;
        url: &amp;#39;%env(resolve:DATABASE_URL)%&amp;#39;
    orm:
        auto_generate_proxy_classes: &amp;#39;%kernel.debug%&amp;#39;
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: yml # annotation ou xml
                dir: &amp;#39;%kernel.project_dir%/config/doctrine/mapping&amp;#39; # configuration du mapping
                prefix: &amp;#39;App\Entity&amp;#39;
                alias: App&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On retrouve dans cette configuration la variable &lt;code&gt;%kernel.project_dir%&lt;/code&gt; faisant référence au dossier racine de notre projet. Lorsqu’un paramètre de configuration est définit dans le framework. De la même façon, il est possible d’accéder à une variable d’environnement via la syntaxe &lt;code&gt;%env(MA_VARIABLE)%&lt;/code&gt; (comme dans le fichier Doctrine pour accéder à la chaine de connexion à la base de données).&lt;/p&gt;

&lt;p&gt;Notons également la présence du paramètre &lt;code&gt;env(DATABASE_URL)&lt;/code&gt; permettant de définir le paramètre contenant la chaîne de connexion à notre base de données dans le cas où la variable d’environnement n’existerait pas.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Si vous analysez l’arborescence des fichiers, vous constaterez qu’il existe deux fichiers de configuration Doctrine : &lt;code&gt;config/packages/doctrine.yaml&lt;/code&gt; et &lt;code&gt;config/packages/prod/doctrine.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Le premier est un fichier de configuration commun à tous les environnements. Il est ensuite possible de définir une configuration spécifique pour un environnement (défini par la variable &lt;code&gt;APP_ENV&lt;/code&gt; du fichier &lt;code&gt;.env&lt;/code&gt;). Pour cela, il suffit de déposer le fichier de configuration dans un sous-dossier portant le nom de l’environnement pour lequel on souhaite surcharger la configuration et le framework le prendra automatiquement en compte.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nous n’irons pas plus loin dans la configuration de Doctrine. Si vous après ce tutorial, vous souhaitez en savoir plus, je vous conseille de consulter la &lt;a href=&quot;http://symfony.com/doc/current/doctrine.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;documentation officielle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Créons maintenant les classes associées à notre modèle de données. Elles vont représenter les informations de la base de données sous la forme d’objets PHP (ces derniers sont appelés des entités) que l’on va pouvoir manipuler dans notre code. Lors de l’installation de Doctrine, Flex a ajouté un dossier &lt;code&gt;src/Entity&lt;/code&gt; dans lequel nous allons créer nos classes.&lt;/p&gt;

&lt;p&gt;Les entités sont de simples objets PHP dont les propriétés vont correspondre aux champs de notre base de données. Commençons par la table la plus simple, la table &lt;code&gt;category&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Entity/Category.php

namespace App\Entity;

class Category
{
    private $id;
    private $name;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Maintenant passons à la table &lt;code&gt;job&lt;/code&gt;. Un emploi étant lié à une catégorie, notre table contient une clé étrangère vers la catégorie associée. Dans notre entité, cette information va se modéliser sous la forme d’une propriété dont la valeur sera une instance de l’entité associée à la table catégorie (donc un objet &lt;code&gt;Category&lt;/code&gt;).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Entity/Job.php

namespace App\Entity;

class Job
{
    private $id;
    private $category; // instance de Category
    private $type;
    private $company;
    private $logo;
    private $url;
    private $position;
    private $location;
    private $description;
    private $howToApply;
    private $token;
    private $isPublic;
    private $isActivated;
    private $email;
    private $expiresAt;
    private $createdAt;
    private $updatedAt;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;La table qui va gérer les informations d’affiliation est un peu plus complexe. Dans notre modèle, une société peut-être être affiliée à plusieurs catégories et une catégorie peut avoir des affiliations de plusieurs sociétés. Avec une base de données relationnelle, cela se traduit par la création d’une table d’association pour gérer cette information (il s’agit de la table &lt;code&gt;CategoryAffiliate&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Puisque nous avons dit qu’une table de notre base de données correspondait à un objet PHP, il devrait donc être nécessaire de créer deux nouveaux objets pour gérer cette relation. Mais en réalité cette table ne sert qu’à modéliser le fait qu’un objet &lt;code&gt;Affialite&lt;/code&gt; est rattaché à plusieurs objets &lt;code&gt;Category&lt;/code&gt; et vice-versa. Donc d’un point de vue programmation, un objet &lt;code&gt;Affiliate&lt;/code&gt; devrait avoir une propriété &lt;code&gt;$categories&lt;/code&gt; qui correspond à un tableau d’objet &lt;code&gt;Affiliate&lt;/code&gt; et l’entité &lt;code&gt;Category&lt;/code&gt; une propriété &lt;code&gt;$affiliates&lt;/code&gt; correspondant un tableau d’objet &lt;code&gt;Affiliate&lt;/code&gt;.
 Notre ORM est tout à fait capable de gérer cette problématique. Nous allons donc créer notre objet &lt;code&gt;Affiliate&lt;/code&gt; avec une propriété &lt;code&gt;$categories&lt;/code&gt; que nous ferons correspondre à un tableau d’objet &lt;code&gt;Category&lt;/code&gt;. Doctrine gérera de manière automatique et transparente notre table d’association.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;S’il avait été nécessaire de gérer des informations additionnelles, telles que par exemple la date de création de l’affiliation ou l’utilisateur ayant créé l’affiliation, il aurait été nécessaire de créer une entité supplémentaire et de gérer la relation manuellement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Entity/Affiliate.php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;

class Affiliate
{
    private $id;
    private $categories; // tableau d&amp;#39;objet Category
    private $url;
    private $email;
    private $token;
    private $isActive;
    private $createdAt;

    public function __construct()
    {
        $this-&amp;gt;categories = new ArrayCollection();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Lors de la mise en place d’une relation où l’on va gérer un tableau d’objet, il est nécessaire d’initialiser la propriété en question avec une collection vide. Pour Doctrine, cela passe par la création d’un objet &lt;code&gt;ArrayCollection&lt;/code&gt; comme dans l’exemple précédent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;N’oublions pas de modifier notre objet &lt;code&gt;Category&lt;/code&gt; pour y ajouter la propriété correspondant à nos objets &lt;code&gt;Affialite&lt;/code&gt;. Une catégorie étant également associée à plusieurs emplois, nous allons en profiter pour y ajouter la propriété correspondante.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php // src/Entity/Category.php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;

class Category
{
    private $id;
    private $name;
    private $jobs; // tableau d&amp;#39;objet Job
    private $affiliates; // tableau d&amp;#39;objet Affiliate

    public function __construct()
    {
        $this-&amp;gt;jobs = new ArrayCollection();
        $this-&amp;gt;affiliates = new ArrayCollection();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il est maintenant temps d’indiquer à Doctrine comment l’ORM va pouvoir faire le lien entre nos entités et les tables de la base de données. Pour cela, et comme nous l’avons spécifié précédemment, nous allons placer des fichiers de configuration dans le dossier &lt;code&gt;config/doctrine/mapping&lt;/code&gt;. Tout comme pour l’écriture des classes, nous allons créer un fichier de configuration par entité en suivant la convention &lt;code&gt;NomDeLaClasse.orm.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Les fichiers de configuration vont permettre d’indiquer à quelle table correspondent une entité et les différentes caractéristiques de nos propriétés (colonne de rattachement, type de données, contraintes d’intégrité, ….).&lt;/p&gt;

&lt;p&gt;Commençons par la configuration de notre entité &lt;code&gt;Category&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/doctrine/mapping/Category.orm.yml
App\Entity\Category:
    type: entity

    # clé(s) primaire(s)
    id:
        id:
            type: integer
            generator:
                strategy: AUTO

    # colonne(s) de la table
    fields:
        name:
            type: string
            length: 63

    # relation de type un vers plusieurs
    oneToMany:
        jobs:
            targetEntity: Job
            mappedBy: category

    # relation de type plusieurs vers plusieurs
    manyToMany:
        affiliates:
            targetEntity: Affiliate
            inversedBy: categories&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Comme vous pouvez le constater, les propriétés de correspondant à des relations sont dissociés du reste des propriétés. On distingue quatre types de relation, &lt;a href=&quot;https://goo.gl/ExSdg4 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;One-To-Many&lt;/code&gt;&lt;/a&gt; (relation de type un vers plusieurs), &lt;a href=&quot;https://goo.gl/tgffTs &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;Many-To-One&lt;/code&gt;&lt;/a&gt; (plusieurs vers un), &lt;a href=&quot;https://goo.gl/WBDHLm &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;Many-To-Many&lt;/code&gt;&lt;/a&gt; (relation de plusieurs à plusieurs) et &lt;a href=&quot;https://goo.gl/NA7LFn &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;One-To-One&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Voici la configuration de l’entité &lt;code&gt;Job&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/doctrine/mapping/Job.orm.yml
App\Entity\Job:
    type: entity

    id:
        id:
            type: integer
            generator:
                strategy: AUTO

    fields:
        type:
            type: string
            length: 255
            nullable: true

        company:
            type: string
            length: 255

        logo:
            type: string
            length: 255
            nullable: true

        url:
            type: string
            length: 255
            nullable: true

        position:
            type: string
            length: 255

        location:
            type: string
            length: 255

        description:
            type: text

        howToApply:
            type: text

        token:
            type: string
            length: 255
            unique: true

        isPublic:
            type: boolean
            default: false

        isActivated:
            type: boolean
            default: true

        email:
            type: string
            length: 255

        expiresAt:
            type: datetime

        createdAt:
            type: datetime

        updatedAt:
            type: datetime
            nullable: true

    manyToOne:
        category:
            targetEntity: Category
            inversedBy: jobs
            joinColumn:
                name: category_id
                referencedColumnName: id&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et pour finir le mapping correspondant à l’entité &lt;code&gt;Affiliate&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/doctrine/mapping/Affiliate.orm.yml
App\Entity\Affiliate:
    type: entity

    id:
        id:
            type: integer
            generator:
                strategy: AUTO

    fields:
        url:
            type: string
            length: 255

        email:
            type: string
            length: 255
            unique: true

        token:
            type: string
            length: 255
            unique: true

        createdAt:
            type: datetime

    manyToMany:
        categories:
            targetEntity: Category
            mappedBy: affiliates&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Lorsque nous allons enregistrer un emploi ou une affiliation, nous souhaiterions connaître la date de création et/ou de modification de la donnée écrite. Les entités correspondantes possèdent une propriété &lt;code&gt;createdAt&lt;/code&gt; et/ou &lt;code&gt;updatedAt&lt;/code&gt;. Plutôt que de devoir gérer manuellement cette information, nous allons déléguer ce travail à Doctrine.&lt;/p&gt;

&lt;p&gt;En effet l’ORM possède un gestionnaire d’événement sur lequel nous allons nous brancher afin d’être notifié lors de l’enregistrement et la modification d’une entité. Nous allons donc ajouter cette configuration au mapping de nos entités afin d’indiquer la méthode de l’objet qui sera appelée lors de la propagation de l’événement.&lt;/p&gt;

&lt;p&gt;Pour l’entité &lt;code&gt;Job&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/doctrine/mapping/Job.orm.yml
App\Entity\Job:
    # ...

    lifecycleCallbacks:
        prePersist: [ setCreatedAtValue ] # appelé lors de la création de l&amp;#39;entité
        preUpdate:  [ setUpdatedAtValue ] # appelé lors de la modification de l&amp;#39;entité&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour l’entité &lt;code&gt;Affiliate&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# config/doctrine/mapping/Affiliate.orm.yml
App\Entity\Affiliate:
    # ...

    lifecycleCallbacks:
        prePersist: [ setCreatedAtValue ]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne faudra pas oublier d’ajouter les méthodes correspondantes dans les classes associées. Ces dernières sont appelées avec un paramètre de type &lt;code&gt;LifecycleEventArgs&lt;/code&gt; contenant un certain nombre d’informations sur le contexte d’exécution de l’ORM.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// src/Entity/Job.php
// ...

use Doctrine\Common\Persistence\Event\LifecycleEventArgs;

class Job
{
    // ...

    public function setCreatedAtValue(LifecycleEventArgs $event)
    {
        $this-&amp;gt;createdAt = new DateTime();
    }

    public function setUpdatedAtValue(LifecycleEventArgs $event)
    {
        $this-&amp;gt;updatedAt = new DateTime();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour l’entité &lt;code&gt;Affiliate&lt;/code&gt;, nous souhaitons connaître uniquement la date de
création de la donnée.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// src/Entity/Affiliate.php
// ...

use Doctrine\Common\Persistence\Event\LifecycleEventArgs;

class Affiliate
{
    // ...

    public function setCreatedAtValue(LifecycleEventArgs $event)
    {
        $this-&amp;gt;createdAt = new DateTime();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Signalons l’existence d’un bundle &lt;a href=&quot;https://goo.gl/EZWK72 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;StofDoctrineExtensionBundle&lt;/code&gt;&lt;/a&gt; contenant un ensemble d’extensions Doctrine pouvant être ajoutées à nos entités et possédant entre autres, une extension &lt;code&gt;Timestampable&lt;/code&gt;. Cette dernière permet de gérer de manière automatique les dates de création et de modification d’une entité sans avoir à ajouter manuellement les propriétés correspondantes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maintenant que nous avons indiqué à notre projet comment se connecter à notre base de données, créé nos entités et indiqué la configuration nécessaire à la liaison entre nos objets et le contenu de notre base, nous allons pouvoir initialiser cette dernière. Doctrine va encore nous faciliter le travail dans cette tâche car l’ORM est distribué avec des commandes qui vont nous assister dans ce travail.&lt;/p&gt;

&lt;p&gt;Dans un terminal, nous allons exécuter les commandes suivantes :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ bin/console doctrine:database:create # pour créer la base de données
Created database var/jobeet.db for connection named default


$ bin/console doctrine:schema:create # pour créer la structure des tables
ATTENTION: This operation should not be executed in a production environment.

Creating database schema...
Database schema created successfully!&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Vous pouvez constater que la base de données a été correctement initialisée en ouvrant le fichier contenant les données SQLite qui a été créé (&lt;code&gt;var/jobeet.db&lt;/code&gt;) avec un outil tel que &lt;a href=&quot;http://sqlitebrowser.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;DB Browser for SQLite&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Voilà qui conclut notre section d’introduction au modèle de données. Nous avons maintenant une base de données (presque) prête à être utilisée et qui n’attend plus que nos données.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Retrouvez tous les tutorials Jobeet disponibles depuis le &lt;a href=&quot;/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html&quot;&gt;billet d’introduction de la série&lt;/a&gt;. Le code source de cette application est également disponible sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/03a-modele-donnees &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;. Vous trouverez une branche associée à l’état du projet après chaque chapitre.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Wed, 20 Sep 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/09/20/tutorial-jobeet-symfony-4-partie-3a-le-modele-de-donnees.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/09/20/tutorial-jobeet-symfony-4-partie-3a-le-modele-de-donnees.html</guid>
                </item>
            
        
            
                270
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Partie 2: Le projet</title>
                    <description>&lt;p&gt;Nous avons précédemment créé la structure de notre projet Symfony et préparé notre environnement de développement. Nous allons très prochainement pouvoir commencer à écrire nos premières lignes de code. Mais avant cela, attardons-nous un peu sur les exigences de notre projet.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ce billet est principalement d’une recopie des exigences fonctionnelles du &lt;a href=&quot;https://symfony.com/legacy/doc/jobeet/1_4/fr/02?orm=Doctrine &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;tutorial initial&lt;/a&gt;. Je vous recommande vivement de lire ce dernier car des explications sur les nouvelles pratiques liées à Symfony 4 y sont décrites.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jobeet est un logiciel open-source de recherche d’emploi qui ne fait qu’une seule chose, mais le fait bien. Il est facile d’utilisation, à adapter, à faire évoluer, et à intégrer à votre site internet. Il est multi langues dès le départ, et bien sûr il utilise les dernières technologies du web 2.0 pour améliorer l’expérience utilisateur. Il fournit également des feeds et une API pour interagir avec lui en programmant.&lt;/p&gt;

&lt;p&gt;Le site web Jobeet a quatre types d’utilisateurs :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;L’administrateur : il est propriétaire du site et il a le pouvoir magique&lt;/li&gt;
  &lt;li&gt;L’utilisateur : il visite le site pour chercher un emploi&lt;/li&gt;
  &lt;li&gt;L’annonceur : il soumet une offre d’emploi&lt;/li&gt;
  &lt;li&gt;L’affilié : il re-édite certains emplois sur son site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous allons maintenant découper le projet en scénarios d’utilisation. L’application est séparée en deux interfaces : le frontend (où les utilisateurs interagissent avec le site), et le backend (où les administrateurs gèrent le site).&lt;/p&gt;

&lt;p&gt;Symfony recommande maintenant et générera dorénavant des applications orientées bundle-less. Si vous débutez avec Symfony, un &lt;a href=&quot;https://symfony.com/doc/current/bundles.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;bundle&lt;/a&gt; peut être considéré comme un “plugin”. Ils permettent de regrouper du code qui est regroupé par fonctionnalité et redistribuable dans n’importe quel projet Symfony.&lt;/p&gt;

&lt;p&gt;Nous allons maintenant lister les exigences fonctionnelles de notre application. Ces dernières seront numérotés et débuteront par un &lt;code&gt;F&lt;/code&gt; pour les aspects frontend et par un &lt;code&gt;B&lt;/code&gt; pour les aspects backend.&lt;/p&gt;

&lt;h3 id=&quot;f1-en-tant-quutilisateur-je-vois-les-dernières-offres-actives-sur-la-page-daccueil&quot;&gt;F1: En tant qu’utilisateur, je vois les dernières offres actives sur la page d’accueil&lt;/h3&gt;

&lt;p&gt;Quand un utilisateur vient sur le site Jobeet, il voit une liste des emplois actifs. Les emplois sont classés par catégorie, puis par date de publication (emplois plus récents en premier). Pour chaque emploi, seul le lieu, la position, et la société sont affichés.&lt;/p&gt;

&lt;p&gt;Pour chaque catégorie, la liste ne montre que les 10 premiers emplois et un lien permet de lister tous les emplois pour une catégorie donnée (scénario F2).&lt;/p&gt;

&lt;p&gt;Sur la page d’accueil , l’utilisateur peut affiner la liste des travaux (scénario F3), ou par la soumission d’un nouvel emploi (scénario F5).&lt;/p&gt;

&lt;h3 id=&quot;f2-en-tant-quutilisateur-je-peux-lister-les-emplois-dune-catégorie&quot;&gt;F2: En tant qu’utilisateur, je peux lister les emplois d’une catégorie&lt;/h3&gt;

&lt;p&gt;Quand un utilisateur clique sur un nom de catégorie ou sur le lien “more jobs” sur la page d’accueil, il voit tous les emplois pour cette catégorie triée par date.&lt;/p&gt;

&lt;p&gt;La liste est paginée avec 20 emplois par page.&lt;/p&gt;

&lt;h3 id=&quot;f3-en-tant-quutilisateur-je-peux-affiner-les-offres-dune-catégorie&quot;&gt;F3: En tant qu’utilisateur, je peux affiner les offres d’une catégorie&lt;/h3&gt;

&lt;p&gt;L’utilisateur peut saisir quelques mots-clés pour affiner sa recherche. Les mots-clés peuvent être des mots trouvés dans l’emplacement, la position, la catégorie, ou les champs de l’entreprise.&lt;/p&gt;

&lt;h3 id=&quot;f4-en-tant-quutilisateur-je-peux-visualiser-le-détail-dune-offre&quot;&gt;F4: En tant qu’utilisateur, je peux visualiser le détail d’une offre&lt;/h3&gt;

&lt;p&gt;L’utilisateur peut sélectionner un emploi dans la liste pour afficher des informations plus détaillées.&lt;/p&gt;

&lt;h3 id=&quot;f5-en-tant-quutilisateur-je-peux-soumettre-une-offre-demploi&quot;&gt;F5: En tant qu’utilisateur, je peux soumettre une offre d’emploi&lt;/h3&gt;

&lt;p&gt;Un utilisateur peut soumettre un emploi (il n’est pas nécessaire de devoir créer un compte). Un emploi est composé de plusieurs informations.&lt;/p&gt;

&lt;p&gt;Le processus est simple, avec seulement deux étapes : d’abord, l’utilisateur remplit dans le formulaire toutes les informations nécessaires pour décrire l’emploi, puis il valide les informations en visualisant la page finale de l’emploi.&lt;/p&gt;

&lt;p&gt;Même si l’utilisateur n’a pas de compte, un emploi peut être modifié ultérieurement, grâce à une URL spécifique (protégé par un jeton donné à l’utilisateur lorsque l’emploi est créé).&lt;/p&gt;

&lt;p&gt;Chaque poste de travail est en ligne pendant 30 jours (ce qui est configurable par l’administrateur - voir Histoire B2). Un utilisateur peut revenir réactiver ou prolonger la validité de l’annonce pour un supplément de 30 jours, mais seulement lorsque le travail expire dans moins de 5 jours.&lt;/p&gt;

&lt;h3 id=&quot;f6-en-tant-quutilisateur-je-peux-affilier-ma-société&quot;&gt;F6: En tant qu’utilisateur, je peux affilier ma société&lt;/h3&gt;

&lt;p&gt;Un utilisateur doit demander à devenir un affilié et être autorisés à utiliser l’API Jobeet. Pour postuler, il doit donner des informations.&lt;/p&gt;

&lt;p&gt;Le compte d’ un affilié doit être activé par l’administrateur (scénario B3). Une fois activé, l’affilié reçoit un jeton pour une utilisation avec l’API par email.&lt;/p&gt;

&lt;p&gt;Lors de l’application, l’affilié peut également choisir d’obtenir des emplois auprès d’un sous-ensemble des catégories disponibles.&lt;/p&gt;

&lt;h3 id=&quot;f7-en-tant-quutilisateur-je-peux-exporter--la-liste-des-emplois-actifs&quot;&gt;F7: En tant qu’utilisateur, je peux exporter  la liste des emplois actifs&lt;/h3&gt;

&lt;p&gt;Un affilié peut récupérer la liste des emplois en cours en appelant l’API avec le jeton de sa filiale. La liste peut être retournée en XML, JSON ou YAML.&lt;/p&gt;

&lt;p&gt;La liste contient les informations publiques disponibles pour un emploi.&lt;/p&gt;

&lt;p&gt;L’affilié peut également limiter le nombre d’emplois qui seront retournés, et affiner sa requête en spécifiant une catégorie.&lt;/p&gt;

&lt;h3 id=&quot;b1-en-tant-quadministrateur-je-peux-configurer-le-site&quot;&gt;B1: En tant qu’administrateur, je peux configurer le site&lt;/h3&gt;

&lt;p&gt;L’administrateur peut modifier les catégories disponibles sur le site.&lt;/p&gt;

&lt;h3 id=&quot;b2-en-tant-quadministrateur-je-peux-administrer-les-offres-demploi&quot;&gt;B2: En tant qu’administrateur, je peux administrer les offres d’emploi&lt;/h3&gt;

&lt;p&gt;Un administrateur peut modifier et supprimer tous les emplois affichés.&lt;/p&gt;

&lt;h3 id=&quot;b3-en-tant-quadministrateur-je-peux-gérer-les-affiliés&quot;&gt;B3: En tant qu’administrateur, je peux gérer les affiliés&lt;/h3&gt;

&lt;p&gt;L’administrateur peut créer ou modifier des affiliés. Il est responsable de l’activation d’un affilié et peut également les désactiver.&lt;/p&gt;

&lt;p&gt;Lorsque l’administrateur active une nouvelle filiale, le système crée un jeton unique à utiliser par la filiale.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Retrouvez tous les tutorials Jobeet disponibles depuis le &lt;a href=&quot;/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html&quot;&gt;billet d’introduction de la série&lt;/a&gt;. Le code source de cette application est également disponible sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/02-projet &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;. Vous trouverez une branche associée à l’état du projet après chaque chapitre.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Tue, 19 Sep 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/09/19/tutorial-jobeet-symfony-4-partie-2-le-projet.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/09/19/tutorial-jobeet-symfony-4-partie-2-le-projet.html</guid>
                </item>
            
        
            
        
            
                271
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Partie 1: Démarrage du projet</title>
                    <description>&lt;p&gt;Est-il encore nécessaire de présenter Symfony qui est l’un des frameworks PHP les plus populaires ? Dans cette première partie, nous allons créer la structure de notre application et voir comment s’organise un projet Symfony.&lt;/p&gt;

&lt;p&gt;Démarrons tout d’abord avec les prérequis nécessaires à la mise en place de notre projet. Concernant la version de PHP que vous allez devoir utiliser, un PHP 7.1.3 sera au minimum nécessaire. Dans cette série de billets, nous partirons du principe que vous avez installé les extensions permettant d’accéder à une base de données telle que MySQL ou PostgreSQL (via l’extension PDO et les drivers associés). Le module XML de PHP (alias &lt;code&gt;php-xml&lt;/code&gt;) est également requis. Pour finir, nous supposerons également, que &lt;a href=&quot;https://getcomposer.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Composer&lt;/a&gt;, l’outil de gestion des dépendances PHP est installé sur votre machine.&lt;/p&gt;

&lt;p&gt;Jusqu’à la version 4 de Symfony, la création d’un nouveau projet se faisait au travers d’un outil d’installation spécifique nommé &lt;a href=&quot;https://github.com/symfony/symfony-installer &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Symfony installer&lt;/a&gt;. Ce dernier outil est maintenant remplacé par Composer qui possède une commande &lt;a href=&quot;https://getcomposer.org/doc/03-cli.md#create-project &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;create-project&lt;/a&gt; permettant de créer un nouveau projet PHP. Cette commande est ainsi indépendante du framework. Les équipes de Symfony ont souhaité standardiser la manière de démarrer un projet PHP sans devoir y ajouter des outils spécifiques. La création de notre projet Jobeet se fera ainsi au travers de la commande : &lt;code&gt;composer create-project symfony/skeleton jobeet 4.0.0-RC1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Composer va alors créer un dossier pour notre projet en se basant sur le &lt;a href=&quot;https://github.com/symfony/skeleton &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;modèle de projet Symfony&lt;/a&gt; et télécharger les dépendances associées. Dans sa nouvelle version, le framework fournit dorénavant un composant nommé &lt;a href=&quot;https://github.com/symfony/flex &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Flex&lt;/a&gt;, une surcouche à Composer et qui permet entre autres de configurer automatiquement les dépendances que vous installez dans votre application.&lt;/p&gt;

&lt;p&gt;Analysons la structure de notre projet vide :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;jobeet
├── .env
├── .env.dist
├── composer.json
├── composer.lock
├── config
│   ├── bundles.php
│   ├── packages
│   │   ├── dev
│   │   │   └── routing.yaml
│   │   ├── framework.yaml
│   │   ├── routing.yaml
│   │   └── test
│   │       └── framework.yaml
│   ├── routes.yaml
│   └── services.yaml
├── public
│   └── index.php
├── src
│   ├── Controller
│   └── Kernel.php
└── vendor&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Contrairement aux versions précédentes, le framework a changé l’arborescence de ces fichiers afin de se rapprocher d’une arborescence de fichiers que l’on pourrait retrouver sur des systèmes Unix. On retrouve ainsi les éléments ci-dessous :&lt;/p&gt;

&lt;div class=&quot;row&quot;&gt;
  &lt;div class=&quot;col-2&quot;&gt;&lt;code&gt;config&lt;/code&gt;&lt;/div&gt;
  &lt;div class=&quot;col-10&quot;&gt;Dossier contenant les fichiers de configuration de notre projet&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;row&quot;&gt;
  &lt;div class=&quot;col-2&quot;&gt;&lt;code&gt;public&lt;/code&gt;&lt;/div&gt;
  &lt;div class=&quot;col-10&quot;&gt;Le répertoire contenant les fichiers désservis par les serveurs HTTP&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;row&quot;&gt;
  &lt;div class=&quot;col-2&quot;&gt;&lt;code&gt;src&lt;/code&gt;&lt;/div&gt;
  &lt;div class=&quot;col-10&quot;&gt;Accueillera les fichiers sources PHP contenant toute la logique de notre projet&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;row&quot;&gt;
  &lt;div class=&quot;col-2&quot;&gt;&lt;code&gt;vendor&lt;/code&gt;&lt;/div&gt;
  &lt;div class=&quot;col-10&quot;&gt;Le dossier utilisé par Composer et contenant les dépendances (décrites dans le fichier &lt;code&gt;composer.json&lt;/code&gt;) nécessaires au fonctionnement de notre projet&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;row&quot;&gt;
  &lt;div class=&quot;col-2&quot;&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/div&gt;
  &lt;div class=&quot;col-10&quot;&gt;Fichier contenant la configuration de l&apos;environnement d&apos;exécution de notre code&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;row&quot;&gt;
  &lt;div class=&quot;col-2&quot; style=&quot;text-decoration: line-through;&quot;&gt;&lt;code&gt;Makefile&lt;/code&gt;&lt;/div&gt;
  &lt;div class=&quot;col-10&quot; style=&quot;text-decoration: line-through;&quot;&gt;Décrit les tâches pouvant être exécutées par le framework&lt;/div&gt;
&lt;/div&gt;

&lt;p style=&quot;text-decoration: line-through;&quot;&gt;Même si nous n&apos;avons encore aucune ligne de code ni même de configuration (en réalité Flex s&apos;en est chargé pour nous), nous pouvons démarrer notre application Symfony. Il est possible de démarrer un serveur HTTP au travers de la commande `make serve` (si vous ne connaissez pas l&apos;outil &lt;a href=&quot;https://www.gnu.org/software/make/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Make&lt;/a&gt;, je vous recommande vivement de vous renseigner sur ce dernier).&lt;/p&gt;

&lt;p&gt;Même sans configuration particulière, nous pouvons démarrer notre application grâce au serveur Web embarqué dans PHP au travers de la commande &lt;code&gt;php -S localhost:8000 -t public/&lt;/code&gt;.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20170913-tutorial-jobeet-symfony-4-partie-1-demarrage-projet/jobeet-serve.png &quot; /&gt;&lt;/center&gt;

&lt;p&gt;Par défaut, le framework utilise le serveur Web embarqué avec PHP (ce qui peut être suffisant pour des besoins de développement). Symfony fournit également un bundle &lt;code&gt;symfony/web-server-bundle&lt;/code&gt; (nous reviendrons plus tard sur la notion de bundle) permettant d’apporter une meilleure expérience développeur pour manipuler ce dernier.&lt;/p&gt;

&lt;p&gt;Une fois le serveur démarré, nous pouvons nous connecter sur notre projet en ouvrant un navigateur et en tapant l’URL &lt;code&gt;http://localhost:8000&lt;/code&gt;. Bien entendu, comme nous n’avons encore rien fait, une page d’erreur sera affichée.&lt;/p&gt;

&lt;center&gt;&lt;img src=&quot;/img/blog/20170913-tutorial-jobeet-symfony-4-partie-1-demarrage-projet/jobeet-homepage.png &quot; style=&quot;width: 100%; height: 100%;&quot; /&gt;&lt;/center&gt;

&lt;p&gt;Attardons-nous maintenant le contenu du fichier &lt;code&gt;.env&lt;/code&gt;. Si vous regardez le contenu de ce dernier, vous constaterez qu’il contient un certain nombre de paramètres :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;# This file is a &amp;quot;template&amp;quot; of which env vars needs to be defined in your configuration or in a .env file
# Set variables here that may be different on each deployment target of the app, e.g. development, staging, production.
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration

###&amp;gt; symfony/framework-bundle ###
APP_ENV=dev
APP_DEBUG=1
APP_SECRET=f78d2a48cbd00d92acf418a47a0a5c3e
###&amp;lt; symfony/framework-bundle ###&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notons tout d’abord les commentaires &lt;code&gt;###&amp;gt; symfony/framework-bundle ###&lt;/code&gt; et &lt;code&gt;###&amp;lt; symfony/framework-bundle ###&lt;/code&gt; présent dans le fichier. Ces derniers servent de balise de début et de fin à Flex pour ajouter et supprimer automatiquement la configuration de nos dépendances lors de leurs ajouts ou suppressions dans notre projet.&lt;/p&gt;

&lt;p&gt;Viennent ensuite les différentes variables d’environnement d’exécution de notre application :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;La variable &lt;code&gt;APP_ENV&lt;/code&gt; permet de définir l’environnement courant d’exécution du projet. Généralement 3 environnements sont utilisés : &lt;code&gt;prod&lt;/code&gt; (l’environnement où interagissent les utilisateurs finaux), &lt;code&gt;dev&lt;/code&gt; (l’environnement utilisé pendant les développements) et &lt;code&gt;test&lt;/code&gt; (l’environnement utilisé pour tester automatiquement notre projet).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;La variable &lt;code&gt;APP_DEBUG&lt;/code&gt; de Symfony fournit un ensemble d’outils permettant de faciliter la résolution des bugs pouvant survenir lors du développement. Par exemple, c’est cette dernière qui génère l’affichage de la pile d’appel des fonctions du framework lorsqu’une erreur se produit (comme lorsque nous avons tenté d’accéder à l’application via le navigateur).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Un des principaux avantages d’avoir recours à un framework est d’utiliser un ensemble de composants réutilisables, régulièrement mis à jour et utilisant des bonnes pratiques de développement. Cela permet au framework de guider le développeur et de minimiser les erreurs pouvant faire apparaître des failles de sécurité. La variable &lt;code&gt;APP_SECRET&lt;/code&gt; est ainsi utilisée par certains composants en tant que clé secrète pour générer des jetons de sécurité (comme par exemple lors de la mise en place de formulaire avec les jetons &lt;a href=&quot;https://fr.wikidia.org/wiki/Cross-Site_Request_Forgery &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;CSRF&lt;/a&gt;).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;C’est sur ce point que nous allons conclure ce premier chapitre. À ce stade, nous avons créé notre projet Symfony et rapidement fait le tour de l’organisation des fichiers de notre application. Notre environnement est maintenant prêt et nous allons prochainement pouvoir commencer à développer.&lt;/p&gt;

&lt;p&gt;La section suivante dévoilera les fonctionnalités de notre application et détaillera les besoins fonctionnels et techniques à satisfaire.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Retrouvez tous les tutorials Jobeet disponibles depuis le &lt;a href=&quot;/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html&quot;&gt;billet d’introduction de la série&lt;/a&gt;. Le code source de cette application est également disponible sur &lt;a href=&quot;https://github.com/jdecool/jobeet/tree/01-demarrage-projet &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;. Vous trouverez une branche associée à l’état du projet après chaque chapitre.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Wed, 13 Sep 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/09/13/tutorial-jobeet-symfony-4-partie-1-demarrage-projet.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/09/13/tutorial-jobeet-symfony-4-partie-1-demarrage-projet.html</guid>
                </item>
            
        
            
                272
                <item>
                    <title>Tutorial Jobeet pour Symfony 4 - Introduction</title>
                    <description>&lt;p&gt;J’ai récemment eu l’occasion de relire des articles concernant &lt;em&gt;Jobeet&lt;/em&gt;, un tutorial pour concevoir une application de type “job board” écrit par l’équipe du framework Symfony (la première version à l’époque) afin de se familiariser avec l’outil. Ce dernier avait par la suite été adapté pour Symfony 2.8 par &lt;a href=&quot;https://medium.com/@dragosholban/symfony-2-8-jobeet-tutorial-3a72f67cdbd8 &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Dragos HOLBAN&lt;/a&gt; et traduit tout récemment en français par &lt;a href=&quot;http://jobeet.thuau.fr &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Jonathan THUAU&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lors de l’écriture de ce billet, la version 4 du framework est en cours de finalisation et devrait sortir dans les prochains mois (courant novembre). Cette nouvelle version apporte de nombreuses améliorations et va modifier la manière dont nous travaillons avec l’outil, notamment grâce à un nouveau composant nommé &lt;a href=&quot;https://github.com/symfony/flex &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Flex&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Quand j’ai relu le tutorial, j’ai été déçu que ce dernier ne soit pas disponible pour les dernières versions disponibles du framework. J’ai donc décidé de démarrer une série de billets consacrés à l’adaption du tutorial pour Symfony 4.&lt;/p&gt;

&lt;p&gt;Vous trouverez donc ici, au fur et à mesure de l’écriture des différents billets, les liens vers les nouveaux articles dont le sommaire se trouve ci-dessous :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/09/13/tutorial-jobeet-symfony-4-partie-1-demarrage-projet.html&quot;&gt;Tutorial Jobeet pour Symfony 4 - Partie 1: Démarrage du projet&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/09/19/tutorial-jobeet-symfony-4-partie-2-le-projet.html&quot;&gt;Tutorial Jobeet pour Symfony 4 - Partie 2: Le projet&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/09/20/tutorial-jobeet-symfony-4-partie-3a-le-modele-de-donnees.html&quot;&gt;Tutorial Jobeet pour Symfony 4 - Partie 3A: Le modèle de données&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/09/21/tutorial-jobeet-symfony-4-partie-3b-les-donnees-initiales.html&quot;&gt;Tutorial Jobeet pour Symfony 4 - Partie 3B: Les données initiales&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/09/29/tutorial-jobeet-symfony-4-partie-4a-le-controleur-et-la-vue.html&quot;&gt;Tutorial Jobeet pour Symfony 4 - Partie 4A: Le contrôleur et la vue&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/09/30/tutorial-jobeet-symfony-4-partie-4b-la-gestion-des-assets-avec-twig.html&quot;&gt;Tutorial Jobeet pour Symfony 4 - Partie 4B: La gestion des assets avec Twig&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/10/16/tutorial-jobeet-symfony-4-partie-5-les-routes.html&quot;&gt;Tutorial Jobeet pour Symfony 4 - Partie 5: Les routes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/02/24/tutorial-jobeet-symfony-4-partie-6-aller-plus-loin-avec-le-modele.html&quot;&gt;Tutorial Jobeet pour Symfony 4 - Partie 6: Aller plus loin avec le modèle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr class=&quot;alert-notice&quot; /&gt;

&lt;div class=&quot;alert alert-notice&quot;&gt;
    Par manque de temps, et du fait de nombreux changements personnels et professionnels
    je n&apos;ai pas eu l&apos;occasion de terminer la série d&apos;articles que j&apos;avais initialement
    prévus.
&lt;/div&gt;
&lt;hr class=&quot;alert-notice&quot; /&gt;

&lt;ul&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 7: Jouons avec la page catégorie&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 8: Les tests unitaires&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 9: Les tests fonctionnels&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 10: Les formulaires&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 11: Tester les formulaires&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 12: L’interface d’administration&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 13: La sécurité&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 14: Le flux de données&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 15: Fournir une API&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 16: Envoyer des emails&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 17: La recherche&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 18: L’AJAX&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 19: L’internationalization&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 20: Les bundles&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 21: Le cache&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 22: Le déploiement&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 23: Un autre regard sur Symfony&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depuis la création de ce tutorial, le framework n’a cessé d’évoluer et de nouveaux composants ont fait leurs apparitions. Je tenterai d’ajouter de nouveaux articles afin d’y introduire ces derniers :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 24: Les assets avec Webpack Encore&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 25: Gerer un workflow&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Partie 26: Plus loin avec l’injection de dépendance&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Annexe 1: Environnement Vagrant&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Annexe 2: Environnement Docker&lt;/li&gt;
  &lt;li&gt;Tutorial Jobeet pour Symfony 4 - Annexe 3: Symfony Flex&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;N’hésitez pas à me contacter via &lt;a href=&quot;https://twitter.com/jdecool &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Twitter&lt;/a&gt; ou par &lt;a href=&quot;mailto:contact@jdecool.fr&quot;&gt;mail&lt;/a&gt; pour me faire part de vos observations et remarques.&lt;/p&gt;
</description>
                    <pubDate>Tue, 12 Sep 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/09/12/tutorial-jobeet-symfony-4-introduction.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
                273
                <item>
                    <title>Les dépendances locales récursives dans un projet Composer</title>
                    <description>&lt;p&gt;Dans un précédent billet, j’ai parlé de comment il était possible de
&lt;a href=&quot;/blog/2017/06/26/gerer-les-dependances-composer-dans-un-projet-monorepo.html&quot;&gt;gérer les dépendances PHP dans un projet monorepo avec Composer&lt;/a&gt;.
Si vous avez testé la méthode décrite, vous avez peut-être rencontré des difficultés
lorsqu’une dépendance de votre projet a elle-même une dépendance dans le même dépôt.&lt;/p&gt;

&lt;p&gt;Par exemple, si je développe un site e-commerce avec un composant &lt;code&gt;Cart&lt;/code&gt; pour la
gestion du panier et que je souhaite implémenter ce dernier dans une application
Symfony. Je peux alors être tenté de créer un &lt;code&gt;CartBundle&lt;/code&gt; qui sera intégré dans
l’application principale.&lt;/p&gt;

&lt;p&gt;On aurait l’arborescence ci-dessous :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;applications/
    frontend/
        composer.json # Dépendance vers CartBundle
components/
    Cart/
        composer.json # Autonome
    CartBundle/
        composer.json # Dépendance vers Cart&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le fonctionnement de Composer dans ce cas est clairement expliqué dans
&lt;a href=&quot;https://getcomposer.org/doc/faqs/why-can%27t-composer-load-repositories-recursively.md&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;la documentation&lt;/a&gt;:
les &lt;code&gt;repositories&lt;/code&gt; ne sont pas chargés récursivement. En effet, Composer estime
que ce paramètre de configuration est exceptionnel et doit être utilisé de
manière temporaire.&lt;/p&gt;

&lt;p&gt;Pour pallier ce problème, vous devrez spécifier explicitement les sous dépendances
dans la configuration votre application principale.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;applications/
    frontend/
        composer.json # Dépendance vers CartBundle et explicite vers Cart
components/
    Cart/
        composer.json # Autonome
    CartBundle/
        composer.json # Dépendance vers Cart&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Heureusement, pour éviter d’avoir à spécifier tous les répertoires contenant nos
dépendances, Composer autorise l’utlisation de “wildcard” tel que &lt;code&gt;*&lt;/code&gt; et &lt;code&gt;?&lt;/code&gt;.
Ainsi le fichier &lt;code&gt;composer.json&lt;/code&gt; de notre application pourrait ressembler à :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &amp;quot;repositories&amp;quot;: [
    {
      &amp;quot;type&amp;quot;: &amp;quot;path&amp;quot;,
      &amp;quot;url&amp;quot;: &amp;quot;../../components/*&amp;quot;
    }
  ],
  &amp;quot;require&amp;quot;: {
      &amp;quot;myapp/Cart&amp;quot;: &amp;quot;*&amp;quot;,
      &amp;quot;myapp/CartBundle&amp;quot;: &amp;quot;*&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Thu, 13 Jul 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/07/13/les-dependances-locales-recursives-dans-un-projet-composer.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/07/13/les-dependances-locales-recursives-dans-un-projet-composer.html</guid>
                </item>
            
        
            
        
            
                274
                <item>
                    <title>Gérer les dépendances Composer dans un projet monorepo</title>
                    <description>&lt;p&gt;Mettre en place un monorepo (aka. un dépôt monolithique), c’est mettre en place
un dépôt global regroupant toutes les applications et divers composants de votre
projet. Dans ce billet, nous n’allons pas voir les avantages ou inconvénients de
ce type d’organisation, certains ont fait &lt;a href=&quot;http://danluu.com/monorepo/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;des billets sur les avantages&lt;/a&gt;,
&lt;a href=&quot;http://engineeredweb.com/blog/2016/monorepo-dangers/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;les inconvénients&lt;/a&gt; ou des
&lt;a href=&quot;https://www.youtube.com/watch?v=VBWVNIUGKW0&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;conférences&lt;/a&gt;
à ce sujet. Nous allons plutôt voir comment mettre en place une gestion de
dépendances avec Composer.&lt;/p&gt;

&lt;p&gt;Prenons par exemple, un projet ayant l’arborescence suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;applications/
    api/
        composer.json
    backend/
        composer.json
    frontend/
        composer.json
    worker/
        composer.json
component/
    package1/
        composer.json
    package2/
        composer.json&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;La problématique va être de pouvoir gérer simplement dans les différentes
applications, les dépendances présentes dans le répertoire &lt;code&gt;component&lt;/code&gt;. La
première idée qui peut venir à l’esprit pourrait être de configurer
&lt;a href=&quot;https://getcomposer.org/doc/01-basic-usage.md#autoloading&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;l’autoloading de composer&lt;/a&gt;
afin de faire référence aux différents composants déclarer dans le dépôt.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &amp;quot;autoload&amp;quot;: {
    &amp;quot;psr-4&amp;quot;: {
      &amp;quot;Vendor\\Package1\\&amp;quot;: &amp;quot;../../component/package1/src&amp;quot;,
      &amp;quot;Vendor\\Package2\\&amp;quot;: &amp;quot;../../component/package2/src&amp;quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Bien que cette méthode fonctionne, elle n’est cependant pas optimale car avec
cette dernière, le fichier &lt;code&gt;composer.json&lt;/code&gt; du composant n’est pas utilisé. Or,
ce dernier contient diverses informations sur la bibliothèque. Dans le cas présent,
nous avons dû redéfinir la configuration de l’autoloading de la bibliothèque.&lt;/p&gt;

&lt;p&gt;Composer définit le concept de
&lt;a href=&quot;https://getcomposer.org/doc/05-repositories.md#repository&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;“repositories”&lt;/a&gt;.
Globalement, un repository correspond à une source de composant. Par défaut et
sans que le développeur n’ait besoin de faire quoi que ce soit, Composer définit
Packagist comme source principale.&lt;/p&gt;

&lt;p&gt;Il est bien évidemment possible d’ajouter de nouvelles sources de &lt;code&gt;repository&lt;/code&gt; et
Composer supporte différentes sources de données telle que &lt;code&gt;github&lt;/code&gt;, &lt;code&gt;gitlab&lt;/code&gt;,
&lt;code&gt;vcs&lt;/code&gt; et bien d’autres encore. Mais celle qui va nous intéresser est &lt;code&gt;path&lt;/code&gt;.
Cette dernière va permettre de définir une source de composants correspondant à un
chemin de notre machine.&lt;/p&gt;

&lt;p&gt;Il est donc possible d’utiliser un composant en modifiant le &lt;code&gt;composer.json&lt;/code&gt; d’une
application de la manière suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &amp;quot;repositories&amp;quot;: [
    {
      &amp;quot;type&amp;quot;: &amp;quot;path&amp;quot;,
      &amp;quot;url&amp;quot;: &amp;quot;../../component/package1&amp;quot;
    },
    {
      &amp;quot;type&amp;quot;: &amp;quot;path&amp;quot;,
      &amp;quot;url&amp;quot;: &amp;quot;../../component/package2&amp;quot;
    }
  ],
  &amp;quot;require&amp;quot;: {
      &amp;quot;vendor/package1&amp;quot;: &amp;quot;*&amp;quot;,
      &amp;quot;vendor/package2&amp;quot;: &amp;quot;*&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Au travers de cette modification, il n’est plus nécessaire de redéfinir la
configuration spécifiée dans le composant, puisque Composer va charger ce
dernier comme n’importe quelle autre dépendance de votre projet.&lt;/p&gt;

&lt;p&gt;En faisant des recherches sur la meilleure manière de gérer les dépendances au
sein d’un monorepo, j’ai également découvert un projet expérimental se présentant
sous la forme d’un &lt;a href=&quot;https://github.com/beberlei/composer-monorepo-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;plugin Composer&lt;/a&gt;
dédié à la gestion de ce type d’organisation. Je n’ai pas expérimenté ce dernier
et bien qu’il ne semble plus maintenu, il peut être intéressant d’y jeter un oeil.
Son auteur a publié &lt;a href=&quot;https://beberlei.de/2016/05/28/composer_monorepo_plugin_previously_called_fiddler.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;
un article expliquant l’objectif et le fonctionnement du projet&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Mon, 26 Jun 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/06/26/gerer-les-dependances-composer-dans-un-projet-monorepo.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/06/26/gerer-les-dependances-composer-dans-un-projet-monorepo.html</guid>
                </item>
            
        
            
                275
                <item>
                    <title>Des &quot;metapackages&quot; Composer prochainement dans Symfony</title>
                    <description>&lt;p&gt;C’est en faisant un tour sur les &lt;a href=&quot;https://github.com/symfony&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;dépôts Github de Symfony&lt;/a&gt;
que j’ai découvert la création récente (à partir de la fin du mois de mai) de
“metapackage” Composer. Pour rappel, un métapaquet est une dépendance vide dont
l’objectif est de déclencher l’installation d’autres dépendances.&lt;/p&gt;

&lt;p&gt;Après une recherche rapide sur le blog ainsi que sur la documentation, je n’ai
pas trouvé la trace d’une communication officielle concernant ces nouvelles
dépendances. On peut donc lister à ce jour 4 métapaquets :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;https://github.com/symfony/annotations-pack &quot;&gt;annotations-pack&lt;/a&gt; pour la gestion des annotations&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/symfony/profiler-pack &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;profiler-pack&lt;/a&gt;  permettant d’avoir le Web profiler Symfony&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/symfony/orm-pack &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;orm-pack&lt;/a&gt; pour l’installation des modules Doctrine ORM&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/symfony/debug-pack &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;debug-pack&lt;/a&gt; pour obtenir les composants nécessaires au debug, à la mise en place de tests et à la gestion de logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La création de ces dépendances sera néanmoins pratique pour installer rapidement
des packs de fonctionnalités avec le nouveau mode de distribution de Symfony 4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt;: Suite à mon article, Kevin Dunglas a fait un tweet pour expliquer
rapidement comment ces métapaquets étaient utilisés.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;fr&quot;&gt;&lt;p lang=&quot;fr&quot; dir=&quot;ltr&quot;&gt;C&amp;#39;est utilisé par Flex en fait. C&amp;#39;est grâce à ces paquets que tu peux faire composer req api orm templating par exemple.&lt;/p&gt;&amp;mdash; Kévin Dunglas (@dunglas) &lt;a href=&quot;https://twitter.com/dunglas/status/878211902920413184&quot;&gt;23 juin 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

</description>
                    <pubDate>Fri, 23 Jun 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/06/23/des-metapackages-composer-prochainement-dans-symfony.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/06/23/des-metapackages-composer-prochainement-dans-symfony.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
                276
                <item>
                    <title>Choisissez bien les dépendances de vos projets</title>
                    <description>&lt;p&gt;La semaine dernière, une équipe à côté de laquelle je me trouvais faisait le point
sur un projet. Il y avait alors un échange concernant l’utilisation d’une dépendance
qui n’était pas compatible PHP 7, la version de PHP choisie pour démarrer ce dernier.
Il en a résulté que l’équipe a été obligé de développer le projet en 5.6, ce qui
m’a fortement fait réagir. Bien qu’il puisse y avoir des raisons qui peuvent conduire
à prendre une telle décision, mais dans le cas présent, je trouve cela aberrant.&lt;/p&gt;

&lt;p&gt;Pourquoi ? Tout d’abord parce que la dépendance en question devait être mise en place
pour intégrer Elasticsearch dans le projet. Il existe une multitude de composants
permettant cela et le client officiel est très bien fait. Je peux comprendre que
dans le cas présent, l’équipe souhaitait un bundle Symfony qui ait quelques fonctionnalités
supplémentaires, mais vu l’envergure du projet, n’était-il possible de simplement
utiliser la librairie de base ? Mais si le bundle était si important pour l’équipe,
n’était-il pas possible de faire une PR pour apporter la compatibilité sur la branche
7 de PHP et d’en faire profiter la communauté ?&lt;/p&gt;

&lt;p&gt;Mais au-delà de tout ça, essayons de se poser les bonnes questions. En effet,
&lt;a href=&quot;http://php.net/supported-versions.php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP 5.6 est en fin de vie&lt;/a&gt;.
Cette version n’a plus de support actif et reçoit donc maintenant que des correctifs
de sécurité. PHP 7.0 est sortie il y a maintenant plus de deux ans et PHP 7.1 il y a
un an. Si un composant PHP n’est pas compatible PHP 7, nous sommes alors en droit
de se demander si ce dernier est encore actif et/ou maintenu ? Est-ce vraiment judicieux
de démarrer un nouveau projet dans ces conditions ? Est-ce un pari gagnant dans le long
terme ?&lt;/p&gt;

&lt;p&gt;Pour ma part, la réponse est clairement non, surtout pour un projet qui est amené
à être créé maintenant. Il est primordial de bien choisir les dépendances que l’on
ajoute à un projet et d’avoir également une vision à long terme dans les phases de
conceptions et réflexions autour du projet.&lt;/p&gt;

&lt;p&gt;Si l’on devait conclure ce billet, et si vous n’aviez qu’une seule phrase à retenir,
ça serait :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Ne démarrez pas vos projets sur des versions de PHP déjà obsolètes et utilisez
des dépendances connues et maintenues !&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Votre équipe vous en remerciera, car comme je le tweetais cette semaine :&lt;/p&gt;

&lt;center&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;fr&quot;&gt;&lt;p lang=&quot;fr&quot; dir=&quot;ltr&quot;&gt;N&amp;#39;oubliez pas que lorsque vous prenez des raccourcis dans vos devs, vous finissez irrémédiablement par en payer les conséquences tôt ou tard&lt;/p&gt;&amp;mdash; Jérémy DECOOL (@jdecool) &lt;a href=&quot;https://twitter.com/jdecool/status/860110979987898369&quot;&gt;4 mai 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/center&gt;
</description>
                    <pubDate>Thu, 11 May 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/05/11/choisissez-bien-les-dependances-projets.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/05/11/choisissez-bien-les-dependances-projets.html</guid>
                </item>
            
        
            
                277
                <item>
                    <title>Utiliser Chrome Headless avec Behat</title>
                    <description>&lt;p&gt;Il sera désormais possible à partir de la version 59 de Chrome (la version bêta
au moment où j’écris ces lignes) en mode “headless”, c’est-à-dire sans interface
graphique. Ce mode de fonctionnement est intéressant pour les tests par exemple,
car il ne sera plus obligatoire de “piloter” Chrome via son interface graphique.&lt;/p&gt;

&lt;p&gt;Cette nouvelle fonctionnalité a provoqué un large engouement de la part de la
communauté Web. À tel point que le mainteneur de PhantomJS, un navigateur headless,
également basé sur Webkit &lt;a href=&quot;https://groups.google.com/d/msg/phantomjs/9aI5d-LDuNE/5Z3SMZrqAQAJ&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;a annoncé la fin du projet&lt;/a&gt;.
Donc si comme moi, vous utilisiez ce dernier dans vos scénarios Behat, il va être
nécessaire de changer de solution.&lt;/p&gt;

&lt;p&gt;Pour cela, rien de plus simple. Voici par exemple la configuration Behat pour
Selenium que j’utilisais avec PhantomJS.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# behat.yml
default:
    extensions:
        # ...
        Behat\MinkExtension:
            base_url: http://project.dev
            sessions:
                default:
                    selenium2:
                        wd_host: http://localhost:4444/wd/hub&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour que Selenium puisse interagir avec Chrome, nous allons devoir utiliser un
driver additionnel &lt;a href=&quot;https://sites.google.com/a/chromium.org/chromedriver/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ChromeDriver&lt;/a&gt;.
Une fois ce dernier installé, nous devrons ajouter quelques lignes de configuration
supplémentaires.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# behat.yml
default:
    extensions:
        # ...
        Behat\MinkExtension:
            base_url: http://project.dev
            sessions:
                default:
                    selenium2:
                        browser: chrome
                        wd_host: http://localhost:4444/wd/hub
                        capabilities:
                            chrome:
                                switches:
                                    - &amp;quot;--headless&amp;quot;
                                    - &amp;quot;--disable-gpu&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Parmi les changements, nous spécifions à Selenium d’utiliser Chrome en tant que
navigateur au travers de l’argument &lt;code&gt;browser&lt;/code&gt;. Puis nous spécifions des &lt;code&gt;capabilities&lt;/code&gt;,
c’est-à-dire des options permettant de personnaliser et de configurer la session
Chrome qui sera ouverte. Nous indiquons ainsi vouloir une navigateur &lt;code&gt;headless&lt;/code&gt;.
Le paramètre &lt;code&gt;--disable-gpu&lt;/code&gt; est pour le moment obligatoire.&lt;/p&gt;

&lt;p&gt;Et voilà, en ayant tout juste rajouté 5 lignes de configuration, vous avez remplacé
l’utilisation de PhantomJS par Google Chrome en mode “headless”.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://gist.github.com/jdecool/ec2dbc08e79e66d27b3d56d10ea28bf4&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Le fichier de configuration est disponible sur Gist&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Tue, 09 May 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/05/09/utiliser-chrome-headless-avec-behat.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/05/09/utiliser-chrome-headless-avec-behat.html</guid>
                </item>
            
        
            
        
            
        
            
                278
                <item>
                    <title>Utiliser l&apos;extension PHP Tideways dans Travis CI</title>
                    <description>&lt;p&gt;Tideways est une extension PHP permettant d’ajouter le support du profiling. Le profiling
est une activité qui consiste à collecter un certain nombre d’informations sur l’exécution
d’un code (PHP dans notre cas). Tideways est en réalité la continuité de l’extension
XHProf précédemment développé par Facebook, mais aujourd’hui abandonné au profit de
HHVM. Le principal avantage de cette dernière est que l’extension est compatible avec
les versions 7 de PHP.&lt;/p&gt;

&lt;p&gt;L’extension PHP n’étant pas “standard”, il n’est pas possible de l’activer directement
dans &lt;a href=&quot;https://travis-ci.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Travis CI&lt;/a&gt;.
Il sera donc nécessaire de télécharger, compiler et activer l’extension en amont de
la phase de build. Pour cela, nous allons rajouter les commandes suivantes dans notre
fichier &lt;code&gt;.travis.yml&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# ...
before_scripts:
  # ...
  - wget -Otideways-php.tar.gz https://github.com/tideways/php-profiler-extension/archive/v4.1.1.tar.gz
  - tar xvfz tideways-php.tar.gz -C /tmp
  - cd /tmp/php-profiler-extension-4.1.1
  - phpize
  - ./configure
  - make
  # ...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Les commandes ci-dessus téléchargent les sources de l’extension, les décompressent dans
le répertoire &lt;code&gt;/tmp&lt;/code&gt; et les compilent. Une fois ces étapes, il ne reste plus qu’à activer
l’extension auprès de PHP. Cela peut être fait en ajoutant une ligne dans le fichier
de configuration PHP :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;echo &amp;quot;extension=/tmp/php-profiler-extension-4.1.1/modules/tideways.so&amp;quot; &amp;gt;&amp;gt; ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Sinon il est possible si vous utilisez PHP via la ligne de commande d’activer l’extension
à la volée via l’option &lt;code&gt;-d&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;php -dextension=/tmp/php-profiler-extension-4.1.1/modules/tideways.so vendor/bin/phpunit&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Sun, 23 Apr 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/04/23/utiliser-l-extension-php-tideways-dans-travis-ci.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/04/23/utiliser-l-extension-php-tideways-dans-travis-ci.html</guid>
                </item>
            
        
            
                279
                <item>
                    <title>Ce que je retiens du SymfonyLive 2017</title>
                    <description>&lt;p&gt;J’ai eu l’occasion cette année de participer au SymfonyLive organisé par SensioLabs
à Paris. Tout comme ma dernière participation en 2015, cette année encore, j’ai
pu assister à des conférences de qualité autour de PHP et du framework Symfony.&lt;/p&gt;

&lt;center&gt;&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;fr&quot;&gt;&lt;p lang=&quot;fr&quot; dir=&quot;ltr&quot;&gt;Présent au &lt;a href=&quot;https://twitter.com/hashtag/Symfony_Live?src=hash&quot;&gt;#Symfony_Live&lt;/a&gt; pour la team &lt;a href=&quot;https://twitter.com/novaway&quot;&gt;@novaway&lt;/a&gt; avec &lt;a href=&quot;https://twitter.com/FloChntrl&quot;&gt;@FloChntrl&lt;/a&gt; &lt;a href=&quot;https://t.co/9DUFBCQ3R3&quot;&gt;pic.twitter.com/9DUFBCQ3R3&lt;/a&gt;&lt;/p&gt;&amp;mdash; Jérémy DECOOL (@jdecool) &lt;a href=&quot;https://twitter.com/jdecool/status/847346570295001088&quot;&gt;30 mars 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;&lt;/center&gt;

&lt;p&gt;Mais plus important que les différentes conférences et présentations qui sont
données tout au long de ces 2 jours, c’est l’occasion de rencontrer et de discuter
avec la communauté. Cela permet de voir les tendances émergentes ainsi que celles
qui sont considérées comme acquis auprès de tous. Cela va même au-delà de l’écosystème
Symfony.&lt;/p&gt;

&lt;p&gt;Ce que je retiens donc de cet événement :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Parmi les “buzzwords” actuels, difficile d’éviter de parler des &lt;em&gt;microservices&lt;/em&gt;.
Les acteurs majeurs du Web ont largement migré vers ce type d’architecture. Mais
les microservices ont du mal à se frayer un chemin dans l’écosystème PHP. Bien sur
ce n’est pas une réponse universelle, mais des discussions que je peux avoir avec
de nombreux développeurs, l’idée qui en ressort c’est que le concept est intéressant
mais qu’il ne s’adapte jamais réellement au projet sur lequel on travaille. Il faut
également que ce type d’architecture à un impact organisationnel sur les équipes qui
la mette en place.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;S’il y a bien un autre concept dont on entend de plus en plus parler dans l’écosystème
PHP, c’est le &lt;em&gt;Domain Driven Design (DDD)&lt;/em&gt;. Cette technique de conception n’a rien de
nouveau puisqu’elle existe depuis de nombreuses années. Mais PHP s’étant grandement
professionnalisé, les projets que nous sommes amenés à développer sont de plus en
plus complexes. Dans ce contexte, le DDD nous permet de développer des applications
proches du métier, avec une forte expressivité de ce dernier. Sur ce point, j’ai un
peu l’impression de voir ce qui s’est passé avec la notion de tests logiciels il y
a quelques années. J’ai nettement l’impression que les développeurs, même s’ils ne vont
pas migrer complètement vers ce type de conception, ces derniers vont tenter d’appliquer
les concepts les plus importants dans leurs projets.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Le dernier point que je retiens est la démocratisation des &lt;em&gt;Platform as a service (PaaS)&lt;/em&gt;.
Les problèmes de déploiement et de gestion des ressources serveurs (aka. la “scalabilité”)
est une des préoccupations principale de nos applications. Les services de PaaS
permettent de se concentrer sur le développement de notre projet en proposant de gérer
la partie hébergement. De ce fait, les PaaS sont de plus en plus utilisés et les
offres disponibles pour ce type de service sont de plus en plus nombreuses.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;Si vous êtes intéressé par l’événement, sachez qu’il existe un
&lt;a href=&quot;https://github.com/SymfonyLive/paris-2017-talks&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;dépôt Github&lt;/a&gt;
contenant la description des présentations avec les slides associées. Les présentations
ayant été filmé, les vidéos arriveront prochainement.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Sun, 02 Apr 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/04/02/ce-que-je-retiens-du-symfonylive-2017.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/04/02/ce-que-je-retiens-du-symfonylive-2017.html</guid>
                </item>
            
        
            
                280
                <item>
                    <title>L&apos;extension PHP qui permet de modifier des constantes</title>
                    <description>&lt;p&gt;Comme quoi après des années de pratique d’un langage, ce dernier peut toujours
nous surprendre. Une constante est par définition constante, sa valeur ne change pas.
Lorsque l’on en définit une en PHP via la fonction &lt;code&gt;define&lt;/code&gt;, il est donc impossible de
la changer ou de la modifier.&lt;/p&gt;

&lt;p&gt;Je viens de découvrir une extension PECL qui change tout ça ! Elle s’appelle
&lt;a href=&quot;https://pecl.php.net/package/runkit&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;runkit&lt;/a&gt;.
Cette dernière fournit un ensemble de fonction qui vous permettra entre autres,
d’ajouter, modifier et supprimer des constantes de votre code (le genre de chose
qu’on ne devrait jamais avoir à faire).&lt;/p&gt;

&lt;p&gt;Sachez en tout cas que si vous avez besoin de faire une telle chose, c’est que vous
avez un problème de conception. Et si malgré tout vous souhaitez quand même tester
l’extension, cette dernière ne semble plus maintenue et ne sera certainement pas
compatible PHP7.&lt;/p&gt;
</description>
                    <pubDate>Mon, 06 Mar 2017 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/03/06/l-extension-php-qui-permet-de-modifier-des-constantes.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/03/06/l-extension-php-qui-permet-de-modifier-des-constantes.html</guid>
                </item>
            
        
            
        
            
        
            
                281
                <item>
                    <title>Assurer la compatibilité des tests PHPUnit 6 avec les versions antérieures</title>
                    <description>&lt;p&gt;&lt;a href=&quot;https://github.com/sebastianbergmann/phpunit/releases/tag/6.0.0&quot; target=&quot;_blank&quot; rel=&quot;noopenner noreferer&quot;&gt;PHPUnit version 6&lt;/a&gt;
a été tagguée le 3 février 2017. Cette nouvelle version n’est dorénavant compatible
qu’avec PHP 7+. Un changement majeur introduit par cette nouvelle branche est le
renommage de la classe &lt;code&gt;PHPUnit_Framework_TestCase&lt;/code&gt; en &lt;code&gt;PHPUnit\Framework\TestCase&lt;/code&gt;.
Si comme moi, il vous arrive de tester une même branche de code avec plusieurs
versions de PHP, cela pourra être un problème.&lt;/p&gt;

&lt;p&gt;J’ai eu le cas cette semaine, où, sur une base de code toujours maintenu (mais
plus pour très longtemps) sur les versions PHP 5.3+. Sans rentrer dans les détails
techniques, pour simplifier l’exécution des tests, nous récupérons une version de
PHPUnit avec la définition Composer suivante : &lt;code&gt;phpunit/phpunit:@stable&lt;/code&gt;. Ce n’est
certes pas l’idéal ni une excellente pratique, mais cela fonctionne bien.&lt;/p&gt;

&lt;p&gt;Lors du passage des tests avec PHP 7, nous récupérons ainsi la version 6 de PHPUnit
qui provoque alors une erreur puisque la classe &lt;code&gt;PHPUnit_Framework_TestCase&lt;/code&gt; qui
était utilisée jusque-là n’est plus disponible.&lt;/p&gt;

&lt;p&gt;En attendant la fin du support des versions de PHP 5.x pour le code concerné,
la solution de contournement utilisé a été de définir un alias de classe dans le
“bootstrap” de PHPUnit de la manière suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// Keep PHPUnit &amp;lt; 6.0 BC
if (!class_exists(&amp;#39;PHPUnit_Framework_TestCase&amp;#39;) &amp;amp;&amp;amp; class_exists(&amp;#39;PHPUnit\Framework\TestCase&amp;#39;)) {
    class_alias(&amp;#39;PHPUnit\Framework\TestCase&amp;#39;, &amp;#39;PHPUnit_Framework_TestCase&amp;#39;);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;De ce fait, si la classe &lt;code&gt;PHPUnit_Framework_TestCase&lt;/code&gt; n’existe pas, c’est que nous
sommes certainement en train d’éxecuter la nouvelle version de PHPUnit. Et nous
définissons donc un alias vers la classe &lt;code&gt;PHPUnit\Framework\TestCase&lt;/code&gt; si cette
dernière existe.&lt;/p&gt;
</description>
                    <pubDate>Mon, 20 Feb 2017 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2017/02/20/assurer-la-compatibilite-des-tests-phpunit-6-avec-les-versions-anterieures.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2017/02/20/assurer-la-compatibilite-des-tests-phpunit-6-avec-les-versions-anterieures.html</guid>
                </item>
            
        
            
        
            
                282
                <item>
                    <title>Accèdez facilement à vos constantes Twig avec TwigConstantAccessorBundle</title>
                    <description>&lt;p&gt;C’est sans surprise que la plupart des développeurs Symfony utilisent Twig comme
moteur de templating, il s’agit en effet du moteur fourni par défaut avec le framework.
Il arrive souvent que l’on soit amené à accéder à des constantes dans nos vues.
Pour cela, Twig nous propose différentes solutions.&lt;/p&gt;

&lt;p&gt;La solution la plus évidente est certainement l’utilisation de la fonction
&lt;a href=&quot;http://twig.symfony.com/doc/functions/constant.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;constant&lt;/code&gt;&lt;/a&gt;
ainsi que du &lt;a href=&quot;http://twig.symfony.com/doc/tests/constant.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;test correspondant&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-twig&quot; data-lang=&quot;twig&quot;&gt;{{ constant(&amp;#39;Namespace\\Classname::CONSTANT_NAME&amp;#39;) }}
{{ constant(&amp;#39;RSS&amp;#39;, date) }}

{% if post.status is constant(&amp;#39;Post::PUBLISHED&amp;#39;) %} ... {% endif %}
{% if post.status is constant(&amp;#39;PUBLISHED&amp;#39;, post) %} ... {% endif %}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;La &lt;a href=&quot;http://symfony.com/doc/current/templating/global_variables.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;configuration de Symfony&lt;/a&gt;
permet également de définir un certain nombre de variables, services et constantes
pouvant être accessible directement depuis vos templates Twig.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# app/config/config.yml
twig:
    # ...
    globals:
        my_service:      &amp;quot;@my_service&amp;quot;
        my_constant_foo: foo
        my_constant_bar: bar&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une méthode moins répandue est de passer par une &lt;a href=&quot;http://twig.symfony.com/doc/advanced.html#id1&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;extension Twig&lt;/a&gt;.
Mais attention, cette possibilité est maintenant dépréciée et sera supprimée dans
la version 2 de Twig.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Project_Twig_Extension extends Twig_Extension implements Twig_Extension_GlobalsInterface
{
    public function getGlobals()
    {
        return array(
            &amp;#39;text&amp;#39; =&amp;gt; new Text(),
        );
    }

    // ...
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour réduire tout ce travail de configuration, j’ai créé &lt;a href=&quot;https://github.com/jdecool/TwigConstantAccessorBundle&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;un bundle&lt;/a&gt;
visant à simplifier l’accès aux constantes de classe dans les templates Twig. Une fois
installé, il suffit de définir les classes dont vous souhaitez exposer les constantes
et le bundle s’occupe entièrement de la gestion de la configuration.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# app/config/config.yml
twig_constant_accessor:
    classes:
        - AppBundle\Model\Foo
        - { class: &amp;#39;AppBundle\Model\Bar&amp;#39; }
        - { class: &amp;#39;AppBundle\Model\FooBar&amp;#39;, alias: &amp;#39;FooBarAlias&amp;#39; }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Avec cette configuration, vous aurez directement accès aux constantes dans vos vues
Twig :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-twig&quot; data-lang=&quot;twig&quot;&gt;{{ Foo.MY_CONSTANT }} {# access AppBundle\Model\Foo::MY_CONSTANT #}
{{ Bar.KEY }}  {# access AppBundle\Model\Bar::KEY #}

{% if &amp;#39;value&amp;#39; == FooBarAlias.MY_CONSTANT %}Test is OK{% endif %}
{# FooBarAlias is an alias for AppBundle\Model\FooBar #}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Wed, 28 Sep 2016 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2016/09/28/accedez-facilement-a-vos-constantes-dans-twig-avec-twigaccessorbundle.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2016/09/28/accedez-facilement-a-vos-constantes-dans-twig-avec-twigaccessorbundle.html</guid>
                </item>
            
        
            
        
            
        
            
                283
                <item>
                    <title>Pourquoi les développeurs n&apos;aiment pas Behat ?</title>
                    <description>&lt;p&gt;Depuis sa mise à disposition, Behat est un outil plutôt controversé de la part
des développeurs. Il y a ceux qui l’aiment et ne peuvent s’en passer (c’est mon
cas) et ceux qui bien au contraire le détestent. Pourtant Behat est un outil
formidable qui permet de faire le lien entre les tests d’acceptations et l’application
que l’on est en train de concevoir. Tout cela de manière automatisée. Dit autrement,
il va permettre de valider qu’une demande exprimée en langage métier donne le résultat
attendu. Pourtant, je rencontre très souvent des développeurs qui ne supportent
pas cet outil. J’ai tenté de comprendre pourquoi.&lt;/p&gt;

&lt;p&gt;Avant de tenter de répondre à cette question, je pense qu’il est nécessaire de
comprendre comment fonctionne l’outil. Comme évoqué précédemment, un des plus grands
intérêts de Behat est que l’on va exprimer le test en langue naturelle (peu importe
la langue utilisée). Le format d’écriture ressemble beaucoup à l’expression d’une
user-story utilisé dans la méthode SCRUM.&lt;/p&gt;

&lt;p&gt;Voici par exemple un exemple de scénario :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;# language: fr
Fonctionnalité: Avoir un compte bancaire
    Afin d&amp;#39;offrir aux utilisateurs la possibilité d&amp;#39;avoir un compte bancaire
    Etant donné que je suis inscrit
    Je dois être capable d&amp;#39;ajouter ou de retirer de l&amp;#39;argent sur mon compte

    Scénario:
        Etant donné que je suis un utilisateur connecté
        Et que j&amp;#39;ai un compte bancaire
        Et que le solde de mon compte est de &amp;quot;10&amp;quot; euros
        Quand j&amp;#39;ajoute &amp;quot;5&amp;quot; euros sur mon compte
        Alors mon solde doit être de &amp;quot;15&amp;quot; euros&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Lorsque l’on écrit nos tests Behat, nous commençons par décrire la fonctionnalité
de manière générale puis on décrit un ensemble de cas d’utilisation. Ces derniers
vont alors définir un contexte d’exécution, une série d’événements et terminer par
le résultat attendu.&lt;/p&gt;

&lt;p&gt;Les scénarios Behat peuvent ensuite être exécutés dans un ou plusieurs environnements.
Par exemple, dans le cas d’un site Web, il est tout à fait envisageable d’exécuter
le scénario dans différents navigateurs pour valider que le fonctionnement de
l’application est correct aussi bien sur Firefox que Chrome ou Internet Explorer.&lt;/p&gt;

&lt;p&gt;Une fois les scénarios écrits, il faudra alors que le développeur fasse le lien
entre les étapes des différents scénarios et les actions qui en résultent dans
l’application. Heureusement, Behat nous aide dans cette tâche en prégénérant le
squelette du code correspondant.&lt;/p&gt;

&lt;p&gt;Avec ce que nous venons dire, ne trouvez-vous pas que Behat est un outil extrêmement
intéressant ? Il nous permet :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;De valider notre compréhension du besoin client&lt;/li&gt;
  &lt;li&gt;De valider que notre application réponde correctement à ce dernier&lt;/li&gt;
  &lt;li&gt;De documenter notre application et fournit un esemble de spécifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alors pourquoi un développeur peut ne pas aimer Behat ?&lt;/p&gt;

&lt;p&gt;Comme l’explique son auteur. Avec Behat, un développeur ne teste pas son application,
mais il s’assure que son application réponde aux besoins métiers de son client.
Beaucoup d’applications répondent aux attentes des développeurs, mais peuvent ne
pas couvrir les besoins métiers.&lt;/p&gt;

&lt;p&gt;Toujours d’après son auteur, Behat permet en réalité d’apprendre au développeur
comment fonctionne le métier du client.&lt;/p&gt;

&lt;p&gt;Je pense que si beaucoup de développeurs n’aiment pas Behat c’est à cause de ce dernier
point. Ecrire des bons scénarios Behat est un travail délicat. Il faut exprimer le métier
du client et non pas le fonctionnement de l’application.&lt;/p&gt;

&lt;p&gt;Un développeur aura facilement tendance à écrire ce genre de scénario :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;# language: en
Scenario: Showing delivery cost for a product on the basket page
  Given there is a product:
    | name  | White Marker |
    | price | £5           |
  And I am on the &amp;quot;/catalogue&amp;quot; page
  When I click &amp;quot;Buy&amp;quot; in the &amp;quot;White Marker&amp;quot; product block
  And I go to the &amp;quot;/basket&amp;quot; page
  Then I should see a list with 1 product
  And the overall price should be shown as £9&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Alors que le scénario exprimé du point de vue du client devrait ressembler à :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;# language: en
Scenario: Getting the delivery cost for a single product under £10
  Given a product named &amp;quot;White Marker&amp;quot; and priced £5 was added to the catalogue
  When I add the &amp;quot;White Marker&amp;quot; product from the catalogue to the picked up basket
  Then the overall basket price should be £9&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Mais adopter ce point de vue n’est pas évident pour un développeur, qui a souvent
plus une vision technique que fonctionnelle de l’application. Cela demande donc un
véritable effort de réflexion, en plus de devoir écrire le code correspondant aux
différents scénarios.&lt;/p&gt;

&lt;p&gt;Je reste donc persuader que si de nombreux développeurs n’aiment pas Behat, c’est
essentiellement à cause de leur culture technique et qu’il est difficile pour eux
de faire un volte-face pour se mettre à la place du client.&lt;/p&gt;

&lt;p&gt;Il se peut aussi que comme j’avais tenté de l’expliquer dans un précédent billet,
que les développeurs vont tenter de se lancer directement dans l’écrire de leurs
scénarios Behat, sans prendre le temps de
&lt;a href=&quot;/blog/2015/01/23/comprenez-les-outils-que-vous-utilisez.html&quot;&gt;comprendre les outils qu’ils utilisent&lt;/a&gt;
et la manière de le configurer correctement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ressources :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://speakerdeck.com/everzet/behat-by-example&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Behat by example&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://stakeholderwhisperer.com/posts/2014/10/introducing-modelling-by-example&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Introducing Modelling by Example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
                    <pubDate>Mon, 11 Apr 2016 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2016/04/11/pourquoi-les-developpeurs-n-aiment-pas-behat.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2016/04/11/pourquoi-les-developpeurs-n-aiment-pas-behat.html</guid>
                </item>
            
        
            
                284
                <item>
                    <title>Mon environnement PhpStorm</title>
                    <description>&lt;p&gt;Comme de nombreux développeurs PHP, j’utilise au quotidien l’excellent IDE développé
par Jetbrains, j’ai nommé PhpStorm. Au-delà des nombreuses fonctionnalités fournies
nativement par l’IDE, il est possible d’ajouter des modules complémentaires au
travers des nombreux plugins disponibles.&lt;/p&gt;

&lt;p&gt;Dans ce billet, je vais présenter les principaux modules que j’utilise.&lt;/p&gt;

&lt;p&gt;##&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7320?pr=phpStorm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP Annotation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PHP Annotation est un module qui comme son nom l’indique très bien permet de gérer
les annotations dans l’IDE. C’est notamment grâce à cette extension qu’il sera possible
de naviguer dans les annotations, d’obtenir de l’autocomplétion lors de l’implémentation
de ces dernières, etc.&lt;/p&gt;

&lt;p&gt;##&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7219&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Symfony&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Je développe essentiellement en utilisant le framework &lt;a href=&quot;http://symfony.com&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Symfony&lt;/a&gt;.
Cette extension permet ainsi d’obtenir un support complet du framework dans l’IDE.&lt;/p&gt;

&lt;p&gt;##&lt;a href=&quot;https://plugins.jetbrains.com/plugin/8173?pr=phpStorm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Atoum&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Il m’est impossible de développer sans écrire de tests (et j’espère que vous aussi).
Pour écrire mes tests unitaires, j’utilise &lt;a href=&quot;http://atoum.org&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Atoum&lt;/a&gt;
aussi personnellement que professionnellement. Ce plugin créé tout récemment permet
de faciliter la navigation dans mes tests, mais également de lancer simplement ces
derniers directement dans l’IDE.&lt;/p&gt;

&lt;p&gt;##&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7631?pr=phpStorm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP composer.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Il est aujourd’hui devenu impossible de travailler en PHP sans utiliser
&lt;a href=&quot;http://getcomposer.org&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Composer&lt;/a&gt;, le gestionnaire de
dépendance pour le langage. Cette extension, permet d’avoir l’autocomplétion lors
de l’édition du fichier de définition des dépendances, mais permet également de consulter
les versions des dépendances installées.&lt;/p&gt;

&lt;p&gt;##&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7622?pr=phpStorm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP Inspection EA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PHP Inspection EA est l’un des derniers modules que j’ai découvert, mais il commence
à devenir indispensable. Effectivement, ce dernier permet d’effectuer des analyses
de codes statiques en complément de celles effectuées nativement par l’IDE. Extrêmement
pratique pour améliorer son code !&lt;/p&gt;
</description>
                    <pubDate>Wed, 17 Feb 2016 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2016/02/17/environement-phpstorm.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2016/02/17/environement-phpstorm.html</guid>
                </item>
            
        
            
                285
                <item>
                    <title>Mon retour du DDD Day 2016 à Lyon</title>
                    <description>&lt;p&gt;Le &lt;a href=&quot;https://medium.com/@pockystar/ddd-day-1dab25711fb0&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;DDD-Day&lt;/a&gt;
est une journée de conférence visant à sensibiliser les développeurs PHP au DDD
(le Domain-Driven Design), démystifier son utilisation et les patterns qui y sont
rattachés. J’ai ainsi eu l’occasion d’assister à la première édition de cette
journée qui s’est déroulée le 30 janvier 2016 à Lyon.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;fr&quot;&gt;&lt;p lang=&quot;fr&quot; dir=&quot;ltr&quot;&gt;Le &lt;a href=&quot;https://twitter.com/hashtag/dddday?src=hash&quot;&gt;#dddday&lt;/a&gt; c&amp;#39;est maintenant ! &lt;a href=&quot;https://t.co/VJOesVWTFi&quot;&gt;pic.twitter.com/VJOesVWTFi&lt;/a&gt;&lt;/p&gt;&amp;mdash; Jérémy DECOOL (@jdecool) &lt;a href=&quot;https://twitter.com/jdecool/status/693354842346131458&quot;&gt;30 Janvier 2016&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;Avant de faire un retour rapide sur la journée, je tenais à remercier une nouvelle
fois tous ceux grâce à qui cette journée n’aurait pu avoir lieu : l’AFUP, KnpLabs,
l’atelier des médias, Amabla et Vanoix.&lt;/p&gt;

&lt;p&gt;La journée de conférence a été filmée et sera prochainement disponible sur la chaîne
Youtube de &lt;a href=&quot;https://www.youtube.com/channel/UCGOJGYuCAGziCtqS5s6WLHQ&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;OpenTalk&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;pourquoi-le-ddd-ne-devrait-rien-changer-à-votre-vie-&quot;&gt;Pourquoi le DDD ne devrait rien changer à votre vie ?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/pockystar&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Alexandre Balmes&lt;/a&gt; - &lt;a href=&quot;https://speakerdeck.com/pocky/pourquoi-le-ddd-ne-devrait-rien-changer-a-votre-vie&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Slides&lt;/a&gt; - &lt;a href=&quot;https://www.youtube.com/watch?v=nU-ez8DIko4&amp;amp;list=PLGlYeGauEwnvAsDzJr9DlLYP09UIWoW2I&amp;amp;index=1&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vidéo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cette journée commence par une présentation du Domain-Driven Design, pourquoi et
quand l’utiliser. Derrière cette présentation Alexandre nous explique les bases du
DDD et tente de nous montrer que malgré quelques termes “barbares”, il n’y a rien
de sorcier derrière cet acronyme. Ce n’est au fond que du bon sens et peut s’intégrer
dans tout environnement projet, surtout avec les outils qui sont à notre disposition
en PHP aujourd’hui.&lt;/p&gt;

&lt;h2 id=&quot;get-off-my-domain-&quot;&gt;Get Off My Domain !&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/matthieunapoli&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Matthieu NAPOLI&lt;/a&gt; - &lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;https://mnapoli.fr/presentations/ddd-day &quot;&gt;Slides&lt;/a&gt; - &lt;a href=&quot;https://www.youtube.com/watch?v=Yj_2hUE-Lio&amp;amp;list=PLGlYeGauEwnvAsDzJr9DlLYP09UIWoW2I&amp;amp;index=2&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vidéo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Après l’introduction faite par Alexandre, Matthieu rentre un peu plus dans les
détails techniques d’une application DDD. Il va alors nous présenter les
design-patterns essentiels qui doivent être utilisés dans nos applications. C’était
l’occasion de revoir les notions de : &lt;code&gt;Entity&lt;/code&gt;, &lt;code&gt;Value Object&lt;/code&gt;, &lt;code&gt;Domain Service&lt;/code&gt;,
&lt;code&gt;Repository&lt;/code&gt; (au sens premier du pattern), &lt;code&gt;Aggregate&lt;/code&gt; et de &lt;code&gt;Domain Event&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ne-laissez-pas-les-formulaires-symfony-influencer-votre-modèle&quot;&gt;Ne laissez pas les formulaires Symfony influencer votre modèle&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/jeremyb_&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Jérémy BARTHE&lt;/a&gt; - &lt;a href=&quot;http://jeremybarthe.com/slides/2016-domain-driven-design-day/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Slides&lt;/a&gt; - &lt;a href=&quot;https://www.youtube.com/watch?v=Svndnw8n_SY&amp;amp;list=PLGlYeGauEwnvAsDzJr9DlLYP09UIWoW2I&amp;amp;index=3&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vidéo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En France, nous sommes très nombreux à utiliser le framework PHP Symfony. D’ailleurs
l’ensemble des personnes présentes développaient leurs applications en utilisant
ce dernier. Partant de ce constat, Jérémy nous a fait une présentation technique
sur l’utilisation des formulaires en Symfony. L’objectif de sa présentation était
de nous permettre de garder nos formulaires les plus simples possible tout en
gardant en tête les fondamentaux du DDD : l’utilisation des &lt;code&gt;Value Object&lt;/code&gt; et de
l’&lt;code&gt;Ubiquitous Language&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;soyez-spécifiques&quot;&gt;Soyez spécifiques&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/KPhoen/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Kévin GOMEZ&lt;/a&gt; - &lt;a href=&quot;http://blog.kevingomez.fr/slides-ddd-day-2016/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Slides&lt;/a&gt; - &lt;a href=&quot;https://www.youtube.com/watch?v=mKtsMsBFNsc&amp;amp;list=PLGlYeGauEwnvAsDzJr9DlLYP09UIWoW2I&amp;amp;index=4&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vidéo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kévin nous a fait une présentation sur l’expression des règles métiers dans nos
applications et la manière de les rendre réutilisables. Pour réussir cette tâche,
il nous présente le pattern &lt;code&gt;Specification&lt;/code&gt; et une implémentation de ce pattern
qu’il a écrit dans sa librairie &lt;a href=&quot;https://github.com/K-Phoen/rulerz&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;RulerZ&lt;/a&gt;.
Avec &lt;strong&gt;RulerZ&lt;/strong&gt;, il est alors possible via un DSL (Domain Specific Language), une
règle métier qui peut ensuite être passée de manière complètement transparente à un
&lt;code&gt;QueryBuilder&lt;/code&gt; de Doctrine ou une &lt;code&gt;Query&lt;/code&gt; ElasticSearch.&lt;/p&gt;

&lt;h2 id=&quot;cqrs--quand-les-représentations-ne-sont-pas-symétriques&quot;&gt;CQRS : Quand les Représentations ne sont pas symétriques&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/rgousi&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Guillaume ROSSIGNOL&lt;/a&gt; - &lt;a href=&quot;https://www.youtube.com/watch?v=MTqoADImjTM&amp;amp;list=PLGlYeGauEwnvAsDzJr9DlLYP09UIWoW2I&amp;amp;index=5&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vidéo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CQRS (Command Query Responsibility Segregation) est un pattern applicatif qui
repose sur un principe simple : la séparation, au sein d’une application, des
composants de traitement de lecture et d’écriture. Cette présentation nous a fait
un retour d’expérience sur la “migration” d’une application legacy vers une
architecture orientée DDD et CQRS.&lt;/p&gt;

&lt;p&gt;J’ai trouvé ce retour d’expérience intéressant vis-à-vis du choix que l’équipe a
effectué. Effectivement, l’essentiel des problèmes rencontrés se trouvant côté “front”,
l’équipe a fait le choix de garder l’application legacy en fonctionnement (correspondant
à la logique d’écriture) et de démarrer une nouvelle application qui ne sert qu’à
présenter les informations enregistrées par le back-office (la partie legacy).&lt;/p&gt;

&lt;h2 id=&quot;tour-de-table&quot;&gt;Tour de table&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/pascal_martin&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Pascal MARTIN&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pour terminer cette journée, nous avons eu le droit à une table ronde animée par
Pascal qui a permis à chaque membre de répondre aux questions restées en suspens
lors des présentations.&lt;/p&gt;

&lt;p&gt;Ce fut également un moment d’échange pour permettre aux organisateurs d’avoir le
ressenti de chacun sur cette première édition du DDD Day.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Encore une fois merci à toutes les personnes qui ont rendu cette journée possible :
les membres de l’organisation, les conférenciers ainsi qu’à tous les participants
à cette journée. Ce fut une journée riche en information, mais cela fait également
plaisir de revoir les personnes qui font bouger l’écosystème PHP à Lyon.&lt;/p&gt;

&lt;p&gt;Merci à tous !&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;fr&quot;&gt;&lt;p lang=&quot;fr&quot; dir=&quot;ltr&quot;&gt;Très bonne journée au &lt;a href=&quot;https://twitter.com/hashtag/dddday?src=hash&quot;&gt;#dddday&lt;/a&gt; ! Merci à tous &lt;a href=&quot;https://twitter.com/pockystar&quot;&gt;@pockystar&lt;/a&gt; &lt;a href=&quot;https://twitter.com/vanoix&quot;&gt;@vanoix&lt;/a&gt; &lt;a href=&quot;https://twitter.com/AFUP_lyon&quot;&gt;@AFUP_lyon&lt;/a&gt; pour l&amp;#39;orga et à tous les speakers.&lt;/p&gt;&amp;mdash; Jérémy DECOOL (@jdecool) &lt;a href=&quot;https://twitter.com/jdecool/status/693485524842078210&quot;&gt;30 Janvier 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

</description>
                    <pubDate>Mon, 08 Feb 2016 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2016/02/08/mon-retour-du-ddd-day-2016-a-lyon.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2016/02/08/mon-retour-du-ddd-day-2016-a-lyon.html</guid>
                </item>
            
        
            
        
            
        
            
                286
                <item>
                    <title>Le problème n+1</title>
                    <description>&lt;p&gt;Si vous êtes développeur et que vous travaillez régulièrement avec une base de
données, vous avez très certainement déjà été confronté à des problèmes de
performance liés à des relations de type parent/enfant. L’anti-pattern que l’on
retrouve le plus fréquemment consiste à exécuter une requête pour obtenir la
relation parente puis à récupérer les enfants un à un. C’est un cas qui se produit
souvent lorsque l’on travaille avec des ORM. On parle alors du problème N+1
(“N+1 problem” en anglais).&lt;/p&gt;

&lt;p&gt;Prenons un exemple concret : une base de données permettant de répertorier des
auteurs de livres ainsi que les livres écrits par ces derniers. Nous souhaitons
récupérer la liste des auteurs avec les livres qu’ils ont écrits. Le code permettant
d’afficher ce résultat pourrait ressembler à cela :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// ...
$authors = $pdo-&amp;gt;query(&amp;#39;SELECT * FROM author&amp;#39;)-&amp;gt;fetch();
foreach ($author as $authors) {
    $books = $pdo-&amp;gt;query(&amp;#39;SELECT * FROM book WHERE author_id = &amp;#39;.$author[&amp;#39;id&amp;#39;]);
    // ...
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Avec le code ci-dessous, on constate que les performances de l’application vont
se dégrader de manière exponentielle au fur et à mesure que l’on va renseigner des
auteurs et ajouter des livres. Dans le cas présent, l’on récupère 10 auteurs, 11
requêtes vont être exécutées pour récupérer l’ensemble des informations voulues
(1 pour récupérer les 10 auteurs, puis 10 pour récupérer les livres de chacun des
auteurs).&lt;/p&gt;

&lt;p&gt;Cela vous paraît aberrant ? Vous ne pensez ne jamais écrire un code comme cela ?
Prenons le même exemple en utilisant un ORM (Doctrine par exemple).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Author
{
    /**
     * @Id
     * @Column(name=&amp;quot;id&amp;quot;, type=&amp;quot;int&amp;quot;)
     */
    private $id;

    /**
     * @OneToMany(targetEntity=&amp;quot;Book&amp;quot;, mappedBy=&amp;quot;author&amp;quot;)
     */
    private $books;

    // ...
}

class Book
{
    /**
     * @Id
     * @Column(name=&amp;quot;id&amp;quot;, type=&amp;quot;int&amp;quot;)
     */
    private $id;

    /**
     * @ManyToOne(targetEntity=&amp;quot;Author&amp;quot;)
     */
    private $author;

    // ...
}


$authors = $entityManager-&amp;gt;getRepository(&amp;#39;Author&amp;#39;)-&amp;gt;findAll();
foreach ($author as $authors) {
    $books = $author-&amp;gt;getBooks();
    // ...
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous pensez peut-être que cette solution est plus performante. Pourtant le code
ci-dessus se comporte exactement de la même façon que le précédent. Par défaut
Doctrine (comme de nombreux ORM) génère des classes Proxy afin de ne récupérer
les relations enfant que lorsqu’elles sont demandées. Dans ce cas, Doctrine va
exécuter une requête à chaque appel de la méthode &lt;code&gt;getBooks&lt;/code&gt; pour récupérer les
informations correspondantes.&lt;/p&gt;

&lt;p&gt;La solution pour éviter cela est bien évidemment d’utiliser des jointures SQL afin
de récupérer les informations des auteurs avec les livres qu’ils ont écrit dans
la même requête. La modification du premier exemple conduirait à ce code :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// ...
$authors = $pdo-&amp;gt;query(&amp;#39;SELECT * FROM author JOIN book ON author.id = book.author_id&amp;#39;)-&amp;gt;fetch();&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Les ORM permettent également d’écrire des requêtes en utilisant les jointures afin
de récupérer l’ensemble des données voulu. On peut également configurer le mapping
des relations pour effectuer une récupération agressive des données (mais cela n’est
pas forcément recommandé car il se peut que la récupération des données de la relation
ne soit pas toujours utile) :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// utilisation du QueryBuilder de Doctrine pour récupérer l&amp;#39;ensemble des données en une requête
$authors = $entityManager-&amp;gt;createQueryBuilder()
    -&amp;gt;select([&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;])
    -&amp;gt;from(&amp;#39;Author&amp;#39;, &amp;#39;a&amp;#39;)
    -&amp;gt;join(&amp;#39;Book&amp;#39;, &amp;#39;b&amp;#39;)
    -&amp;gt;getQuery()
    -&amp;gt;getResult()
;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Thu, 17 Dec 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/12/17/le-probleme-n+1.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/12/17/le-probleme-n+1.html</guid>
                </item>
            
        
            
        
            
                287
                <item>
                    <title>Trait ou héritage, il faut choisir</title>
                    <description>&lt;p&gt;C’est avec la version 5.4.0 que les &lt;b&gt;Traits&lt;/b&gt; sont apparus en PHP. Il s’agit
d’un mécanisme de réutilisation de code, introduit dans le but de réduire certaines
limites de l’héritage simple. Je constate que de plus en plus de développeurs se
mettent à utiliser les &lt;b&gt;Traits&lt;/b&gt; à tel point que ces certains n’utilise même
plus l’héritage et préfère avoir recours à ce mécanisme. Que se soit avant de
faire un héritage ou avant d’utiliser un trait, il convient de se poser la question
de quand utiliser l’un ou l’autre ?&lt;/p&gt;

&lt;p&gt;Revenons rapidement sur ce qu’est un &lt;b&gt;héritage&lt;/b&gt;. L’héritage permet d’établir
une relation (de type parent/enfant) entre des classes. Lorsque l’on étend une
classe, la classe fille hérite alors de toutes les méthodes publiques et protégées
de la classe parente. Il est également possible que la classe fille redéfinisse
certaines de ces méthodes pour les adapter à ces spécificités.&lt;/p&gt;

&lt;p&gt;Les &lt;b&gt;traits&lt;/b&gt; quand à eux ont comme seul objectif de partager du code entre
classes sans structuration hiérarchique. Il n’y a donc ici pas de relation entre
les classes qui utilisent les traits. Il partage seulement un comportement.&lt;/p&gt;

&lt;p&gt;Il faut bien avoir cette distinction lorsque vous écrivez du code. Voici par
exemple, du code que j’ai pu voir récemment sur un projet :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait RepositoryTrait
{
    public function find($id)
    { /* ... */ }

    // ...
}

class UserRepository
{
    use RepositoryTrait;

    // ...
}

class GroupRepository
{
    use RepositoryTrait;

    // ...
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il faut faire attention à l’utilisation des traits car ces derniers peuvent
facilement casser les aspects classiques de la programmation objet. Dans ce bout
de code, les classes &lt;code&gt;UserRepository&lt;/code&gt; et &lt;code&gt;GroupRepository&lt;/code&gt; n’implémente pas d’interface
commune, elle utilise un trait. De ce fait, le principe de programmation SOLID peut
facilement être rompu.&lt;/p&gt;

&lt;p&gt;Dans ce cas-là, un héritage semble plus adapté :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;abstract class Repository
{
    public function find($id)
    { /* ... */ }

    // ...
}

class UserRepository extends Repository
{
    use RepositoryTrait;

    // ...
}

class GroupRepository extends Repository
{
    use RepositoryTrait;

    // ...
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;De plus en utilisant un héritage, il est plus facile de faire du typage explicite.
Il est alors possible de vérifier que la classe fournit en paramètre est bien de
type &lt;code&gt;Repository&lt;/code&gt;, ce qui n’est pas possible avec un trait. De plus il sera plus
facile de respecter le &lt;a href=&quot;https://fr.wikipedia.org/wiki/Principe_de_substitution_de_Liskov&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;principe de substitution de Liskov&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Il est préférable d’utiliser les traits pour partager du code avec des classes
qui n’ont pas de lien entre elles. Prenons comme exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait TextFormater
{
    public function sanitize($text)
    {
        return strip_tags($text);
    }
}

class Mailer
{
    use TextFormater;

    public function send($subject, $text)
    {
        mail(&amp;#39;user@mail.com&amp;#39;, $this-&amp;gt;sanitize($subject), $this-&amp;gt;sanitize($text));
    }
}

class ConsoleWriter
{
    use TextFormater;

    public function write($text)
    {
        echo $this-&amp;gt;sanitize($text), PHP_EOL;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Dans l’exemple précédent, la classe &lt;code&gt;Mailer&lt;/code&gt; a pour but d’envoyer un mail à un
utilisateur et la classe &lt;code&gt;Writer&lt;/code&gt; de générer un affichage à l’écran. Ces deux
classes n’ont aucun rapport et pourtant elles ont toutes les deux besoins de
traiter du texte fourni en paramètre. L’héritage n’a ici pas sa place et il
convient donc de partager le code via l’utilisation d’un trait.&lt;/p&gt;

&lt;p&gt;Un autre exemple concret de l’utilisation des traits est l’implémentation du
pattern &lt;a href=&quot;https://fr.wikipedia.org/wiki/Singleton_(patron_de_conception)&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Singleton&lt;/a&gt;.
Lorsque l’on souhaite utiliser ce dernier, le premier réflexe pourrait être de
créer une classe &lt;code&gt;Singleton&lt;/code&gt; et de faire hériter nos objets de cette dernière.
Mais ce n’est pas toujours possible à cause de l’impossibilité de faire de
l’héritage multiple. Partant du principe qu’il est préférable de favoriser un
héritage “fonctionnel”, il convient dans ce cas-là, d’avoir recours aux traits pour
résoudre le problème :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait Singleton
{
    protected static $instance = null;

    public static function getInstance()
    {
        if (null === $this-&amp;gt;instance) {
            $this-&amp;gt;instance = new self;
        }

        return $this-&amp;gt;instance;
    }
}

class Example extends Article
{
    use Singleton;
}

$expl = Example::getInstance();&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;b&gt;Héritage&lt;/b&gt; et &lt;b&gt;Trait&lt;/b&gt;, les deux concepts ont leur place dans nos projets,
utilisons-les à bon escient et au bon moment.&lt;/p&gt;
</description>
                    <pubDate>Sat, 28 Nov 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/11/28/trait-ou-heritage-il-faut-choisir.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/11/28/trait-ou-heritage-il-faut-choisir.html</guid>
                </item>
            
        
            
                288
                <item>
                    <title>Support complet de Gitlab dans Composer</title>
                    <description>&lt;p&gt;Inutile de vous présenter Composer l’outil de gestion des dépendances massivement
utilisé par les développeurs PHP. Voici un court billet pour vous annoncer que ce
dernier intègre désormais le support complet de Gitlab.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/composer/composer/issues/3241&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;L’issue Github&lt;/a&gt;
datée du 26 août 2014 et demandée le support de l’API Gitlab pour la gestion des
dépendances (avec notamment la gestion des droits).&lt;/p&gt;

&lt;p&gt;C’est désormais chose faite, un an et demi après, Composer fournit maintenant
&lt;a href=&quot;https://github.com/composer/composer/pull/3765&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;un driver Gitlab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pour l’utiliser dans votre projet, il vous suffira de configurer votre fichier
&lt;code&gt;composer.json&lt;/code&gt; avec les éléments suivants :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
    &amp;quot;repositories&amp;quot;: [
        { &amp;quot;type&amp;quot;: &amp;quot;gitlab&amp;quot;, &amp;quot;url&amp;quot;: &amp;quot;http://gitlab.mysrv.com/path/to/my_project&amp;quot; }
    ],
    &amp;quot;config&amp;quot;: {
        &amp;quot;gitlab-domains&amp;quot;: [&amp;quot;gitlab.mysrv.com&amp;quot;]
    },
    &amp;quot;require&amp;quot;: {
        &amp;quot;vendor/my-project&amp;quot;: &amp;quot;~1.0&amp;quot;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Wed, 25 Nov 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/11/25/support-complet-de-gitlab-dans-composer.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/11/25/support-complet-de-gitlab-dans-composer.html</guid>
                </item>
            
        
            
                289
                <item>
                    <title>Les patterns &quot;Builder&quot; et &quot;Factory&quot;: même combat</title>
                    <description>&lt;p&gt;Aujourd’hui, j’ai envie de vous parler des patterns “Builder” et “Factory”. S’il
y en a un qui est bien connu des développeurs, on ne fait pas toujours la distinction
entre ces deux patterns qui ont un objectif similaire: la &lt;b&gt;création de nouveaux objets&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;La &lt;b&gt;factory&lt;/b&gt; (ou &lt;i&gt;fabrique&lt;/i&gt; en français) est un patron de conception qui
permet de créer un objet sans avoir à connaître la classe exacte de l’objet retourné.
Ce dernier est alors construit en une seule étape. Exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class DatabaseConnectionFactory
{
    private $dbParams;

    public function __construct(array $params)
    {
        $this-&amp;gt;dbParams = $params;
    }

    public function create($type)
    {
        switch ($type) {
            case &amp;#39;mysql&amp;#39;:
                return new MysqlDatabaseConnection($this-&amp;gt;dbParams);

            case &amp;#39;pgsql&amp;#39;:
                $conn = new PgsqlDatabaseConnection();
                $conn-&amp;gt;setParams($this-&amp;gt;dbParams);

                return $conn;

            default:
                throw new \InvalidArgumentException();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le pattern &lt;b&gt;builder&lt;/b&gt; quand à lui se focalise sur la construction d’objets plus
complexes qui ne peuvent être créés en une seule étape. Il est parfois nécessaire que
la création d’un objet nécessite plusieurs étapes. C’est alors que les objets de
type &lt;b&gt;builder&lt;/b&gt; sont utilisés. Exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class WebserviceClientBuilder
{
    private $serviceUrl;

    public function __construct($url)
    {
        $this-&amp;gt;serviceUrl = $url;
    }

    public function build()
    {
        $clientStrategyFactory = new clientStrategyFactory();
        $client = $clientStrategyFactory-&amp;gt;create($this-&amp;gt;serviceUrl);

        return new WebserviceClient($client);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;L’exemple ci-dessus est très simple, mais on voit bien que la création d’un client
pour utiliser le web service nécessite la création de deux objets : le premier permettant
de définir la stratégie à adopter pour accéder à ce dernier et le second est le client
à proprement parlé.&lt;/p&gt;

&lt;p&gt;Il existe également d’autres patrons de conception permettant de créer des objets,
mais ces derniers étant moins utilisé (donc certains que je n’ai jamais pratiqués),
je n’en parlerai pas. Je vous invite notamment à découvrir les patterns
&lt;a href=&quot;https://en.wikipedia.org/wiki/Object_pool_pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Object pool&lt;/a&gt;
et &lt;a href=&quot;https://en.wikipedia.org/wiki/Prototype_pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Prototype&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Mon, 16 Nov 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/11/16/les-patterns-builder-et-factory-meme-combat.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/11/16/les-patterns-builder-et-factory-meme-combat.html</guid>
                </item>
            
        
            
        
            
        
            
                290
                <item>
                    <title>Gérer du multithread en PHP avec pthreads</title>
                    <description>&lt;p&gt;PHP est par “défaut” un langage mono-thread et ne permet donc, par définition, de
ne gérer qu’un seul processus à la fois. Or il peut s’avérer extrêmement avantageux
de gérer plusieurs processus afin d’effectuer un certain nombre de tâches en parallèle.
Il existe heureusement une extension PECL pour faire cela :
&lt;a href=&quot;http://pecl.php.net/package/threads&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;pthreads&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;« pthreads est une API orientée objet qui permet le multi-threading en PHP. Il
inclut tous les outils nécessaires pour créer des applications multi-threadées
pour le Web ou pour la console. Les applications PHP peuvent créer, lire, écrire,
exécuter et synchroniser des Threads, des Workers, et des objets Threaded. »&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tout cela de manière très simple. La seule contrainte est que l’extension se base
sur les threads POSIX et nécessitera donc l’installation du projet
&lt;a href=&quot;https://sourceware.org/pthreads-win32/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;pthreads-win32&lt;/a&gt;
pour pouvoir fonctionner sous Windows.&lt;/p&gt;

&lt;p&gt;Voici un exemple très simple de l’utilisation de l’extension :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
$thread = new class extends Thread {
    public function run() {
        echo &amp;quot;Hello World\n&amp;quot;;
    }
};

$thread-&amp;gt;start() &amp;amp;&amp;amp; $thread-&amp;gt;join();
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;J’ai par ailleurs publié une image Docker permettant d’exécuter PHP 7.0 préconfiguré
avec l’extension pthreads. Tout cela est disponible sur le
&lt;a href=&quot;https://hub.docker.com/r/jdecool/php-pthreads/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Docker Hub&lt;a&gt;&lt;/a&gt;.&lt;/a&gt;&lt;/p&gt;
</description>
                    <pubDate>Mon, 12 Oct 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/10/12/gerer-du-multithread-en-php-avec-pthreads.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/10/12/gerer-du-multithread-en-php-avec-pthreads.html</guid>
                </item>
            
        
            
                291
                <item>
                    <title>Exporter un fichier CSV avec Symfony2</title>
                    <description>&lt;p&gt;Lorsque l’on travaille sur des applications de gestion, il est fréquent de devoir
exporter des fichiers de données dans divers formats. Le plus simple des formats
étant le CSV.&lt;/p&gt;

&lt;p&gt;La plupart du temps, l’export passe par la génération d’un fichier temporaire qui
est ensuite envoyé à l’utilisateur au travers d’un téléchargement de fichier via
le navigateur. Pourtant Symfony2 fournit des outils permettant d’éviter de manipuler
directement les fichiers temporaires via les &lt;code&gt;StreamedResponse&lt;/code&gt; et les flux PHP.&lt;/p&gt;

&lt;p&gt;La classe &lt;a href=&quot;http://api.symfony.com/master/Symfony/Component/HttpFoundation/StreamedResponse.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;StreamedResponse&lt;/code&gt;&lt;/a&gt;
permet de retourner un flux de réponse au client. Le contenu de la réponse est
représenté par une fonction PHP au lieu d’une chaine de caractère :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function generateCsvAction() {
    $repository = $this-&amp;gt;get(&amp;#39;app.repository.user&amp;#39;);

    $response = new StreamedResponse();
    $response-&amp;gt;setCallback(function() use ($repository) {
        $handle = fopen(&amp;#39;php://output&amp;#39;, &amp;#39;w+&amp;#39;);

        fputcsv($handle, [&amp;#39;Firstname&amp;#39;, &amp;#39;Lastname&amp;#39;, &amp;#39;Birthday&amp;#39;], &amp;#39;;&amp;#39;);

        $results = $repository-&amp;gt;findActiveUsers();
        foreach ($results as $user) {
            fputcsv(
                $handle,
                [$user-&amp;gt;getFirstname(), $user-&amp;gt;getLastname(), $user-&amp;gt;getBirthday()],
                &amp;#39;;&amp;#39;
             );
        }

        fclose($handle);
    });

    $response-&amp;gt;setStatusCode(200);
    $response-&amp;gt;headers-&amp;gt;set(&amp;#39;Content-Type&amp;#39;, &amp;#39;text/csv; charset=utf-8&amp;#39;);
    $response-&amp;gt;headers-&amp;gt;set(&amp;#39;Content-Disposition&amp;#39;,&amp;#39;attachment; filename=&amp;quot;export-users.csv&amp;quot;&amp;#39;);

    return $response;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Fri, 09 Oct 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/10/09/exporter-un-fichier-csv-avec-symfony2.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/10/09/exporter-un-fichier-csv-avec-symfony2.html</guid>
                </item>
            
        
            
                292
                <item>
                    <title>Objets-Valeurs et immutabilité</title>
                    <description>&lt;p&gt;J’ai déjà parlé plusieurs fois des &lt;a href=&quot;/blog/2015/03/25/les-objets-valeurs-ou-value-object.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Objets-Valeurs (Value Objects)&lt;/a&gt;
et &lt;a href=&quot;/blog/2015/06/23/quand-utiliser-le-pattern-value-object.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;quand les utiliser&lt;/a&gt;,
mais je n’ai encore jamais évoqué le concept d’immutabilité qui en est indissociable.&lt;/p&gt;

&lt;p&gt;Un objet immutable possède un état qui ne peut pas changer au cours du temps. Pourquoi
est-il aussi important qu’un objet-valeurs ne puisse changer ? Les objets-valeurs ont pour
objectif de représenter une donnée à un instant T. Ce qui a de la valeur pour un tel
objet est son état. De ce fait, si l’état de notre objet change, il ne peut être
représenté par une même instance (contrairement à un objet de type &lt;code&gt;Entité&lt;/code&gt;) puisque
c’est l’état de notre objet qui détermine son identité.&lt;/p&gt;

&lt;p&gt;Prenons un exemple concret. Si dans un modèle objet, nous souhaitons représenter
une couleur par un Objet-Valeurs, il est logique que ce dernier soit immutable.
L’état caractèristique de l’objet et la couleur qui lui est associé, si cette
dernière change, elle ne peut être associé à la même instance et il devra s’agir
d’un nouvel objet.&lt;/p&gt;

&lt;p&gt;Cela veut également dire que s’il est possible d’effectuer des opérations sur notre
Objet-Valeurs, le résultat provoquera la création d’un nouvel Objet-Valeurs. Reprenons
l’exemple de notre objet &lt;code&gt;Money&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Money
{
  /** @var int */
  private $amount;

  /** @var string */
  private $currency;

  /**
   * @param int    $amount
   * @param string $currency
   */
  public function __construct($amount, $currency)
  {
    $this-&amp;gt;amount   = $amount;
    $this-&amp;gt;currency = $currency;
  }

  /**
   * @return int
   */
  public function getAmount()
  {
    return $this-&amp;gt;amount;
  }

  /**
   * @return float
   */
  public function getFormatedAmount()
  {
    return round($this-&amp;gt;amount / 100, 2);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous souhaitons ajouter la possibilité d’additionner deux objets de types &lt;code&gt;Money&lt;/code&gt;.
Pour cela nous ajoutons le code suivant :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Money
{
  // ...

  /**
   * @param Money $money
   * @return static
   */
  public function add(Money $money)
  {
    // check same currency

    $value = $this-&amp;gt;amount + $money-&amp;gt;getAmoun();

    return new static($value, $this-&amp;gt;currency);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le code ci-dessus permet d’ajouter deux objets &lt;code&gt;Money&lt;/code&gt; et l’objet qui en résulte
est un nouvel objet-valeurs. Il est ainsi possible d’ajouter des comportements
à ces derniers tout en conservant le principe d’immutabilité.&lt;/p&gt;
</description>
                    <pubDate>Tue, 21 Jul 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/07/21/objets-valeurs-et-immutabilite.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/07/21/objets-valeurs-et-immutabilite.html</guid>
                </item>
            
        
            
                293
                <item>
                    <title>Ecrire des logs manipulables</title>
                    <description>&lt;p&gt;La notion de logs (ou journaux en français) est primordiale en développement. Ce
sont eux qui permettent de faire remonter les informations générées par l’application
une application. Ces derniers peuvent se présenter sous différentes formes : fichiers,
base de données, sortie écran… Mais comment est-il possible de traiter ces différents
retours facilement ?&lt;/p&gt;

&lt;p&gt;Les données recueillies par les fichiers de logs informent des diverses situations
rencontrées lors de l’exécution du code. Cela peut être des cas inattendus (exceptions),
des erreurs, des informations sur les actions effectuées par les utilisateurs. Ils
permettent aux développeurs de retracer l’exécution d’un bout de code. Les fichiers
alors générés peuvent contenir un grand nombre d’informations et les analyser peut
s’avérer être une tâche longue et complexe.&lt;/p&gt;

&lt;p&gt;Bien entendu les informations écrites dans les fichiers de logs sont dépendantes
de la phase de vie du projet. Il est ainsi fort probable que pendant la phase
de développement, une grande quantité d’informations soient enregistrées afin d’avoir
un maximum de détails. Par contre, une fois en production, le niveau de log sera
réduit à son minimum afin d’avoir les informations essentielles.&lt;/p&gt;

&lt;p&gt;De ce fait, une analyse automatique des logs s’avère très intéressante et peut
révéler de nombreuses informations. Mais comment écrire des logs qui puissent
être facilement traités et analysés par une machine ?&lt;/p&gt;

&lt;p&gt;Pour cela, je vais m’appuyer sur les &lt;a href=&quot;http://www.php-fig.org&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PSR&lt;/a&gt;
(Propose a Standards Recommendation) de PHP et plus particulièrement la
&lt;a href=&quot;http://www.php-fig.org/psr/psr-3/&quot;&gt;PSR-3&lt;/a&gt; qui concerne les bibliothèques
de journalisation. Si vous ne connaissez pas les PSR, il s’agit de recommandations
validées par le &lt;a href=&quot;http://www.php-fig.org&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP FIG&lt;/a&gt;
(PHP Framework Interoperability Group) ayant pour objectif d’améliorer l’interopérabilité
entre les frameworks PHP.&lt;/p&gt;

&lt;p&gt;Pour résumer rapidement, dans PSR-3, on retrouve 8 niveaux de log avec autant de
méthodes associées. Par exemple, le niveau &lt;code&gt;DEBUG&lt;/code&gt; est associé à la méthode
&lt;code&gt;public function debug($message, array $context = array())&lt;/code&gt;. On retrouve également
une fonction générique &lt;code&gt;public function log($level, $message, array $context = array())&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;Ce que l’on peut remarquer avec les signatures de ces fonctions, c’est qu’elle ne
se limite pas à écrire un message dans un fichier. Elles permettent également de renseigner
un contexte d’exécution. Il est donc intéressant lorsque l’on écrit une ligne de log,
de fournir des données sur l’état de l’application au moment donné. Par exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$logger-&amp;gt;info(&amp;#39;User deletion&amp;#39;, [
  &amp;#39;userLogged&amp;#39;  =&amp;gt; $userLogged-&amp;gt;getId(),
  &amp;#39;userDeleted&amp;#39; =&amp;gt; $userDeleted-&amp;gt;getId(),
]);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le code ci-dessus génère la ligne suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;[2015-06-28 16:11:12] myapp.INFO: User deletion {&amp;quot;userLogged&amp;quot;:1234,&amp;quot;userDeleted&amp;quot;:7412} []&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cet exemple n’est pas très intéressant, je me limite à fournir les identifiants des
utilisateurs concernés par l’action de suppression d’un utilisateur. De plus, si
l’utilisateur est physiquement supprimé de la base de données, le fait d’avoir son
identifiant est inutile. Il aurait été plus logique de donner les objets à la fonction
de log qui les aurait sérialisés pour l’écrire du fichier.&lt;/p&gt;

&lt;p&gt;Il est intéressant de noter que la ligne de log générée automatiquement analysée
car elle est créée selon un schéma bien défini. Elle peut ainsi être découpée en 5 :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;La date d’enregistrement : &lt;code&gt;2015-06-28 16:11:12&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Le niveau de log : &lt;code&gt;myapp.INFO&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Le message de log : &lt;code&gt;User deletion&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Le contexte d’exécution : &lt;code&gt;{&quot;userLogged&quot;:1234,&quot;userDeleted&quot;:7412}&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Des données additionnels (dont je ne parle pas dans ce billet) : &lt;code&gt;[]&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Il est donc très facile d’analyser les fichiers de manière automatique. L’analyse
peut permettre d’obtenir des informations sur les erreurs rencontrées le plus souvent
par l’application, mais également de faire des statistiques sur certaines utilisations
de l’application. Ce travail d’analyse peut être réduit à son minimum car ce genre de
logs peut être envoyé et analysé par des plateformes de type ELK (Elasticsearch
Logstash Kibana).&lt;/p&gt;

&lt;p&gt;Notons au passage que cette ligne de log aurait pu être plus difficilement analysable
si elle avait été écrite de la manière suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$logger-&amp;gt;info(sprintf(&amp;#39;User &amp;quot;%d&amp;quot; delete user &amp;quot;%d&amp;quot;&amp;#39;,
  $userLogged-&amp;gt;getId(),
  $userDeleted-&amp;gt;getId()
);
// =&amp;gt; [2015-06-28 16:11:12] myapp.INFO: User &amp;quot;1234&amp;quot; delete user &amp;quot;7412&amp;quot; []&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le problème avec cette méthode est que le message de log n’est pas “unique” pour
une action donnée. Effectivement, l’action ayant déclenché l’écriture de la ligne
est mélangée avec son contexte d’exécution.&lt;/p&gt;

&lt;p&gt;Si vous souhaitez en savoir plus sur l’utilisation des fichiers de logs, Grégoire
PINEAU, développeur chez SensioLabs a fait une excellente présentation lors du
&lt;a href=&quot;https://speakerdeck.com/lyrixx/symfony-live-2015-paris-monitorer-sa-prod&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;SFLive 2015&lt;/a&gt;
sur le sujet.&lt;/p&gt;
</description>
                    <pubDate>Wed, 15 Jul 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/07/15/ecrire-des-logs-manipulables.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/07/15/ecrire-des-logs-manipulables.html</guid>
                </item>
            
        
            
        
            
                294
                <item>
                    <title>Quand utiliser le pattern Value Object ?</title>
                    <description>&lt;p&gt;Il y a quelques mois, je parlais du
&lt;a href=&quot;/blog/2015/03/25/les-objets-valeurs-ou-value-object.html&quot;&gt;pattern Objets-Valeur&lt;/a&gt;
(ou Value Object en anglais). Dans le billet présentant brièvement ce patron de
conception, j’évoquais le fait qu’il est souvent méconnu des développeurs PHP. De
ce fait les exemples sont assez rares et il est alors difficile de savoir quand
et/ou comment l’utiliser.&lt;/p&gt;

&lt;p&gt;Lorsque l’on évoque ce pattern, le principal exemple qui est cité est celui de
l’objet &lt;code&gt;Money&lt;/code&gt; permettant de gérer la notion d’argent (valeur + devise). Mais il
existe une multitude de projet ne faisait pas appel à cette notion et de ce fait
le concept reste abstrait.&lt;/p&gt;

&lt;p&gt;Il peut être pourtant relativement simple de détecter à quel moment nous pouvons
et/ou devons utiliser un Objet-Valeurs. La principale structure de données utilisées
par les développeurs PHP est le tableau associatif. Dans bien de cas, ce dernier
peut être remplacé par un objet de type Objet-Valeurs.&lt;/p&gt;

&lt;p&gt;Par exemple, prenons le code suivant qui parse un fichier XML :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function parseLine($data)
{
    // ... code permettant de traiter les données

    return [
        &amp;#39;name&amp;#39;        =&amp;gt; trim($columns[0]-&amp;gt;textContent),
        &amp;#39;frequency&amp;#39;   =&amp;gt; trim($columns[1]-&amp;gt;textContent),
        &amp;#39;location&amp;#39;    =&amp;gt; trim($columns[2]-&amp;gt;textContent),
        &amp;#39;transmitter&amp;#39; =&amp;gt; trim($columns[3]-&amp;gt;textContent),
    ];
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Dans cet exemple, la fonction renvoie un tableau associatif contenant les informations
d’une donnée lue dans un fichier XML. On pourrait dans ce cas utiliser un Value-Objet.
Cela aurait de multiples avantages :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Avoir un objet où il serait possible d’avoir les données caractéristiques de
l’élément (les propriétés) ainsi qu’un ensemble de comportement que nous pouvons
y appliquer (les méthodes).&lt;/li&gt;
  &lt;li&gt;Cela permet de rendre le code plus lisible et compréhensible par les autres
développeurs. Pas besoin d’analyser le contenu de la variable puisque cette dernière
est décrite au travers d’un objet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On pourrait ainsi réécrire le code sous la forme suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function parseLine($data)
{
    // ... code permettant de traiter les données

    return new RadioFrequency(
        trim($columns[0]-&amp;gt;textContent),
        trim($columns[1]-&amp;gt;textContent),
        trim($columns[2]-&amp;gt;textContent),
        trim($columns[3]-&amp;gt;textContent)
    );
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notons que si nous souhaitons faire les choses correctement, il serait nécessaire
de déléguer la création de notre objet &lt;code&gt;RadioFrequency&lt;/code&gt; à une fabrique (Factory).
Bien que cela peut sembler compléxifier notre code en rajoutant une classe
supplémentaire, c’est en réalité tout le contraire. Au travers de cette modification,
nous respectons le principe de responsabilité unique.&lt;/p&gt;

&lt;p&gt;Ainsi, le code final serait :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function __construct($radioFrequencyFactory)
{
    $this-&amp;gt;radioFrequencyFactory = $radioFrequencyFactory;
}

public function parseLine($data)
{
    // ... code permettant de traiter les données

    return $this-&amp;gt;radioFrequencyFactory-&amp;gt;createFromDOMNode($columns);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Au final, n’est-il pas plus agréable de manipuler un objet clairement identifié et
ayant un comportement défini plutôt qu’un tableau où il est difficile de connaître son
contenu et les actions qui peuvent y être appliquées sans effectuer une inspection
xDebug ou sans dumper ce dernier ?&lt;/p&gt;
</description>
                    <pubDate>Tue, 23 Jun 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/06/23/quand-utiliser-le-pattern-value-object.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/06/23/quand-utiliser-le-pattern-value-object.html</guid>
                </item>
            
        
            
        
            
                295
                <item>
                    <title>Manipuler un champ Select2 avec Behat</title>
                    <description>&lt;p&gt;Si vous développez en PHP, vous devez très certainement connaître Behat, le framework
permettant de faire du développement piloté par le comportement (BDD, Behaviour Driven
Development en anglais). Personnellement, je suis toujours embêté lorsque je me
retrouve à écrire des scénarios devant manipuler une interface qui utilise le composant
&lt;a href=&quot;https://select2.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Select2&lt;/a&gt; car il n’est pas possible
de manipuler ce champ au travers de l’extension Mink.&lt;/p&gt;

&lt;p&gt;Pour cela, il est nécessaire de créer des contextes personnalisés qui ajouteront des actions
permettant de manipuler la liste déroulante. Etant donnée que je n’ai rien trouvé sur le Web,
je vous partage le code qui permet d’interagir avec un champ Select2 dans un scénarii Behat.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * @When /^(?:|I )fill in select2 input &amp;quot;(?P&amp;lt;field&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot; with &amp;quot;(?P&amp;lt;value&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot; and select &amp;quot;(?P&amp;lt;entry&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot;$/
 */
public function fillInSelectInputWithAndSelect($field, $value, $entry)
{
    $page = $this-&amp;gt;getSession()-&amp;gt;getPage();

    $inputField = $page-&amp;gt;find(&amp;#39;css&amp;#39;, $field);
    if (!$inputField) {
        throw new \Exception(&amp;#39;No field found&amp;#39;);
    }

    $choice = $inputField-&amp;gt;getParent()-&amp;gt;find(&amp;#39;css&amp;#39;, &amp;#39;.select2-selection&amp;#39;);
    if (!$choice) {
        throw new \Exception(&amp;#39;No select2 choice found&amp;#39;);
    }
    $choice-&amp;gt;press();

    $select2Input = $page-&amp;gt;find(&amp;#39;css&amp;#39;, &amp;#39;.select2-search__field&amp;#39;);
    if (!$select2Input) {
        throw new \Exception(&amp;#39;No input found&amp;#39;);
    }
    $select2Input-&amp;gt;setValue($value);

    $this-&amp;gt;getSession()-&amp;gt;wait(1000);

    $chosenResults = $page-&amp;gt;findAll(&amp;#39;css&amp;#39;, &amp;#39;.select2-results li&amp;#39;);
    foreach ($chosenResults as $result) {
        if ($result-&amp;gt;getText() == $entry) {
            $result-&amp;gt;click();
            break;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Avec ma société, nous avons également décidé de publier une
&lt;a href=&quot;https://packagist.org/packages/novaway/common-contexts&quot; target=&quot;_target&quot;&gt;extension Behat&lt;/a&gt;
installable via Composer contenant un contexte permettant la manipulation complète d’un champ
Select2. Cette dernière est disponible sur
&lt;a href=&quot;https://github.com/novaway/BehatCommonContext&quot; target=&quot;_target&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Wed, 03 Jun 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/06/03/manipuler-un-champ-select2-avec-behat.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/06/03/manipuler-un-champ-select2-avec-behat.html</guid>
                </item>
            
        
            
                296
                <item>
                    <title>Le pattern &quot;conteneur&quot; (Container)</title>
                    <description>&lt;p&gt;Lorsque l’on développe une application, le principal défi est de donner du sens
à son code afin qu’il soit facilement compréhensible et maintenable.
Une des nombreuses bonnes pratiques est alors d’utiliser des
&lt;a href=&quot;/blog/2015/03/25/les-objets-valeurs-ou-value-object.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;objets-valeurs&lt;/a&gt;
plutôt que de simples variables. Pour stocker et manipuler ces derniers, il est possible
d’utiliser un &lt;a href=&quot;https://en.wikipedia.org/wiki/Container_(abstract_data_type)&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;conteneur&lt;/a&gt;,
un patron de conception souvent utilisé en parallèle.&lt;/p&gt;

&lt;p&gt;En programmation objet, un conteneur n’est ni plus ni moins qu’un objet ayant pour
but de gérer une collection d’élément. C’est un pattern assez peu utilisé dans le
monde PHP car de nombreux développeurs ont pris l’habitude d’utiliser les tableaux
associatifs fournis par le langage.&lt;/p&gt;

&lt;p&gt;Il y a pourtant de multiples avantages à utiliser un conteneur dans vos applications.
On peut citer :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Éviter la duplication de code dans les classes&lt;/li&gt;
  &lt;li&gt;Respecter le principe de responsabilité unique (vos objets métiers n’ont pas à se soucier de comment parcourir
ou rechercher un élément dans un tableau, une liste ou une collection)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour vous aider à manipuler des conteneurs dans votre code, PHP fournit quelques
interfaces qui peuvent être utiles lors de la mise en place de ces derniers :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://php.net/manual/fr/class.iterator.php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Iterator&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://php.net/manual/fr/class.traversable.php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Traversable&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://php.net/manual/fr/class.arrayaccess.php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ArrayAccess&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Je suis toujours étonné que ce pattern ne soit pas plus connu et utilisé des développeurs
PHP. Je rencontre souvent des développeurs qui me disent que la mise en place d’un tel
objet est inutile et n’apporte pas de valeur (si ce n’est de complexifier inutilement
le code). Sachez que vous manipulez ce genre de classe très souvent sans vous en rendre
compte. Lorsque vous utilisez l’ORM Doctrine par exemple, qui utilise un conteneur
&lt;code&gt;ArrayCollection&lt;/code&gt; pour la gestion des associations multiples des entités.&lt;/p&gt;
</description>
                    <pubDate>Wed, 22 Apr 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/04/22/le-pattern-conteneur-container.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/04/22/le-pattern-conteneur-container.html</guid>
                </item>
            
        
            
        
            
                297
                <item>
                    <title>Pas de logique dans les vues de vos applications MVC</title>
                    <description>&lt;p&gt;La semaine dernière je suis tombé sur un article de blog écrit par
&lt;a href=&quot;https://twitter.com/lkdjiin&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Xavier NAYRAC&lt;/a&gt;, intitulé
“&lt;a href=&quot;https://lkdjiin.github.io/blog/2015/03/28/pas-de-logique-dans-les-vues-rails/&quot; target=&quot;_target&quot;&gt;Pas de logique dans les vues Rails&lt;/a&gt;”.
Bien que l’article donne des exemples en Ruby, les propos du billet sont valables
pour tous les langages et toutes les applications de type MVC. L’article étant
pertinent, je relaie l’information.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://lkdjiin.github.io/blog/2015/03/28/pas-de-logique-dans-les-vues-rails/&quot; target=&quot;_target&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
</description>
                    <pubDate>Tue, 07 Apr 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/04/07/pas-de-logique-dans-les-vues-de-vos-applications-mvc.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/04/07/pas-de-logique-dans-les-vues-de-vos-applications-mvc.html</guid>
                </item>
            
        
            
                298
                <item>
                    <title>Utiliser les événements Symfony2 pour un code SOLID</title>
                    <description>&lt;p&gt;En tant que développeur, nous essayons d’écrire du code simple, maintenable et
facilement extensible. Pour cela, nous tentons de mettre en oeuvre les principes
évoqués dans l’acronyme &lt;a href=&quot;https://fr.wikipedia.org/wiki/SOLID_%28informatique%29&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;SOLID&lt;/a&gt;.
Le “S” correspond à la notion de “Single Responsibility” (responsabilité unique).
Cela signifie que vos classes doivent avoir une et une seule responsabilité, une
seule raison d’exister.&lt;/p&gt;

&lt;p&gt;Prenons pour exemple, un service Symfony dont l’objectif est d’enregistrer les
données d’un utilisateur issu d’un formulaire. Le code pourrait alors ressembler
à quelque chose comme cela :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// Controller/UserController.php
public function newAction(Request $request)
{
  $user = new User();

  $form = $this-&amp;gt;createForm(&amp;#39;user_form&amp;#39;, $user, [
    &amp;#39;action&amp;#39; =&amp;gt; $this-&amp;gt;generateUrl(&amp;#39;app_new_user&amp;#39;),
    &amp;#39;method&amp;#39; =&amp;gt; &amp;#39;POST&amp;#39;
  ]);
  $form-&amp;gt;add(&amp;#39;submit&amp;#39;, &amp;#39;submit&amp;#39;, [&amp;#39;label&amp;#39; =&amp;gt; &amp;#39;Create&amp;#39;]);

  $form-&amp;gt;handleRequest($request);
  if ($form-&amp;gt;isValid()) {
    $manager = $this-&amp;gt;get(&amp;#39;app.user_manager&amp;#39;);
    $manager-&amp;gt;save($user);

    return $this-&amp;gt;redirectToRoute(&amp;#39;user_show&amp;#39;, [&amp;#39;id&amp;#39; =&amp;gt; $user-&amp;gt;getId()]);
  }

  return [ &amp;#39;form&amp;#39; =&amp;gt; $form-&amp;gt;createView() ];
}

// Manager/UserManager.php
public function save(User $user)
{
  $this-&amp;gt;entityManager-&amp;gt;persist($user);
  $this-&amp;gt;entityManager-&amp;gt;flush();
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ce code respecte bien le principe de responsabilité unique, chacune de nos
classes n’a qu’un seul objectif. Mais imaginons maintenant que nous souhaitons
envoyer un email à l’utilisateur que nous venons de créer (pour le notifier de la
création de son compte et lui envoyer ses informations de connexion par exemple).&lt;/p&gt;

&lt;p&gt;Rien de difficile, il suffit alors de modifier notre manager pour envoyer le mail
lors de la création du compte :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// Manager/UserManager.php
public function save(User $user)
{
  $this-&amp;gt;entityManager-&amp;gt;persist($user);
  $this-&amp;gt;entityManager-&amp;gt;flush();

  $this-&amp;gt;emailManager-&amp;gt;sendNewAccountNotification($user);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Sauf qu’en ajoutant cette ligne, nous venons de casser le principe de responsabilité
unique de notre service gérant les utilisateurs. Réfléchissez bien, si vous souhaitez
réutiliser la classe &lt;code&gt;UserManager&lt;/code&gt; dans une autre application, mais que cette dernière
ne souhaite pas envoyer de notification, comment allez-vous faire ?&lt;/p&gt;

&lt;p&gt;Il vous faudra alors supprimer tous les appels à notre &lt;code&gt;EmailManager&lt;/code&gt; ou mettre en
place des conditions permettant de ne pas exécuter cette fonction. Dans le cas
présent, l’exemple est très simple et cela peut être fait facilement. Mais imaginez
une application de plusieurs milliers de lignes. Cela est tout de suite plus compliqué.&lt;/p&gt;

&lt;p&gt;Heureusement, Symfony2 nous permet d’éviter ce couplage très simplement, au travers
de la gestion des événements. L’idée est très simple, une fois l’enregistrement
du nouvel utilisateur effectué, nous allons émettre un signal afin d’indiquer le
succès de la création. Ce signal pourra alors être capter par différentes classes afin
de déclencher différentes actions (un envoi de notification dans notre exemple).&lt;/p&gt;

&lt;p&gt;Commençons par modifier notre classe &lt;code&gt;UserManager&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// Manager/UserManager.php
public function __construct(EventDispatcherInterface $dispatcher, ...)
{
  $this-&amp;gt;dispatcher = $dispatcher;
  // ...
}

public function save(User $user)
{
  $this-&amp;gt;entityManager-&amp;gt;persist($user);
  $this-&amp;gt;entityManager-&amp;gt;flush();

  $this-&amp;gt;dispatcher-&amp;gt;dispatch(&amp;#39;user.create&amp;#39;, new UserEvent($user));
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;La classe &lt;code&gt;UserEvent&lt;/code&gt; est tout simple un conteneur pour les informations traitées
par le formulaire (ici notre utilisateur créé).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;use Symfony\Component\EventDispatcher\Event;

class UserEvent extends Event
{
  private $user;

  public function __construct(User $user)
  {
    $this-&amp;gt;user = $user;
  }

  public function getUser()
  {
      return $this-&amp;gt;user;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne reste plus qu’à intercepter le signal et à envoyer notre email :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class UserNotificationListener implements EventSubscriberInterface
{
  private $emailManager;

  public function __construct(EmailManagerInterface $emailManager)
  {
    $this-&amp;gt;emailManager = $emailManager;
  }

  public function onUserCreate(UserEvent $event)
  {
    $this-&amp;gt;emailManager-&amp;gt;sendNewAccountNotification($event-&amp;gt;getUser());
  }

  public static function getSubscribedEvents()
  {
    return [
      &amp;#39;user.create&amp;#39; =&amp;gt; &amp;#39;onUserCreate&amp;#39;,
    ];
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Sans oublier de déclarer le service qui va bien :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;// Resources/config/services.yml
services:
  listener.user_mailer_notification:
    class: Listener\UserNotificationListener
    arguments:
      - @app.manager.email
    tags:
      - { name: kernel.event_subscriber }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous avons donc maintenant des classes qui ont bien une responsabilité unique. De
ce fait, le risque d’erreur est réduit. De plus cela, les rend plus facilement
testables. Il sera également plus facile de les réutiliser dans d’autres projets.&lt;/p&gt;

&lt;p&gt;Si vous souhaitez obtenir plus d’information concernant la gestion des événements
dans Symonfy2, je vous invite à lire &lt;a href=&quot;http://symfony.com/doc/current/components/event_dispatcher/introduction.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;la documentation&lt;/a&gt;
très bien rédigé à son sujet.&lt;/p&gt;
</description>
                    <pubDate>Mon, 06 Apr 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/04/06/programmation-evenement.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/04/06/programmation-evenement.html</guid>
                </item>
            
        
            
                299
                <item>
                    <title>Intégration continue sur RaspberryPI avec PHPCI</title>
                    <description>&lt;p&gt;Je possède un RaspberryPi que j’utilise essentiellement en tant que serveur
personnel. J’y entrepose entre autres quelques petits dépôts Git que je souhaite
garder privée. J’y ai également quelques scripts qui me permettent d’automatiser
le déploiement de quelques applications (comme ce blog par exemple).&lt;/p&gt;

&lt;p&gt;Pour automatiser un certain nombre de tâches et pour éviter de le faire
manuellement, je souhaitais utiliser un outil de type intégration continue. Mon
premier choix c’est alors porté sur le bien connu Jenkins. Bien évidemment, vu la
puissance de la machine (que ce soit un RPi 1 ou 2), Jenkins est lent et ne permet
pas d’être utilisé de manière fluide.&lt;/p&gt;

&lt;p&gt;En recherchant sur le Web des solutions alternatives, je suis retombé sur
&lt;a href=&quot;https://www.phptesting.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHPCI&lt;/a&gt;, une plateforme
d’intégration continue spécialement conçue pour PHP et développé en PHP.&lt;/p&gt;

&lt;p&gt;PHPCI ne se limite pas aux projets PHP. Je l’utilise pour une variété de tâches,
notamment grâce à un plugin “Shell” qui me permet d’exécuter des commandes bash
sur mes projets.&lt;/p&gt;

&lt;p&gt;La solution est simple, légère et extrêmement fluide sur mon Raspberry2. Un vrai
bonheur ! Comme je le racontais sur Twitter, je regrette vraiment de ne pas m’y
être penché plus tôt.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot; lang=&quot;fr&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;http://t.co/0HiuvDcMWl&quot;&gt;pic.twitter.com/0HiuvDcMWl&lt;/a&gt;&lt;/p&gt;&amp;mdash; Jérémy DECOOL (@jdecool)
  &lt;a href=&quot;https://twitter.com/jdecool/status/577145197626277890&quot;&gt;15 Mars 2015&lt;/a&gt;
&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;En plus, le code de l’outil est plutôt simple et pouvoir mettre les mains dans le
code est un vrai bonheur. De plus, l’équipe du produit (développé par une société
anglaise pour ces besoins) est très sympathique et réactive sur Github.&lt;/p&gt;

&lt;p&gt;Bien que cela ne fasse que quelques semaines que je l’utilise, l’outil me semble
efficace et me semble une réelle alternative à Jenkins. Le seul véritable
inconvénient, c’est le manque de plugin qui se fera sans doute sentir pour des
projets complexes.&lt;/p&gt;
</description>
                    <pubDate>Wed, 01 Apr 2015 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/04/01/integration-continue-sur-raspberrypi-avec-phpci.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/04/01/integration-continue-sur-raspberrypi-avec-phpci.html</guid>
                </item>
            
        
            
        
            
                300
                <item>
                    <title>Les objets-valeurs (Value Object)</title>
                    <description>&lt;p&gt;Tout récemment, nous avons eu un gros débat sur la conception d’un projet sur
lequel je travaille. Le débat portait sur l’utilisation ou non d’un objet de type
Objets-Valeur (ou Value Object en anglais). Il est vrai que dans l’environnement
PHP l’utilisation de ce type d’objet est plutôt rare et méconnu (il est plus
courant de voir utiliser des tableaux associatifs).&lt;/p&gt;

&lt;p&gt;Les objets de type Objets-Valeurs sont généralement des petits objets dont leur
objectif est d’apporter une notion sémantique au code. Contrairement à un autre
objet, on s’intéresse à son contenu (la valeur de ses attributs) plutôt qu’à sa
référence. Ces derniers sont généralement immuables.&lt;/p&gt;

&lt;p&gt;L’exemple par excellence est certainement le cas de la monnaie. Si vous souhaitez
gérer une notion d’argent, la monnaie peut alors être géré au moyen d’un
Objet-Valeur, qui pourrait être défini comme ci-dessous :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Money
{
  /** @var int */
  private $amount;

  /** @var string */
  private $currency;

  /**
   * @param int    $amount
   * @param string $currency
   */
  public function __construct($amount, $currency)
  {
    $this-&amp;gt;amount   = $amount;
    $this-&amp;gt;currency = $currency;
  }

  /**
   * @return int
   */
  public function getAmount()
  {
    return $this-&amp;gt;amount;
  }

  /**
   * @return float
   */
  public function getFormatedAmount()
  {
    return round($this-&amp;gt;amount / 100, 2);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cette exemple correspond à une implémentation du
&lt;a href=&quot;http://martinfowler.com/eaaCatalog/money.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;pattern monnaie&lt;/a&gt;
décrit par Martin Fowler.&lt;/p&gt;

&lt;p&gt;Il est alors plus agréable et compréhensible de manipuler un objet représentant
une donnée précise qu’une variable de type entier ou flottante ayant une faible
sémantique.&lt;/p&gt;

&lt;p&gt;Ce type d’objet est très utilisé dans une conception pilotée par le domaine (Domain
Driven Design) où le code est fortement basé sur le domaine métier et la logique
associée. Ils permettent alors d’avoir un code compréhensible et avec un minimum
d’ambiguïté.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;PS:&lt;/em&gt; si vous êtes intéressé par une implémentation d’un Objet-Valeur permettant de
gérer les devises. Sebastian BERGMANN a écrit une
&lt;a href=&quot;https://github.com/sebastianbergmann/money&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;implémentation complète en PHP&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Wed, 25 Mar 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/03/25/les-objets-valeurs-ou-value-object.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/03/25/les-objets-valeurs-ou-value-object.html</guid>
                </item>
            
        
            
        
            
                301
                <item>
                    <title>PHP-CLI cet outil méconnu</title>
                    <description>&lt;p&gt;De nombreux développeurs travaillent avec PHP pour concevoir des sites ou des
applications dans des environnements Web. On commence même à voir apparaître de
nombreux outils utilisant PHP en ligne de commande. Pourtant ils sont peu nombreux
à connaitre les possibilités offertes par PHP-CLI.&lt;/p&gt;

&lt;p&gt;Prenons par exemple le cas d’un développeur PHP souhaitant tester une fonction
PHP afin de l’utiliser. Je ne compte plus le nombre de développeurs ayant créé
un fichier &lt;code&gt;test.php&lt;/code&gt; afin de tester du code (fichier exécuté aussi bien via un
serveur Web qu’en ligne de commande).&lt;/p&gt;

&lt;p&gt;Effectivement, il est fréquent que les développeurs ignorent que PHP-CLI fournit
un &lt;a href=&quot;http://php.net/manual/fr/features.commandline.interactive.php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;shell interactif&lt;/a&gt;
permettant d’exécuter du code PHP (et donc de tester une fonction par exemple).
Pour démarrer ce dernier, il suffit d’utiliser la commande &lt;code&gt;php -a&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Prenons maintenant le cas du développeur qui souhaite afficher les informations
de configuration PHP. Là encore, de nombreuses personnes créeront un fichier
contenant un appel à la fonction &lt;code&gt;phpinfo()&lt;/code&gt;. PHP-CLI fournit pourtant une
option permettant d’afficher ces informations au travers de l’option &lt;code&gt;-i&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;De même si &lt;code&gt;phpinfo()&lt;/code&gt; est également utilisé pour connaitre les modules PHP
activées sur la machine, l’option &lt;code&gt;-m&lt;/code&gt; permet d’obtenir la liste des modules
actuellement actifs.&lt;/p&gt;

&lt;p&gt;Il est de plus en plus fréquent d’utiliser des applications PHP en ligne de
commande. L’exemple le plus courant est très certainement &lt;a href=&quot;http://getcomposer.org&quot;&gt;Composer&lt;/a&gt;.
N’avez-vous jamais lancé un &lt;code&gt;composer update&lt;/code&gt; et obtenu en réponse un message
du genre &lt;code&gt;Fatal Error: Allowed Memory Size of 134217728 Bytes Exhausted&lt;/code&gt; ?&lt;/p&gt;

&lt;p&gt;Dans ce cas-là, il n’est pas toujours possible de modifier temporairement la
configuration décrite dans le fichier &lt;code&gt;php.ini&lt;/code&gt;. Vous me direz qu’il est toujours
possible d’utiliser un appel à la fonction &lt;code&gt;ini_set&lt;/code&gt;. Mais cela vous obligera à
modifier votre code. Pour éviter cela (car en plus de ne pas être propre, c’est
parfois impossible à mettre en oeuvre), PHP-CLI vous donne la possibilité de
modifier une valeur de configuration au travers de l’argument &lt;code&gt;-d&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Par exemple pour exécuter Composer avec une limite mémoire à 1 Go, il est
possible d’utiliser la commande suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;php -d memory_limit=1024M /usr/local/bin/composer update&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il est bien évidemment possible de cumuler les variables de configuration à
modifier. Vous pouvez donc appelez Composer en activer dynamiquement une
extension PHP :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;php -d memory_limit=1024M -d extension=blackfire.so composer.php&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;N’hésitez donc pas à découvrir ou redécouvrir les nombreuses possibilités de
&lt;a href=&quot;http://php.net/manual/fr/features.commandline.php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PHP-CLI&lt;/a&gt;,
cela pourra vous servir et vous faciliter la vie au quotidien.&lt;/p&gt;
</description>
                    <pubDate>Fri, 06 Mar 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/03/06/php-cli-cet-outil-meconnu.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/03/06/php-cli-cet-outil-meconnu.html</guid>
                </item>
            
        
            
        
            
                302
                <item>
                    <title>Tests fonctionnels et gestion des dates</title>
                    <description>&lt;p&gt;Je travaille actuellement sur une application de gestion de prise de rendez-vous
à destination des professionnels. Afin de s’assurer du fonctionnement correct de
la plateforme, cette dernière est testée au travers de nombreux scénarios Behat.&lt;/p&gt;

&lt;p&gt;L’exécution des tests Behat est relativement classique. Avant chaque scénario, un
jeu de données est chargée en base de données afin de dérouler les différentes
étapes. Néanmoins cette méthode présente un inconvénient majeur puisque le jeu
de données est statique.&lt;/p&gt;

&lt;p&gt;Or lorsque l’on travaille avec des gestions de date, cela peut poser quelques
problèmes. Effectivement, si on prend par exemple un scénario de prise de rendez-vous
en tenant compte qu’un rendez-vous ne peut pas être pris pour une date antérieure
à la date du jour, il sera nécessaire de mettre régulièrement le jeu de données
à jour afin d’éviter que les données de ce dernier ne soient obsolètes. La date
de la machine exécutant les tests étant la date de référence.&lt;/p&gt;

&lt;p&gt;Pour résoudre ce problème, on peut penser à plusieurs solutions. La première est
certainement de générer un jeu de données dynamique afin d’avoir des données relatives
à la date du jour. Je n’aime pas trop cette solution pour diverses raisons. La
première car elle nécessite du développement (et que j’ai peu de temps :)). La
seconde, car on “perd” un certain contrôle sur le jeu de données et les tests vont
nécessiter plus de codes. Or je souhaite garder ces tests aussi simple que possible.&lt;/p&gt;

&lt;p&gt;La seconde solution, m’ayant traversé l’idée était de modifier la date système lors
de l’exécution des tests. Il ne m’a pas fallu longtemps pour me dire que c’était
une très mauvaise idée !&lt;/p&gt;

&lt;p&gt;Dans le cas présent, afin de faire le minimum de modification sur l’existant, en
introduisant le minimum de développement, j’ai eu recours à une application nommée
&lt;a href=&quot;https://github.com/wolfcw/libfaketime&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;faketime&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cette application permet d’intercepter les appels systèmes de récupération de la
date et l’heure. Faketime permet ainsi de démarrer un processus en altérant de
manière virtuelle la date de la machine uniquement pour le processus désigné.&lt;/p&gt;

&lt;p&gt;Lancer l’exécution des scénarios Behat en utilisant &lt;code&gt;faketime&lt;/code&gt; se fait au travers
d’une simple ligne de commande :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;faketime &amp;#39;2014-10-05 12:30:00&amp;#39; bin/behat  # Utilisation d&amp;#39;une date précise
faketime &amp;#39;last Friday&amp;#39; bin/behat          # Utilisation d&amp;#39;une date relative&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
                    <pubDate>Wed, 11 Feb 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/02/11/tests-fonctionnels-et-gestion-des-dates.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/02/11/tests-fonctionnels-et-gestion-des-dates.html</guid>
                </item>
            
        
            
                303
                <item>
                    <title>Ne faites pas de &quot;composer update&quot;</title>
                    <description>&lt;p&gt;Un titre provoquant certes, je devrais plutôt dire : “ne faites pas de composer
update” d’un seul coup sur la totalité des dépendances de votre projet (et en
plus c’est une mauvaise pratique) ! Et à plus forte raison si votre application
n’est pas bien couverte par des tests automatisés.&lt;/p&gt;

&lt;p&gt;Personnellement, j’ai eu le cas pas plus tard qu’aujourd’hui. Un développeur
devait réaliser une évolution mineure sur le projet sur lequel je travaille. Ni
une, ni deux, il ajoute une nouvelle dépendance à la main dans le &lt;code&gt;composer.json&lt;/code&gt;
et exécute un &lt;code&gt;composer update&lt;/code&gt; pour l’installer.&lt;/p&gt;

&lt;p&gt;Je passe le fait de ne pas avoir utilisé la ligne de commande adaptée pour cette
opération (un &lt;code&gt;composer require vendor/package&lt;/code&gt;), mais en plus il n’a pas pris la
peine de lancer les tests (unitaires et fonctionnels) présent sur le projet.&lt;/p&gt;

&lt;p&gt;Le résultat ? De nombreuses dépendances du projet ont été mise à jour. Certaines
introduisaient des “breaking changes” (dans un changement de version mineur) et
50% du projet est devenu non opérationnel en 30 secondes !&lt;/p&gt;
</description>
                    <pubDate>Thu, 05 Feb 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/02/05/ne-faites-pas-de-composer-update.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/02/05/ne-faites-pas-de-composer-update.html</guid>
                </item>
            
        
            
                304
                <item>
                    <title>Mocker un service Symfony2 dans un test Behat</title>
                    <description>&lt;p&gt;Il arrive fréquemment que l’on soit amené à utiliser des services Web tiers dans
les applications que nous concevons. Lors de l’écriture de nos tests, nous
utilisons des mocks (bouchons) afin de simuler le comportement de ces derniers.&lt;/p&gt;

&lt;p&gt;Dans le cas d’un test Behat le premier réflexe est certainement d’accéder au
containeur de dépendances depuis un contexte afin de modifier ce dernier à la
volée.&lt;/p&gt;

&lt;p&gt;Malheureusement cette solution ne fonctionne pas. Effectivement le conteneur de
dépendance n’est pas partagé entre notre application et les contextes utilisées
par Behat. Toute modification de nos dépendances est alors sans effet.&lt;/p&gt;

&lt;p&gt;Une solution consiste alors à utiliser une configuration du conteneur de
dépendances en fonction de l’environnement Symfony utilisé et d’exécuter nos
scénarios sur l’environnement correspondant.&lt;/p&gt;

&lt;p&gt;Commençons donc par modifier la configuration du service que nous souhaitons
mocker (un composant Facebook dans notre exemple) :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# src/AppBundle/Resources/config/services.yml
services:
    app.component.facebook:
        class: %app.component.facebook.class%&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Définissons maintenant la classe à utiliser dans le cas “normal” :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# app/config/config.yml
parameters:
    app.component.facebook.class: Component\Facebook\Facebook&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne nous reste plus qu’à surcharger ce paramètre lorsque l’on utilise
l’application dans son environnement de test :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# app/config/config_test.yml
parameters:
    app.component.facebook.class: AppBundle\Tests\Mock\Component\Facebook\FacebookMock&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Maintenant cette configuration terminée, en configurant Behat pour qu’il utilise
l’environnement de test de Symfony, les différents scénarios seront exécutés avec
la version de l’application utilisant les bouchons.&lt;/p&gt;

&lt;p&gt;La technique utilisée ici est simple, mais peut être contraignante si vous avez
un certain nombre de classes à “mocker”. Sur
&lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html &quot;&gt;son blog&lt;/a&gt;,
Grégoire Pineau (consultant chez SensioLabs) donne une autre alternative pour
réaliser cette tâche et surtout plus pertinente si vous travaillez sur de grosses
applications.&lt;/p&gt;
</description>
                    <pubDate>Tue, 03 Feb 2015 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2015/02/03/mocker-un-service-symfony2-dans-un-test-behat.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2015/02/03/mocker-un-service-symfony2-dans-un-test-behat.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                305
                <item>
                    <title>Protégez l&apos;accès à vos ressources</title>
                    <description>&lt;p&gt;Ce weekend, je baladais sur le site d’une agence musicale qui offre à ses clients
des prestations audiovisuelles. La société offre notamment un accès à un large
catalogue de musique à condition d’être un professionel du domaine de l’audiovisuel.
Sauf qu’en réalité, avec un peu de débrouille, l’ensemble du catalogue est librement
accessible.&lt;/p&gt;

&lt;p&gt;Effectivement, lorsque l’on navigue sur le site en question, un lecteur audio
permet d’écouter les titres disponibles. En analysant les trames réseaux, il est
facile d’accéder à l’adresse du fichier qui est écouté :&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog/20141116-protegez-l-acces-a-vos-ressources/capture-reseau.png&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Rien que via cette URL on peut récupérer le morceau complet au format MP3 encodé
en 128 kbps. En analysant l’adresse du fichier, il est aisé de reconnaitre le
schéma adopté &lt;code&gt;http://site.com/musicfiles/[format]/[id-musique].[format]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En recherchant les différents morceaux, les identifiants des musiques sont facilement
récupérables car ils apparaissent dans la barre d’adresse du navigateur.&lt;/p&gt;

&lt;p&gt;Cela aurait pu s’arrêter ici, mais en lisant les mentions légales du site, on
apprend qu’il est possible de télécharger les différents morceaux en format
“MP3 (320 kbps)”, “AIFF” ou “WAV” (non compressé). Et oui c’est l’ensemble du
catalogue dans tous les formats disponibles (et même haute définition) qui
sont librement accessibles.&lt;/p&gt;

&lt;p&gt;Il aurait pourtant été facile de protéger l’accès aux ressources en utilisant
un petit script PHP comme celui ci-dessous :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;ob_clean();

header(&amp;#39;Content-Type: audio/mp3&amp;#39;);
header(&amp;#39;Content-Disposition: filename=&amp;quot;fichier.mp3&amp;quot;&amp;#39;);
flush();

readfile(&amp;#39;chemin/vers/le/fichier.mp3&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Lorsque vous concevez un site, pensez bien à la sécurité des données de ce dernier
et pas seulement celles qui sont présentes en base de données.&lt;/p&gt;

&lt;p&gt;PS: un autre élément également inquiétant, les en-têtes renvoyées par le
serveur Apache et qui indique que la version de PHP utilisée est la 5.2.17. Soit
une version qui n’est plus maintenue depuis le 6 janvier 2011 !&lt;/p&gt;
</description>
                    <pubDate>Sun, 16 Nov 2014 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/11/16/protegez-l-acces-a-vos-ressources.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/11/16/protegez-l-acces-a-vos-ressources.html</guid>
                </item>
            
        
            
        
            
        
            
                306
                <item>
                    <title>Accéder aux commandes Doctrine dans Symfony2</title>
                    <description>&lt;p&gt;Vous ne le saviez peut-être pas, mais lorsque vous démarrez un projet Symfony2,
Doctrine est fourni avec un certain nombre de commandes prédéfinit. Parmi ces
dernières certaines peuvent être très utiles comme par exemple une commande
permettant d’importer un fichier SQL en base de données.&lt;/p&gt;

&lt;p&gt;Si vous ne le saviez pas, c’est normal, car ces commandes n’apparaissent pas par
défaut dans la console de Symfony. Mais étant donné qu’elles utilisent le
composant &lt;code&gt;Console&lt;/code&gt; du framework, il est facile d’y avoir accès.&lt;/p&gt;

&lt;p&gt;Pour cela, la solution la plus simple est de créer une commande dans son projet
et de la faire hériter de la commande Doctrine à laquelle on souhaite accéder.&lt;/p&gt;

&lt;p&gt;Par exemple, si je souhaite accéder à la commande de Doctrine DBAL permettant
d’insérer un fichier SQL en base de données, je peux écrire la commande ci-dessous :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;namespace JDecool\Bundle\DemoBundle\Command;

use Doctrine\DBAL\Tools\Console\Command;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ImportCommand extends Command\ImportCommand
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $container = $this-&amp;gt;getApplication()-&amp;gt;getKernel()-&amp;gt;getContainer();

        $doctrine = $container-&amp;gt;get(&amp;#39;doctrine&amp;#39;);

        $em = $doctrine-&amp;gt;getEntityManager();
        $db = $em-&amp;gt;getConnection();

        $helperSet = $this-&amp;gt;getHelperSet();
        $helperSet-&amp;gt;set(new ConnectionHelper( $db ), &amp;#39;db&amp;#39;);
        $helperSet-&amp;gt;set(new EntityManagerHelper( $em ), &amp;#39;em&amp;#39;);

        parent::execute($input, $output);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous souhaitez connaitre l’ensemble des commandes proposées par Doctrine, la
liste est disponible sur la &lt;a href=&quot;http://doctrine-orm.readthedocs.org/en/latest/reference/tools.html#command-overview&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;documentation officielle du projet&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Fri, 24 Oct 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/10/24/acceder-aux-commandes-doctrine-dans-un-projet-symfony2.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/10/24/acceder-aux-commandes-doctrine-dans-un-projet-symfony2.html</guid>
                </item>
            
        
            
        
            
                307
                <item>
                    <title>Injecter la Request dans un service Symfony2</title>
                    <description>&lt;p&gt;C’est une question qui revient souvent chez les développeurs, comment injecter
la Request Symfony2 dans un service ? La réponse ne semble pas si simple à
trouver car dans de très nombreux cas, je constate que pour pallier le problème,
c’est tout le conteneur de dépendances qui est transmet au service en question.
Une très mauvaise pratique !&lt;/p&gt;

&lt;p&gt;C’était effectivement, à “une époque”, l’unique solution car Symfony gérant
plusieurs Request, le problème de l’injection est très complexe. Depuis la
version 2.4 du framework, un service “request_stack” est désormais disponible.&lt;/p&gt;

&lt;p&gt;Si vous souhaitez avoir plus d’informations sur ce service et son utilisation, je
vous renvoie vers &lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;http://symfony.com/fr/doc/current/book/service_container.html#injecter-la-request &quot;&gt;la documentation du framework&lt;/a&gt;
expliquant comment utiliser la RequestStack.&lt;/p&gt;
</description>
                    <pubDate>Thu, 09 Oct 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/10/09/injecter-la-request-dans-un-service-symfony2.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/10/09/injecter-la-request-dans-un-service-symfony2.html</guid>
                </item>
            
        
            
                308
                <item>
                    <title>Comment obtenir l&apos;opcode d&apos;un script PHP ?</title>
                    <description>&lt;p&gt;Si vous suivez un peu ce qui se passe dans la sphère PHP, vous avez peut-être
vu passer une explication d’Igor WIEDLER concernant l’utilisation de l’instruction
&lt;code&gt;goto&lt;/code&gt; dans une librairie qu’il a écrit. Pour justifier son choix, Igor s’est
appuyé sur l’opcode résultant de l’interprétation de son code. Je me suis alors
demandé comment est-il possible de générer l’opcode résultant d’un script PHP.&lt;/p&gt;

&lt;p&gt;Avant toute chose, je vais rappeler rapidement ce qu’est l’opcode. Lorsqu’un script
PHP est exécuté, la machine virtuelle PHP va transformer le script lu en un langage
intermédiaire qui représente des opérations de base. Ces instructions de bas niveau
sont généralement appelé “&lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;http://php.net/manual/fr/internals2.opcodes.php &quot;&gt;opcode&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;Il y a plusieurs raisons qui peuvent pousser un développeur à visualiser l’opcode
d’un fichier PHP. Tout d’abord par curiosité intellectuelle afin de comprendre
comment fonctionne le langage. Il faut également savoir que par défaut, la machine
virtuelle PHP n’optimise pas le code source qui est lu. Dans le cas, où il n’y a ni
mécanisme d’optimisation, ni cache d’opcode, il peut être intéressant de connaitre
l’opcode généré afin de faire quelques optimisations de son code.&lt;/p&gt;

&lt;p&gt;Le moyen qui est certainement le plus simple pour visualiser l’opcode d’un code PHP
est de se rendre sur le site &lt;a href=&quot;http://3v4l.org&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;3v4l&lt;/a&gt; qui
permet de tester du code sur plusieurs versions de PHP, mais il fournit également
l’opcode résultant aussi bien pour le ZendEngine (la machine virtuelle PHP) que pour
HHVM (la machine virtuelle de Facebook compatible PHP).&lt;/p&gt;

&lt;p&gt;La solution précédente est simple, mais vous oblige à posséder une connexion Internet
et à copier/coller vos différents scripts sur le site. Une autre solution consiste
alors à installer une extension PHP qui nous permettra de visualiser la représentation
interne des scripts exécutés.&lt;/p&gt;

&lt;p&gt;L’extension en question s’appelle &lt;a href=&quot;http://derickrethans.nl/projects.html#vld&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vulcan Logic Disassembler&lt;/a&gt;.
Cette dernière s’installe facilement au travers la commande &lt;code&gt;pecl&lt;/code&gt; ou en récupérant son
code source et en le compilant.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ pecl install vld-beta&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une fois l’extension installée, vous pourrez visualiser l’opcode d’un script via la commande :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ php -dextension=vld.so -dvld.active=1 mon-script.php

Finding entry points
Branch analysis from position: 0
Return found
filename:       /in/t0fJ6
function name:  (null)
number of ops:  2
compiled vars:  none
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  &amp;gt;   ECHO                                                     &amp;#39;Hello+World&amp;#39;
         1    &amp;gt; RETURN                                                   1&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous vous intéressez au fonctionnement interne de PHP, je ne peux que vous conseillez
de lire &lt;a href=&quot;http://www.phpinternalsbook.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ce livre collaboratif&lt;/a&gt;
expliquant et décrivant le fonctionne interne de PHP.&lt;/p&gt;

&lt;p&gt;Vous pourrez également en apprendre un peu plus avec le livre
&lt;a href=&quot;https://leanpub.com/developper-une-extension-php&quot; target=&quot;_balnk&quot;&gt;développer une extension PHP&lt;/a&gt;
de Pascal MARTIN, qui tient également un blog où chaque mois, il résume ce qui se passe
sur &lt;a href=&quot;http://news.php.net/php.internals&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;@internals&lt;/a&gt;, la mailing-list
des développeurs de PHP.&lt;/p&gt;

&lt;hr /&gt;

&lt;ul&gt;
  &lt;li&gt;L’&lt;a href=&quot;https://github.com/igorw/retry/issues/3&quot; target=&quot;_target&quot;&gt;issue Github&lt;/a&gt; dont il est question dans l’article&lt;/li&gt;
  &lt;li&gt;Un &lt;a href=&quot;http://blog.pascal-martin.fr/post/aller-plus-loin-avec-dump-opcodes-script-php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;article&lt;/a&gt; de
Pascal MARTIN sur une utilisation avancée de Vulcan Logic Disassembler.&lt;/li&gt;
&lt;/ul&gt;
</description>
                    <pubDate>Tue, 07 Oct 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/10/07/comment-obtenir-l-opcode-d-un-script-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/10/07/comment-obtenir-l-opcode-d-un-script-php.html</guid>
                </item>
            
        
            
                309
                <item>
                    <title>Un adapteur générique pour appeler les fonctions natives de PHP</title>
                    <description>&lt;p&gt;L’adaptateur (adapter en anglais) est l’un des design patterns les plus connus
et utilisés en programmation. “Il permet de convertir l’interface d’une classe
en une autre interface que le client attend. L’adaptateur fait fonctionner
ensemble des classes qui n’auraient pas pu fonctionner sans lui, à cause d’une
incompatibilité d’interfaces” (définition Wikipedia).&lt;/p&gt;

&lt;p&gt;Il ne s’agit ni plus ni moins que d’un proxy qui va se charger de faire des appels
d’une classe à un autre en transformant les données dans le format attendu. Les
adaptateurs sont très utiles pour l’écriture de tests unitaires car ils permettent
au travers de l’injection de dépendances d’être remplacé par un mock. C’est un
cas d’utilisation très pratique lorsque l’on souhaite tester des blocs de code
faisant appel à des fonctions natives de PHP.&lt;/p&gt;

&lt;p&gt;Par exemple, si l’on souhaite lire un fichier de configuration, nous pourrions
écrire le code suivant :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function getUserConfiguration($user, $key)
{
    $configurationFile = file_get_contents($user-&amp;gt;getConfigurationFile());

    $configuration = $this-&amp;gt;parse($configurationFile);
    return $configuration[$key];
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le code ci-dessus pose problème, car il est impossible de contrôler l’exécution
de la méthode &lt;code&gt;file_get_contents&lt;/code&gt;. Si le test échoue, cela ne sera pas forcément
lié au code qui a été écrit, mais peut-être au système de fichiers qui est
indisponible au moment du test.&lt;/p&gt;

&lt;p&gt;On peut alors écrire le test en utilisant un adaptateur :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function getUserConfiguration($user, $key)
{
    $configurationFile = $this-&amp;gt;fileAdapter-&amp;gt;getFileContent($user-&amp;gt;getConfigurationFile());

    $configuration = $this-&amp;gt;parse($configurationFile);
    return $configuration[$key];
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;De cette manière, le code devient facilement testable, puisqu’il suffira
d’utiliser un mock de la classe &lt;code&gt;fileAdapter&lt;/code&gt; implémentant la méthode
&lt;code&gt;getFileContent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C’est d’ailleurs en jetant un oeil dans le code source
d’&lt;a href=&quot;https://github.com/atoum/atoum&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Atoum&lt;/a&gt;, un framework
de tests unitaires (que je recommande vivement) que j’ai découvert un adaptateur
générique permettant de tester naturellement les fonctions natives de PHP
(également présenté dans un très bon &lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;http://jubianchi.fr/atoum-adapters.htm &quot;&gt;article de Julien BIANCHI&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Voici le code de l’adaptateur :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// disponible à l&amp;#39;URL suivante =&amp;gt; https://github.com/atoum/atoum/blob/master/classes/adapter.php
class adapter
{
    public function __call($functionName, $arguments)
    {
        return $this-&amp;gt;invoke($functionName, $arguments);
    }

    public function invoke($functionName, array $arguments = array())
    {
        return call_user_func_array($functionName, $arguments);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cette classe est très astucieuse et fait appel
“&lt;a href=&quot;http://php.net/manual/fr/language.oop5.magic.php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;aux méthodes magiques”&lt;/a&gt;
de PHP afin de rediriger les appels de l’adaptateur vers les fonctions natives du langage.
De cette manière, plus besoin de créer une multitude de classes adaptateurs pour
garantir que l’ensemble de notre code soit testable unitairement.&lt;/p&gt;

&lt;p&gt;Attention toutefois à ne pas en abuser, les adaptateurs comme tout principe de
programmation doivent être utilisé de manière judicieuse.&lt;/p&gt;
</description>
                    <pubDate>Wed, 01 Oct 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/10/01/creer-un-adapteur-generique-pour-appeler-les-fonctions-natives-de-php.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/10/01/creer-un-adapteur-generique-pour-appeler-les-fonctions-natives-de-php.html</guid>
                </item>
            
        
            
                310
                <item>
                    <title>Des problèmes avec PHPStorm sous Linux ? Vérifier votre JRE</title>
                    <description>&lt;p&gt;J’utilise depuis peu un nouvel environnement de développement basé sur une
distribution Linux (Ubuntu 14.04). Ce fut l’occasion de réinstaller un stack
LAMP complète avec l’IDE par excellence, j’ai nommé PHPStorm.&lt;/p&gt;

&lt;p&gt;A priori aucun problème et tout se déroule correctement. C’est lors de
l’utilisation de PHPStorm que je me suis aperçu de nombreux petits soucis
(essentiellement graphique) mais très dérangeant pour un usage au quotidien.&lt;/p&gt;

&lt;p&gt;Par exemple, la fenêtre d’auto-complétion qui ne se positionne pas au niveau du
curseur, mais dans le coin en haut à droite de l’écran :&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog/20140929-phpstorm-java/phpstorm-java-oracle.png&quot; alt=&quot;PHPStorm avec le JDK Oracle&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;J’ai pourtant suivi la
&lt;a href=&quot;http://wiki.jetbrains.net/intellij/Installing_and_running_PHPStorm_on_Ubuntu&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;procédure d’installation de Jetbrains&lt;/a&gt;
en installant une version officiel du JDK d’Oracle :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ java -version

java version &amp;quot;1.8.0_20&amp;quot;
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et pourtant c’est en remettant une version OpenJDK installée de base sur l’OS
que l’ensemble de mes problèmes ont disparus.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ java -version

java version &amp;quot;1.7.0_65&amp;quot;
OpenJDK Runtime Environment (IcedTea 2.5.2) (7u65-2.5.2-3~14.04)
OpenJDK 64-Bit Server VM (build 24.65-b04, mixed mode)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;J’ai alors immédiatement tenté l’installation du JDK 7 d’Oracle. Et là, aucun
problème non plus. C’est donc bien la version 8 de Java qui est en défaut.&lt;/p&gt;

&lt;p&gt;Donc si vous utilisez PHPStorm sous une Ubuntu (mais peut-être que le problème
est présent sur les autres OS), pensez bien à vérifier le JDK installé sur votre
machine.&lt;/p&gt;
</description>
                    <pubDate>Mon, 29 Sep 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/09/29/des-problemes-avec-phpstorm-sous-ubuntu-surveillez-votre-version-de-java.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/09/29/des-problemes-avec-phpstorm-sous-ubuntu-surveillez-votre-version-de-java.html</guid>
                </item>
            
        
            
                311
                <item>
                    <title>Monitorez vos applications Symfony2 !</title>
                    <description>&lt;p&gt;Vous venez de terminer votre application Symfony2 et de la déployer en
production. Tous vos tests (unitaires et fonctionnels) sont au vert. Pourtant
qu’est-ce qui vous assure que tout fonctionne correctement sur votre serveur ?
Afin d’obtenir des informations sur ce qui se passe, il est nécessaire de
mettre en place une solution de monitoring au sein de votre projet Symfony2.&lt;/p&gt;

&lt;p&gt;Pour cela, je ne peux que vous conseiller d’utiliser le bundle
&lt;a href=&quot;https://github.com/SoCloz/SoclozMonitoringBundle&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;SoclozMonitoringBundle&lt;/a&gt;
qui vous permettra de monitorer (simplement et rapidement) le code Symfony sur vos serveurs de
production afin de :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;vous envoyer des emails lors du lancement d’exception&lt;/li&gt;
  &lt;li&gt;profiler le code PHP et d’envoyer les informations à &lt;a href=&quot;https://github.com/etsy/statsd/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;statsd&lt;/a&gt;
(un système de statistique)&lt;/li&gt;
  &lt;li&gt;logguer les informations de profiling&lt;/li&gt;
  &lt;li&gt;ajouter des en-têtes HTTP en cas de bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La mise en place (ainsi que la configuration) de ce bundle est extrêmement simple et
rapide. Il vous faudra bien sûr, commencer par déclarer la dépendance dans votre
fichier &lt;em&gt;composer.json&lt;/em&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
    &amp;quot;require&amp;quot;: {
        ...,
        &amp;quot;socloz/monitoring-bundle&amp;quot;: &amp;quot;dev-master&amp;quot;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et activer le bundle dans le kernel de Symfony :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class AppKernel extends Kernel
{
    public function registerBundles()
    {
        // ...

        if ($this-&amp;gt;getEnvironment() == &amp;#39;prod&amp;#39;) {
            $bundles[] = new Socloz\MonitoringBundle\SoclozMonitoringBundle();
        }

        // ...

        return $bundles;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une fois ces deux opérations effectués, il ne vous reste plus qu’à configurer
le bundle. Par exemple, l’exemple ci-dessous enverra un mail à l’adresse
&lt;em&gt;monitoring@mon-serveur.com&lt;/em&gt; lorsqu’une exception sera levée par l’application
sauf pour les exceptions &lt;em&gt;NotFoundHttpException&lt;/em&gt; et &lt;em&gt;AccessDeniedHttpException&lt;/em&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;socloz_monitoring:
    exceptions:
        enable: true
        ignore:
            - &amp;#39;Symfony\Component\HttpKernel\Exception\NotFoundHttpException&amp;#39;
            - &amp;#39;Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException&amp;#39;
    profiler:
        enable: false
    mailer:
        enable: true
        from: system@mon-serveur.com
        to: monitoring@mon-serveur.com
    statsd:
        enable: false&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Un bundle indispensable pour garder le contrôle et améliorer la qualité de vos
projets.&lt;/p&gt;
</description>
                    <pubDate>Tue, 23 Sep 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/09/23/monitorez-vos-applications-symfony2.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/09/23/monitorez-vos-applications-symfony2.html</guid>
                </item>
            
        
            
                312
                <item>
                    <title>Protéger un champ de formulaire Symfony2 avec un droit</title>
                    <description>&lt;p&gt;Il arrive fréquemment que l’on souhaite restreindre l’accès à certains champs de
nos formulaires avec un (ou plusieurs) droit(s) utilisateur. Pour cela, une
majorité de développeurs passera un paramètre dans le tableau d’options reçu
par la fonction &lt;em&gt;buildForm&lt;/em&gt; obligeant alors à modifier le code du formulaire pour
traiter l’information reçue. Nous allons voir une alternative beaucoup plus
élégante.&lt;/p&gt;

&lt;p&gt;Pour cela nous allons créer &lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;http://symfony.com/fr/doc/current/cookbook/form/create_form_type_extension.html &quot;&gt;une extension de type de formulaire&lt;/a&gt;
qui se chargera de la vérification des droits et affichera ou non auprès de l’
utilisateur de notre application.&lt;/p&gt;

&lt;p&gt;Prenons pour exemple un formulaire permettant de créer un billet de blog :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

namespace JDecool\Bundle\ForumBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class ThreadType extends AbstractType
{
     /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            -&amp;gt;add(&amp;#39;title&amp;#39;)
            -&amp;gt;add(&amp;#39;content&amp;#39;, &amp;#39;textarea&amp;#39;)
            -&amp;gt;add(&amp;#39;solved&amp;#39;, &amp;#39;checkbox&amp;#39;, [
                &amp;#39;required&amp;#39;   =&amp;gt; false,
            ])
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver-&amp;gt;setDefaults(array(
            &amp;#39;data_class&amp;#39; =&amp;gt; &amp;#39;JDecool\Bundle\ForumBundle\Entity\Thread&amp;#39;
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return &amp;#39;forum_thread&amp;#39;;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Maintenant que notre formulaire est défini, nous allons restreindre l’affichage
du champ &lt;em&gt;solved&lt;/em&gt; uniquement au utilisateurs ayant le rôle &lt;em&gt;ROLE_ADMIN&lt;/em&gt;. Pour cela
nous allons simplement ajouter une nouvelle propriété au champ correspondant.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        -&amp;gt;add(&amp;#39;title&amp;#39;)
        -&amp;gt;add(&amp;#39;content&amp;#39;, &amp;#39;textarea&amp;#39;)
        -&amp;gt;add(&amp;#39;solved&amp;#39;, &amp;#39;checkbox&amp;#39;, [
            &amp;#39;required&amp;#39;   =&amp;gt; false,
            &amp;#39;is_granted&amp;#39; =&amp;gt; &amp;#39;ROLE_ADMIN&amp;#39;,
        ])
    ;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour que ce paramètre soit interprété, nous allons créer notre extension de type
formulaire en lui injectant le &lt;a href=&quot;http://api.symfony.com/master/Symfony/Component/Security/Core/SecurityContext.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;SecurityContext&lt;/a&gt;
Symfony afin de vérifier les droits de l’utilisateur connecté.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

namespace JDecool\Bundle\ForumBundle\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;

class SecurityTypeExtension extends AbstractTypeExtension
{
    /**
     * The security context
     * @var SecurityContextInterface
     */
    private $securityContext;

    /**
     * Object constructor
     */
    public function __construct(SecurityContextInterface $securityContext)
    {
        $this-&amp;gt;securityContext = $securityContext;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $grant = $options[&amp;#39;is_granted&amp;#39;];
        if (null === $grant || $this-&amp;gt;securityContext-&amp;gt;isGranted($grant)) {
            return;
        }

        $builder-&amp;gt;addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
            $form = $event-&amp;gt;getForm();
            if ($form-&amp;gt;isRoot()) {
                return;
            }

            $form-&amp;gt;getParent()-&amp;gt;remove($form-&amp;gt;getName());
        });
    }

    /**
     * {@inheritdoc}
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver-&amp;gt;setDefaults(array(&amp;#39;is_granted&amp;#39; =&amp;gt; null));
    }

    /**
     * {@inheritdoc}
     */
    public function getExtendedType()
    {
        return &amp;#39;form&amp;#39;;
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne reste plus qu’à enregistrer notre extension en tant que service :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;services:
    jdecool_forum_bundle.security_type_extension:
        class: JDecool\Bundle\ForumBundle\Form\Extension\SecurityTypeExtension
        arguments:
            - @security.context
        tags:
            - { name: form.type_extension, alias: form }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et voilà, il est maintenant possible de restreindre n’importe quel champ de
formulaire avec un droit utilisateur sans avoir besoin de donner en paramètre
l’utilisateur connecté ou à manipuler ce dernier dans notre classe de formulaire.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Mise à jour du 10/09/2014:&lt;/strong&gt; Suite à de nombreuses réactions sur Twitter, je tiens
à rendre à Ceasar ce qui appartient à Ceasar. &lt;strong&gt;Je ne suis pas l’auteur de ce code&lt;/strong&gt;.
Ce code a été trouvé dans un projet sur lequel je travaillais et que j’ai souhaité
partager. Après des discussions, l’auteur est &lt;a href=&quot;http://alexandre-salome.fr/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Alexandre SALOME&lt;/a&gt;,
consultant chez SensioLabs qui avait donné cette astuce lors d’une
&lt;a href=&quot;http://alexandre-salome.fr/media/formulaires-symfony2.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;présentation à un sfPot à Paris&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Tue, 09 Sep 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/09/09/proteger-un-champ-de-formulaire-symfony2-avec-un-droit.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/09/09/proteger-un-champ-de-formulaire-symfony2-avec-un-droit.html</guid>
                </item>
            
        
            
                313
                <item>
                    <title>Utiliser une branche Github avec Composer</title>
                    <description>&lt;p&gt;Lorsque l’on travaille sur un projet avec de multiples dépendances, il arrive que l’on soit amené à faire évoluer l’une d’entre elles pour les besoins de notre développement. Nous devons alors spécifier à Composer sur qu’elle branche il doit travailler.&lt;/p&gt;

&lt;p&gt;Pour cela, nous allons prendre pour exemple une dépendance récupérée depuis un dépôt Github (le fonctionnement est similaire pour tout autre dépôt, via un Satis par exemple).&lt;/p&gt;

&lt;p&gt;Il faut donc tout d’abord spécifier à Composer sur quel dépôt il va devoir travailler. Cette configuration se fait au travers d’une section “repositories” à ajouter dans le fichier &lt;code&gt;composer.json&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
	// ...

    &quot;repositories&quot;: [
        {
            &quot;type&quot;: &quot;git&quot;,
            &quot;url&quot;: &quot;https://github.com/jdecool/my-lib&quot;
        }
    ],

    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Une fois le dépôt spécifié, il ne reste plus qu’à indiquer la branche de travail de votre dépendance.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
	// ...

	&quot;require&quot;: {
        // ...
        &quot;vendor/my-lib&quot;: &quot;dev-my-branch&quot;
    },

    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Vous pouvez maintenant effectuer un &lt;code&gt;composer update vendor/my-lib&lt;/code&gt; pour mettre à jour la dépendance avec la branche du dépôt que vous venez de configurer.&lt;/p&gt;
</description>
                    <pubDate>Fri, 05 Sep 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/09/05/utiliser-une-branche-github-avec-composer.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/09/05/utiliser-une-branche-github-avec-composer.html</guid>
                </item>
            
        
            
                314
                <item>
                    <title>Protégez vos actions Symfony2</title>
                    <description>&lt;p&gt;Il est parfois excessif de créer un formulaire pour effectuer certaines actions
au sein d’une application. Il est alors d’usage d’utiliser un lien HTML
permettant d’effectuer ces opérations (c’est par exemple souvent le cas pour
supprimer un élément dans une liste). Il est alors important de sécuriser ses
actions.&lt;/p&gt;

&lt;p&gt;Pour cela, imaginons une liste d’élément s’affichant de la manière suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&amp;lt;table&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;Mon élement 1&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;a href=&amp;quot;/element/1/delete&amp;quot;&amp;gt;Supprimer&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;

  &amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;Mon élement 2&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;a href=&amp;quot;/element/2/delete&amp;quot;&amp;gt;Supprimer&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Bien sûr il ne s’agit là que d’un exemple, et dans une application réelle, il
faudrait que l’action de suppression soit effectuée au travers d’une méthode
POST ou DELETE.&lt;/p&gt;

&lt;p&gt;En supposant qu’il y ait déjà une restriction en fonction des droits de l’
utilisateur connecté, nous allons donc protéger cette action d’une attaque de type
&lt;a href=&quot;http://fr.wikipedia.org/wiki/Cross-Site_Request_Forgery&quot;&gt;CSRF&lt;/a&gt;. Pour cela nous
allons voir comment générer un jeton qui permettra d’identifier la requête qui
sera effectuée.&lt;/p&gt;

&lt;p&gt;Nous allons donc commencer par créer le jeton dans l’action qui affiche la liste
et ajouter la vérification du token dans l’action de suppression :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// src/JDecool/MyBundle/Controller/DemoController.php

public function listAction(Request $request)
{
	// ...
	$token = $this-&amp;gt;get(&amp;#39;form.csrf_provider&amp;#39;)-&amp;gt;generateCsrfToken(&amp;#39;element_list&amp;#39;);

	return $this-&amp;gt;render(&amp;#39;MyBundle::list.html.twig&amp;#39;, [
		// ...
		&amp;#39;token&amp;#39; =&amp;gt; $token,
	]);
}

public function deleteAction(Request $request, $id)
{
	if (!$this-&amp;gt;get(&amp;#39;form.csrf_provider&amp;#39;)-&amp;gt;isCsrfTokenValid(&amp;#39;element_list&amp;#39;, $token)) {
		$this-&amp;gt;redirect(&amp;#39;message_list&amp;#39;);
	}

	// ...
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une fois ce code ajouté, il ne restera plus qu’à passer le jeton lors de la
génération du lien de l’action.&lt;/p&gt;

&lt;p&gt;Notez également que le jeton généré ici restera valide pour l’ensemble de la
durée de la session de l’utilisateur et qu’il pourra être réutilisé. Le jeton
est également généré à l’aide du paramètre &lt;code&gt;secret&lt;/code&gt;présent dans le fichier
&lt;code&gt;app/config/parameters.yml&lt;/code&gt; d’où l’importance de modifier ce dernier.&lt;/p&gt;

&lt;p&gt;C’est une méthode simple et rapide qui vise à protéger votre application, et
pourtant peu de développeurs l’utilise.&lt;/p&gt;

</description>
                    <pubDate>Tue, 02 Sep 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/09/02/protegez-vos-actions-symfony2.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/09/02/protegez-vos-actions-symfony2.html</guid>
                </item>
            
        
            
                315
                <item>
                    <title>Visualiser le code coverage des tests Atoum dans PHPStorm</title>
                    <description>&lt;p&gt;Lorsque je parle d’Atoum auprès de personnes qui ne l’utilisent pas, un des éléments qui revient très souvent (pour les utilisateurs de PHPStorm tout du moins), est l’impossibilité de visualiser la couverture du code par les tests directement dans l’IDE. Or à partir de maintenant, cela sera un argument qui ne tient plus.&lt;/p&gt;

&lt;p&gt;Effectivement, il est désormais possible de visualiser le code coverage des tests Atoum directement dans PHPStorm grâce au plugin &lt;a href=&quot;http://plugins.jetbrains.com/plugin/6167?pr=phpStorm&quot;&gt;PHPUnit code coverage&lt;/a&gt; qui est désormais compatible avec les fichiers de type “clover” générés par Atoum.&lt;/p&gt;

&lt;p&gt;Pour installer le plugin dans l’IDE, il faut aller dans les préférences de l’outil, puis dans la section “Plugin”. Cliquez sur le bouton “Browse repositories…” pour parcourir l’ensemble des plugins disponibles, et recherchez “PHPUnit code coverage” pour l’installer (il sera nécessaire de redémarrer PHPStorm).&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog//20140819-atoum-phpstorm/phpstorm-plugin.png&quot; alt=&quot;Installation PHPUnit code coverage&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Pour pouvoir utiliser le plugin, nous allons maintenant devoir configurer Atoum pour que ce dernier nous génère un rapport de type “clover”. Ce rapport se présente sous la forme d’un fichier XML décrivant tous les fichiers qui ont été parcouru par nos tests et indique quelles lignes ont été executé ou non. Le plugin lit ce fichier pour retranscrire son contenu dans l’IDE.&lt;/p&gt;

&lt;p&gt;Pour générer ce rapport, il faut ajouter les lignes ci-dessous dans votre fichier &lt;strong&gt;.atoum.php&lt;/strong&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$cloverWriter = new atoum\writers\file(__DIR__.&amp;#39;/build/atoum.clover.xml&amp;#39;);
$cloverReport = new atoum\reports\asynchronous\clover();
$cloverReport-&amp;gt;addWriter($cloverWriter);
$runner-&amp;gt;addReport($cloverReport);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Une fois cela effectué, il ne vous reste plus qu’à configurer le plugin en lui indiquant où trouver le fichier généré. Il faut alors retourner dans les préférences de PHPStorm et ouvrir la section “PHPUnit code coverage”:&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog//20140819-atoum-phpstorm/phpstorm-configuration.png&quot; alt=&quot;Configuration PHPUnit code coverage&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;La configuration terminée, vous pourrez alors visualiser les blocs de votre code couvert par les tests et ceux qui ne le sont pas.&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog//20140819-atoum-phpstorm/phpstorm-code-coverage.png&quot; alt=&quot;PHPStorm Atoum code coverage&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;&lt;a href=&quot;http://plugins.jetbrains.com/plugin/6167?pr=phpStorm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Plugin PHPUnit code coverage&lt;/a&gt;&lt;/p&gt;
</description>
                    <pubDate>Tue, 19 Aug 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/08/19/visualiser-le-code-coverage-des-tests-atoum-dans-phpstorm.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/08/19/visualiser-le-code-coverage-des-tests-atoum-dans-phpstorm.html</guid>
                </item>
            
        
            
                316
                <item>
                    <title>Des dépôts Composer privés avec Satis</title>
                    <description>&lt;p&gt;Composer est certainement l’un des outils PHP les plus connus et utilisé
à l’heure actuelle. Il permet en effet de gérer très facilement les dépendances
d’un projet PHP. Cependant, (trop) peu de développeurs connaissent ou utilisent
Satis, l’outil de création de dépôts Composer.&lt;/p&gt;

&lt;p&gt;Pourtant Satis est un outil très pratique et permet de mettre en place une gestion
de dépôts privés. On peut également l’utiliser afin de mirrorer nos dépendances
Composer provenant de Github, ce qui pourrait permettre de continuer à travailler
même si Github venait à ne plus être disponible.&lt;/p&gt;

&lt;p&gt;Si peu de personnes utilisent Satis, c’est peut-être à cause de sa mise en place qui
peut sembler (au premier abord du moins) complexe. Pourtant, nous allons voir qu’il
n’y a rien de compliqué et que la configuration de Satis ressemble étrangement à celle
de Composer.&lt;/p&gt;

&lt;p&gt;Nous allons donc commencer par installer Satis. Bien entendu, nous allons utiliser
Composer pour cela. L’installation de Satis se fait en une ligne de commande:
&lt;code&gt;php composer.phar create-project composer/satis --stability=dev --keep-vcs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Une fois installé, il faut configurer Satis en lui indiquant comment accéder à
nos dépôts. Voici un exemple de fichier de configuration:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
    &amp;quot;name&amp;quot;: &amp;quot;Satis&amp;quot;,
    &amp;quot;homepage&amp;quot;: &amp;quot;https://satis.local.dev&amp;quot;,
    &amp;quot;archive&amp;quot;: {
        &amp;quot;directory&amp;quot;: &amp;quot;dist&amp;quot;,
        &amp;quot;absolute-directory&amp;quot; : &amp;quot;/home/satis/dist&amp;quot;,
        &amp;quot;format&amp;quot;: &amp;quot;zip&amp;quot;,
        &amp;quot;skip-dev&amp;quot;: true
    },
    &amp;quot;repositories&amp;quot;: [
        { &amp;quot;type&amp;quot;: &amp;quot;composer&amp;quot;, &amp;quot;url&amp;quot;: &amp;quot;https://packagist.org&amp;quot; },
        { &amp;quot;type&amp;quot;: &amp;quot;git&amp;quot;, &amp;quot;url&amp;quot;: &amp;quot;git://git.local.dev/my-bundle&amp;quot; },
    ],
    &amp;quot;require&amp;quot;: {
        &amp;quot;symfony/symfony&amp;quot;: &amp;quot;2.5.*&amp;quot;,
        &amp;quot;doctrine/orm&amp;quot;: &amp;quot;*&amp;quot;,
        &amp;quot;private/my-bundle&amp;quot;: &amp;quot;*&amp;quot;
    },
    &amp;quot;require-all&amp;quot;: true
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Je ne vais pas détailler ici l’ensemble des éléments de la configuration de Satis, je
vous invite à lire la documentation de Composer pour cela. J’attire cependant votre
attention sur la section &lt;code&gt;require&lt;/code&gt; qui, comme dans un fichier &lt;code&gt;composer.json&lt;/code&gt;, indique
les dépendances que vous souhaitez mettre à disposition. Dans l’exemple ci-dessus, la configuration
crée un miroir des dépôts &lt;code&gt;symfony/symfony&lt;/code&gt; et &lt;code&gt;doctrine/orm&lt;/code&gt; ainsi qu’un dépôt privé
permettant d’exposer le composant &lt;code&gt;private/my-bundle&lt;/code&gt; provenant d’un dépôt Git.&lt;/p&gt;

&lt;p&gt;Pour démarrer la création du dépôt Satis, il suffit d’exécuter la commande:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;php /path/to/satis/bin/satis build /path/to/satis.conf /path/to/packages-repository&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne reste plus qu’à donner un accès au dépôt que l’on vient de créer en configurant
un serveur Web:&lt;br /&gt;&lt;code&gt;php -S 0.0.0.0:4680 -t /path/to/packages-repository&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Votre dépôt Satis est maintenant configuré et vous pouvez l’utiliser dans l’ensemble
de vos projets PHP via Composer. Il faudra, au préalable, préciser l’adresse de
votre dépôt Satis dans le fichier &lt;code&gt;composer.json&lt;/code&gt; en ajoutant une section &lt;code&gt;repositories&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&amp;quot;repositories&amp;quot;: [
    {
        &amp;quot;type&amp;quot;: &amp;quot;composer&amp;quot;,
        &amp;quot;url&amp;quot;: &amp;quot;http://your-mirror-server:4680&amp;quot;
    }
],&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;https://getcomposer.org/doc/articles/handling-private-packages-with-satis.md&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Documentation de Satis&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/composer/satis&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Dépôt Github&lt;/a&gt;&lt;/p&gt;
</description>
                    <pubDate>Tue, 12 Aug 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/08/12/des-depots-composer-prives-avec-satis.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/08/12/des-depots-composer-prives-avec-satis.html</guid>
                </item>
            
        
            
                317
                <item>
                    <title>Tester rapidement un site mobile avec Behat</title>
                    <description>&lt;p&gt;J’utilise Behat au quotidien pour écrire mes tests fonctionnels. J’ai récemment eu besoin de mettre en place
des tests pour un site mobile conjointement aux tests existant sur le projet. N’ayant que peu de temps alloué
sur le projet, j’ai dû mettre en place des derniers très rapidement.&lt;/p&gt;

&lt;p&gt;Pour cela, la solution qui me semblait la plus évidente était de modifier le user-agent du navigateur utilisé
en le remplaçant par celui d’un appareil mobile. Or j’utilise Behat avec le driver Selenium2 qui n’autorise pas
cette modification.&lt;/p&gt;

&lt;p&gt;Les navigateurs modernes sont capables de gérer différentes configurations au travers d’un gestionnaire de profils.
Une autre solution consiste alors à utiliser ces derniers en créant un profil qui aurait le user-agent voulu.
Mes tests étant exécutés par Firefox, il suffit de démarrer ce dernier avec l’option &lt;code&gt;-p&lt;/code&gt; pour ouvrir le
gestionnaire de profil.&lt;/p&gt;

&lt;p&gt;Une fois le nouveau profil créé, nous allons accéder à l’interface de configuration de Firefox en saisissant
&lt;code&gt;about:config&lt;/code&gt; dans la barre d’adresse. Il ne reste plus qu’à ajouter une nouvelle clé de configuration (si
elle n’existe pas) nommée &lt;code&gt;general.useragent.override&lt;/code&gt; en lui donnant la valeur désirée (dans mon cas un iPhone
4S utilisant Safari &lt;code&gt;Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML,
like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Il ne reste plus qu’à créer une archive ZIP contenant le profil que l’on vient d’ajouter. Ces derniers se
trouvent dans les dossiers suivants :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Mac :&lt;/em&gt; &lt;code&gt;/Users/&amp;lt;loginUtilisateur&amp;gt;/Library/Application\ Support/Firefox/Profiles/&amp;lt;identifiant.NomDuProfil&amp;gt;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Linux :&lt;/em&gt; &lt;code&gt;~/.mozilla/firefox/&amp;lt;identifiant.NomDuProfil&amp;gt;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Windows :&lt;/em&gt; &lt;code&gt;C:\Users\&amp;lt;loginUtilisateur&amp;gt;\AppData\Roaming\Mozilla\Firefox\Profiles\&amp;lt;identifiant.NomDuProfil&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maintenant il ne nous reste plus qu’à configurer un nouveau profil Behat pour l’exécution de nos tests avec ce
nouvel environnement. Voici une configuration type que j’utilise dans mes projets Symfony2 :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;mobile:
    paths:
        features: features/mobile
        bootstrap: %behat.paths.features%/bootstrap
    extensions:
        Behat\Symfony2Extension\Extension:
            mink_driver: true
            kernel:
                env: test
                debug: true
        Behat\MinkExtension\Extension:
            base_url: http://localhost:8000/app_test.php
            browser_name: firefox
            goutte: ~
            selenium2:
                capabilities:
                    firefox:
                        profile: &amp;#39;/chemin/vers/le/profil/firefox-mobile.zip&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous pouvez maintenant lancer vos tests avec un navigateur mobile en exécutant behat : &lt;code&gt;bin/behat --profile mobile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cette installation rapide est surtout destinée à pouvoir écrire des tests fonctionnels rapidement et peut convenir
pour des projets de faibles envergures. De plus elle ne fonctionne que pour des sites Web devant fonctionner sur
mobile. Pour des tests plus pérennes ou pour des projets plus conséquents il conviendra d’utiliser des outils
spécialisés dans les tests mobiles tels que &lt;a href=&quot;https://saucelabs.com&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;SauceLabs&lt;/a&gt;,
&lt;a href=&quot;http://appium.io&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Appium&lt;/a&gt;, &lt;a href=&quot;http://selendroid.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Selendroid&lt;/a&gt;,
&lt;a href=&quot;https://ios-driver.github.io/ios-driver/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ios-driver&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Wed, 06 Aug 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/08/06/tester-rapidement-un-site-mobile-avec-behat.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/08/06/tester-rapidement-un-site-mobile-avec-behat.html</guid>
                </item>
            
        
            
                318
                <item>
                    <title>Présentation &quot;Introduction PHPUnit et Atoum&quot;</title>
                    <description>&lt;p&gt;Il y a quelques jours, j’ai eu l’occasion de parler des tests unitaires en PHP lors d’une
présentation chez mon employeur actuel (Novaway). L’objectif était d’introduire les concepts
fondamentaux liés aux tests unitaires et de présenter rapidement les 2 principaux frameworks
de tests PHP : PHPUnit et Atoum.&lt;/p&gt;

&lt;p&gt;J’ai réalisé les slides de la manière la plus objective possible en essayant de ne pas tenir
compte de mon opinion concernant chacun des frameworks de manière à être le plus neutre possible.&lt;/p&gt;

&lt;p&gt;&lt;span style=&quot;text-decoration: line-through;&quot;&gt;Les slides sont disponibles sur
&lt;a href=&quot;#&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Github&lt;/a&gt;
et vous pouvez accéder à la
&lt;a href=&quot;#&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;présentation en ligne&lt;/a&gt;.&lt;/span&gt;
Les slides ne sont plus disponibles sur Github, mais sont dorénavant
&lt;a href=&quot;/talks/20140731-tests-unitaires-en-php-atoum-phpunit/index.html &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;consultable sur ce site&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Thu, 31 Jul 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/07/31/presentation-introduction-phpunit-atoum.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/07/31/presentation-introduction-phpunit-atoum.html</guid>
                </item>
            
        
            
                319
                <item>
                    <title>Atoum et la gestion du error_reporting</title>
                    <description>&lt;p&gt;J’utilise depuis quelques années maintenant Atoum pour écrire les tests unitaires sur mes différents projets
(Open Source et professionnels). Pourtant cela ne m’empêche pas d’avoir encore quelques surprises lors
de l’écriture de mes tests.&lt;/p&gt;

&lt;p&gt;Ce weekend, en écrivant des tests sur une librairie PHP “legacy”, et en profitant pour y faire un peu de
refactoring, mes tests Atoum me généraient une erreur PHP (E_STRICT) que je ne parvenais pas à reproduire via
une application test.&lt;/p&gt;

&lt;p&gt;La seule explication de ce phénomène est qu’&lt;strong&gt;Atoum surchage la configuration error_reporting de votre
environnement PHP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Après une rapide réflexion, cela n’est en soi pas une mauvaise idée, car cela vous permet de vous assurer que
votre code ne contient réellement aucune erreur et cela indépendamment de la configuration qui sera utilisée
sur la machine qui utilisera votre code (comme l’explique Frédéric HARDY lors d’un
&lt;a href=&quot;https://twitter.com/mageekguy/status/490935784326037506&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;échange de tweet&lt;/a&gt;).&lt;/p&gt;
</description>
                    <pubDate>Mon, 21 Jul 2014 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/blog/2014/07/21/atoum-error-reporting.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/blog/2014/07/21/atoum-error-reporting.html</guid>
                </item>
            
        
    </channel>
</rss>
