はじめに
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)
では先の束縛が優先されるので
デフォルトの束縛が行われている PackageModule
の install
より先に
実装した MySchemeCollectionProvider
を SchemeCollectionInterface
に束縛します
/**
* {@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->resource
は ResourceInterface
で実態は 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
で
実際の処理は $invoker
の invoke
メソッドが担当しているので、そちらを見にいきます
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);
$request
の resourceObject
の対応しているメソッドを実行していることがわかりました
この resourceObject
は Request
を生成するときにコンストラクタで渡されています
BEAR\Resource\Resource
BEAR\Resource\Resource
の uri
メソッドに戻ってみると、自身の newInstance
メソッドで生成されていることがわかります
$ro = $this->newInstance($uri);
/**
* {@inheritdoc}
*/
public function newInstance($uri) : ResourceObject
{
return $this->factory->newInstance($uri);
}
実際は factory
の newInstance
メソッドで生成されているので、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
をうけとり、Adapter
が ResourceObject
を生成していることがわかりました
SchemeCollectionInterface
の実態を確認しにいきましょう
BEAR\Resource\Module\ResourceClientModule
ResourceClientModule
を確認すると
SchemeCollectionInterface
に SchemeCollectionProvider
を束縛されていることがわかりました
$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;
}
SchemeCollectionProvider
が SchemeCollection
に 各種 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];
}
}
scheme
と host
を index
として、adapter
を管理していることがわかりました
利用している Adapter
は AppAdapter
なので中身を確認しておきましょう
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.Di
の Injector
で各 Resource
のインスタンスを取得していることがわかりました
ここまで追えれば SchemeCollectionProvider
をカスタマイズして、
SchemeCollection
に service
スキーマを追加してやれば、やりたいことが実現できることがわかります!
備考
記事ではどういう束縛が設定されていて、結果的になにが注入されているのかという部分の確認方法は触れませんでした
束縛を確認するには var/tmp/{context}
フォルダを確認するとコンパイルされたファイルが並ぶので、各Interface名のファイルをみれば、どういう束縛が行われているか確認できます