> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Développement de packages Laravel

> Découvrez le développement de packages Laravel autour du service provider : publication de la configuration, des vues, des migrations et façades, découverte automatique et chargement différé.

## Qu'est-ce qu'un package

En Laravel, un package est un package Composer qui ajoute des fonctionnalités à votre application. Il en existe deux grandes catégories :

* **Packages autonomes** — bibliothèques PHP génériques indépendantes de Laravel (ex. : Carbon, Pest).
* **Packages Laravel** — packages intégrés à Laravel comportant routes, contrôleurs, vues, configuration, etc.

Ce guide traite du second cas, le développement de packages spécifiques à Laravel. Il exige une compréhension approfondie de la structure interne de Laravel : service providers, façades, publication des fichiers de configuration, etc.

<Info>
  Pour écrire des tests de package, utilisez [Orchestra Testbench](https://github.com/orchestral/testbench). Vous pouvez alors rédiger vos tests comme dans une application Laravel classique.
</Info>

## Découverte automatique du package

Lors de l'installation d'un package, Laravel lit la section `extra.laravel` du `composer.json` pour enregistrer automatiquement les service providers et les façades.

```json theme={null}
"extra": {
    "laravel": {
        "providers": [
            "Acme\\Courier\\CourierServiceProvider"
        ],
        "aliases": {
            "Courier": "Acme\\Courier\\Facades\\Courier"
        }
    }
}
```

Avec cette configuration, l'utilisateur n'a pas besoin d'éditer manuellement `bootstrap/providers.php` : le package est chargé automatiquement.

### Désactiver la découverte automatique

Pour désactiver la découverte automatique d'un package spécifique côté utilisateur, il suffit de le déclarer dans le `composer.json` de l'application.

```json theme={null}
"extra": {
    "laravel": {
        "dont-discover": [
            "acme/courier"
        ]
    }
}
```

## Rôle du service provider

Le service provider est le point d'entrée du package. C'est là que vous enregistrez auprès de Laravel les ressources telles que vues, configuration, migrations et routes.

Un service provider hérite de `Illuminate\Support\ServiceProvider` et possède deux méthodes : `register` et `boot`.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    /**
     * Enregistrer les services du package
     */
    public function register(): void
    {
        // Les bindings dans le service container se font ici
        $this->mergeConfigFrom(
            __DIR__.'/../config/courier.php', 'courier'
        );

        $this->app->singleton(CourierManager::class, function ($app) {
            return new CourierManager($app['config']['courier']);
        });
    }

    /**
     * Bootstraper les services du package
     */
    public function boot(): void
    {
        // L'enregistrement des ressources se fait ici
        $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
        $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

        $this->publishesMigrations([
            __DIR__.'/../database/migrations' => database_path('migrations'),
        ]);

        $this->publishes([
            __DIR__.'/../config/courier.php' => config_path('courier.php'),
        ], 'courier-config');

        $this->publishes([
            __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
        ], 'courier-views');
    }
}
```

<Warning>
  N'enregistrez pas d'écouteurs d'événements, de routes ou de vues dans `register`. Vous risqueriez d'utiliser par erreur un service d'un autre provider pas encore chargé. Toute opération autre que les bindings doit se faire dans `boot`.
</Warning>

## Publication du fichier de configuration

### publishes() — exposer un fichier

En appelant `publishes()` dans `boot`, vous permettez aux utilisateurs de copier le fichier de configuration dans leur application via la commande `vendor:publish`.

```php theme={null}
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ]);
}
```

Après publication, les valeurs de configuration se récupèrent comme n'importe quelle config Laravel.

```php theme={null}
$value = config('courier.option');
```

### mergeConfigFrom() — fusionner avec les valeurs par défaut

En utilisant `mergeConfigFrom()` dans `register`, les valeurs par défaut du package sont utilisées même lorsque l'utilisateur n'a pas publié le fichier de configuration.

```php theme={null}
public function register(): void
{
    $this->mergeConfigFrom(
        __DIR__.'/../config/courier.php', 'courier'
    );
}
```

<Warning>
  `mergeConfigFrom()` ne fusionne pas les tableaux imbriqués en profondeur. Pour une configuration à plusieurs niveaux, si l'utilisateur n'en définit qu'une partie, les options restantes peuvent ne pas être fusionnées.
</Warning>

### Séparer les groupes de publication par tag

En passant un tag en second argument de `publishes()`, l'utilisateur peut publier uniquement les ressources dont il a besoin.

```php theme={null}
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ], 'courier-config');

    $this->publishesMigrations([
        __DIR__.'/../database/migrations/' => database_path('migrations'),
    ], 'courier-migrations');
}
```

```shell theme={null}
# Publier uniquement le fichier de configuration
php artisan vendor:publish --tag=courier-config

