Skip to main content

Service Providers

Service providers are the central place to configure and bootstrap your addon. They register routes, views, commands, event listeners, and other services.

Provider Structure​

Addons typically have three providers:

ProviderPurpose
YourAddonServiceProviderMain provider - views, config, hooks, commands
RouteServiceProviderRoute registration
EventServiceProviderEvent listeners

Main Service Provider​

Complete Example​

// Providers/EmailValidatorServiceProvider.php
<?php

namespace Addons\EmailValidator\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use Illuminate\Console\Scheduling\Schedule;

class EmailValidatorServiceProvider extends ServiceProvider
{
protected string $moduleName = 'EmailValidator';
protected string $moduleNameLower = 'emailvalidator';

/**
* Boot the application services.
*/
public function boot(): void
{
$this->registerHooks();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->registerMigrations();
$this->registerCommands();
$this->registerSchedule();
$this->registerMiddleware();
$this->registerViewComposers();
}

/**
* Register the service provider.
*/
public function register(): void
{
$this->app->register(RouteServiceProvider::class);
$this->app->register(EventServiceProvider::class);

// Register addon services
$this->registerServices();
}

/**
* Load hook files
*/
protected function registerHooks(): void
{
$hooksPath = module_path($this->moduleName, 'hooks');

foreach (glob($hooksPath . '/*.php') as $file) {
require_once $file;
}
}

/**
* Register translations
*/
protected function registerTranslations(): void
{
$langPath = resource_path('lang/modules/' . $this->moduleNameLower);

if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
$this->loadJsonTranslationsFrom($langPath);
} else {
$this->loadTranslationsFrom(
module_path($this->moduleName, 'Resources/lang'),
$this->moduleNameLower
);
$this->loadJsonTranslationsFrom(
module_path($this->moduleName, 'Resources/lang')
);
}
}

/**
* Register config
*/
protected function registerConfig(): void
{
$configPath = module_path($this->moduleName, 'Config/config.php');

$this->publishes([
$configPath => config_path($this->moduleNameLower . '.php'),
], 'config');

$this->mergeConfigFrom($configPath, $this->moduleNameLower);
}

/**
* Register views
*/
protected function registerViews(): void
{
$viewPath = resource_path('views/modules/' . $this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'Resources/views');

$this->publishes([
$sourcePath => $viewPath
], ['views', $this->moduleNameLower . '-views']);

$this->loadViewsFrom(
array_merge($this->getPublishableViewPaths(), [$sourcePath]),
$this->moduleNameLower
);
}

/**
* Register migrations
*/
protected function registerMigrations(): void
{
$this->loadMigrationsFrom(
module_path($this->moduleName, 'Database/Migrations')
);
}

/**
* Register artisan commands
*/
protected function registerCommands(): void
{
if ($this->app->runningInConsole()) {
$this->commands([
\Addons\EmailValidator\Console\ValidateEmails::class,
\Addons\EmailValidator\Console\CleanupResults::class,
\Addons\EmailValidator\Console\SyncDisposableDomains::class,
]);
}
}

/**
* Register scheduled tasks
*/
protected function registerSchedule(): void
{
$this->callAfterResolving(Schedule::class, function (Schedule $schedule) {
// Daily cleanup of old results
$schedule->command('emailvalidator:cleanup --days=30')
->dailyAt('03:00')
->onOneServer();

// Sync disposable domains weekly
$schedule->command('emailvalidator:sync-disposable')
->weekly()
->sundays()
->at('04:00');

// Process pending validations every 5 minutes
$schedule->command('emailvalidator:process-pending')
->everyFiveMinutes()
->withoutOverlapping();
});
}

/**
* Register middleware aliases
*/
protected function registerMiddleware(): void
{
$router = $this->app['router'];

$router->aliasMiddleware(
'emailvalidator.quota',
\Addons\EmailValidator\Http\Middleware\CheckQuota::class
);

$router->aliasMiddleware(
'emailvalidator.api',
\Addons\EmailValidator\Http\Middleware\ApiAuth::class
);
}

