Core - v1.2.0
Service provider
ArtisanPackServiceProvider is the abstract base class every ArtisanPack UI package should extend instead of Illuminate\Support\ServiceProvider. It removes the boilerplate previously hand-rolled by each package — config merging, command registration, view loading, route loading, container singleton registration — and reduces a typical package's service provider to a handful of declarative properties.
Why use it
A typical package's service provider previously looked like this:
public function register(): void
{
$this->mergeConfigFrom( __DIR__ . '/../config/media-library.php', 'artisanpack.media-library' );
$this->app->singleton( 'media-library', fn () => new MediaLibrary );
$this->app->singleton( ThumbnailGenerator::class );
}
public function boot(): void
{
if ( $this->app->runningInConsole() ) {
$this->publishes( [
__DIR__ . '/../config/media-library.php' => config_path( 'artisanpack/media-library.php' ),
], 'media-library-config' );
$this->commands( [ ProcessThumbnails::class, GenerateAvif::class ] );
}
$this->loadViewsFrom( __DIR__ . '/../resources/views', 'media-library' );
$this->loadRoutesFrom( __DIR__ . '/../routes/web.php' );
Blade::component( 'media-library::picker', MediaLibraryPicker::class );
}
With ArtisanPackServiceProvider it becomes:
class MediaLibraryServiceProvider extends \ArtisanPackUI\Core\ArtisanPackServiceProvider
{
protected ?string $configFile = __DIR__ . '/../config/media-library.php';
protected ?string $configKey = 'artisanpack.media-library';
protected ?string $configPublishTag = 'media-library-config';
protected ?string $viewsPath = __DIR__ . '/../resources/views';
protected ?string $viewsNamespace = 'media-library';
protected ?string $routesPath = __DIR__ . '/../routes/web.php';
protected array $commands = [
ProcessThumbnails::class,
GenerateAvif::class,
];
protected array $singletons = [
'media-library' => MediaLibrary::class,
ThumbnailGenerator::class, // self-bound when key is numeric
];
protected function registerPackageBindings(): void
{
// Bindings unique to media-library that don't fit the $singletons array.
}
protected function bootPackage(): void
{
Blade::component( 'media-library::picker', MediaLibraryPicker::class );
}
}
Configurable properties
| Property | Type | Purpose |
|---|---|---|
$configFile |
string|null |
Absolute path to the package's config file. Merged in register() and published in boot(). |
$configKey |
string|null |
Configuration key the package config is merged under (e.g. artisanpack.media-library). |
$configPublishTag |
string|null |
Publish tag used by vendor:publish. Defaults to <configKey>-config. |
$commands |
array<class-string> |
Artisan commands registered when the application runs in the console. |
$singletons |
array<int|string, callable|class-string> |
Container singletons keyed by abstract. Numeric keys are treated as self-bound class-strings. Closures and class-strings both work as values. |
$viewsPath |
string|null |
Absolute path to the package's views directory. |
$viewsNamespace |
string|null |
View namespace used when loading the views. |
$routesPath |
string|null |
Absolute path to the package's routes file. |
Any property left as null ( or as an empty array, for $commands / $singletons ) is skipped at boot time, so you only opt into the parts your package needs.
Required hook
Child providers must implement registerPackageBindings() for any bindings that do not fit the $singletons array — interface-to-implementation contracts, scoped bindings, conditional bindings, etc.
protected function registerPackageBindings(): void
{
$this->app->bind(
\ArtisanPackUI\MediaLibrary\Contracts\ThumbnailGenerator::class,
\ArtisanPackUI\MediaLibrary\Services\GdThumbnailGenerator::class,
);
$this->app->scoped( RequestScopedCache::class );
}
Optional hook
Override bootPackage() for any boot-time logic that does not map to the standard hooks — Blade components, Livewire components, gates, policies, event listeners, custom middleware aliases, etc.
protected function bootPackage(): void
{
\Illuminate\Support\Facades\Blade::component( 'media-library::picker', MediaLibraryPicker::class );
\Livewire\Livewire::component( 'media-library.gallery', \ArtisanPackUI\MediaLibrary\Livewire\Gallery::class );
\Illuminate\Support\Facades\Gate::define(
'delete-media',
[ \ArtisanPackUI\MediaLibrary\Policies\MediaPolicy::class, 'delete' ],
);
}
Boot order
When Laravel boots the provider, ArtisanPackServiceProvider runs the following in order:
register():
registerSingletons()— every entry in$singletonsis bound as a container singleton.mergePackageConfig()—$configFileis merged under$configKeywhen both are set.registerPackageBindings()— the abstract method you implement.
boot():
- When the application runs in the console:
publishPackageAssets()(publishes$configFileto the application's config directory) andregisterPackageCommands()(registers$commands). loadPackageViews()— when both$viewsPathand$viewsNamespaceare set.loadPackageRoutes()— when$routesPathis set.bootPackage()— the optional hook you may override.
This order matches the conventions Laravel itself uses, and matches what SecurityServiceProvider, MediaLibraryServiceProvider, and the other sibling-package providers used to do by hand. Migrating to ArtisanPackServiceProvider does not change observed behaviour for downstream applications.