# Publier tous les fichiers fournis par le provider
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider"
```

## Enregistrement des routes

Chargez le fichier de routes via `loadRoutesFrom()`. Si le cache des routes de l'application est actif, cet appel est automatiquement ignoré.

```php theme={null}
public function boot(): void
{
    $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
```

Dans ce fichier, indiquez les contrôleurs du package.

```php theme={null}
// routes/web.php
use Acme\Courier\Http\Controllers\TrackingController;
use Illuminate\Support\Facades\Route;

Route::prefix('courier')->group(function () {
    Route::get('/track/{id}', [TrackingController::class, 'show'])
        ->name('courier.track');
});
```

## Publication des migrations

`publishesMigrations()` permet de publier les fichiers de migration. Laravel met automatiquement à jour l'horodatage lors de la publication.

```php theme={null}
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}
```

## Publication des vues

### loadViewsFrom() — enregistrer les vues

`loadViewsFrom()` enregistre un répertoire de vues. Le second argument est le namespace qui permet de référencer les vues sous la forme `package::view`.

```php theme={null}
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}
```

Une fois enregistrée, la vue est référencée via le namespace du package.

```php theme={null}
Route::get('/dashboard', function () {
    return view('courier::dashboard');
});
```

Laravel cherche la vue à deux endroits. Il regarde d'abord dans le dossier `resources/views/vendor/courier` de l'application, puis dans le répertoire des vues du package. Cela permet à l'utilisateur de personnaliser les vues.

### Publier les vues

```php theme={null}
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');

    $this->publishes([
        __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
    ], 'courier-views');
}
```

### Enregistrer des composants Blade

Si votre package inclut des composants, enregistrez-les dans `boot`.

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Acme\Courier\View\Components\AlertComponent;

public function boot(): void
{
    Blade::component('courier-alert', AlertComponent::class);
}
```

Vous pouvez aussi enregistrer un namespace de composants pour les déclarer en lot.

```php theme={null}
use Illuminate\Support\Facades\Blade;

public function boot(): void
{
    Blade::componentNamespace('Acme\\Courier\\View\\Components', 'courier');
}
```

```blade theme={null}
{{-- Enregistrement individuel --}}
<x-courier-alert />

{{-- Enregistrement via namespace --}}
<x-courier::alert />
```

## Publication des fichiers de traduction

`loadTranslationsFrom()` enregistre les fichiers de traduction. Les traductions se référencent sous la forme `package::file.key`.

```php theme={null}
public function boot(): void
{
    $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

    $this->publishes([
        __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
    ]);
}
```

```php theme={null}
// Utilisation de la traduction
echo trans('courier::messages.welcome');
```

Pour des fichiers JSON, utilisez `loadJsonTranslationsFrom()`.

```php theme={null}
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}
```

## Enregistrement des commandes

Les commandes Artisan du package s'enregistrent via `commands()`. Il est courant de ne les enregistrer qu'en environnement console.

```php theme={null}
use Acme\Courier\Console\Commands\InstallCommand;
use Acme\Courier\Console\Commands\SyncCommand;

public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            InstallCommand::class,
            SyncCommand::class,
        ]);
    }
}
```

### Intégration à la commande optimize

Si votre package possède son propre cache, vous pouvez l'intégrer à `php artisan optimize` et `php artisan optimize:clear` via la méthode `optimizes()`.

