Zum Hauptinhalt springen

Was ist ein Paket?

Ein Paket in Laravel ist ein Composer-Paket, das die Anwendung um Funktionalität erweitert. Es gibt zwei Arten:
  • Standalone-Pakete — universelle PHP-Bibliotheken ohne Laravel-Abhängigkeit (z. B. Carbon, Pest).
  • Laravel-Pakete — Pakete mit Laravel-integrierten Bausteinen wie Routen, Controllern, Views oder Konfiguration.
Diese Anleitung behandelt Letztere: die Entwicklung Laravel-spezifischer Pakete. Sie erfordert ein solides Verständnis interner Laravel-Konzepte wie Service Provider, Facades und der Veröffentlichung von Konfigurationsdateien.
Für Pakettests nutzen Sie Orchestra Testbench. Damit lassen sich Pakettests genauso schreiben wie in einer normalen Laravel-Anwendung.

Auto-Discovery für Pakete

Bei der Installation liest Laravel den Abschnitt extra.laravel aus der composer.json und registriert Service Provider und Facades automatisch.
"extra": {
    "laravel": {
        "providers": [
            "Acme\\Courier\\CourierServiceProvider"
        ],
        "aliases": {
            "Courier": "Acme\\Courier\\Facades\\Courier"
        }
    }
}
Mit dieser Einstellung müssen Nutzer die bootstrap/providers.php nicht anpassen — das Paket wird automatisch geladen.

Auto-Discovery deaktivieren

Wollen Nutzer die Erkennung für ein bestimmtes Paket deaktivieren, tragen sie es in der composer.json ihrer Anwendung ein.
"extra": {
    "laravel": {
        "dont-discover": [
            "acme/courier"
        ]
    }
}

Rolle des Service Providers

Der Service Provider ist der Einstiegspunkt Ihres Pakets. Hier bündeln Sie die Registrierung von Views, Konfiguration, Migrationen, Routen und weiteren Ressourcen. Ein Service Provider erbt von Illuminate\Support\ServiceProvider und hat zwei Methoden: register und boot.
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    /**
     * Services des Pakets registrieren
     */
    public function register(): void
    {
        // Bindings im Service Container hier vornehmen
        $this->mergeConfigFrom(
            __DIR__.'/../config/courier.php', 'courier'
        );

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

    /**
     * Services des Pakets bootstrappen
     */
    public function boot(): void
    {
        // Ressourcen hier registrieren
        $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');
    }
}
Registrieren Sie im register-Methodenkörper keine Event-Listener, Routen oder Views. Sie könnten Services eines noch nicht geladenen Providers unabsichtlich aufrufen. Alles außer Bindings gehört in boot.

Konfigurationsdateien veröffentlichen

publishes() — Dateien veröffentlichen

Ein Aufruf von publishes() in boot erlaubt Nutzern, Dateien über vendor:publish in die eigene Anwendung zu kopieren.
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ]);
}
Auf veröffentlichte Werte greifen Sie ganz normal per config-Helper zu.
$value = config('courier.option');

mergeConfigFrom() — mit Defaults mergen

Mit mergeConfigFrom() in register gelten Paket-Defaults auch dann, wenn der Nutzer die Datei nicht veröffentlicht hat.
public function register(): void
{
    $this->mergeConfigFrom(
        __DIR__.'/../config/courier.php', 'courier'
    );
}
mergeConfigFrom() merged nicht tief in verschachtelte Arrays. Bei mehrdimensionalen Konfigurationen können vom Nutzer nur teilweise definierte Bereiche fehlende Optionen zurücklassen.

Veröffentlichungsgruppen per Tag

Mit dem zweiten Argument von publishes() können Nutzer gezielt einzelne Ressourcengruppen veröffentlichen.
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');
}
# Nur die Konfigurationsdatei veröffentlichen
php artisan vendor:publish --tag=courier-config

# Alle Ressourcen des Providers veröffentlichen
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider"

Routen registrieren

loadRoutesFrom() lädt eine Routen-Datei. Bei aktiver Routen-Cache-Kompilierung der Anwendung wird der Aufruf automatisch übersprungen.
public function boot(): void
{
    $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
In der Routen-Datei referenzieren Sie Ihre Paket-Controller.
// 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');
});

Migrationen veröffentlichen

Mit publishesMigrations() veröffentlichen Sie Migrationen. Laravel aktualisiert dabei die Zeitstempel automatisch.
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}

Views veröffentlichen

loadViewsFrom() — Views registrieren

Registrieren Sie das View-Verzeichnis mit loadViewsFrom(). Über den Namensraum im zweiten Argument nutzen Sie Views wie package::view.
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}
Nach der Registrierung greifen Sie über den Paket-Namensraum darauf zu:
Route::get('/dashboard', function () {
    return view('courier::dashboard');
});
Laravel sucht Views an zwei Orten: zuerst in resources/views/vendor/courier der Anwendung, ansonsten im Paketverzeichnis. So können Nutzer Views anpassen.

Views veröffentlichen

public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');

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

Blade-Komponenten registrieren

Bringt Ihr Paket Komponenten mit, registrieren Sie sie in boot.
use Illuminate\Support\Facades\Blade;
use Acme\Courier\View\Components\AlertComponent;

public function boot(): void
{
    Blade::component('courier-alert', AlertComponent::class);
}
Mit einem Komponenten-Namensraum registrieren Sie mehrere Komponenten auf einmal.
use Illuminate\Support\Facades\Blade;

