Pint, Rector, Larastan et Pest : les essentiels pour réussir

Fait partie de la série Améliorer votre blog VitePress avec une API Laravel

Maintenant que le projet Laravel est en place et que sa structure nous est familière, il est temps de rendre la base de code plus facile à maintenir.

À mesure que le projet grandira, un formatage cohérent, la refactorisation automatique, l'analyse statique et les tests nous aideront à avancer rapidement sans perdre confiance. Dans cet article, nous allons configurer quatre outils qui soutiennent ce workflow : Pint, Rector, Larastan et Pest.

  1. Pint formate le code PHP selon un style cohérent. Dans l'écosystème JavaScript, il se rapproche des règles de formatage de Prettier ou d'ESLint.
  2. Rector refactorise automatiquement le code PHP. Il peut moderniser la syntaxe, supprimer les constructions obsolètes et appliquer des améliorations à l'ensemble du projet.
  3. Larastan effectue une analyse statique adaptée à Laravel. Il détecte de nombreux problèmes potentiels avant l'exécution du code.
  4. Pest est un framework de test qui privilégie des tests simples et expressifs. C'est l'équivalent PHP le plus proche d'outils comme Jest dans l'écosystème JavaScript.

Utiliser ces outils dès le début rend la base de code plus facile à maintenir et fournit une fondation claire et cohérente aux futurs contributeurs. Cela nous fait également gagner du temps à mesure que le projet grandit.

Pint

Pint est préinstallé avec Laravel, il n'y a donc rien à installer. Nous devons seulement le configurer et ajouter un script Composer pour formater le code.

Créez un fichier pint.json à la racine du projet. Ce fichier nous permet de choisir les règles à appliquer à l'ensemble de la base de code.

json
{
  "preset": "laravel",
  "rules": {
    "array_push": true,
    "backtick_to_shell_exec": true,
    "date_time_immutable": true,
    "declare_strict_types": true,
    "lowercase_keywords": true,
    "lowercase_static_reference": true,
    "final_class": true,
    "final_internal_class": true,
    "final_public_method_for_abstract_class": true,
    "fully_qualified_strict_types": true,
    "global_namespace_import": {
      "import_classes": true,
      "import_constants": true,
      "import_functions": true
    },
    "mb_str_functions": true,
    "modernize_types_casting": true,
    "new_with_parentheses": false,
    "no_superfluous_elseif": true,
    "no_useless_else": true,
    "no_multiple_statements_per_line": true,
    "ordered_class_elements": {
      "order": [
        "use_trait",
        "case",
        "constant",
        "constant_public",
        "constant_protected",
        "constant_private",
        "property_public",
        "property_protected",
        "property_private",
        "construct",
        "destruct",
        "magic",
        "phpunit",
        "method_abstract",
        "method_public_static",
        "method_public",
        "method_protected_static",
        "method_protected",
        "method_private_static",
        "method_private"
      ],
      "sort_algorithm": "none"
    },
    "ordered_interfaces": true,
    "ordered_traits": true,
    "protected_to_private": true,
    "self_accessor": true,
    "self_static_accessor": true,
    "strict_comparison": true,
    "visibility_required": true
  }
}

