1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Symfonyでサービスロケーター

Posted at

次のような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メソッドではUpdateServiceFetchServiceしか使っておらず、同じくdeleteメソッドではDeleteServiceFetchServiceしか使っていません。
利用しないのに、コンストラクタへ渡すためだけにそのサービスがインスタンス化されるのはメモリの無駄ですね。そんな時に使えるのがサービスロケーターです。

サービスロケーターを使う

サービスロケーターとは、簡単にいうと「サービスの取得を抽象化する」ものです。このサービスロケーターがサービスの場所や生成方法を管理しているので、必要な時に必要なサービスだけを取得することができます。

サービスロケーターを使うパターンに書き直すと、このようになります。

<?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()が呼ばれたタイミングでインスタンス化が行われます。なので、サービスクラスの生成が必要な時のみ行われることになるため、メモリの節約に繋がります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?