14
3

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 5 years have passed since last update.

エキサイトAdvent Calendar 2018

Day 8

BEAR.SundayのResourceにカスタムスキームを追加する方法

Last updated at Posted at 2018-12-08

はじめに

BEAR.Sundayを利用している方向けの記事となります

BEAR.SundayにはRay.Di, Ray.Aop, BEAR.Resourceという3つの大きな要素があるのですが、今回はBEAR.Resourceのスキームのカスタマイズについて紹介します

デフォルトは2つのスキームで

$ro = $this->resource->get('app://self/weekday', ['year' => '2001', 'month' => '1', 'day' => '1']);

Resourceの呼び出しは上記のように行います

BEAR.Sundayではデフォルトで app://, page:// のスキームが用意されています

小規模であれば、この2つのスキームだけで問題ないのですが、規模が大きくなってくると、スキームを増やし役割ごとに分割したくなることもあるでしょう

今回は app://, page:// 以外のスキームで呼び出しができるようにカスタマイズする方法を紹介します

デモリポジトリ

スキームの追加

serviceスキームを定義して service:// で呼び出しできるようにカスタマイズすると仮定して、実装していきます

呼び出しイメージ

$summationResource = $this->resource->get('service://self/summation', ['a' => 10, 'b' => 7]);

MySchemeCollectionProviderを実装する

スキームを管理しているのは SchemeCollection

SchemeCollection にスキームを設定しているのは SchemeCollectionProvider です

BEAR\Resource\Module\BEAR\Resource\Module\SchemeCollectionProviderを参考に

カスタマイズした MySchemeCollectionProvider を実装します

service:// を実現するために下記の1行を追加します

class SchemeCollectionProvider implements ProviderInterface
{
    /**
     * @var string
     */
    private $appName;

    /**
     * @var InjectorInterface
     */
    private $injector;

    /**
     * @param string $appName
     *
     * @AppName("appName")
     */
    public function __construct($appName, InjectorInterface $injector)
    {
        $this->appName = $appName;
        $this->injector = $injector;
    }

    /**
     * Return instance
     *
     * @return SchemeCollection
     */
    public function get()
    {
        $schemeCollection = new SchemeCollection;
        $pageAdapter = new AppAdapter($this->injector, $this->appName);
+       $appAdapter = new AppAdapter($this->injector, $this->appName);
        $serviceAdapter = new AppAdapter($this->injector, $this->appName);
        $schemeCollection->scheme('page')->host('self')->toAdapter($pageAdapter);
        $schemeCollection->scheme('app')->host('self')->toAdapter($appAdapter);
+       $schemeCollection->scheme('service')->host('self')->toAdapter($serviceAdapter);

        return $schemeCollection;
    }
}

実装したMySchemeCollectionProviderをSchemeCollectionInterfaceに束縛する

BEAR.Sunday(Ray.Di) では先の束縛が優先されるので

デフォルトの束縛が行われている PackageModuleinstall より先に

実装した MySchemeCollectionProviderSchemeCollectionInterface に束縛します

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $appDir = $this->appMeta->appDir;
        require_once $appDir . '/env.php';

+        $this->bind(SchemeCollectionInterface::class)->toProvider(SchemeCollectionProvider::class);

        $this->install(new PackageModule);
    }

serviceスキームを利用する

これでserviceスキームが定義でき、利用できるようになりました

下記のように service://self/summation と呼び出せます

CLI等で実行してみてください

    public function onGet(string $name = 'BEAR.Sunday') : ResourceObject
    {
        $weekdayResource = $this->resource->get('app://self/weekday', ['year' => '2001', 'month' => '1', 'day' => '1']);
        $summationResource = $this->resource->get('service://self/summation', ['a' => 10, 'b' => 7]);

        $this->body = [
            'greeting' => 'Hello ' . $name,
            'weekday' => $weekdayResource->body,
            'summationResource' => $summationResource->body,
        ];

        return $this;
    }

