Laravel Framework

Service Providers

15 min Lesson 24 of 45

Service Providers

Service Providers are the central place of all Laravel application bootstrapping. They are responsible for binding services into the container, registering event listeners, middleware, routes, and other setup tasks. Understanding service providers is key to understanding how Laravel applications are configured and bootstrapped.

What are Service Providers?

Service Providers are classes that register and bootstrap components of your Laravel application. Every Laravel application has at least one service provider (AppServiceProvider), and the framework itself includes many providers for core services.

Basic Service Provider Structure:
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // Bind services into the container
        // This runs BEFORE any boot() method
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        // Perform post-registration bootstrapping
        // This runs AFTER all providers have registered
    }
}
Key Difference: The register() method should only bind things into the service container. The boot() method is called after all providers have been registered, so you can safely use other services in boot().

The Register Method

The register method is where you bind services, interfaces, and other dependencies into the service container:

Using the Register Method:
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\PaymentGateway;
use App\Services\StripePaymentGateway;
use App\Contracts\ReportGeneratorInterface;
use App\Services\PdfReportGenerator;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Simple binding
        $this->app->bind(PaymentGateway::class, function ($app) {
            return new StripePaymentGateway(
                config('services.stripe.key')
            );
        });

        // Singleton binding
        $this->app->singleton(
            ReportGeneratorInterface::class,
            PdfReportGenerator::class
        );

        // Register config file
        $this->mergeConfigFrom(
            __DIR__.'/../config/mypackage.php',
            'mypackage'
        );

        // Conditional registration based on environment
        if ($this->app->environment('local')) {
            $this->app->register(DebugServiceProvider::class);
        }
    }
}
Important Rule: Never attempt to use any services, routes, or other functionality in the register method. Only bind things into the container. If you need to use services, do it in the boot method.

The Boot Method

The boot method runs after all service providers have been registered, making it safe to use other services:

Using the Boot Method:
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Blade;
use App\Models\User;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Share data with all views
        View::share('appName', config('app.name'));

        // Define authorization gates
        Gate::define('update-post', function (User $user, $post) {
            return $user->id === $post->user_id;
        });

        // Register Blade directives
        Blade::directive('datetime', function ($expression) {
            return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
        });

        // Register model observers
        User::observe(UserObserver::class);

        // Publish package assets
        $this->publishes([
            __DIR__.'/../resources/views' => resource_path('views/vendor/mypackage'),
        ], 'mypackage-views');

        // Register macros
        \Illuminate\Support\Str::macro('titleSnake', function ($value) {
            return \Illuminate\Support\Str::title(
                str_replace('_', ' ', $value)
            );
        });
    }
}

Dependency Injection in Boot Method

You can type-hint dependencies in the boot method, and the container will automatically inject them:

Boot Method Dependency Injection:
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Contracts\View\Factory as ViewFactory;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        //
    }

    /**
     * Dependencies automatically injected
     */
    public function boot(
        ResponseFactory $response,
        ViewFactory $view
    ): void {
        // Use injected dependencies
        $view->composer('*', function ($view) {
            $view->with('currentYear', date('Y'));
        });

        // Add custom response macro
        $response->macro('caps', function ($value) use ($response) {
            return $response->make(strtoupper($value));
        });
    }
}

Creating Custom Service Providers

When building larger applications, it's good practice to create dedicated service providers for different components:

Creating a Custom Provider:
// Generate a new service provider
php artisan make:provider PaymentServiceProvider

// app/Providers/PaymentServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\Payment\StripeGateway;
use App\Services\Payment\PayPalGateway;
use App\Contracts\PaymentGatewayInterface;

class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Bind payment gateway based on config
        $this->app->singleton(
            PaymentGatewayInterface::class,
            function ($app) {
                $driver = config('payment.default');

                return match($driver) {
                    'stripe' => new StripeGateway(
                        config('payment.stripe.key')
                    ),
                    'paypal' => new PayPalGateway(
                        config('payment.paypal.client_id')
                    ),
                    default => throw new \Exception("Unsupported payment driver: {$driver}")
                };
            }
        );
    }

    public function boot(): void
    {
        // Publish configuration
        $this->publishes([
            __DIR__.'/../config/payment.php' => config_path('payment.php'),
        ], 'payment-config');
    }
}

