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

# Canaux de notification personnalisés dans un package et vérification de l'envoi

> Découvrez comment fournir un canal de notification Laravel 13 depuis un package : résolution par nom de classe, routage des destinataires, mise en file d'attente et tests de l'envoi réel.

Pour ajouter un mode d'envoi de notifications depuis un package, concevez séparément **le choix du canal** et **la détermination du destinataire**. Cette page prend l'exemple d'un petit canal qui consigne des événements d'exploitation dans les logs, et explique le chemin de résolution des classes fournies par le package ainsi que les vérifications nécessaires lors des mises à jour. Si vous le remplacez par un envoi vers un service externe, la répartition des responsabilités entre canal et notification reste la même.

Cette page s'appuie sur l'implémentation du Laravel Framework `v13.38.0`. Pour les opérations de base sur les notifications, consultez [Notifications](/fr/notifications.md) ; pour l'environnement de test du package, consultez [Tester un package](/fr/advanced/package-testing.md).

## Le nom de classe désigne le mode d'envoi, la route désigne le destinataire

Le nom de classe de canal renvoyé par `via()` indique sur quel objet appeler `send()`. La valeur obtenue via `routeNotificationFor()`, en revanche, est le destinataire utilisé par ce canal. Les deux n'ont pas besoin de porter le même nom.

| Emplacement | Exemple de cette page | Rôle |
| - | - | - |
| `via()` de la notification | `OperationsLogChannel::class` | Résolution du canal et envoi |
| Route de l'objet notifiable | `operationsLog` → `operations` | Destinataire utilisé dans le canal |
| `viaQueues()` de la notification | `OperationsLogChannel::class` → `operations-log` | File de destination du job en cas de mise en file d'attente |

`NotificationSender` transmet le nom de canal choisi par `via()` à `ChannelManager::driver()`. Si `ChannelManager` ne trouve ce nom ni parmi les noms intégrés ni parmi les noms enregistrés, et que le nom correspond à une classe existante, il résout cette classe via le conteneur. **Si vous l'utilisez uniquement par nom de classe, ni `Notification::extend()` ni service provider de package ne sont nécessaires.** Assurez-vous simplement que l'autoload de Composer trouve la classe et que le conteneur peut résoudre les dépendances du constructeur.

```mermaid theme={null}
flowchart LR
    A["Choisir un nom de classe dans via()"] --> B["ChannelManager::driver()"]
    B --> C{"Déjà résolu ?"}
    C -->|Oui| D["Canal conservé"]
    C -->|Non| E["Création par le conteneur"]
    E --> D
    D --> F["send() récupère le destinataire et envoie"]
```

## Fournir le canal lui-même dans le package

Le package d'exemple `Acme\OperationsLog` consigne des noms d'événements ne contenant aucune information sensible, accompagnés du nom de l'équipe d'exploitation destinataire. Dans une application Laravel, `Psr\Log\LoggerInterface` est lié au service de logs : vous pouvez donc le recevoir dans le constructeur.

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

namespace Acme\OperationsLog;

use Illuminate\Notifications\Notification;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;

final class OperationsLogChannel
{
    public function __construct(private readonly LoggerInterface $logger)
    {
    }

    public function send(object $notifiable, Notification $notification): void
    {
        $destination = $notifiable->routeNotificationFor('operationsLog', $notification);

        if (! is_string($destination) || $destination === '') {
            throw new InvalidArgumentException('Operations log destination is required.');
        }

        $this->logger->info('Operations notification', [
            'destination' => $destination,
            'event' => $notification->toOperationsLog($notifiable),
        ]);
    }
}
```

L'objet reçu est soit un modèle utilisant le trait `Notifiable`, soit un destinataire à la demande créé par `Notification::route()`. Dans le premier cas, c'est `routeNotificationForOperationsLog()` qui fournit la valeur ; dans le second, c'est la valeur enregistrée avec `route('operationsLog', ...)`. Pour éviter de considérer la notification comme envoyée en l'absence de destinataire, cet exemple lève une exception. Si vous le remplacez par un véritable envoi externe, vérifiez également le destinataire et le résultat de l'envoi, et ne masquez pas un échec en le traitant comme un succès.

> [!WARNING]
>
> N'incluez ni jetons d'accès ni données personnelles dans les noms d'événements ou les destinataires consignés dans les logs. Cet exemple est un envoi vers les logs destiné à illustrer la résolution du canal et les tests ; il ne s'agit pas d'une implémentation d'envoi vers un service externe.

## Définir la notification et le destinataire côté application

La notification de l'application renvoie la classe du canal et définit uniquement le message transmis au canal. Avec `ShouldQueue` et `Queueable`, un appel classique à `notify()` passe par la file d'attente.

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

namespace App\Notifications;

use Acme\OperationsLog\OperationsLogChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class HealthCheckPassed extends Notification implements ShouldQueue
{
    use Queueable;

    public function via(object $notifiable): array
    {
        return [OperationsLogChannel::class];
    }

    public function toOperationsLog(object $notifiable): string
    {
        return 'health-check-passed';
    }

    public function viaQueues(): array
    {
        return [OperationsLogChannel::class => 'operations-log'];
    }
}
```

Lorsque vous indiquez le destinataire à la volée, la clé de `Notification::route()` n'est **pas le nom de classe du canal**, mais la clé utilisée dans le canal avec `routeNotificationFor('operationsLog', ...)`.

```php theme={null}
use App\Notifications\HealthCheckPassed;
use Illuminate\Support\Facades\Notification;

Notification::route('operationsLog', 'operations')
    ->notify(new HealthCheckPassed());
```

