タイトルが長い(笑
いやまぁ色々と調べていてそれはそれで後で記事にするのですが。
Slim4で使われているContainerについてちょいと調べたので、備忘録かてがて。
Slim3では "pimple/pimple" が使われていたのですが、Slim4では "php-di/php-di" が使われています。
……いや「plainなinstallすると色々とアレがナニ」な感じで色々と色々なのですが、その辺はまた別に記載いたします*1。
似たようなもんか……と思いきや色々と差異があったりするので、その辺を調べてみました。
一端、ざっくりとSlim4をインストールしてある、とか思ってくださいませ。
ちと別件もあるので少し余計なものが入ってますが、composer.jsonが
{ "require": { "slim/slim": "^4.4", "twig/twig": "^3.0", "php-di/php-di": "^6.0", "slim/psr7": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "autoload": { "psr-4": { "App\\": "app/" } } }
大体こんな感じ。
で、まずは準備。
<?php declare(strict_types=1); // 基準になるディレクトリ(最後の / はない形式で) define('BASEPATH', realpath(__DIR__ . '/..')); // オートローダ require(BASEPATH . '/vendor/autoload.php'); /* * */ use DI\ContainerBuilder; use Psr\Container\ContainerInterface; // Builderインスタンスの生成 $containerBuilder = new ContainerBuilder(); $container = $containerBuilder->build(); var_dump( get_class($container) );
どうもお作法として
・まずcontainerBuilderを作る
・containerBuilderからcontainerを作る
って感じらしいので、上述のように。
string(12) "DI\Container"
ってな感じで、OK。
以前の、Slim3(というかpimple)のようなやり方を一応試してみる。
// $container['test'] = function ($c) { return \stdClass(); }; var_dump($container->get('test'));
Fatal error: Uncaught Error: Cannot use object of type DI\Container as array in
一発でアウト。
ほむ。配列系のやつ*2、持ってないんだなぁ。
で、色々と調査。
どうも
・設定は「ContainerBuilderに対して行う」
・メソッド「addDefinitions()」を使う
ようなので、ちょいとコードを書き換え。
大まかには
<?php declare(strict_types=1); // 基準になるディレクトリ(最後の / はない形式で) define('BASEPATH', realpath(__DIR__ . '/..')); // オートローダ require(BASEPATH . '/vendor/autoload.php'); /* * */ use DI\ContainerBuilder; use Psr\Container\ContainerInterface; // Builderインスタンスの生成 $containerBuilder = new ContainerBuilder(); // Builderに対して設定 XXXXXXXXXXXXXXXXX // Containerインスタンスの生成 $container = $containerBuilder->build(); // 以下、getつかって色々 XXXXXXXXXXXXXXXXX
って感じになるぽ。
最後に全ソース書くので、一端、局所で説明していきます。
まず、Slimだとお馴染みsetting。
// 配列の設定 $containerBuilder->addDefinitions([ 'setting' => [ 'setting_test' => 'OK', ], ]);
ってやると
var_dump($container->get('setting'));
で取れます。
array(1) { ["setting_test"]=> string(2) "OK" }
うんまぁこの辺はよし。
次。
古くからある「あらかじめDIに書いておくとどこからでも同一インスタンスが取得できる」のは、今回「Definitions」って呼称するようです。
設置の仕方は上述と一緒。
// 「Classを明示的に設定」のテスト: Definitions class Foo { } // $containerBuilder->addDefinitions([ 'testFoo' => function(ContainerInterface $c) { $obj = new \Foo(); $obj->s = $c->get('setting')['setting_test']; return $obj; }, ]);
ってやると
// var_dump($container->get('testFoo')); var_dump($container->get('testFoo'));
で
object(Foo)#28 (1) { ["s"]=> string(2) "OK" } object(Foo)#28 (1) { ["s"]=> string(2) "OK" }
って取れまして、同じインスタンスNo持ってるので「同一のものが取れている」事がわかります。
だからまぁ「addDefinitionsで括ってやれば大体Slim3の頃と同じ事が出来る」で、基本は終了でございます。
とりあえず最低限としては「Slim3で"配列への代入"で書いていた所を、addDefinitionsメソッドで包んでやればOK」って感じぽい。
ここから、追加調査。
少し気になる記述があったので試してみる……曰く「Autowiring」。
http://php-di.org/doc/autowiring.html
Autowiring is an exotic word that represents something very simple: the ability of the container to automatically create and inject dependencies.
ぐぐる先生翻訳
自動配線は、非常に単純なものを表すエキゾチックな単語です。依存関係を自動的に作成および注入するコンテナの機能です。
自動的? automatically create? ふぁ?
実験。
// 「解決出来るクラス名渡したら設定しなくてもDIしてくれるよ」テスト: Autowiring class Test { } var_dump($container->get('Test')); var_dump($container->get('Test'));
object(Test)#37 (0) { } object(Test)#37 (0) { }
………取れてる………しかも(ある意味)ちゃんと「シングルトン(同一インスタンス)」で。
……いいのかどうか、悩む挙動だなぁ。
なお試してませんが
$containerBuilder->useAutowiring(false);
で、この挙動はoffに出来るぽい。
……幾分色々と思案される所ではあるような気がする……なんとなく、おいちゃん的には(=業務的には)offっておいたほうが安全な気がせんでもない。
お次。
……どうも「コンストラクタの時、型指定をすると"その型のインスタンスをgetして取ってきてくれる"」とかいう、絶妙に微妙な機能があるっぽい*3。
とりあえず、まずは「無指定の時」の実験。
class Test2 { public function __construct($o) { $this->o = $o; var_dump( get_clas($o) ); } } var_dump($container->get('Test2')); var_dump($container->get('Test2')); echo "\n";
Fatal error: Uncaught DI\Definition\Exception\InvalidDefinition: Entry "Test2" cannot be resolved: Parameter $o of __construct() has no value defined or guessable
をや?
「has no value defined or guessable」?
あぁそうかここ別に「なにかが必ず渡ってくる」って訳でもないから、か。
では改めて「型を指定して」実験。
class Test3 { public function __construct(Test $o) { $this->o = $o; } } var_dump($container->get('Test3')); var_dump($container->get('Test3')); echo "\n";
object(Test3)#36 (1) { ["o"]=> object(Test)#37 (0) { } } object(Test3)#36 (1) { ["o"]=> object(Test)#37 (0) { } }
確かに入ってるなぁ。しかもちゃんと「Container経由」のインスタンスが。
……あれ? ってことは?
// $containerBuilder->addDefinitions([ 'testFooTest' => function(Test $c) { $obj = new \Foo(); $obj->s = $c; return $obj; }, ]);
してから
var_dump($container->get('testFooTest')); var_dump($container->get('testFooTest')); echo "\n";
object(Foo)#43 (1) { ["s"]=> object(Test)#40 (0) { } } object(Foo)#43 (1) { ["s"]=> object(Test)#40 (0) { } }
予想通りではある……便利とも言えるけど、気をつけないと割と諸刃っぽいなぁ。
でもまぁせっかくなんでもうちょっと突っ込み。
暗黙のクラスは「自作のクラスじゃなくて、PHPが元々持っているクラス」でも行けるのか?
class Test4 { public function __construct(\ArrayObject $o) { $this->o = $o; } } var_dump($container->get('Test4')); echo "\n";
Fatal error: Uncaught DI\Definition\Exception\InvalidDefinition: Entry "Test4" cannot be resolved: Entry "ArrayObject" cannot be resolved: The parameter "input" of __construct() has no type defined or guessable. It has a default value, but the default value can't be read through Reflection because it is a PHP internal class.
ほむ。
「it is a PHP internal class.」
なのか。んじゃこれは駄目だな。
……「クラス名じゃないけど、Containerに指定しているkey名」だと、どうだろう?
class Test5 { public function __construct(testFoo $o) { $this->o = $o; } } var_dump($container->get('Test5')); // Fatal error: Uncaught ReflectionException: Class testFoo does not exist in / echo "\n";
Fatal error: Uncaught ReflectionException: Class testFoo does not exist in ...
うんまぁそうかPHPのparseでエラーになるかそりゃそうだ。
というわけで
・とりあえず「Slim3の頃と同じような感じで使いたい」なら「ContainerBuilderのインスタンスにaddDefinitions()してからbuild()メソッドで$containerインスタンスを作る」
・containerのget、或いはaddDefinitions()に渡すfunction()の引数、またはコンストラクタに「自作クラスのクラス名」を渡すと、それを自動的に取ってきてくれる
ってのがわかった感じ。
一応「Slim3からの移植」にはさほどの差し障りがないなぁ、というのと「ちょっと便利が過ぎて少し微妙」な機能があるので、使いどころかなぁ、的な印象でございます。
最後に、今回のコードの大体の全景を。
<?php declare(strict_types=1); // 基準になるディレクトリ(最後の / はない形式で) define('BASEPATH', realpath(__DIR__ . '/..')); // オートローダ require(BASEPATH . '/vendor/autoload.php'); /* * */ use DI\ContainerBuilder; use Psr\Container\ContainerInterface; // Builderインスタンスの生成 $containerBuilder = new ContainerBuilder(); // 配列の設定 $containerBuilder->addDefinitions([ 'setting' => [ 'setting_test' => 'OK', ], ]); // 「Classを明示的に設定」のテスト: Definitions class Foo { } // $containerBuilder->addDefinitions([ 'testFoo' => function(ContainerInterface $c) { $obj = new \Foo(); $obj->s = $c->get('setting')['setting_test']; return $obj; }, // タイプヒンティングで「別のクラス」を指定したら、(解決できるなら)それを解決する 'testFooHoge' => function(Test $t) { $obj = new \Foo(); $obj->o = $t; return $obj; }, ]); // $containerBuilder->addDefinitions([ 'testFooTest' => function(Test $c) { $obj = new \Foo(); $obj->s = $c; return $obj; }, ]); // Containerインスタンスの生成 $container = $containerBuilder->build(); // var_dump($container->get('setting')); echo "\n"; // var_dump($container->get('testFoo')); var_dump($container->get('testFoo')); echo "\n"; var_dump($container->get('testFooHoge')); var_dump($container->get('testFooHoge')); echo "\n"; // 「解決出来るクラス名渡したら設定しなくてもDIしてくれるよ」テスト: Autowiring class Test { } var_dump($container->get('Test')); var_dump($container->get('Test')); echo "\n"; /* // Fatal error: Uncaught DI\Definition\Exception\InvalidDefinition: Entry "Test2" cannot be resolved: Parameter $o of __construct() has no value defined or guessable class Test2 { public function __construct($o) { $this->o = $o; var_dump( get_clas($o) ); } } var_dump($container->get('Test2')); var_dump($container->get('Test2')); echo "\n"; */ class Test3 { public function __construct(Test $o) { $this->o = $o; } } var_dump($container->get('Test3')); var_dump($container->get('Test3')); echo "\n"; // var_dump($container->get('testFooTest')); var_dump($container->get('testFooTest')); echo "\n"; // これは駄目なのか:It has a default value, but the default value can't be read through Reflection because it is a PHP internal class. class Test4 { public function __construct(\ArrayObject $o) { $this->o = $o; } } //var_dump($container->get('Test4')); // Fatal error: Uncaught DI\Definition\Exception\InvalidDefinition: Entry "Test4" cannot be resolved: Entry "ArrayObject" cannot be resolved: The parameter "input" of __construct() has no type defined or guessable. It has a default value, but the default value can't be read through Reflection because it is a PHP internal class. //echo "\n"; // 流石にこれは駄目か /* class Test5 { public function __construct(testFoo $o) { $this->o = $o; } } var_dump($container->get('Test5')); // Fatal error: Uncaught ReflectionException: Class testFoo does not exist in / echo "\n"; */