public function boot(): void
{
    Blade::componentNamespace('Acme\\Courier\\View\\Components', 'courier');
}
{{-- Bei Einzelregistrierung --}}
<x-courier-alert />

{{-- Bei Namespace-Registrierung --}}
<x-courier::alert />

Übersetzungen veröffentlichen

Mit loadTranslationsFrom() registrieren Sie Übersetzungsdateien. Übersetzungen referenzieren Sie als package::file.key.
public function boot(): void
{
    $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

    $this->publishes([
        __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
    ]);
}
// Verwendung
echo trans('courier::messages.welcome');
Für JSON-Übersetzungen verwenden Sie loadJsonTranslationsFrom().
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}

Commands registrieren

Artisan-Commands Ihres Pakets registrieren Sie über commands() — üblicherweise nur im Console-Kontext.
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,
        ]);
    }
}

In optimize einklinken

Hat Ihr Paket eigene Caches, integrieren Sie sich per optimizes() in php artisan optimize und php artisan optimize:clear.
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->optimizes(
            optimize: 'courier:cache',
            clear: 'courier:clear-cache',
        );
    }
}

Informationen im about-Command ergänzen

Mit AboutCommand::add() erweitern Sie die Ausgabe von php artisan about.
use Illuminate\Foundation\Console\AboutCommand;

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

Facades erstellen

Mit einer Facade rufen Sie Container-Bindings wie statische Methoden auf.
1

Service-Klasse erstellen

<?php

namespace Acme\Courier;

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

    public function send(string $to, string $message): bool
    {
        // Nachricht senden
        return true;
    }

    public function track(string $id): array
    {
        // Sendungsinformationen abrufen
        return ['status' => 'delivered'];
    }
}
2

Facade-Klasse erstellen

Erben Sie von Illuminate\Support\Facades\Facade und geben Sie in getFacadeAccessor() den Container-Schlüssel zurück.
<?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;
    }
}
3

Im Service Provider binden

public function register(): void
{
    $this->app->singleton(\Acme\Courier\CourierManager::class, function ($app) {
        return new \Acme\Courier\CourierManager($app['config']['courier']);
    });
}
4

In composer.json registrieren

"extra": {
    "laravel": {
        "providers": [
            "Acme\\Courier\\CourierServiceProvider"
        ],
        "aliases": {
            "Courier": "Acme\\Courier\\Facades\\Courier"
        }
    }
}
PHPDoc-@method-Annotationen ermöglichen IDE-Vervollständigung.
// Aufruf über die Facade
use Acme\Courier\Facades\Courier;

Courier::send('[email protected]', 'Paket ist zugestellt');
$status = Courier::track('ABC-123');

DeferrableProvider — Lazy Loading umsetzen

Provider, die nur Bindings vornehmen, können durch DeferrableProvider lazy geladen werden. Der Provider wird erst geladen, wenn der Service wirklich benötigt wird — das verbessert die Performance.
<?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']);
        });
    }

    /**
     * Liste der von diesem Provider bereitgestellten Services
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
        return [CourierManager::class];
    }
}
Laravel kompiliert und speichert die von Deferred Providern bereitgestellten Services. Nur wenn ein in provides() gelisteter Service aufgelöst wird, lädt Laravel den Provider.
Nutzen Sie DeferrableProvider nicht für Provider, die Ressourcen (Views, Routen, Event-Listener usw.) registrieren. Beim Deferring werden diese Ressourcen nicht registriert.

Pakete testen

Für den Test eines Pakets nutzen Sie Orchestra Testbench. Sie schreiben die Tests, als säßen Sie in einer normalen Laravel-Anwendung.
composer require --dev orchestra/testbench
Registrieren Sie den Service Provider Ihres Pakets, indem Sie getPackageProviders() überschreiben.
<?php

namespace Acme\Courier\Tests;

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

class TestCase extends BaseTestCase
{
    /**
     * Service Provider des Pakets registrieren
     */
    protected function getPackageProviders($app): array
    {
        return [
            CourierServiceProvider::class,
        ];
    }

    /**
     * Facade-Aliase des Pakets registrieren
     */
    protected function getPackageAliases($app): array
    {
        return [
            'Courier' => \Acme\Courier\Facades\Courier::class,
        ];
    }

    /**
     * Testumgebung konfigurieren
     */
    protected function defineEnvironment($app): void
    {
        $app['config']->set('courier.api_key', 'test-key');
    }
}
<?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('[email protected]', 'Testnachricht');

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

Veröffentlichung auf Composer

Best Practices für die Veröffentlichung auf Packagist. Grundkonfiguration der composer.json
{
    "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
}
Mit einer Abhängigkeit zu illuminate/support (statt zum kompletten illuminate/framework) binden Sie nur die benötigten Komponenten ein. Halten Sie den Abhängigkeitsbaum Ihres Pakets klein.
Beispiel für die Verzeichnisstruktur
acme/courier/
├── config/
│   └── courier.php
├── database/
│   └── migrations/
│       └── 2024_01_01_000000_create_courier_logs_table.php
├── lang/
│   └── de/
│       └── 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

Verwandte Seiten

Service Provider

Details zu register/boot sowie zu Deferred Providern.

Versionskompatibilität verwalten

Strategien für Laravel-/PHP-Major-Upgrades und die Testmatrix in GitHub Actions.
Zuletzt geändert am 13. Juli 2026