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

# Laravel-packages ontwikkelen

> Hoe je Laravel-packages ontwikkelt met de service provider als kern: van het publiceren van configuratie, views, migrations en facades tot automatische detectie en lazy loading.

## Wat is een package?

Een package in Laravel is een Composer-package dat functionaliteit toevoegt aan je applicatie. Er zijn grofweg twee soorten packages:

* **Standalone packages** — generieke PHP-libraries die niet van Laravel afhankelijk zijn (bijvoorbeeld Carbon, Pest)
* **Laravel-packages** — packages met functionaliteit die met Laravel is geïntegreerd, zoals routes, controllers, views en configuratie

Deze gids behandelt de tweede soort: het ontwikkelen van Laravel-specifieke packages. Voor packageontwikkeling heb je diepgaande kennis nodig van de interne structuur van Laravel, zoals service providers, facades en het publiceren van configuratiebestanden.

<Info>
  Voor het schrijven van tests voor je package gebruik je [Orchestra Testbench](https://github.com/orchestral/testbench). Je kunt packagetests schrijven zoals je dat in een gewone Laravel-applicatie doet.
</Info>

## Automatische detectie van packages

Wanneer een package wordt geïnstalleerd, leest Laravel de sectie `extra.laravel` uit `composer.json` en registreert de service providers en facades automatisch.

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

Met deze configuratie wordt je package automatisch geladen, zonder dat gebruikers `bootstrap/providers.php` handmatig hoeven aan te passen.

<Info>
  Hoe deze automatische detectie is geïmplementeerd en wanneer de cache opnieuw wordt opgebouwd, lees je in detail in [De interne structuur van package discovery](/nl/advanced/package-discovery).
</Info>

### Automatische detectie uitschakelen

Als een gebruiker de automatische detectie van een specifiek package wil uitschakelen, stelt die dat in de `composer.json` van de applicatie in.

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

## De rol van de service provider

De service provider is het toegangspunt van je package. Hier centraliseer je alle logica om resources zoals views, configuratie, migrations en routes bij Laravel te registreren.

Een service provider erft van `Illuminate\Support\ServiceProvider` en heeft twee methodes: `register` en `boot`.

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

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    /**
     * Registreer de services van het package
     */
    public function register(): void
    {
        // Bindings aan de service container doe je hier
        $this->mergeConfigFrom(
            __DIR__.'/../config/courier.php', 'courier'
        );

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

    /**
     * Bootstrap de services van het package
     */
    public function boot(): void
    {
        // Resources registreer je hier
        $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>
  Registreer geen event listeners, routes of views in de `register`-methode. Je zou per ongeluk services kunnen gebruiken van een andere service provider die nog niet is geladen. Doe alles behalve bindings altijd in de `boot`-methode.
</Warning>

## Configuratiebestanden publiceren

### publishes() — bestanden publiceren

Als je in de `boot`-methode `publishes()` aanroept, kunnen gebruikers met het `vendor:publish`-commando het configuratiebestand naar hun eigen applicatie kopiëren.

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

Na publicatie haal je de configuratiewaarden op zoals bij elke andere config-toegang.

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

### mergeConfigFrom() — samenvoegen met standaardwaarden

Als je `mergeConfigFrom()` gebruikt in de `register`-methode, worden de standaardwaarden van het package ook gebruikt wanneer de gebruiker het configuratiebestand niet heeft gepubliceerd.

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

<Warning>
  `mergeConfigFrom()` voegt geneste arrays niet tot op diepere niveaus samen. Bij configuraties met multidimensionale arrays kan het gebeuren dat, wanneer de gebruiker maar een deel definieert, de overige opties niet worden samengevoegd.
</Warning>

### Publicatiegroepen scheiden met tags

Als je een tag opgeeft als tweede argument van `publishes()`, kunnen gebruikers alleen de resources publiceren die ze nodig hebben.

```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}
# Alleen het configuratiebestand publiceren
php artisan vendor:publish --tag=courier-config

# Alle bestanden publiceren die de provider aanbiedt
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider"
```

## Routes registreren

Laad routebestanden met `loadRoutesFrom()`. Als de routecache van de applicatie actief is, wordt dit automatisch overgeslagen.

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

In het routebestand verwijs je naar de controllers van het 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');
});
```

## Migrations publiceren

Met `publishesMigrations()` kun je migrationbestanden publiceren. Laravel werkt de timestamps bij publicatie automatisch bij.

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

## Views publiceren

### loadViewsFrom() — views registreren

Registreer de viewsmap met `loadViewsFrom()`. Met de namespace uit het tweede argument verwijs je naar views in het formaat `package::view`.

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

Na registratie verwijs je naar views via de package-namespace.

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

Laravel zoekt views op twee plekken. Eerst controleert het de map `resources/views/vendor/courier` van de applicatie; als die er niet is, gebruikt het de viewsmap van het package. Zo kunnen gebruikers de views aanpassen.

### Views publiceren

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

### Blade-componenten registreren

Als je componenten in je package opneemt, registreer je die in de `boot`-methode.

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

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

Je kunt ook in één keer registreren via een componentnamespace.

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

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

```blade theme={null}
{{-- Bij individuele registratie --}}
<x-courier-alert />

{{-- Bij namespace-registratie --}}
<x-courier::alert />
```

## Vertaalbestanden publiceren

Registreer vertaalbestanden met `loadTranslationsFrom()`. Naar vertalingen verwijs je in het formaat `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}
// Vertalingen gebruiken
echo trans('courier::messages.welcome');
```

Voor JSON-vertaalbestanden gebruik je `loadJsonTranslationsFrom()`.

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

## Commando's registreren

Artisan-commando's van je package registreer je met de `commands()`-methode. Het is gebruikelijk om ze alleen in de console-omgeving te registreren.

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

### Integratie met het optimize-commando

Als je package een eigen cache heeft, kun je met de `optimizes()`-methode integreren met `php artisan optimize` en `php artisan optimize:clear`.

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

### Informatie toevoegen aan het `about`-commando

Gebruik `AboutCommand::add()` om package-informatie toe te voegen aan de uitvoer van `php artisan about`.

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

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

## Een facade maken

Met een facade kun je een binding uit de service container aanroepen alsof het statische methodes zijn.

<Steps>
  <Step title="Maak een serviceklasse">
    ```php theme={null}
    <?php

    namespace Acme\Courier;

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

        public function send(string $to, string $message): bool
        {
            // Logica voor het versturen van berichten
            return true;
        }

        public function track(string $id): array
        {
            // Logica voor het ophalen van trackinginformatie
            return ['status' => 'delivered'];
        }
    }
    ```
  </Step>

  <Step title="Maak een facade-klasse">
    Erf van `Illuminate\Support\Facades\Facade` en geef in `getFacadeAccessor()` de bindingssleutel van de service container terug.

    ```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="Bind in de 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="Registreer in composer.json">
    ```json theme={null}
    "extra": {
        "laravel": {
            "providers": [
                "Acme\\Courier\\CourierServiceProvider"
            ],
            "aliases": {
                "Courier": "Acme\\Courier\\Facades\\Courier"
            }
        }
    }
    ```
  </Step>
