Saltar contenido

Package Development

Introduction

Packages are the primary way of adding functionality to Laravel. Packages might be anything from a great way to work with dates like Carbon or a package that allows you to associate files with Eloquent models like Spatie's Laravel Media Library.

There are different types of packages. Some packages are stand-alone, meaning they work with any PHP framework. Carbon and PHPUnit are examples of stand-alone packages. Any of these packages may be used with Laravel by requiring them in your composer.json file.

On the other hand, other packages are specifically intended for use with Laravel. These packages may have routes, controllers, views, and configuration specifically intended to enhance a Laravel application. This guide primarily covers the development of those packages that are Laravel specific.

A Note On Facades

When writing a Laravel application, it generally does not matter if you use contracts or facades since both provide essentially equal levels of testability. However, when writing packages, your package will not typically have access to all of Laravel's testing helpers. If you would like to be able to write your package tests as if the package were installed inside a typical Laravel application, you may use the Orchestral Testbench package.

Package Discovery

In a Laravel application's config/app.php configuration file, the providers option defines a list of service providers that should be loaded by Laravel. When someone installs your package, you will typically want your service provider to be included in this list. Instead of requiring users to manually add your service provider to the list, you may define the provider in the extra section of your package's composer.json file. In addition to service providers, you may also list any facades you would like to be registered:

"extra": {
"laravel": {
"providers": [
"Barryvdh\\Debugbar\\ServiceProvider"
],
"aliases": {
"Debugbar": "Barryvdh\\Debugbar\\Facade"
}
}
},

Once your package has been configured for discovery, Laravel will automatically register its service providers and facades when it is installed, creating a convenient installation experience for your package's users.

Opting Out Of Package Discovery

If you are the consumer of a package and would like to disable package discovery for a package, you may list the package name in the extra section of your application's composer.json file:

"extra": {
"laravel": {
"dont-discover": [
"barryvdh/laravel-debugbar"
]
}
},

You may disable package discovery for all packages using the * character inside of your application's dont-discover directive:

"extra": {
"laravel": {
"dont-discover": [
"*"
]
}
},

Service Providers

Service providers are the connection point between your package and Laravel. A service provider is responsible for binding things into Laravel's service container and informing Laravel where to load package resources such as views, configuration, and localization files.

A service provider extends the Illuminate\Support\ServiceProvider class and contains two methods: register and boot. The base ServiceProvider class is located in the illuminate/support Composer package, which you should add to your own package's dependencies. To learn more about the structure and purpose of service providers, check out their documentation.

Resources

Configuration

Typically, you will need to publish your package's configuration file to the application's config directory. This will allow users of your package to easily override your default configuration options. To allow your configuration files to be published, call the publishes method from the boot method of your service provider:

/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/../config/courier.php' => config_path('courier.php'),
]);
}

Now, when users of your package execute Laravel's vendor:publish command, your file will be copied to the specified publish location. Once your configuration has been published, its values may be accessed like any other configuration file:

$value = config('courier.option');

Warning
You should not define closures in your configuration files. They can not be serialized correctly when users execute the config:cache Artisan command.

Default Package Configuration

You may also merge your own package configuration file with the application's published copy. This will allow your users to define only the options they actually want to override in the published copy of the configuration file. To merge the configuration file values, use the mergeConfigFrom method within your service provider's register method.

The mergeConfigFrom method accepts the path to your package's configuration file as its first argument and the name of the application's copy of the configuration file as its second argument:

/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
__DIR__.'/../config/courier.php', 'courier'
);
}

Warning
This method only merges the first level of the configuration array. If your users partially define a multi-dimensional configuration array, the missing options will not be merged.

Routes

If your package contains routes, you may load them using the loadRoutesFrom method. This method will automatically determine if the application's routes are cached and will not load your routes file if the routes have already been cached:

/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}

Migrations

If your package contains database migrations, you may use the loadMigrationsFrom method to inform Laravel how to load them. The loadMigrationsFrom method accepts the path to your package's migrations as its only argument:

/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}

Once your package's migrations have been registered, they will automatically be run when the php artisan migrate command is executed. You do not need to export them to the application's database/migrations directory.

Translations

If your package contains translation files, you may use the loadTranslationsFrom method to inform Laravel how to load them. For example, if your package is named courier, you should add the following to your service provider's boot method:

/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
$this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
}

Package translations are referenced using the package::file.line syntax convention. So, you may load the courier package's welcome line from the messages file like so:

echo trans('courier::messages.welcome');

Publishing Translations

If you would like to publish your package's translations to the application's lang/vendor directory, you may use the service provider's publishes method. The publishes method accepts an array of package paths and their desired publish locations. For example, to publish the translation files for the courier package, you may do the following:

