次のような3つのサービスクラスがあります。
<?php
namespace App\Services\SomeServices;
use stdClass;
class FetchService
{
public function fetch(int $id): stdClass
{
$data = new stdClass();
$data->id = $id;
return $data;
}
}
<?php
namespace App\Services\SomeServices;
use Psr\Log\LoggerInterface;
use stdClass;
class DeleteService
{
public function __construct(protected LoggerInterface $logger)
{}
public function delete(stdClass $class)
{
$this->logger->info('delete');
}
}
<?php
namespace App\Services\SomeServices;
use Psr\Log\LoggerInterface;
use stdClass;
class UpdateService
{
public function __construct(protected LoggerInterface $logger)
{}
public function update(stdClass $class)
{
$this->logger->info('delete');
}
}
そして、これらのサービスを利用するこのようなサービスがあります。
<?php
namespace App\Services;
use App\Services\SomeServices\DeleteService;
use App\Services\SomeServices\FetchService;
use App\Services\SomeServices\UpdateService;
class ServiceManager
{
public function __construct(
protected UpdateService $updateService,
protected DeleteService $deleteService,
protected FetchService $fetchService
) {}
public function update(int $id)
{
$data = $this->fetchService->fetch($id);
$this->updateService->update($data);
}
public function delete(int $id)
{
$data = $this->fetchService->fetch($id);
$this->deleteService->delete($data);
}
}
SymfonyのDIコンテナにより、コンストラクタの各サービスは自動的に注入されます。しかし、よくみるとupdate
メソッドではUpdateService
とFetchService
しか使っておらず、同じくdelete
メソッドではDeleteService
とFetchService
しか使っていません。
利用しないのに、コンストラクタへ渡すためだけにそのサービスがインスタンス化されるのはメモリの無駄ですね。そんな時に使えるのがサービスロケーターです。
サービスロケーターを使う
サービスロケーターとは、簡単にいうと「サービスの取得を抽象化する」ものです。このサービスロケーターがサービスの場所や生成方法を管理しているので、必要な時に必要なサービスだけを取得することができます。
サービスロケーターを使うパターンに書き直すと、このようになります。
<?php
namespace App\Services;
use App\Services\SomeServices\DeleteService;
use App\Services\SomeServices\FetchService;
use App\Services\SomeServices\UpdateService;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
class ServiceManagerWithLocator implements ServiceSubscriberInterface
{
public function __construct(
protected ContainerInterface $locator
) {}
public static function getSubscribedServices(): array
{
return [
DeleteService::class,
FetchService::class,
UpdateService::class,
];
}
public function update(int $id)
{
$data = $this->locator->get(FetchService::class)->fetch($id);
$this->locator->get(UpdateService::class)->update($data);
}
public function delete(int $id)
{
$data = $this->locator->get(FetchService::class)->fetch($id);
$this->locator->get(DeleteService::class)->delete($data);
}
}
コンストラクタで渡されている$locator
がサービスロケーターです。
サービスロケーターを使うクラスは、ServiceSubscriberInterface
を実装し、getSubscribedServices()
でロケーターに管理してほしいサービスのクラスを返します。
サービスを取得する時は$locator->get(FetchService::class)
のように、サービスロケーターのget()
メソッドに対象のクラスを指定します。ここでget()
できるサービスは、getSubscribedServices()
で指定したものに限られます。
$locator->get(FetchService::class)
メソッドが呼ばれた時、FetchService
が既に一度でも生成されていればそのインスタンスを返し、まだ未生成の場合は、初めてget()が呼ばれたタイミングでインスタンス化が行われます。なので、サービスクラスの生成が必要な時のみ行われることになるため、メモリの節約に繋がります。