</Steps>

Door PHPDoc-`@method`-annotaties aan de facademethodes toe te voegen, werkt de autocomplete van je IDE.

```php theme={null}
// De service aanroepen via de facade
use Acme\Courier\Facades\Courier;

Courier::send('user@example.com', 'Je pakket is aangekomen');
$status = Courier::track('ABC-123');
```

## DeferrableProvider — lazy loading implementeren

Een provider die alleen bindings aan de service container toevoegt, kan lazy loading realiseren door de interface `DeferrableProvider` te implementeren. Omdat de provider pas wordt geladen wanneer de service echt nodig is, verbetert de performance van je applicatie.

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

    /**
     * Geef de lijst van services terug die deze provider aanbiedt
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
        return [CourierManager::class];
    }
}
```

Laravel compileert en bewaart de lijst van services die deferred providers aanbieden. De provider wordt alleen geladen wanneer een van de services uit `provides()` wordt opgelost.

<Warning>
  Gebruik `DeferrableProvider` niet voor providers die resources moeten registreren (views, routes, event listeners, enz.). Bij lazy loading blijven die resources anders ongeregistreerd.
</Warning>

## Packages testen

Om je package op zichzelf te testen gebruik je [Orchestra Testbench](https://github.com/orchestral/testbench). Je kunt packagetests schrijven alsof je in een gewone Laravel-applicatie zit.

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

Overschrijf in je testcase `getPackageProviders()` om de service provider van je package te registreren.

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

namespace Acme\Courier\Tests;

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

class TestCase extends BaseTestCase
{
    /**
     * Registreer de service providers van het package
     */
    protected function getPackageProviders($app): array
    {
        return [
            CourierServiceProvider::class,
        ];
    }

    /**
     * Registreer de facade-aliassen van het package
     */
    protected function getPackageAliases($app): array
    {
        return [
            'Courier' => \Acme\Courier\Facades\Courier::class,
        ];
    }

    /**
     * Omgevingsconfiguratie voor tests
     */
    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', 'Testbericht');

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

## Publiceren op Composer

Best practices voor het publiceren van je package op [Packagist](https://packagist.org/).

**Basisconfiguratie van `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>
  Door afhankelijk te zijn van `illuminate/support` in plaats van heel `illuminate/framework`, neem je alleen de Laravel-componenten op die je nodig hebt. Houd de dependency tree van je package klein.
</Tip>

**Voorbeeld van een directorystructuur**

```
acme/courier/
├── config/
│   └── courier.php
├── database/
│   └── migrations/
│       └── 2024_01_01_000000_create_courier_logs_table.php
├── lang/
│   └── ja/
│       └── 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
```

## Gerelateerde pagina's

<Columns cols={2}>
  <Card title="Service providers" icon="plug" href="/nl/service-providers">
    Bekijk de details van de `register`- en `boot`-methodes van service providers en van deferred providers.
  </Card>

  <Card title="Versiecompatibiliteit beheren" icon="git-branch" href="/nl/advanced/package-versioning">
    Uitleg over strategieën voor major-upgrades van Laravel en PHP en de testmatrixconfiguratie van GitHub Actions.
  </Card>
</Columns>


## Related topics

- [Laravel-packages testen met Orchestra Testbench](/nl/advanced/package-testing.md)
- [CHANGELOG en releasebeheer voor packages](/nl/advanced/package-changelog.md)
- [Statische analyse van packages (PHPStan / Larastan)](/nl/advanced/package-static-analysis.md)
- [Laravel Package Skeleton — officiële startertemplate voor packages](/nl/blog/package-skeleton-introduction.md)
- [Versiecompatibiliteit van packages beheren](/nl/advanced/package-versioning.md)
