Creating installer commands for Laravel packages
One of the joys of using packages in the Laravel ecosystem is how easy they are to install. Packages can be pulled in using Composer, and Laravel will automatically discover them.
In this post, you'll learn how to easily add an install
command, making it even easier for package users to start using a package.
Package install commands are great for the package user
Most packages will ask you to publish the config file, migrations, or some assets in their installation instructions. You probably have already executed commands such as:
php artisan vendor:publish --tag=awesome-package-config
php artisan vendor:publish --tag=awesome-package-migrations
php artisan vendor:publish --tag=awesome-package-assets
...
Some packages go about it a little bit better. Laravel Horizon, for instance, has a nifty install command that will:
- publish the config file
- create a Horizon service provider within your app
- register that service provider in your config/app.php
This is very convenient for the user to get up and running quickly.
In Horizon's source code, you can read the code that powers that install command. As a package developer, it's not too much trouble creating such a command. Still, specific tasks - such as registering a service provider in the config/app.php in the application - can take some time to get right.
Most package developers don't create install commands because it takes too long to get them right. That's why I spent some time making creating such install commands easy.
Easily create package install commands
Laravel Package Tools package is a package we made to help package creators. Here's what a typical service provider of a package could look like.
class BackupServiceProvider extends ServiceProvider
{
public function boot()
{
$this->publishes([
__DIR__.'/../config/backup.php' => config_path('backup.php'),
], 'config');
$this->publishes([
__DIR__.'/../resources/lang' => "{$this->app['path.lang']}/vendor/backup",
]);
$this->loadTranslationsFrom(__DIR__.'/../resources/lang/', 'backup');
}
public function register()
{
$this->mergeConfigFrom(__DIR__.'/../config/backup.php', 'backup');
$this->app->bind('command.backup:run', BackupCommand::class);
$this->app->bind('command.backup:clean', CleanupCommand::class);
$this->app->bind('command.backup:list', ListCommand::class);
$this->app->bind('command.backup:monitor', MonitorCommand::class);
$this->commands([
'command.backup:run',
'command.backup:clean',
'command.backup:list',
'command.backup:monitor',
]);
}
Using the PackageServiceProvider
provided by spatie/laravel-package-tools, the code above can be rewritten like this:
class BackupServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
$package
->name('laravel-backup')
->hasConfigFile()
->hasTranslations()
->hasCommands([
BackupCommand::class,
CleanupCommand::class,
ListCommand::class,
MonitorCommand::class,
]);
}
}
In the latest version of laravel-package-tools, we've added new functionality to add an installer for your package. This is how you can do that.
class YourServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
$package
->name('your-package')
->hasConfigFile()
->hasTranslations()
->hasCommands([
BackupCommand::class,
CleanupCommand::class,
ListCommand::class,
MonitorCommand::class,
])
->hasInstallCommand(function(InstallCommand $command) {
$command
->publishConfigFile()
->publishMigrations()
->askToRunMigrations()
->copyAndRegisterServiceProviderInApp()
->askToStarRepoOnGitHub();
});
}
}
Using this code, your package will gain an install command that will:
- copy the config file
- publish the migration of your package
- ask to run the migrations
- copy a service provider stub to the app where the package is installed in. That service provider will also be registered in
config/app.php
of the app - ask if the repo should be starred on GitHub
Using hasInstallCommand
is much quicker than writing an install command by hand.
We'll be using hasInstallCommand
ourselves in our upcoming Dynamic Servers package. Here's what the code in the service provider of that package looks like.
// ...
->hasInstallCommand(function (InstallCommand $command) {
$command
->publishConfigFile()
->copyAndRegisterServiceProviderInApp()
->publishMigrations()
->askToRunMigrations()
->askToStarRepoOnGitHub('spatie/laravel-dynamic-servers')
->endWith(function (InstallCommand $installCommand) {
$installCommand->line('');
$installCommand->info("We've added app\Providers\DynamicServersProvider to your project.");
$installCommand->info("Feel free to customize it to your needs.");
$installCommand->line('');
$installCommand->info('You can view all docs at https://spatie.be/docs/laravel-dynamic-servers');
$installCommand->line('');
$installCommand->info('Thank you very much for installing this package!');
});
Notice there's also an endWith
function that you can use to make any customization you might like.
Here's what it looks like when we run dynamic-servers:install
in a project where the package is installed.
In closing
We hope this new functionality in spatie/laravel-package-tools will help package creators create an install command. I'm sure package users will be happy with such as command.
This isn't the first package that our team has built. On our company website, check out all our open source packages in this long list. If you want to support us, consider picking up any of our paid products.
What are your thoughts on "Creating installer commands for Laravel packages"?