Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

Simplifying service providers in Laravel packages

Original – by Freek Van der Herten – 3 minute read

In almost every Laravel package, there is a ServiceProvider class responsible for registering bindings package resources such as config files, views, migrations, ...

While preparing a workshop that I gave on package development, I watched some videos of our own Laravel package training video course again. While watching the videos on using the service provider again, I had the idea to simplify how resources can be registered.

Meanwhile, I've fleshed out the idea and release it as a new package called laravel-package-tools. In this blog post, I'd like to introduce the package to you.

Introducing the PackageServiceProvider

Let's look at a service provider that registers a config file, migrations, views, translations, and a command. It uses several functions that are explained in Laravel's package development docs.

namespace YourVendor\YourAwesomePackage;

use Illuminate\Support\ServiceProvider;
use YourVendor\YourAwesomePackage\Commands\YourAwesomeCommand;

class YourAwesomePackageServiceProvider extends ServiceProvider
{
    public function boot()
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__ . '/../config/laravel-awesome-package.php' => config_path('laravel-awesome-package.php'),
            ], 'config');

            $this->publishes([
                __DIR__ . '/../resources/views' => base_path('resources/views/vendor/laravel-awesome-package'),
            ], 'views');

            $migrationFileName = 'create_package_table.php';
            if (! $this->migrationFileExists($migrationFileName)) {
                $this->publishes([
                    __DIR__ . "/../database/migrations/{$migrationFileName}.stub" => database_path('migrations/' . date('Y_m_d_His', time()) . '_' . $migrationFileName),
                ], 'migrations');
            }

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

        $this->loadViewsFrom(__DIR__ . '/../resources/views', 'laravel-awesome-package');
    }

    public function register()
    {
        $this->mergeConfigFrom(__DIR__ . '/../config/laravel-awesome-package.php', 'laravel-awesome-package');
    }

    public static function migrationFileExists(string $migrationFileName): bool
    {
        $len = strlen($migrationFileName);
        foreach (glob(database_path("migrations/*.php")) as $filename) {
            if ((substr($filename, -$len) === $migrationFileName)) {
                return true;
            }
        }

        return false;
    }
}

Our laravel-package-tools package contains a PackageServiceProvider class .

Let extend that PackageServiceProvider to vastly simplify the code above. The class below is equivalent to class in the previous code snippet.

namespace YourVendor\YourAwesomePackage;

use OriginalVendor\LaravelPackageTools\Package;
use OriginalVendor\LaravelPackageTools\PackageServiceProvider;
use YourVendor\YourAwesomePackage\Commands\YourAwesomeCommand;

class SkeletonServiceProvider extends PackageServiceProvider
{
    public function configurePackage(Package $package): void
    {
        $package
            ->name('your-awesome-package')
            ->hasConfigFile()
            ->hasViews()
            ->hasMigration('create_package_table')
            ->hasCommand(YourAwesomeCommand::class);
    }
}

That is way cleaner, right? I've already updated some of our packages to make use of our PackageServiceProvider. Here's the service provider from laravel-backup.

namespace Spatie\Backup;

// import statements omitted for brevity

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,
            ]);
    }

    public function packageRegistered()
    {
        $this->app['events']->subscribe(EventHandler::class);

        $this->app->singleton(ConsoleOutput::class);

        $this->app->bind(CleanupStrategy::class, config('backup.cleanup.strategy'));
    }
}

In closing

To know more about the PackageServiceProvider header over to the readme of laravel-package-tools on GitHub.

I've not PRed these changes to Laravel because the functions provided by PackageServiceProvider assume an opinionated package structure.

To kickstart your next Laravel package, you could consider using our Laravel package skeleton, which already uses the shiny new PackageServiceProvider out of the box. Here's a video that explains how to use the Laravel package skeleton (spoiler: it's very easy).

Be sure to take a look at this list of Laravel and PHP packages our team has released previously. I'm pretty sure there's something there for your next project.

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

What are your thoughts on "Simplifying service providers in Laravel packages"?

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.