/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
$this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
 
$this->publishes([
__DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
]);
}

Now, when users of your package execute Laravel's vendor:publish Artisan command, your package's translations will be published to the specified publish location.

Views

To register your package's views with Laravel, you need to tell Laravel where the views are located. You may do this using the service provider's loadViewsFrom method. The loadViewsFrom method accepts two arguments: the path to your view templates and your package's name. For example, if your package's name is courier, you would add the following to your service provider's boot method:

/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}

Package views are referenced using the package::view syntax convention. So, once your view path is registered in a service provider, you may load the dashboard view from the courier package like so:

Route::get('/dashboard', function () {
return view('courier::dashboard');
});

Overriding Package Views

When you use the loadViewsFrom method, Laravel actually registers two locations for your views: the application's resources/views/vendor directory and the directory you specify. So, using the courier package as an example, Laravel will first check if a custom version of the view has been placed in the resources/views/vendor/courier directory by the developer. Then, if the view has not been customized, Laravel will search the package view directory you specified in your call to loadViewsFrom. This makes it easy for package users to customize / override your package's views.

Publishing Views

If you would like to make your views available for publishing to the application's resources/views/vendor directory, you may use the service provider's publishes method. The publishes method accepts an array of package view paths and their desired publish locations:

/**
* Bootstrap the package services.
*
* @return void
*/
public function boot()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
 
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
]);
}

Now, when users of your package execute Laravel's vendor:publish Artisan command, your package's views will be copied to the specified publish location.

View Components

If you are building a package that utilizes Blade components or placing components in non-conventional directories, you will need to manually register your component class and its HTML tag alias so that Laravel knows where to find the component. You should typically register your components in the boot method of your package's service provider:

use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;
 
/**
* Bootstrap your package's services.
*
* @return void
*/
public function boot()
{
Blade::component('package-alert', AlertComponent::class);
}

Once your component has been registered, it may be rendered using its tag alias:

<x-package-alert/>

Autoloading Package Components

Alternatively, you may use the componentNamespace method to autoload component classes by convention. For example, a Nightshade package might have Calendar and ColorPicker components that reside within the Nightshade\Views\Components namespace:

use Illuminate\Support\Facades\Blade;
 
/**
* Bootstrap your package's services.
*
* @return void
*/
public function boot()
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

This will allow the usage of package components by their vendor namespace using the package-name:: syntax:

<x-nightshade::calendar />
<x-nightshade::color-picker />

Blade will automatically detect the class that's linked to this component by pascal-casing the component name. Subdirectories are also supported using "dot" notation.

Anonymous Components

If your package contains anonymous components, they must be placed within a components directory of your package's "views" directory (as specified by the loadViewsFrom method). Then, you may render them by prefixing the component name with the package's view namespace:

<x-courier::alert />

"About" Artisan Command

Laravel's built-in about Artisan command provides a synopsis of the application's environment and configuration. Packages may push additional information to this command's output via the AboutCommand class. Typically, this information may be added from your package service provider's boot method:

use Illuminate\Foundation\Console\AboutCommand;
 
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
}

Commands

To register your package's Artisan commands with Laravel, you may use the commands method. This method expects an array of command class names. Once the commands have been registered, you may execute them using the Artisan CLI:

use Courier\Console\Commands\InstallCommand;
use Courier\Console\Commands\NetworkCommand;
 
/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
InstallCommand::class,
NetworkCommand::class,
]);
}
}

Public Assets

Your package may have assets such as JavaScript, CSS, and images. To publish these assets to the application's public directory, use the service provider's publishes method. In this example, we will also add a public asset group tag, which may be used to easily publish groups of related assets:

/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/../public' => public_path('vendor/courier'),
], 'public');
}

Now, when your package's users execute the vendor:publish command, your assets will be copied to the specified publish location. Since users will typically need to overwrite the assets every time the package is updated, you may use the --force flag:

php artisan vendor:publish --tag=public --force

Publishing File Groups

You may want to publish groups of package assets and resources separately. For instance, you might want to allow your users to publish your package's configuration files without being forced to publish your package's assets. You may do this by "tagging" them when calling the publishes method from a package's service provider. For example, let's use tags to define two publish groups for the courier package (courier-config and courier-migrations) in the boot method of the package's service provider:

/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php')
], 'courier-config');
 
$this->publishes([
__DIR__.'/../database/migrations/' => database_path('migrations')
], 'courier-migrations');
}

Now your users may publish these groups separately by referencing their tag when executing the vendor:publish command:

php artisan vendor:publish --tag=courier-config