Décomposons cette configuration :

  • preset : Cette option permet de choisir une configuration prédéfinie pour Pint. Ici, nous utilisons le preset laravel, qui contient un ensemble de règles recommandées pour les projets Laravel.
  • rules : Cette section contient les règles spécifiques que nous voulons appliquer à notre base de code. Chaque règle peut être activée ou désactivée avec true ou false.
  • array_push : Force l'utilisation de la syntaxe d'ajout à un tableau plutôt que de la fusion de tableaux.
  • backtick_to_shell_exec : Convertit les backticks en appels à la fonction shell_exec.
  • date_time_immutable : Force l'utilisation d'objets de date et d'heure immuables.
  • declare_strict_types : Force l'utilisation des types stricts dans les fichiers PHP.
  • lowercase_keywords : Force l'utilisation de mots-clés PHP en minuscules.
  • lowercase_static_reference : Force l'utilisation de références statiques en minuscules.
  • final_class : Force l'utilisation de classes finales.
  • final_internal_class : Force l'utilisation de classes finales pour les classes internes.
  • final_public_method_for_abstract_class : Force l'utilisation de méthodes publiques finales dans les classes abstraites.
  • fully_qualified_strict_types : Force l'utilisation de types strictement qualifiés dans les fichiers PHP.
  • global_namespace_import : Force l'import des classes, constantes et fonctions de l'espace de noms global.
  • mb_str_functions : Force l'utilisation des fonctions de chaînes multioctets.
  • modernize_types_casting : Force l'utilisation d'une conversion de types moderne en PHP.
  • new_with_parentheses : Force l'utilisation de parenthèses lors de la création de nouveaux objets.
  • no_superfluous_elseif : Supprime les instructions elseif superflues.
  • no_useless_else : Supprime les instructions else inutiles.
  • no_multiple_statements_per_line : Force l'utilisation d'une seule instruction par ligne.
  • ordered_class_elements : Force l'ordre des éléments d'une classe, comme les traits, les constantes, les propriétés et les méthodes.
  • ordered_interfaces : Force l'ordre des interfaces.
  • ordered_traits : Force l'ordre des traits.
  • protected_to_private : Force l'utilisation de la visibilité privée pour les propriétés et les méthodes lorsque c'est possible.
  • self_accessor : Force l'utilisation des accesseurs self pour les propriétés et les méthodes.
  • self_static_accessor : Force l'utilisation des accesseurs statiques self.
  • strict_comparison : Force l'utilisation d'opérateurs de comparaison stricts.
  • visibility_required : Force la présence des mots-clés de visibilité pour les propriétés et les méthodes.

Note

Si vous avez des questions sur l'une de ces règles, n'hésitez pas à les poser dans les commentaires.

Ajoutons maintenant une commande personnalisée pour vérifier notre code. Ajoutez une commande lint à votre fichier composer.json :

json
{
  "scripts": {
    "lint": "pint"
  }
}

Désormais, nous pouvons exécuter composer lint pour formater le code. Pint traitera les fichiers des dossiers app, config, database et routes afin de conserver un style cohérent.

Vous devriez obtenir quelque chose comme ceci :

bash
 composer lint
> pint

  ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓

  ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Laravel
    FIXED   ......................................................................................................................... 26 files, 26 style issues fixed
 app/Http/Controllers/Controller.php                                                                            declare_strict_types, blank_line_after_opening_tag
 app/Models/User.php                                                                               final_class, declare_strict_types, blank_line_after_opening_tag
 app/Providers/AppServiceProvider.php                                                              final_class, declare_strict_types, blank_line_after_opening_tag
 bootstrap/app.php                                                                                              declare_strict_types, blank_line_after_opening_tag
 bootstrap/providers.php                                                                                        declare_strict_types, blank_line_after_opening_tag
 config/app.php                                                                                                 declare_strict_types, blank_line_after_opening_tag
 config/auth.php                                                                                                final_class, declare_strict_types, blank_line_after_opening_tag
 config/cache.php                                                                                               declare_strict_types, blank_line_after_opening_tag
 config/database.php                                                                                            declare_strict_types, blank_line_after_opening_tag
 config/filesystems.php                                                                                        declare_strict_types, blank_line_after_opening_tag
 config/logging.php                                                                                             declare_strict_types, blank_line_after_opening_tag
 config/mail.php                                                                                                declare_strict_types, blank_line_after_opening_tag
 config/queue.php                                                                                               declare_strict_types, blank_line_after_opening_tag
 config/services.php                                                                                            declare_strict_types, blank_line_after_opening_tag
 config/session.php                                                                                             declare_strict_types, blank_line_after_opening_tag
 database/factories/UserFactory.php                                          final_class, self_static_accessor, declare_strict_types, blank_line_after_opening_tag
 database/migrations/0001_01_01_000000_create_users_table.php                class_definition, declare_strict_types, blank_line_after_opening_tag, braces_position
 database/migrations/0001_01_01_000001_create_cache_table.php                 class_definition, declare_strict_types, blank_line_after_opening_tag, braces_position
 database/migrations/0001_01_01_000002_create_jobs_table.php                 class_definition, declare_strict_types, blank_line_after_opening_tag, braces_position
 database/seeders/DatabaseSeeder.php                                                               final_class, declare_strict_types, blank_line_after_opening_tag
 public/index.php                                                                                               declare_strict_types, blank_line_after_opening_tag
 routes/console.php                                                                                             declare_strict_types, blank_line_after_opening_tag
 routes/web.php                                                                                                 declare_strict_types, blank_line_after_opening_tag
 tests/Feature/ExampleTest.php                                                                                  declare_strict_types, blank_line_after_opening_tag
 tests/TestCase.php                                                                                             declare_strict_types, blank_line_after_opening_tag
 tests/Unit/ExampleTest.php                                                                                     declare_strict_types, blank_line_after_opening_tag

