Service provider là gì ?


Ở bài trước service container là nơi quản lý các class dependency và thực hiện quá trình dependency injection. Chúng ta có thể bind 1 class, interface với service container và có thể resolve chúng ra ở mọi nơi.
Laravel bind rất nhiều thứ trong core của nó vào trong service container để chúng ta gọi ra dùng trong ứng dụng.

'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
Laravel\Tinker\TinkerServiceProvider::class,
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,

 Đây chính là nơi khai báo các service provider sau này khi muốn viết một service provider hay sử dụng các package từ packgist bạn cũng thường phải khai báo thêm service provider mới tại đây.
Thay vì phải sử dụng app()->bind(), app()->singleton, app()->instance() ... như thông thường ở file config/app.php này chúng ta tiến hành khai báo mảng các service provider để laravel tự động tìm đến các service provider này và bind chúng vào trong container . Các thành phần của mảng này là tên các class đi kèm namespace của chúng.
Các service provider là nơi sẽ thực hiện việc khai báo service và bind vào trong service container. Đây là mail service provider

<?php
namespace Illuminate\Mail;
use Swift_Mailer;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
class MailServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerSwiftMailer();
$this->registerIlluminateMailer();
$this->registerMarkdownRenderer();
}
/**
* Register the Illuminate mailer instance.
*
* @return void
*/
protected function registerIlluminateMailer()
{
$this->app->singleton('mailer', function ($app) {
$config = $app->make('config')->get('mail');
// Once we have create the mailer instance, we will set a
container instance
// on the mailer. This allows us to resolve mailer classes via
containers
// for maximum testability on said classes instead of passing
Closures.
$mailer = new Mailer(
$app['view'], $app['swift.mailer'], $app['events']
);
if ($app->bound('queue')) {
$mailer->setQueue($app['queue']);
}
// Next we will set all of the global addresses on this
mailer, which allows
// for easy unification of all "from" addresses as well as
easy debugging
// of sent messages since they get be sent into a single email
address.
foreach (['from', 'reply_to', 'to'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
});
}
/**
* Set a global address on the mailer by type.
*
* @param \Illuminate\Mail\Mailer $mailer
* @param array $config
* @param string $type
* @return void
*/
protected function setGlobalAddress($mailer, array $config, $type)
{
$address = Arr::get($config, $type);
if (is_array($address) && isset($address['address'])) {
$mailer->{'always'.Str::studly($type)}($address['address'],
$address['name']);
}
}
/**
* Register the Swift Mailer instance.
*
* @return void
*/
public function registerSwiftMailer()
{
$this->registerSwiftTransport();
// Once we have the transporter registered, we will register the
actual Swift
// mailer instance, passing in the transport instances, which
allows us to
// override this transporter instances during app start-up if
necessary.
$this->app->singleton('swift.mailer', function ($app) {
return new Swift_Mailer($app['swift.transport']->driver());
});
}
/**
* Register the Swift Transport instance.
*
* @return void
*/
protected function registerSwiftTransport()
{
$this->app->singleton('swift.transport', function ($app) {
return new TransportManager($app);
});
}
/**
* Register the Markdown renderer instance.
*
* @return void
*/
protected function registerMarkdownRenderer()
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/resources/views' =>
resource_path('views/vendor/mail'),
], 'laravel-mail');
}
$this->app->singleton(Markdown::class, function () {
return new Markdown($this->app->make('view'), [
'theme' => config('mail.markdown.theme', 'default'),
'paths' => config('mail.markdown.paths', []),
]);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
'mailer', 'swift.mailer', 'swift.transport', Markdown::class,
];
}
}

 

Tất cả service provider đều kế thừa từ ServiceProvider abstract

 