/**
* Register view composers
*/
protected function registerViewComposers(): void
{
// Share data with all addon views
View::composer('emailvalidator::*', function ($view) {
$view->with('addonVersion', config('emailvalidator.version', '1.0.0'));
});

// Share user stats with dashboard views
View::composer(['emailvalidator::index', 'emailvalidator::dashboard'], function ($view) {
if (auth()->check()) {
$view->with('userCredits', auth()->user()->email_validation_credits ?? 0);
}
});
}

/**
* Register addon services/bindings
*/
protected function registerServices(): void
{
// Bind validator service
$this->app->singleton('emailvalidator', function ($app) {
return new \Addons\EmailValidator\Services\ValidatorService(
config('emailvalidator.api.endpoint'),
config('emailvalidator.api.key')
);
});

// Bind as interface
$this->app->bind(
\Addons\EmailValidator\Contracts\ValidatorInterface::class,
\Addons\EmailValidator\Services\ValidatorService::class
);
}

/**
* Get view paths that can be published
*/
private function getPublishableViewPaths(): array
{
$paths = [];

foreach (config('view.paths') as $path) {
if (is_dir($path . '/modules/' . $this->moduleNameLower)) {
$paths[] = $path . '/modules/' . $this->moduleNameLower;
}
}

return $paths;
}

/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [
'emailvalidator',
\Addons\EmailValidator\Contracts\ValidatorInterface::class,
];
}
}

Route Service Provider​

// Providers/RouteServiceProvider.php
<?php

namespace Addons\EmailValidator\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider
{
protected string $moduleNamespace = 'Addons\EmailValidator\Http\Controllers';

public function boot(): void
{
parent::boot();
}

public function map(): void
{
$this->mapWebRoutes();
$this->mapApiRoutes();
}

protected function mapWebRoutes(): void
{
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('EmailValidator', '/Routes/web.php'));
}

protected function mapApiRoutes(): void
{
Route::prefix('api')
->middleware('api')
->namespace($this->moduleNamespace . '\Api')
->group(module_path('EmailValidator', '/Routes/api.php'));
}
}

Role-Based Routes​

protected function mapWebRoutes(): void
{
// Always load common routes
Route::middleware(['web', 'auth'])
->namespace($this->moduleNamespace)
->group(module_path('EmailValidator', '/Routes/common.php'));

// Load admin routes for admin users
if ($this->isAdmin()) {
Route::middleware(['web', 'auth', 'admin'])
->namespace($this->moduleNamespace)
->group(module_path('EmailValidator', '/Routes/admin.php'));
}

// Load client routes for client users
if ($this->isClient()) {
Route::middleware(['web', 'auth'])
->namespace($this->moduleNamespace)
->group(module_path('EmailValidator', '/Routes/client.php'));
}
}

private function isAdmin(): bool
{
if (!auth()->check()) {
return false;
}

return auth()->user()->role_id === 1;
}

private function isClient(): bool
{
if (!auth()->check()) {
return false;
}

return auth()->user()->is_client === 1;
}

Event Service Provider​

// Providers/EventServiceProvider.php
<?php

namespace Addons\EmailValidator\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings.
*/
protected $listen = [
// Laravel events
\Illuminate\Auth\Events\Login::class => [
\Addons\EmailValidator\Listeners\UserLoggedIn::class,
],

// Custom addon events
\Addons\EmailValidator\Events\ValidationCompleted::class => [
\Addons\EmailValidator\Listeners\UpdateUserCredits::class,
\Addons\EmailValidator\Listeners\SendNotification::class,
],

\Addons\EmailValidator\Events\BatchProcessed::class => [
\Addons\EmailValidator\Listeners\NotifyBatchComplete::class,
],
];

/**
* The subscriber classes to register.
*/
protected $subscribe = [
\Addons\EmailValidator\Listeners\ValidationSubscriber::class,
];

public function boot(): void
{
parent::boot();
}
}

Event Listener Example​

// Listeners/UpdateUserCredits.php
<?php