```php theme={null}
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->optimizes(
            optimize: 'courier:cache',
            clear: 'courier:clear-cache',
        );
    }
}
```

### Ajouter des informations à la commande `about`

Pour ajouter des informations sur votre package à la sortie de `php artisan about`, utilisez `AboutCommand::add()`.

```php theme={null}
use Illuminate\Foundation\Console\AboutCommand;

public function boot(): void
{
    AboutCommand::add('Courier Package', fn () => ['Version' => '1.0.0']);
}
```

## Créer une façade

Une façade permet d'appeler un binding du service container comme s'il s'agissait d'une méthode statique.

<Steps>
  <Step title="Créer la classe de service">
    ```php theme={null}
    <?php

    namespace Acme\Courier;

    class CourierManager
    {
        public function __construct(
            protected array $config,
        ) {}

        public function send(string $to, string $message): bool
        {
            // Traitement d'envoi du message
            return true;
        }

        public function track(string $id): array
        {
            // Traitement de récupération des informations de suivi
            return ['status' => 'delivered'];
        }
    }
    ```
  </Step>

  <Step title="Créer la classe façade">
    Héritez de `Illuminate\Support\Facades\Facade` et renvoyez la clé de binding du service container depuis `getFacadeAccessor()`.

    ```php theme={null}
    <?php

    namespace Acme\Courier\Facades;

    use Illuminate\Support\Facades\Facade;

    /**
     * @method static bool send(string $to, string $message)
     * @method static array track(string $id)
     *
     * @see \Acme\Courier\CourierManager
     */
    class Courier extends Facade
    {
        protected static function getFacadeAccessor(): string
        {
            return \Acme\Courier\CourierManager::class;
        }
    }
    ```
  </Step>

  <Step title="Binder dans le service provider">
    ```php theme={null}
    public function register(): void
    {
        $this->app->singleton(\Acme\Courier\CourierManager::class, function ($app) {
            return new \Acme\Courier\CourierManager($app['config']['courier']);
        });
    }
    ```
  </Step>

  <Step title="Enregistrer dans composer.json">
    ```json theme={null}
    "extra": {
        "laravel": {
            "providers": [
                "Acme\\Courier\\CourierServiceProvider"
            ],
            "aliases": {
                "Courier": "Acme\\Courier\\Facades\\Courier"
            }
        }
    }
    ```
  </Step>
</Steps>

En annotant les méthodes de la façade avec `@method` en PHPDoc, l'auto-complétion de l'IDE est activée.

```php theme={null}
// Appel du service via la façade
use Acme\Courier\Facades\Courier;

Courier::send('user@example.com', 'Votre colis est arrivé');
$status = Courier::track('ABC-123');
```

## DeferrableProvider — mettre en place le chargement différé

Un provider qui n'effectue que des bindings dans le service container peut être chargé de manière différée en implémentant l'interface `DeferrableProvider`. Le provider n'étant chargé que lorsque le service est réellement demandé, cela améliore les performances de l'application.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(CourierManager::class, function ($app) {
            return new CourierManager($app['config']['courier']);
        });
    }

    /**
     * Retourner la liste des services fournis par ce provider
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
        return [CourierManager::class];
    }
}
```

Laravel compile et stocke la liste des services fournis par les providers différés. Le provider n'est chargé que lorsqu'un service listé dans `provides()` est résolu.

<Warning>
  N'utilisez pas `DeferrableProvider` pour un provider qui doit enregistrer des ressources (vues, routes, écouteurs d'événements, etc.). Si le chargement est différé, ces ressources risquent de ne jamais être enregistrées.
</Warning>

## Tester un package

Pour tester le package seul, utilisez [Orchestra Testbench](https://github.com/orchestral/testbench). Vous pouvez rédiger vos tests comme si vous étiez à l'intérieur d'une application Laravel classique.

```shell theme={null}
composer require --dev orchestra/testbench
```

Dans le TestCase, redéfinissez `getPackageProviders()` pour enregistrer le service provider du package.

```php theme={null}
<?php

namespace Acme\Courier\Tests;

use Acme\Courier\CourierServiceProvider;
use Orchestra\Testbench\TestCase as BaseTestCase;

class TestCase extends BaseTestCase
{
    /**
     * Enregistrer le service provider du package
     */
    protected function getPackageProviders($app): array
    {
        return [
            CourierServiceProvider::class,
        ];
    }

    /**
     * Enregistrer les alias de façade du package
     */
    protected function getPackageAliases($app): array
    {
        return [
            'Courier' => \Acme\Courier\Facades\Courier::class,
        ];
    }

    /**
     * Configuration d'environnement de test
     */
    protected function defineEnvironment($app): void
    {
        $app['config']->set('courier.api_key', 'test-key');
    }
}
```

```php theme={null}
<?php

