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

# Aangepaste notificatiekanalen in een package en verificatie van de bezorging

> Lever notificatiekanalen van Laravel 13 vanuit een package en lees hoe resolven via de klassenaam, het routeren van bestemmingen, wachtrijverwerking en het testen van de echte bezorging werken.

Wanneer je vanuit een package extra manieren wilt bieden om notificaties te bezorgen, ontwerp je **de keuze van het kanaal** en **het bepalen van de bestemming** los van elkaar. Op deze pagina gebruiken we een klein kanaal dat operationele gebeurtenissen naar de log schrijft als voorbeeld, en leggen we uit via welk pad de klassen uit je package worden geresolved en welke verificatie nodig is bij updates. Ook als je dit vervangt door verzending naar een externe dienst, blijft de verdeling van verantwoordelijkheden tussen kanaal en notificatie hetzelfde.

Deze pagina is gecontroleerd aan de hand van de implementatie van Laravel Framework `v13.38.0`. Zie [Notificaties](/nl/notifications.md) voor de basisbewerkingen van notificaties en [Packages testen](/nl/advanced/package-testing.md) voor de testomgeving van een package.

## De klassenaam is de bezorgwijze, de route is de bestemming

De kanaalklassenaam die je uit `via()` teruggeeft, bepaalt van welk object `send()` wordt aangeroepen. De waarde die je uit `routeNotificationFor()` ophaalt, is daarentegen de bestemming die dat kanaal gebruikt. De twee hoeven niet dezelfde naam te hebben.

| Plaats | Voorbeeld op deze pagina | Rol |
| - | - | - |
| `via()` van de notificatie | `OperationsLogChannel::class` | Resolven van het kanaal en bezorgen |
| Route van het notifiable object | `operationsLog` → `operations` | Bestemming die binnen het kanaal wordt gebruikt |
| `viaQueues()` van de notificatie | `OperationsLogChannel::class` → `operations-log` | Bestemming van de job wanneer je in de wachtrij plaatst |

`NotificationSender` geeft de kanaalnaam die `via()` heeft gekozen door aan `ChannelManager::driver()`. Als `ChannelManager` de naam niet vindt bij de ingebouwde of geregistreerde namen en de naam een bestaande klasse is, resolvet hij die klasse via de container. **Als je het kanaal alleen via de klassenaam gebruikt, heb je geen `Notification::extend()` of service provider in je package nodig.** Zorg er wel voor dat de klasse via de autoload van Composer gevonden wordt en dat de container de afhankelijkheden van de constructor kan resolven.

```mermaid theme={null}
flowchart LR
    A["Klassenaam kiezen in via()"] --> B["ChannelManager::driver()"]
    B --> C{"Al geresolved?"}
    C -->|Ja| D["Vastgehouden kanaal"]
    C -->|Nee| E["Aanmaken via de container"]
    E --> D
    D --> F["Bestemming ophalen en bezorgen in send()"]
```

## Het kanaal zelf vanuit het package leveren

Het voorbeeldpackage `Acme\OperationsLog` legt gebeurtenisnamen zonder gevoelige informatie vast, samen met de naam van het operationele team dat de notificatie ontvangt. In een Laravel-applicatie is `Psr\Log\LoggerInterface` gekoppeld aan de logservice, dus je kunt deze via de constructor ontvangen.

```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),
        ]);
    }
}
```

Het ontvangen object is naar verwachting een model dat de trait `Notifiable` gebruikt, of een on-demand bestemming die `Notification::route()` aanmaakt. In het eerste geval wordt de waarde van `routeNotificationForOperationsLog()` teruggegeven, in het tweede geval de waarde die met `route('operationsLog', ...)` is geregistreerd. Om te voorkomen dat een notificatie zonder bestemming als bezorgd wordt behandeld, gooit dit voorbeeld een exception. Ook als je dit vervangt door echte externe bezorging, controleer je de bestemming en het verzendresultaat, en ontwerp je het zo dat fouten niet stilletjes als succes worden behandeld.

> [!WARNING]
>
> Neem geen toegangstokens of persoonsgegevens op in de gebeurtenisnamen of bestemmingen die je naar de log schrijft. Dit voorbeeld is een logverzending om het resolven van kanalen en het testen te laten zien, en geen implementatie van bezorging naar een externe dienst.

## Notificatie en bestemming definiëren aan de kant van de gebruiker

De notificatie in de applicatie geeft de kanaalklasse terug en definieert alleen het bericht dat aan het kanaal wordt doorgegeven. Met `ShouldQueue` en `Queueable` loopt een gewone aanroep van `notify()` via de wachtrij.

```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'];
    }
}
```