// Register in config/app.php providers array
'providers' => [
    // ... other providers
    App\Providers\PaymentServiceProvider::class,
],
Organization Tip: Create separate service providers for different areas of your application (PaymentServiceProvider, NotificationServiceProvider, ReportServiceProvider, etc.) to keep code organized and maintainable.

Deferred Providers

Deferred providers only load when their services are actually needed, improving application performance:

Creating a Deferred Provider:
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Support\DeferrableProvider;
use App\Services\ReportGenerator;

class ReportServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(ReportGenerator::class, function ($app) {
            return new ReportGenerator(
                $app->make('db'),
                $app->make('config')
            );
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
        // List all services this provider offers
        return [
            ReportGenerator::class,
        ];
    }
}

// Benefits:
// - Provider only loads when ReportGenerator is needed
// - Reduces bootstrap time
// - Improves performance for requests that don't need reports
When to Defer: Use deferred providers for services that aren't needed on every request (PDF generators, external API clients, complex calculators). Don't defer providers that register routes, middleware, or other bootstrap-time necessities.

Publishing Package Assets

Service providers allow packages to publish configuration files, views, migrations, and other assets:

Publishing Assets:
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class MyPackageServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Publish configuration
        $this->publishes([
            __DIR__.'/../config/mypackage.php' => config_path('mypackage.php'),
        ], 'mypackage-config');

        // Publish views
        $this->publishes([
            __DIR__.'/../resources/views' => resource_path('views/vendor/mypackage'),
        ], 'mypackage-views');

        // Publish migrations
        $this->publishes([
            __DIR__.'/../database/migrations' => database_path('migrations'),
        ], 'mypackage-migrations');

        // Publish assets (CSS, JS, images)
        $this->publishes([
            __DIR__.'/../public' => public_path('vendor/mypackage'),
        ], 'mypackage-assets');

        // Publish multiple asset groups at once
        $this->publishes([
            __DIR__.'/../config/mypackage.php' => config_path('mypackage.php'),
            __DIR__.'/../resources/views' => resource_path('views/vendor/mypackage'),
        ], 'mypackage-all');
    }
}

// Users publish with artisan commands:
php artisan vendor:publish --tag=mypackage-config
php artisan vendor:publish --tag=mypackage-views
php artisan vendor:publish --tag=mypackage-all
php artisan vendor:publish --provider="App\Providers\MyPackageServiceProvider"

Loading Package Resources

Service providers can load views, translations, migrations, and routes from packages:

Loading Package Resources:
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class MyPackageServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Load package views
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'mypackage');

        // Load package translations
        $this->loadTranslationsFrom(__DIR__.'/../lang', 'mypackage');

        // Load package migrations
        $this->loadMigrationsFrom(__DIR__.'/../database/migrations');

        // Load package routes
        $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
    }
}

// Using loaded resources:

// Views: return view('mypackage::dashboard');
// Translations: __('mypackage::messages.welcome')
// Migrations: run automatically with php artisan migrate
// Routes: automatically registered
Practice Exercise 1: Create a NotificationServiceProvider that registers different notification channels (Email, SMS, Slack) into the container. Use config('notification.default') to determine which channel to bind as the default. Add a boot method that publishes the notification configuration file.
Practice Exercise 2: Build a LoggingServiceProvider that sets up different logging channels based on environment (local uses daily files, production uses Slack + database). Make it a deferred provider that only loads when the logger is actually used. Include dependency injection in the boot method to configure log formatting.
Practice Exercise 3: Create an ApiServiceProvider that registers API routes from a package, loads API-specific middleware, publishes API documentation views, and shares API versioning data with all views. Test publishing the views and accessing them in a blade template.

Summary

Service Providers are the cornerstone of Laravel application configuration:

  • Register Method: Bind services into the container only
  • Boot Method: Bootstrap services, views, gates, etc. after registration
  • Dependency Injection: Boot method can type-hint dependencies
  • Custom Providers: Organize application by creating dedicated providers
  • Deferred Providers: Load only when needed for better performance
  • Publishing: Allow users to customize package assets
  • Loading Resources: Make package views, translations, and routes available

In the next lesson, we'll explore Artisan Console Commands and how to create custom CLI tools for your Laravel application.