<?php
namespace Illuminate\Support;
use Illuminate\Console\Application as Artisan;
abstract class ServiceProvider
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* The paths that should be published.
*
* @var array
*/
protected static $publishes = [];
/**
* The paths that should be published by group.
*
* @var array
*/
protected static $publishGroups = [];
/**
* Create a new service provider instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Merge the given configuration with the existing configuration.
*
* @param string $path
* @param string $key
* @return void
*/
protected function mergeConfigFrom($path, $key)
{
$config = $this->app['config']->get($key, []);
$this->app['config']->set($key, array_merge(require $path,
$config));
}
/**
* Load the given routes file if routes are not already cached.
*
* @param string $path
* @return void
*/
protected function loadRoutesFrom($path)
{
if (! $this->app->routesAreCached()) {
require $path;
}
}
/**
* Register a view file namespace.
*
* @param string $path
* @param string $namespace
* @return void
*/
protected function loadViewsFrom($path, $namespace)
{
if (is_dir($appPath = $this->app-
>resourcePath().'/views/vendor/'.$namespace)) {
$this->app['view']->addNamespace($namespace, $appPath);
}
$this->app['view']->addNamespace($namespace, $path);
}
/**
* Register a translation file namespace.
*
* @param string $path
* @param string $namespace
* @return void
*/
protected function loadTranslationsFrom($path, $namespace)
{
$this->app['translator']->addNamespace($namespace, $path);
}
/**
* Register a database migration path.
*
* @param array|string $paths
* @return void
*/
protected function loadMigrationsFrom($paths)
{
$this->app->afterResolving('migrator', function ($migrator) use
($paths) {
foreach ((array) $paths as $path) {
$migrator->path($path);
}
});
}
/**
* Register paths to be published by the publish command.
*
* @param array $paths
* @param string $group
* @return void
*/
protected function publishes(array $paths, $group = null)
{
$this->ensurePublishArrayInitialized($class = static::class);
static::$publishes[$class] =
array_merge(static::$publishes[$class], $paths);
if ($group) {
$this->addPublishGroup($group, $paths);
}
/**
* Ensure the publish array for the service provider is initialized.
*
* @param string $class
* @return void
*/
protected function ensurePublishArrayInitialized($class)
{
if (! array_key_exists($class, static::$publishes)) {
static::$publishes[$class] = [];
}
}
/**
* Add a publish group / tag to the service provider.
*
* @param string $group
* @param array $paths
* @return void
*/
protected function addPublishGroup($group, $paths)
{
if (! array_key_exists($group, static::$publishGroups)) {
static::$publishGroups[$group] = [];
}
static::$publishGroups[$group] = array_merge(
static::$publishGroups[$group], $paths
);
}
/**
* Get the paths to publish.
*
* @param string $provider
* @param string $group
* @return array
*/
public static function pathsToPublish($provider = null, $group = null)
{
if (! is_null($paths = static::pathsForProviderOrGroup($provider,
$group))) {
return $paths;
}
return collect(static::$publishes)->reduce(function ($paths, $p) {
return array_merge($paths, $p);
}, []);
}
/**
* Get the paths for the provider or group (or both).
*
* @param string|null $provider
* @param string|null $group
* @return array
*/
protected static function pathsForProviderOrGroup($provider, $group)
{
if ($provider && $group) {
return static::pathsForProviderAndGroup($provider, $group);
} elseif ($group && array_key_exists($group,
static::$publishGroups)) {
return static::$publishGroups[$group];
} elseif ($provider && array_key_exists($provider,
static::$publishes)) {
return static::$publishes[$provider];
} elseif ($group || $provider) {
return [];
}
}
/**
* Get the paths for the provdider and group.
*
* @param string $provider
* @param string $group
* @return array
*/
protected static function pathsForProviderAndGroup($provider, $group)
{
if (! empty(static::$publishes[$provider]) && !
empty(static::$publishGroups[$group])) {
return array_intersect_key(static::$publishes[$provider],
static::$publishGroups[$group]);
}
return [];
}
/**
* Register the package's custom Artisan commands.
*
* @param array|mixed $commands
* @return void
*/
public function commands($commands)
{
$commands = is_array($commands) ? $commands : func_get_args();
Artisan::starting(function ($artisan) use ($commands) {
$artisan->resolveCommands($commands);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [];
}
/**
* Get the events that trigger this service provider to register.
*
* @return array
*/
public function when()
{
return [];
}
/**
* Determine if the provider is deferred.
*
* @return bool
*/
public function isDeferred()
{
return $this->defer;
}
/**
* Get a list of files that should be compiled for the package.
*
* @deprecated
*
* @return array
*/
public static function compiles()
{
return [];
}
}

 

Trong service provider chúng ta bắt buộc phải thực thi một method register()
Đây chính là nơi thực hiện bind service vào trong container

 

$this->app->singleton('mailer', function ($app) {
$config = $app->make('config')->get('mail');
// Once we have create the mailer instance, we will set a container
instance
// on the mailer. This allows us to resolve mailer classes via
containers
// for maximum testability on said classes instead of passing
Closures.
$mailer = new Mailer(
$app['view'], $app['swift.mailer'], $app['events']
);
if ($app->bound('queue')) {
$mailer->setQueue($app['queue']);
}
// Next we will set all of the global addresses on this mailer, which
allows
// for easy unification of all "from" addresses as well as easy
debugging
// of sent messages since they get be sent into a single email
address.
foreach (['from', 'reply_to', 'to'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
});

 

Trong toàn ứng dụng chúng ta chỉ khởi tạo duy nhất 1 lần service mail mà thôi do đó ta sử dụng phương thức singleton() để binding mailer. Trong service provider mail có 1 thuộc tính là :

 

public function provides()
{
return [
'mailer', 'swift.mailer', 'swift.transport', Markdown::class,
];
}

 Hàm này sẽ cho hệ thống biết sẽ phải trả về service nào. Trong ví dụ trên laravel trả về các service mailer ... Khi bạn viết các sử lý liên quan đến các service khác hãy viết các xử lý này trong
method boot() vì khi này laravel đã duyệt qua tất cả các service provider. Nếu bạn viết các xử lý liên quan đến các service khác trong method register() rất có thể laravel chưa duyệt qua service đó và có thể phát sinh lỗi ngoài mong muốn. Ví dụ về method boot()

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

Bạn có thể thực hành tạo cho mình một service để làm việc với các file pdf chẳng hạn như việc tạo ra các file pdf . Bạn có thể tạo ra 1 service provider mới là PdfServiceProvider cho mục đính thực hành.