Pour envoyer depuis un modèle de l'application, ajoutez la méthode suivante au modèle qui utilise `Notifiable`.

```php theme={null}
use Illuminate\Notifications\Notification;

public function routeNotificationForOperationsLog(Notification $notification): string
{
    return 'operations';
}
```

Avec `$user->notify(new HealthCheckPassed())` sur ce modèle, le même canal est sélectionné. Configurez une connexion de file d'attente et lancez un worker qui traite la file `operations-log`.

```shell theme={null}
php artisan queue:work --queue=operations-log
```

`OperationsLogChannel` lui-même est conservé par le Manager pour chaque canal : **ne stockez donc ni le destinataire ni le contenu de la notification dans les propriétés du canal**, et récupérez-les à chaque fois depuis les arguments de `send()`. Ainsi, même un worker de file d'attente qui tourne longtemps ne conserve pas les informations de la notification précédente.

## Si vous avez besoin d'un alias court

Uniquement si vous voulez permettre aux utilisateurs d'indiquer dans `via()` un nom court plutôt qu'un nom de classe, utilisez `Notification::extend()` dans le service provider habituel du package.

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

namespace Acme\OperationsLog;

use Illuminate\Support\Facades\Notification;
use Illuminate\Support\ServiceProvider;

class OperationsLogServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Notification::extend(
            'acme-operations-log',
            fn ($app) => $app->make(OperationsLogChannel::class),
        );
    }
}
```

Faites charger le provider avant le début de l'envoi, par exemple grâce à la [découverte automatique des packages](/fr/advanced/package-discovery.md). Dans les notifications qui utilisent l'alias, remplacez `via()` par `['acme-operations-log']` et utilisez ce même nom comme clé de `viaQueues()`. **`operationsLog`, utilisé pour récupérer le destinataire, est une clé distincte** et n'a pas besoin d'être modifié. Choisissez un nom qui n'entre pas en conflit avec les noms de canaux intégrés.

Le Manager conserve les canaux résolus par nom. Même si vous réenregistrez le traitement de création de l'alias dans le même processus, l'instance déjà créée n'est pas remplacée. Vérifiez aussi tout changement de méthode d'enregistrement dans une application ou un worker fraîchement démarré.

## Tester l'envoi indépendamment de fake

`Notification::fake()` sert à vérifier **l'intention** d'envoyer une notification. Il ne permet pas de confirmer que `send()` du canal ou la récupération de la route fonctionnent. Pour tester l'envoi, utilisez le vrai Manager de notifications et remplacez `LoggerInterface` par une version de test.

L'exemple suivant s'exécute depuis le [`TestCase` de Testbench](/fr/advanced/package-testing). Avec `notifyNow()`, le traitement du canal s'exécute sans lancer de worker, même pour une notification `ShouldQueue`.

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

namespace Acme\OperationsLog\Tests;

use Acme\OperationsLog\OperationsLogChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification as BaseNotification;
use Illuminate\Support\Facades\Notification;
use Orchestra\Testbench\TestCase;
use Psr\Log\LoggerInterface;

class OperationsLogChannelTest extends TestCase
{
    public function test_channel_writes_the_routed_event(): void
    {
        $notification = new class extends BaseNotification implements ShouldQueue {
            use Queueable;

            public function via(object $notifiable): array
            {
                return [OperationsLogChannel::class];
            }

            public function toOperationsLog(object $notifiable): string
            {
                return 'health-check-passed';
            }
        };

        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('info')->with(
            'Operations notification',
            ['destination' => 'operations', 'event' => 'health-check-passed'],
        );

        $this->app->instance(LoggerInterface::class, $logger);

        Notification::route('operationsLog', 'operations')->notifyNow($notification);
    }
}
```

Vérifiez en outre qu'une exception est levée en l'absence de destinataire et, si vous fournissez un alias, qu'il se résout depuis un démarrage à neuf. Si vous passez par la file d'attente, testez aussi, dans une application de vérification qui fait tourner un worker séparé, que le job arrive dans la file `operations-log` et qu'il est traité.

`NotificationSender` découpe les notifications `ShouldQueue` en un job par canal et choisit la file d'après la clé de `viaQueues()`. Si `send()` du canal lève une exception, il déclenche `NotificationFailed` puis relance l'exception ; s'il se termine normalement, il déclenche `NotificationSent`. Les paramètres de nouvelle tentative de la file suivent la configuration de l'application. Notez que si vous masquez l'exception à l'intérieur du canal, un envoi échoué sera traité comme un succès.

## Sources primaires consultées

* [Documentation officielle de Laravel 13 : canaux de notification personnalisés et files d'attente](https://github.com/laravel/docs/blob/13.x/notifications.md#custom-channels)
* [Documentation officielle de Laravel 13 : découverte automatique des packages](https://github.com/laravel/docs/blob/13.x/packages.md#package-discovery)
* [ChannelManager : résolution à partir du nom de classe](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/ChannelManager.php)
* [NotificationSender : envoi, file d'attente et événements par canal](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/NotificationSender.php)
* [RoutesNotifications et AnonymousNotifiable : routes des destinataires](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/RoutesNotifications.php)
* [Manager : conservation et extension des drivers](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Support/Manager.php)
* [Application : liaison de LoggerInterface](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Foundation/Application.php)


## Related topics

- [Notifications](/fr/notifications.md)
- [Canal de notification - Laravel Bluesky](/fr/packages/laravel-bluesky/notification.md)
- [Context](/fr/context.md)
- [Vérification de l'e-mail (Email Verification)](/fr/verification.md)
- [Au cœur du driver de broadcasting Mercure](/fr/advanced/mercure-broadcasting.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.