Concepts

Service Providers

How each layer wires its migrations, seeders, routes, views and config through a provider of its own.

Every layer has a service provider: it is where the layer plugs itself into the application. The provider extends Lomkit\Osdd\LayerServiceProvider instead of Laravel's ServiceProvider, which adds these methods:

MethodUseGuide
loadSeeders()Register the layer's seeders for osdd:seed and LayersSeederSeeders
overrideConfigFrom()Override the config of any package with a layer fileConfiguration
withRouting()Mount the layer's route filesRoutes
withViews(), withTranslations(), withComponents()Register views, translations and Blade components under the layer nameViews and components

Laravel's own methods work as usual: loadMigrationsFrom() adds the layer's migrations to php artisan migrate, commands() registers its Artisan commands. Every signature is in the API reference.

osdd:layer generates this provider with the provider generator:

functional/orders/src/Providers/OrdersServiceProvider.php
<?php

namespace Functional\Orders\Providers;

use Lomkit\Osdd\LayerServiceProvider;

class OrdersServiceProvider extends LayerServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->loadMigrationsFrom(__DIR__ . '/../../database/migrations');
        }

        $this->withRouting(
            web: __DIR__ . '/../../routes/web.php',
            api: __DIR__ . '/../../routes/api.php',
            commands: __DIR__ . '/../../routes/console.php',
            channels: __DIR__ . '/../../routes/channels.php',
        );

        $this->withViews(__DIR__ . '/../../resources/views');
        $this->withTranslations(__DIR__ . '/../../lang');
    }

    public function register(): void
    {
        //
    }
}

Missing route files, views and lang folders are skipped, so every call is safe in a fresh layer. The generated provider registers no seeder: the seeder generator, or osdd:seeder, adds the loadSeeders() call.

Registering the provider

Laravel boots the provider because the layer's composer.json lists it:

functional/orders/composer.json
"extra": {
    "laravel": {
        "providers": [
            "Functional\\Orders\\Providers\\OrdersServiceProvider"
        ]
    }
}

osdd:layer and osdd:provider add this entry for you. Run composer update functional/orders once after adding a provider so Laravel rebuilds its package manifest. Do not list layer providers in bootstrap/providers.php.

Generators keep it in sync

Several generators add the line their file needs to the layer provider, so the new file is loaded without editing anything:

CommandAdds to the provider
osdd:migration, osdd:*-table$this->loadMigrationsFrom(...) in boot()
osdd:seeder$this->loadSeeders([...]) in boot()
osdd:config$this->overrideConfigFrom(...) in register()
osdd:command$this->commands([...]) in boot()
osdd:view$this->withViews(...) in boot()
osdd:component$this->withViews(...) and $this->withComponents(...) in boot()

A call the provider already makes is never added twice, whatever its layout. When the layer has no provider, or the method is missing, the generator prints the line to add by hand. See Generators.

Policies

Laravel discovers a policy that sits in the Policies namespace of its model's layer, the same way it does for app/:

ModelPolicy found
Functional\Orders\Models\OrderFunctional\Orders\Policies\OrderPolicy
Functional\Users\Models\UserFunctional\Users\Policies\UserPolicy

osdd:policy OrderPolicy --model=Order --layer=functional/orders creates the policy in that namespace, so there is nothing to register. Call Gate::policy() only for a policy whose name or namespace does not follow the convention:

use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::policy(Order::class, OrderAccessPolicy::class);
}

Commands, observers and events

Anything a Laravel service provider does works in a layer provider. Observers are never discovered: register them here.

public function boot(): void
{
    Order::observe(OrderObserver::class);

    Event::listen(OrderPlaced::class, SendOrderConfirmation::class);

    $this->commands([PruneAbandonedOrders::class]);
}

osdd:command adds the commands() call for you. With osdd.events.discover on, the listeners of the layer's src/Listeners are registered without Event::listen(): see Events.

Keep each layer's wiring in its own provider. A provider that registers classes of another layer hides a dependency that composer.json does not show.

Two providers named OsddServiceProvider

An OSDD project has two classes with that short name:

ClassBelongs toDoes
Lomkit\Osdd\OsddServiceProviderThe package, in vendor/Registers the osdd:* commands, the layer discovery and its cache, the about section, event discovery, the Tinker and IDE Helper support
Technical\Osdd\Providers\OsddServiceProviderYour application, created by osdd:startA layer provider like any other: loads technical/osdd/config/osdd.php, the skeleton migrations, and binds LayersSeeder as the default seeder