Cubre el desarrollo de paquetes Laravel centrado en los service providers: publicación de configuración, vistas, migraciones y facades, autodiscovery y carga diferida.
Un paquete en Laravel es un paquete de Composer que añade funcionalidad a tu aplicación. Existen dos grandes tipos.
Paquetes stand-alone — bibliotecas PHP genéricas que no dependen de Laravel (por ejemplo, Carbon, Pest).
Paquetes Laravel — paquetes con funcionalidad integrada con Laravel: rutas, controladores, vistas, configuración, etc.
Esta guía trata el segundo caso: paquetes específicos para Laravel. El desarrollo de paquetes requiere entender a fondo la estructura interna de Laravel: service providers, facades, publicación de archivos de configuración, etc.
Para escribir tests de tu paquete, usa Orchestra Testbench. Podrás escribir los tests como si estuvieras dentro de una aplicación Laravel normal.
El service provider es el punto de entrada del paquete. En él centralizas el registro en Laravel de vistas, configuración, migraciones, rutas y demás recursos.Un service provider extiende Illuminate\Support\ServiceProvider y tiene dos métodos principales: register y boot.
<?phpnamespace Acme\Courier;use Illuminate\Support\ServiceProvider;class CourierServiceProvider extends ServiceProvider{ /** * Registra los servicios del paquete */ public function register(): void { // Los bindings al contenedor van aquí $this->mergeConfigFrom( __DIR__.'/../config/courier.php', 'courier' ); $this->app->singleton(CourierManager::class, function ($app) { return new CourierManager($app['config']['courier']); }); } /** * Bootea los servicios del paquete */ public function boot(): void { // Los registros de recursos van aquí $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'); }}
No registres event listeners, rutas o vistas dentro del método register. Podrías acabar utilizando por error servicios de otro provider aún no cargado. Todo lo que no sean bindings debe hacerse en boot.
mergeConfigFrom() — mezclar con valores por defecto
Con mergeConfigFrom() en register, se aplican los valores por defecto del paquete aunque el usuario no haya publicado el archivo.
public function register(): void{ $this->mergeConfigFrom( __DIR__.'/../config/courier.php', 'courier' );}
mergeConfigFrom() no mezcla en profundidad los arrays anidados. En configuraciones con arrays multinivel, si el usuario define solo parte de las opciones, el resto puede no mezclarse.
Con el segundo argumento de publishes() puedes indicar una etiqueta para que el usuario publique solo los recursos que necesite.
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');}
# Publica solo el archivo de configuraciónphp artisan vendor:publish --tag=courier-config# Publica todos los archivos que ofrece el providerphp artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider"
Con loadViewsFrom() registras un directorio de vistas. El segundo argumento es el namespace con el que referencias las vistas usando paquete::vista.
public function boot(): void{ $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');}
Tras el registro, referencia las vistas con el namespace del paquete.
Route::get('/dashboard', function () { return view('courier::dashboard');});
Laravel busca las vistas en dos ubicaciones. Primero mira resources/views/vendor/courier de la aplicación y, si no existe, usa el directorio de vistas del paquete. Esto permite al usuario personalizarlas.
Si el paquete incluye componentes, regístralos en boot.
use Illuminate\Support\Facades\Blade;use Acme\Courier\View\Components\AlertComponent;public function boot(): void{ Blade::component('courier-alert', AlertComponent::class);}
También puedes registrarlos en bloque usando un namespace de componente.
use Illuminate\Support\Facades\Blade;public function boot(): void{ Blade::componentNamespace('Acme\\Courier\\View\\Components', 'courier');}
{{-- Con registro individual --}}<x-courier-alert />{{-- Con registro por namespace --}}<x-courier::alert />
Los comandos Artisan del paquete se registran con el método commands(). Es habitual registrarlos solo en entorno de consola.
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, ]); }}
Los facades permiten invocar bindings del contenedor como métodos estáticos.
1
Crea la clase de servicio
<?phpnamespace Acme\Courier;class CourierManager{ public function __construct( protected array $config, ) {} public function send(string $to, string $message): bool { // Envío del mensaje return true; } public function track(string $id): array { // Obtiene la información de seguimiento return ['status' => 'delivered']; }}
2
Crea la clase facade
Extiende Illuminate\Support\Facades\Facade y devuelve la clave del binding del contenedor en getFacadeAccessor().
public function register(): void{ $this->app->singleton(\Acme\Courier\CourierManager::class, function ($app) { return new \Acme\Courier\CourierManager($app['config']['courier']); });}
Añadiendo anotaciones @method en el docblock de la facade activas el autocompletado en el IDE.
// Invocación a través de la facadeuse Acme\Courier\Facades\Courier;Courier::send('[email protected]', 'Ha llegado tu paquete');$status = Courier::track('ABC-123');
Los providers que solo hacen bindings al contenedor pueden implementar la interfaz DeferrableProvider para cargarse de forma diferida. El provider no se carga hasta que el servicio se necesita realmente, lo que mejora el rendimiento de la aplicación.
<?phpnamespace 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']); }); } /** * Devuelve los servicios que provee este provider * * @return array<int, string> */ public function provides(): array { return [CourierManager::class]; }}
Laravel compila y almacena la lista de servicios que proveen los providers diferidos. El provider solo se carga cuando se resuelve algún servicio de provides().
No uses DeferrableProvider en providers que necesitan registrar recursos (vistas, rutas, event listeners, etc.). Si se cargan de forma diferida, esos recursos quedarán sin registrar.
Al depender de illuminate/support, incluyes solo los componentes de Laravel que necesita el paquete en lugar de illuminate/framework completo. Mantén pequeño el árbol de dependencias.