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:
| Provider | Purpose |
|---|---|
YourAddonServiceProvider | Main provider - views, config, hooks, commands |
RouteServiceProvider | Route registration |
EventServiceProvider | Event 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);
} else {
$this->loadTranslationsFrom(
module_path($this->moduleName, 'Resources/lang'),
$this->moduleNameLower
);
}
}
/**
* 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
- Keep providers focused - Main provider for bootstrap, Route provider for routes, Event provider for events
- Use deferred loading - For services not needed on every request
- Register in correct method -
register()for bindings,boot()for bootstrapping - Check running context - Use
$this->app->runningInConsole()for CLI-only code - Document provides() - List all services for deferred providers