まとめ

スキームの追加そのものはとっても簡単です!

これで規模が大きくなっても問題なし!役割ごとに分割して、肥大化を防ぎましょう!

Lets BEAR.Sunday!

なぜSchemeCollectionProviderをカスタマイズすればいいのか

ここからは Resource の処理を追いながら、なぜ SchemeCollectionProvider をカスタマイズすればいいのか解説します

Resourceの実行

/**
 * @var ResourceInterface
 */
protected $resource;

$ro = $this->resource->get('app://self/weekday', ['year' => '2001', 'month' => '1', 'day' => '1']);

この処理が実行されたときに、どういう順番でBEAR.Sundayが処理していくのか追っていきます

BEAR\Resource\Resource

$this->resourceResourceInterface で実態は BEAR\Resource\Resource であり、このクラスの get メソッドが呼び出されます

    public function get(string $uri, array $query = []) : ResourceObject
    {
        $this->method = Request::GET;

        return $this->uri(new Uri($uri))($query);
    }

自身の uri メソッドを呼び出して、その結果を実行しているので、uri メソッドを確認します

    /**
     * {@inheritdoc}
     */
    public function uri($uri) : RequestInterface
    {
        if (is_string($uri)) {
            $uri = new Uri($uri);
        }
        $uri->method = $this->method;
        $ro = $this->newInstance($uri);
        $ro->uri = $uri;
        $this->request = new Request($this->invoker, $ro, $uri->method, $uri->query, [], $this->linker);
        $this->method = 'get';

        return $this->request;
    }

Request を生成して、返しています

ということは get メソッドでは Request__invoke が実行されることがわかりました

BEAR\Resource\Request, BEAR\Resource\AbstractRequest

    /**
     * {@inheritdoc}
     */
    public function __invoke(array $query = null) : ResourceObject
    {
        if (is_array($query)) {
            $this->query = array_merge($this->query, $query);
        }
        $this->resourceObject->uri->query = $this->query;
        if ($this->links && $this->linker instanceof LinkerInterface) {
            return $this->linker->invoke($this);
        }

        return $this->invoker->invoke($this);
    }

__invoke が実装されているのは AbstractRequest

実際の処理は $invokerinvoke メソッドが担当しているので、そちらを見にいきます

BEAR\Resource\Invoker

    /**
     * {@inheritdoc}
     */
    public function invoke(AbstractRequest $request) : ResourceObject
    {
        $onMethod = 'on' . ucfirst($request->method);
        if (! method_exists($request->resourceObject, $onMethod) === true) {
            return $this->methodNoExists($request);
        }
        $params = $this->params->getParameters([$request->resourceObject, $onMethod], $request->query);
        $response = call_user_func_array([$request->resourceObject, $onMethod], $params);
        if (! $response instanceof ResourceObject) {
            $request->resourceObject->body = $response;
            $response = $request->resourceObject;
        }

        return $response;
    }

$response = call_user_func_array([$request->resourceObject, $onMethod], $params);

$requestresourceObject の対応しているメソッドを実行していることがわかりました

この resourceObjectRequest を生成するときにコンストラクタで渡されています

BEAR\Resource\Resource

BEAR\Resource\Resourceuri メソッドに戻ってみると、自身の newInstance メソッドで生成されていることがわかります

$ro = $this->newInstance($uri);
    /**
     * {@inheritdoc}
     */
    public function newInstance($uri) : ResourceObject
    {
        return $this->factory->newInstance($uri);
    }

実際は factorynewInstance メソッドで生成されているので、Factory を確認しにいきます

BEAR\Resource\Factory

    /**
     * Resource adapter biding config
     *
     * @var SchemeCollectionInterface
     */
    private $scheme;

    /**
     * {@inheritdoc}
     *
     * @throws \BEAR\Resource\Exception\UriException
     */
    public function newInstance($uri) : ResourceObject
    {
        if (is_string($uri)) {
            $uri = new Uri($uri);
        }
        $adapter = $this->scheme->getAdapter($uri);

        return $adapter->get($uri);
    }