La sortie affiche chaque fichier et les règles que Pint lui a appliquées. Un signifie que Pint a modifié le fichier ; un . signifie qu'il était déjà correctement formaté.

Ajoutons également une commande test:lint pour la CI. Au lieu de modifier les fichiers, elle vérifiera le formatage et retournera une erreur si un fichier doit être modifié.

json
{
  "scripts": {
    "lint": "pint",
    "test:lint": "pint --test"
  }
}

Parfait !

Rector

Le deuxième outil est Rector, qui complète Pint. Pint modifie le formatage, tandis que Rector modifie la structure et le comportement du code en fonction de ses règles. Par exemple :

php
class SomeClass
{
    public function getValue(int $number)
    {
        if ($number) {
            return 100;
        }

        return 500;
    }
}

Serait refactorisé ainsi :

php
final class SomeClass
{
    public function getValue(int $number): int
    {
        return $number ? 100 : 500;
    }
}

En appliquant la règle ReturnTypeFromStrictTernaryRector.

Installons Rector avec Composer :

bash
composer require rector/rector --dev

Créez un fichier de configuration rector.php :

php
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__.'/app',
        __DIR__.'/bootstrap/app.php',
        __DIR__.'/config',
        __DIR__.'/database',
        __DIR__.'/public',
    ])
    ->withPreparedSets(
        deadCode: true,
        codeQuality: true,
        typeDeclarations: true,
        privatization: true,
        earlyReturn: true,
        strictBooleans: true,
    )
    ->withPhpSets();

Cette configuration est un peu plus détaillée que celle de Pint. Voici le rôle de chaque partie :

  • declare(strict_types=1) : Cette ligne active le typage strict dans le fichier et demande à PHP d'appliquer les déclarations de type.
  • use Rector\Config\RectorConfig : Cette ligne importe la classe RectorConfig, utilisée pour configurer Rector.
  • return RectorConfig::configure() : Cette ligne démarre la configuration de Rector.
  • ->withPaths([...]) : Cette méthode indique les chemins des dossiers et fichiers que Rector doit analyser et refactoriser. Ici, nous incluons app, bootstrap/app.php, config, database et public.
  • ->withPreparedSets(...) : Cette méthode active des ensembles de règles prédéfinis pour Rector. Nous activons plusieurs ensembles :
    • deadCode : Supprime le code mort du projet.
    • codeQuality : Améliore la qualité du code en appliquant différentes bonnes pratiques.
    • typeDeclarations : Ajoute des déclarations de type aux fonctions et aux méthodes.
    • privatization : Rend les propriétés et les méthodes privées lorsque c'est possible.
    • earlyReturn : Applique des retours anticipés pour améliorer la lisibilité.
    • strictBooleans : Force des comparaisons booléennes strictes.
  • ->withPhpSets() : Cette méthode active un ensemble de règles spécialement conçues pour PHP.