Als je de bestemming ter plekke opgeeft, is de sleutel van `Notification::route()` **niet de kanaalklassenaam**, maar de sleutel van `routeNotificationFor('operationsLog', ...)` die binnen het kanaal wordt aangeroepen.

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

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

Wil je vanuit een model in de applicatie verzenden, voeg dan de volgende methode toe aan het model dat `Notifiable` gebruikt.

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

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

Ook met `$user->notify(new HealthCheckPassed())` op dit model wordt hetzelfde kanaal gekozen. Configureer een wachtrijverbinding en start een worker die de wachtrij `operations-log` verwerkt.

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

`OperationsLogChannel` zelf wordt per kanaal in de Manager vastgehouden. **Sla de bestemming of de inhoud van de notificatie daarom niet op in properties van het kanaal**, maar haal ze bij elke aanroep uit de argumenten van `send()`. Zo neemt ook een langlopende queue worker geen gegevens van een vorige notificatie mee.

## Als je een korte alias nodig hebt

Alleen als je gebruikers in `via()` een korte naam wilt laten opgeven in plaats van een klassenaam, gebruik je `Notification::extend()` in de gewone service provider van je 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),
        );
    }
}
```

Laat de provider laden voordat de bezorging begint, bijvoorbeeld via [automatische packagedetectie](/nl/advanced/package-discovery.md). In notificaties die de alias kiezen, verander je `via()` in `['acme-operations-log']` en gebruik je dezelfde naam als sleutel in `viaQueues()`. **`operationsLog` voor het ophalen van de bestemming is een aparte sleutel** en hoeft niet te veranderen. Kies een naam die niet botst met de namen van ingebouwde kanalen.

De Manager houdt geresolvede kanalen per naam vast. Als je de aanmaakroutine van de alias in hetzelfde proces opnieuw registreert, wordt een al aangemaakte instantie niet vervangen. Verifieer wijzigingen in de registratie ook in een nieuw opgestarte applicatie of worker.

## De bezorging los van fake testen

`Notification::fake()` is bedoeld om de **intentie** om een notificatie te verzenden te verifiëren. Je kunt er niet mee controleren of `send()` van het kanaal en het ophalen van de route werken. Gebruik voor het testen van de bezorging de echte notificatiemanager en vervang `LoggerInterface` door een testversie.

Het volgende voorbeeld draait vanuit [de `TestCase` van Testbench](/nl/advanced/package-testing). Met `notifyNow()` kun je de verwerking van het kanaal uitvoeren zonder een worker te starten, ook voor een `ShouldQueue`-notificatie.

```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);
    }
}
```

Controleer daarnaast dat er een exception optreedt als er geen bestemming is, en dat een alias, als je die aanbiedt, vanaf een nieuwe start kan worden geresolved. Gebruik je het kanaal via de wachtrij, test dan ook in een aparte verificatieapplicatie met een draaiende queue worker dat jobs in de wachtrij `operations-log` aankomen en worden verwerkt.

`NotificationSender` splitst een `ShouldQueue`-notificatie per kanaal op in jobs en kiest de wachtrij aan de hand van de sleutels van `viaQueues()`. Als `send()` van het kanaal een exception gooit, vuurt hij `NotificationFailed` af en gooit de exception opnieuw. Keert `send()` normaal terug, dan vuurt hij `NotificationSent` af. De instellingen voor nieuwe pogingen in de wachtrij volgen de configuratie van de applicatie. Let op: als je exceptions binnen het kanaal stilletjes afvangt, wordt een mislukte bezorging als succes behandeld.

## Geraadpleegde primaire bronnen

* [Officiële documentatie van Laravel 13: aangepaste notificatiekanalen en wachtrijen](https://github.com/laravel/docs/blob/13.x/notifications.md#custom-channels)
* [Officiële documentatie van Laravel 13: automatische packagedetectie](https://github.com/laravel/docs/blob/13.x/packages.md#package-discovery)
* [ChannelManager: resolven vanuit de klassenaam](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/ChannelManager.php)
* [NotificationSender: bezorging, wachtrijen en events per kanaal](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/NotificationSender.php)
* [RoutesNotifications en AnonymousNotifiable: routes naar bestemmingen](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/RoutesNotifications.php)
* [Manager: vasthouden en uitbreiden van drivers](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Support/Manager.php)
* [Application: binding van LoggerInterface](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Foundation/Application.php)


## Related topics

- [Verificatie (Verify)](/nl/packages/laravel-bluesky/verify.md)
- [Storage-drivers registreren in een package en opnieuw resolven](/nl/advanced/package-filesystem-drivers.md)
- [Views van packages overschrijven en bijwerken](/nl/advanced/package-views.md)
- [Migrations van packages publiceren en bijwerken](/nl/advanced/package-migrations.md)
- [Packageconfiguratie samenvoegen en cachen](/nl/advanced/package-config-merging.md)


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