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

# Eigene Benachrichtigungskanäle in Paketen und Prüfung der Zustellung

> Erläutert, wie Sie Benachrichtigungskanäle für Laravel 13 aus einem Paket bereitstellen, mit Auflösung über den Klassennamen, Routing des Empfängers, Queue-Verarbeitung und Tests der tatsächlichen Zustellung.

Wenn Sie aus einem Paket heraus weitere Zustellwege für Benachrichtigungen anbieten möchten, trennen Sie im Entwurf **die Auswahl des Kanals** von **der Bestimmung des Empfängers**. Diese Seite zeigt am Beispiel eines kleinen Kanals, der Betriebsereignisse protokolliert, wie die vom Paket bereitgestellte Klasse aufgelöst wird und was Sie bei Updates prüfen müssen. Auch wenn Sie die Protokollierung durch den Versand an einen externen Dienst ersetzen, bleibt die Aufgabenteilung zwischen Kanal und Benachrichtigung dieselbe.

Diese Seite basiert auf der Implementierung von Laravel Framework `v13.38.0`. Die Grundlagen finden Sie unter [Notifications](/de/notifications.md), die Testumgebung für Pakete unter [Paket-Tests](/de/advanced/package-testing.md).

## Der Klassenname bestimmt den Zustellweg, die Route den Empfänger

Der Kanal-Klassenname, den `via()` zurückgibt, legt fest, auf welchem Objekt `send()` aufgerufen wird. Der Wert aus `routeNotificationFor()` ist dagegen der Empfänger, den dieser Kanal verwendet. Beide müssen nicht denselben Namen tragen.

| Stelle | Beispiel auf dieser Seite | Aufgabe |
| - | - | - |
| `via()` der Benachrichtigung | `OperationsLogChannel::class` | Auflösung des Kanals und Zustellung |
| Route des benachrichtigbaren Objekts | `operationsLog` → `operations` | Empfänger, der im Kanal verwendet wird |
| `viaQueues()` der Benachrichtigung | `OperationsLogChannel::class` → `operations-log` | Ziel des Jobs, wenn die Benachrichtigung in die Queue gestellt wird |

`NotificationSender` übergibt den von `via()` gewählten Kanalnamen an `ChannelManager::driver()`. Findet `ChannelManager` den Namen weder unter den eingebauten noch unter den registrierten Namen und existiert eine Klasse mit diesem Namen, löst er diese Klasse über den Container auf. **Wenn Sie den Kanal nur über den Klassennamen nutzen, brauchen Sie weder `Notification::extend()` noch einen Service Provider im Paket.** Sorgen Sie lediglich dafür, dass das Composer-Autoloading die Klasse findet und der Container die Abhängigkeiten des Konstruktors auflösen kann.

```mermaid theme={null}
flowchart LR
    A["Klassennamen in via() wählen"] --> B["ChannelManager::driver()"]
    B --> C{"Bereits aufgelöst?"}
    C -->|Ja| D["Vorgehaltener Kanal"]
    C -->|Nein| E["Über den Container erzeugen"]
    E --> D
    D --> F["Empfänger in send() ermitteln und zustellen"]
```

## Den Kanal selbst im Paket bereitstellen

Das Beispielpaket `Acme\OperationsLog` protokolliert Ereignisnamen ohne vertrauliche Informationen zusammen mit dem Namen des Betriebsteams, das benachrichtigt wird. In einer Laravel-Anwendung ist `Psr\Log\LoggerInterface` an den Log-Dienst gebunden, sodass Sie es über den Konstruktor erhalten können.

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

Als übergebenes Objekt sind entweder ein Model mit dem Trait `Notifiable` oder ein On-Demand-Empfänger aus `Notification::route()` vorgesehen. Im ersten Fall liefert `routeNotificationForOperationsLog()` den Wert, im zweiten der mit `route('operationsLog', ...)` registrierte Wert. Damit eine Benachrichtigung ohne Empfänger nicht als zugestellt gilt, wirft dieses Beispiel eine Exception. Auch wenn Sie die Protokollierung durch eine echte externe Zustellung ersetzen, prüfen Sie Empfänger und Versandergebnis und entwerfen Sie den Kanal so, dass Fehler nicht stillschweigend als Erfolg verbucht werden.

> [!WARNING]
>
> Nehmen Sie keine Zugriffstoken oder personenbezogenen Daten in die protokollierten Ereignisnamen oder Empfänger auf. Dieses Beispiel ist eine Log-Ausgabe zur Veranschaulichung von Kanalauflösung und Tests und keine Implementierung einer Zustellung an externe Dienste.

## Benachrichtigung und Empfänger in der Anwendung definieren

Die Benachrichtigung der Anwendung gibt die Kanalklasse zurück und definiert nur die Nachricht, die an den Kanal übergeben wird. Mit `ShouldQueue` und `Queueable` laufen normale `notify()`-Aufrufe über die Queue.

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

Wenn Sie den Empfänger ad hoc angeben, ist der Schlüssel von `Notification::route()` **nicht der Kanal-Klassenname**, sondern der Schlüssel, den der Kanal mit `routeNotificationFor('operationsLog', ...)` abfragt.

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

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