namespace Addons\EmailValidator\Listeners;

use Addons\EmailValidator\Events\ValidationCompleted;

class UpdateUserCredits
{
public function handle(ValidationCompleted $event): void
{
$user = $event->user;
$count = $event->validationCount;

$user->decrement('email_validation_credits', $count);

// Fire hook for other addons
run_hook_in_background('EmailValidationCreditsUsed', [
'user' => $user,
'credits_used' => $count,
'remaining' => $user->fresh()->email_validation_credits,
]);
}
}

Event Subscriber Example​

// Listeners/ValidationSubscriber.php
<?php

namespace Addons\EmailValidator\Listeners;

use Illuminate\Events\Dispatcher;

class ValidationSubscriber
{
public function handleValidationStarted($event): void
{
// Log start
}

public function handleValidationCompleted($event): void
{
// Log completion
}

public function handleValidationFailed($event): void
{
// Handle failure
}

public function subscribe(Dispatcher $events): array
{
return [
\Addons\EmailValidator\Events\ValidationStarted::class => 'handleValidationStarted',
\Addons\EmailValidator\Events\ValidationCompleted::class => 'handleValidationCompleted',
\Addons\EmailValidator\Events\ValidationFailed::class => 'handleValidationFailed',
];
}
}

Registering in module.json​

Providers must be listed in module.json:

{
"name": "EmailValidator",
"alias": "emailvalidator",
"providers": [
"Addons\\EmailValidator\\Providers\\EmailValidatorServiceProvider",
"Addons\\EmailValidator\\Providers\\EventServiceProvider"
],
"files": [
"Helpers/functions.php"
]
}

Service Container Bindings​

Singleton Binding​

// Register once, return same instance
$this->app->singleton('emailvalidator', function ($app) {
return new ValidatorService(
config('emailvalidator.api.endpoint'),
config('emailvalidator.api.key')
);
});

// Usage
$validator = app('emailvalidator');

Interface Binding​

// Bind interface to implementation
$this->app->bind(
ValidatorInterface::class,
ValidatorService::class
);

// Usage with dependency injection
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}

Contextual Binding​

$this->app->when(ImportController::class)
->needs(ValidatorInterface::class)
->give(BulkValidatorService::class);

$this->app->when(SingleController::class)
->needs(ValidatorInterface::class)
->give(RealTimeValidatorService::class);

Deferred Providers​

For performance, defer loading until needed:

class EmailValidatorServiceProvider extends ServiceProvider
{
protected $defer = true;

public function register(): void
{
$this->app->singleton('emailvalidator', function ($app) {
return new ValidatorService();
});
}

public function provides(): array
{
return ['emailvalidator'];
}
}

Publishing Assets​

public function boot(): void
{
// Publish config
$this->publishes([
module_path($this->moduleName, 'Config/config.php')
=> config_path('emailvalidator.php'),
], 'emailvalidator-config');

// Publish views
$this->publishes([
module_path($this->moduleName, 'Resources/views')
=> resource_path('views/vendor/emailvalidator'),
], 'emailvalidator-views');

// Publish assets
$this->publishes([
module_path($this->moduleName, 'Resources/assets/dist')
=> public_path('vendor/emailvalidator'),
], 'emailvalidator-assets');

// Publish migrations
$this->publishes([
module_path($this->moduleName, 'Database/Migrations')
=> database_path('migrations'),
], 'emailvalidator-migrations');
}

Publish with:

php artisan vendor:publish --tag=emailvalidator-config
php artisan vendor:publish --tag=emailvalidator-views
php artisan vendor:publish --provider="Addons\EmailValidator\Providers\EmailValidatorServiceProvider"

Best Practices​

  1. Keep providers focused - Main provider for bootstrap, Route provider for routes, Event provider for events
  2. Use deferred loading - For services not needed on every request
  3. Register in correct method - register() for bindings, boot() for bootstrapping
  4. Check running context - Use $this->app->runningInConsole() for CLI-only code
  5. Document provides() - List all services for deferred providers