SchemeCollectionInterface を実装したクラス経由で、Adapter をうけとり、AdapterResourceObject を生成していることがわかりました

SchemeCollectionInterface の実態を確認しにいきましょう

BEAR\Resource\Module\ResourceClientModule

ResourceClientModule を確認すると

SchemeCollectionInterfaceSchemeCollectionProvider を束縛されていることがわかりました

$this->bind(SchemeCollectionInterface::class)->toProvider(SchemeCollectionProvider::class);

BEAR\Resource\Module\SchemeCollectionProvider

    /**
     * Return instance
     *
     * @return SchemeCollection
     */
    public function get()
    {
        $schemeCollection = new SchemeCollection;
        $pageAdapter = new AppAdapter($this->injector, $this->appName);
        $appAdapter = new AppAdapter($this->injector, $this->appName);
        $schemeCollection->scheme('page')->host('self')->toAdapter($pageAdapter);
        $schemeCollection->scheme('app')->host('self')->toAdapter($appAdapter);

        return $schemeCollection;
    }

SchemeCollectionProviderSchemeCollection に 各種 Adapter を設定しています

BEAR\Resource\SchemeCollection

final class SchemeCollection implements SchemeCollectionInterface
{
    /**
     * Scheme
     *
     * @var string
     */
    private $scheme = '';

    /**
     * Application name
     *
     * @var string
     */
    private $app = '';

    /**
     * @var AdapterInterface[]
     */
    private $collection = [];

    /**
     * {@inheritdoc}
     */
    public function scheme(string $scheme) : SchemeCollectionInterface
    {
        $this->scheme = $scheme;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function host(string $host) : SchemeCollectionInterface
    {
        $this->app = $host;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function toAdapter(AdapterInterface $adapter) : SchemeCollectionInterface
    {
        $this->collection[$this->scheme . '://' . $this->app] = $adapter;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @throws SchemeException
     */
    public function getAdapter(AbstractUri $uri) : AdapterInterface
    {
        $schemeIndex = $uri->scheme . '://' . $uri->host;
        if (! array_key_exists($schemeIndex, $this->collection)) {
            throw new SchemeException($uri->scheme . '://' . $uri->host);
        }

        return $this->collection[$schemeIndex];
    }
}

schemehostindex として、adapter を管理していることがわかりました

利用している AdapterAppAdapter なので中身を確認しておきましょう

BEAR\Resource\Module\AppAdapter

    /**
     * {@inheritdoc}
     *
     * @throws ResourceNotFoundException
     * @throws \Ray\Di\Exception\Unbound
     */
    public function get(AbstractUri $uri) : ResourceObject
    {
        if (substr($uri->path, -1) === '/') {
            $uri->path .= 'index';
        }
        $path = str_replace('-', '', ucwords($uri->path, '/-'));
        $class = sprintf('%s%s\Resource\%s', $this->namespace, $this->path, str_replace('/', '\\', ucwords($uri->scheme) . $path));
        try {
            $instance = $this->injector->getInstance($class);
        } catch (Unbound $e) {
            throw $this->getNotFound($uri, $e, $class);
        }

        return $instance;
    }

Ray.DiInjector で各 Resource のインスタンスを取得していることがわかりました

ここまで追えれば SchemeCollectionProvider をカスタマイズして、

SchemeCollectionservice スキーマを追加してやれば、やりたいことが実現できることがわかります!

備考

記事ではどういう束縛が設定されていて、結果的になにが注入されているのかという部分の確認方法は触れませんでした

束縛を確認するには var/tmp/{context} フォルダを確認するとコンパイルされたファイルが並ぶので、各Interface名のファイルをみれば、どういう束縛が行われているか確認できます

14
3
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
14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?