namespace Acme\Courier\Tests\Feature;

use Acme\Courier\Facades\Courier;
use Acme\Courier\Tests\TestCase;

class CourierTest extends TestCase
{
    public function test_can_send_message(): void
    {
        $result = Courier::send('user@example.com', 'Message de test');

        $this->assertTrue($result);
    }
}
```

## Publier sur Composer

Voici les bonnes pratiques pour publier un package sur [Packagist](https://packagist.org/).

**Configuration de base du `composer.json`**

```json theme={null}
{
    "name": "acme/courier",
    "description": "A Laravel courier package",
    "type": "library",
    "license": "MIT",
    "require": {
        "php": "^8.2",
        "illuminate/support": "^11.0||^12.0||^13.0"
    },
    "require-dev": {
        "orchestra/testbench": "^9.0||^10.0",
        "phpunit/phpunit": "^11.0"
    },
    "autoload": {
        "psr-4": {
            "Acme\\Courier\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Acme\\Courier\\Tests\\": "tests/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Acme\\Courier\\CourierServiceProvider"
            ],
            "aliases": {
                "Courier": "Acme\\Courier\\Facades\\Courier"
            }
        }
    },
    "minimum-stability": "stable",
    "prefer-stable": true
}
```

<Tip>
  En dépendant de `illuminate/support`, vous n'incluez que les composants Laravel nécessaires, pas la totalité de `illuminate/framework`. Cela permet de garder un arbre de dépendances léger.
</Tip>

**Exemple de structure de répertoires**

```
acme/courier/
├── config/
│   └── courier.php
├── database/
│   └── migrations/
│       └── 2024_01_01_000000_create_courier_logs_table.php
├── lang/
│   └── fr/
│       └── messages.php
├── resources/
│   └── views/
│       └── dashboard.blade.php
├── routes/
│   └── web.php
├── src/
│   ├── Console/
│   │   └── Commands/
│   │       └── InstallCommand.php
│   ├── Facades/
│   │   └── Courier.php
│   ├── Http/
│   │   └── Controllers/
│   │       └── TrackingController.php
│   ├── CourierManager.php
│   └── CourierServiceProvider.php
├── tests/
│   ├── Feature/
│   └── TestCase.php
├── composer.json
└── README.md
```

## Pages associées

<Columns cols={2}>
  <Card title="Service providers" icon="plug" href="/fr/service-providers">
    Détails sur les méthodes `register` et `boot` du service provider et les providers différés.
  </Card>

  <Card title="Gestion de la compatibilité de versions" icon="git-branch" href="/fr/advanced/package-versioning">
    Stratégie pour gérer les mises à jour majeures de Laravel et PHP et configuration de la matrice de test GitHub Actions.
  </Card>
</Columns>


## Related topics

- [Laravel Package Skeleton — le template de démarrage pour packages officiels](/fr/blog/package-skeleton-introduction.md)
- [Laravel Maestro — l'orchestrateur de développement des starter kits](/fr/blog/maestro-introduction.md)
- [Guide de développement d'applications intégrées à l'API du moteur - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/app-guide.md)
- [Laravel Agent Detector — package de détection d'agents IA](/fr/blog/agent-detector-introduction.md)
- [Gestion de la compatibilité de versions de package](/fr/advanced/package-versioning.md)