Wenn Sie aus einem Model der Anwendung heraus versenden, fügen Sie dem Model mit `Notifiable` die folgende Methode hinzu.

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

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

Auch `$user->notify(new HealthCheckPassed())` für dieses Model wählt denselben Kanal. Konfigurieren Sie eine Queue-Verbindung und starten Sie einen Worker, der die Queue `operations-log` abarbeitet.

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

Da `OperationsLogChannel` selbst pro Kanal im Manager vorgehalten wird, **speichern Sie Empfänger oder Nachrichteninhalt nicht in Eigenschaften des Kanals**, sondern entnehmen Sie sie bei jedem Aufruf den Argumenten von `send()`. So schleppt auch ein lange laufender Queue-Worker keine Daten aus vorherigen Benachrichtigungen mit.

## Wenn Sie einen kurzen Alias benötigen

Nur wenn Anwender in `via()` statt des Klassennamens einen kurzen Namen angeben sollen, verwenden Sie `Notification::extend()` in einem normalen Service Provider des Pakets.

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

Sorgen Sie etwa über die [automatische Paket-Erkennung](/de/advanced/package-discovery.md) dafür, dass der Provider geladen wird, bevor die Zustellung beginnt. In Benachrichtigungen, die den Alias verwenden, ändern Sie `via()` auf `['acme-operations-log']` und den Schlüssel in `viaQueues()` auf denselben Namen. **`operationsLog` für die Ermittlung des Empfängers ist ein separater Schlüssel** und muss nicht geändert werden. Wählen Sie einen Namen, der nicht mit eingebauten Kanalnamen kollidiert.

Der Manager hält aufgelöste Kanäle pro Name vor. Wenn Sie die Erzeugungslogik eines Alias im selben Prozess erneut registrieren, wird eine bereits erzeugte Instanz nicht ersetzt. Prüfen Sie Änderungen an der Registrierung daher auch in einer neu gestarteten Anwendung bzw. einem neu gestarteten Worker.

## Die Zustellung unabhängig von fake testen

`Notification::fake()` dient dazu, die **Absicht** zu prüfen, eine Benachrichtigung zu versenden. Ob `send()` des Kanals und die Ermittlung der Route funktionieren, lässt sich damit nicht feststellen. Verwenden Sie für Zustelltests den echten Notification-Manager und ersetzen Sie `LoggerInterface` durch eine Testvariante.

Das folgende Beispiel läuft über den [`TestCase` von Testbench](/de/advanced/package-testing). Mit `notifyNow()` führen Sie die Verarbeitung des Kanals auch für `ShouldQueue`-Benachrichtigungen aus, ohne einen Worker zu starten.

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

Prüfen Sie außerdem, dass ohne Empfänger eine Exception geworfen wird und dass sich ein angebotener Alias nach einem Neustart auflösen lässt. Wenn Sie den Kanal über die Queue nutzen, testen Sie zusätzlich in einer Prüfanwendung mit laufendem Queue-Worker, dass Jobs in der Queue `operations-log` ankommen und verarbeitet werden.

`NotificationSender` teilt `ShouldQueue`-Benachrichtigungen pro Kanal in Jobs auf und wählt die Queue anhand des Schlüssels in `viaQueues()`. Wirft `send()` des Kanals eine Exception, löst er `NotificationFailed` aus und wirft die Exception erneut; kehrt `send()` normal zurück, löst er `NotificationSent` aus. Die Wiederholungseinstellungen der Queue richten sich nach der Konfiguration der Anwendung. Beachten Sie: Wenn Sie Exceptions im Kanal verschlucken, gilt eine fehlgeschlagene Zustellung als Erfolg.

## Verwendete Primärquellen

* [Offizielle Laravel-13-Dokumentation: Eigene Benachrichtigungskanäle und Queues](https://github.com/laravel/docs/blob/13.x/notifications.md#custom-channels)
* [Offizielle Laravel-13-Dokumentation: Automatische Paket-Erkennung](https://github.com/laravel/docs/blob/13.x/packages.md#package-discovery)
* [ChannelManager: Auflösung über den Klassennamen](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/ChannelManager.php)
* [NotificationSender: Zustellung, Queue und Events pro Kanal](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/NotificationSender.php)
* [RoutesNotifications und AnonymousNotifiable: Empfänger-Routen](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/RoutesNotifications.php)
* [Manager: Vorhalten und Erweitern von Treibern](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Support/Manager.php)
* [Application: Binding von LoggerInterface](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Foundation/Application.php)


## Related topics

- [Middleware-Registrierung und Geltungsbereich in Paketen](/de/advanced/package-middleware.md)
- [GeneratorCommand — Eigene make:-Befehle implementieren](/de/advanced/generator-command.md)
- [Blade-Erweiterungen und Kompilierungscache in Paketen](/de/advanced/package-blade-extensions.md)
- [Hooks nach der Service-Auflösung in Paketen](/de/advanced/package-service-resolution.md)
- [Paketkonfiguration zusammenführen und cachen](/de/advanced/package-config-merging.md)


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