Ajoutons maintenant une commande personnalisée à composer.json :

json
{
  "scripts": {
    "refactor": "rector"
  }
}

Nous pouvons désormais lancer composer refactor. Rector inspectera les fichiers de app, bootstrap/app.php, config, database et public, puis appliquera les transformations configurées. Vous devriez voir une sortie similaire à celle-ci :

bash
 composer refactor
> rector
 20/20 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
8 files with changes
====================

1) database/migrations/0001_01_01_000001_create_cache_table.php:12

    ---------- begin diff ----------
@@ @@
      */
     public function up(): void
     {
-        Schema::create('cache', function (Blueprint $table) {
+        Schema::create('cache', function (Blueprint $table): void {
             $table->string('key')->primary();
             $table->mediumText('value');
             $table->integer('expiration');
         });

-        Schema::create('cache_locks', function (Blueprint $table) {
+        Schema::create('cache_locks', function (Blueprint $table): void {
             $table->string('key')->primary();
             $table->string('owner');
             $table->integer('expiration');
    ----------- end diff -----------

Applied rules:
 * AddClosureVoidReturnTypeWhereNoReturnRector
#  ...

Note

La sortie est abrégée ici. Votre terminal devrait lister tous les fichiers modifiés par Rector.

La sortie liste chaque fichier modifié, affiche un diff et indique les règles appliquées.

Ajoutons également une commande test:refactor pour la CI. Elle exécute Rector en mode simulation et signale les changements sans modifier les fichiers.

json
{
  "scripts": {
    "refactor": "rector",
    "test:refactor": "rector --dry-run"
  }
}

Larastan

Installez Larastan avec Composer :

bash
composer require --dev "larastan/larastan:^3.0"

Créez un fichier phpstan.neon :

yml
includes:
  - vendor/larastan/larastan/extension.neon
  - vendor/phpstan/phpstan/conf/bleedingEdge.neon

parameters:
  level: 6

  paths:
    - app
    - config
    - bootstrap
    - database/factories
    - routes

Cette configuration indique à Larastan quoi charger et quelles parties du projet analyser :

  • includes : Cette section inclut des fichiers de configuration supplémentaires qui étendent les fonctionnalités de Larastan. Ici, nous incluons larastan/larastan/extension.neon et phpstan/phpstan/conf/bleedingEdge.neon.
  • parameters : Cette section contient les principaux paramètres de configuration de Larastan.
  • level : Ce paramètre définit le niveau de rigueur de l'analyse. Les niveaux élevés détectent davantage de problèmes potentiels, mais peuvent aussi produire plus de faux positifs. Le niveau 6 constitue un compromis raisonnable pour ce projet, mais vous pouvez commencer au niveau 2 et l'augmenter progressivement.
  • paths : Ce paramètre indique les dossiers et fichiers que Larastan doit analyser. Ici, nous incluons app, config, bootstrap, database/factories et routes.

Ajoutons maintenant une commande personnalisée à composer.json :

json
{
  "scripts": {
    "test:types": "phpstan analyse"
  }
}

Contrairement à Pint et Rector, Larastan n'est pas automatique et ne peut pas corriger le code à votre place. Il signale plutôt les erreurs et les avertissements que vous devez corriger manuellement.

Lancez composer test:types pour analyser les fichiers de app, config, bootstrap, database/factories et routes. Larastan signale les problèmes à corriger manuellement. Vous devriez voir une sortie similaire à celle-ci :

bash
 composer test:types
> phpstan analyse
Note: Using configuration file /Users/esoub/dev/p/mimram/phpstan.neon.
 20/20 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ---------------------------
  Line   routes/console.php
 ------ ---------------------------
  :9     Undefined variable: $this
         🪪  variable.undefined
 ------ ---------------------------

 [ERROR] Found 1 error

Script phpstan analyse handling the test:types event returned with error code 1

L'erreur vient de la commande par défaut située dans routes/console.php, dont nous n'avons pas besoin pour ce projet. Supprimez-la pour corriger le problème.

php
<?php

use Illuminate\Foundation\Inspiring; 
use Illuminate\Support\Facades\Artisan; 

Artisan::command('inspire', function () { 
    $this->comment(Inspiring::quote()); 
})->purpose('Display an inspiring quote'); 

Relancez composer test:types :

bash
 composer test:types
> phpstan analyse
Note: Using configuration file /Users/esoub/dev/p/mimram/phpstan.neon.
 20/20 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 [OK] No errors

Pest

Le dernier outil est Pest, un framework de test PHP qui privilégie des tests simples et expressifs. Pest est le framework de test par défaut d'un nouveau projet Laravel, il n'y a donc rien à installer.

Nous allons le configurer et ajouter quelques commandes personnalisées à composer.json.

Commençons par modifier le fichier phpunit.xml :

xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Http">
            <directory>tests/Http</directory>
        </testsuite>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
    </testsuites>
    <source>
        <include>
            <directory>app</directory>
            <directory>config</directory>
            <directory>routes</directory>
        </include>
    </source>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="APP_MAINTENANCE_DRIVER" value="file"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="CACHE_STORE" value="array"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
        <env name="MAIL_MAILER" value="array"/>
        <env name="PULSE_ENABLED" value="false"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

Cette configuration définit les suites de tests, les fichiers sources de l'application et l'environnement utilisé pendant les tests :

  • <?xml version="1.0" encoding="UTF-8"?> : Cette ligne indique la version et l'encodage XML.
  • <phpunit ...> : Cette ligne démarre la configuration de PHPUnit.
  • xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" : Cet attribut indique l'espace de noms XML pour l'instance de schéma.
  • xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" : Cet attribut indique l'emplacement du schéma XML de PHPUnit.
  • bootstrap="vendor/autoload.php" : Cet attribut indique le fichier de bootstrap de PHPUnit, chargé d'importer les dépendances nécessaires.
  • colors="true" : Cet attribut active les couleurs dans la sortie de la console.
  • <testsuites> : Cette section définit les suites de tests du projet.
  • <testsuite name="Http"> : Cette ligne définit une suite nommée Http.
  • <directory>tests/Http</directory> : Cette ligne indique le dossier contenant les tests de la suite Http.
  • <testsuite name="Unit"> : Cette ligne définit une suite nommée Unit.
  • <directory>tests/Unit</directory> : Cette ligne indique le dossier contenant les tests de la suite Unit.
  • <source> : Cette section définit les dossiers sources du projet.
  • <include> : Cette section inclut les dossiers indiqués dans les sources.
  • <directory>app</directory> : Cette ligne indique le dossier app comme dossier source.
  • <directory>config</directory> : Cette ligne indique le dossier config comme dossier source.
  • <directory>routes</directory> : Cette ligne indique le dossier routes comme dossier source.
  • <php> : Cette section définit les variables d'environnement PHP du projet.
  • <env name="APP_ENV" value="testing"/> : Cette ligne définit APP_ENV sur testing.
  • <env name="APP_MAINTENANCE_DRIVER" value="file"/> : Cette ligne définit APP_MAINTENANCE_DRIVER sur file.
  • <env name="BCRYPT_ROUNDS" value="4"/> : Cette ligne définit BCRYPT_ROUNDS sur 4.
  • <env name="CACHE_STORE" value="array"/> : Cette ligne définit CACHE_STORE sur array.
  • <env name="DB_CONNECTION" value="sqlite"/> : Cette ligne définit DB_CONNECTION sur sqlite.
  • <env name="DB_DATABASE" value=":memory:"/> : Cette ligne définit DB_DATABASE sur :memory:.
  • <env name="MAIL_MAILER" value="array"/> : Cette ligne définit MAIL_MAILER sur array.
  • <env name="PULSE_ENABLED" value="false"/> : Cette ligne désactive PULSE_ENABLED.
  • <env name="QUEUE_CONNECTION" value="sync"/> : Cette ligne définit QUEUE_CONNECTION sur sync.
  • <env name="SESSION_DRIVER" value="array"/> : Cette ligne définit SESSION_DRIVER sur array.
  • <env name="TELESCOPE_ENABLED" value="false"/> : Cette ligne désactive TELESCOPE_ENABLED.

Ces variables donnent aux tests un environnement prévisible, ce qui limite les comportements instables et simplifie la configuration.

Comme nous avons changé le dossier des tests d'intégration de Feature à Http, renommez tests/Feature en tests/Http.

bash
mv tests/Feature/** tests/Http/** && sed -i '' 's/Feature/Http/g' tests/Pest.php

Le dossier de tests contient maintenant deux dossiers et deux fichiers :

  1. tests/Http : Contient les tests d'intégration de l'application.
  2. tests/Unit : Contient les tests unitaires de l'application.
  3. tests/Pest.php : Contient la configuration de Pest.
  4. tests/TestCase.php : Contient la classe de test de base de l'application.

Enfin, ajoutons quelques commandes personnalisées à composer.json :

json
{
  "scripts": {
    "test:type-coverage": "pest --type-coverage --min=100",
    "test:unit": "pest --parallel --coverage --min=100",
    "test": [
      "@test:lint",
      "@test:refactor",
      "@test:types",
      "@test:type-coverage",
      "@test:unit"
    ]
  }
}

Ces commandes ont les rôles suivants :

  • test:type-coverage : Exécute les tests avec la couverture des types et exige une couverture de 100 % grâce à --min=100.
  • test:unit : Exécute les tests avec la couverture des lignes et exige que chaque ligne soit couverte grâce à --coverage et --min=100.
  • test : Exécute le formatage, la refactorisation, la vérification des types, la couverture des types et la suite de tests. C'est la commande que nous utiliserons dans la CI.

Pour que test:type-coverage fonctionne, nous devons installer un plugin Pest :

bash
composer require pestphp/pest-plugin-type-coverage --dev

Note

La couverture de code nécessite xdebug.

Dernières réflexions

Avec Pint, Rector, Larastan et Pest configurés, le projet dispose maintenant d'un workflow qualité reproductible. Le formatage, la refactorisation, l'analyse statique et les tests peuvent nous accompagner lorsque la base de code grandit, au lieu de devenir une corvée que nous repoussons.

Les fondations Laravel sont prêtes, nous pouvons donc commencer à construire des fonctionnalités par-dessus. Ensuite, nous ajouterons l'authentification GitHub avec Socialite afin que les utilisateurs puissent s'identifier avant de gérer leurs commentaires.

Pd

Merci de me lire ! Je m'appelle Estéban, et j'adore écrire sur le développement web et le parcours humain qui l'entoure.

Je code depuis plusieurs années maintenant, et j'apprends encore de nouvelles choses chaque jour. J'aime partager mes connaissances avec les autres, car j'aurais aimé avoir accès à des ressources aussi claires et complètes lorsque j'ai commencé à apprendre la programmation.

Si vous avez des questions ou souhaitez discuter, n'hésitez pas à commenter ci-dessous ou à me contacter sur Bluesky, X, et LinkedIn.

J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. N'hésitez pas à le partager avec vos amis ou sur les réseaux sociaux, et laissez un commentaire ou une réaction ci-dessous, cela me ferait très plaisir ! Si vous souhaitez soutenir mon travail, vous pouvez me sponsoriser sur GitHub !

Continuer la lectureAuthentification sociale simplifiée avec Laravel Socialite

Réactions

Discussions

Ajouter un commentaire

Vous devez être connecté pour accéder à cette fonctionnalité.