Help us understand the problem. What is going on with this article?

PHPのDIコンテナを作った話

More than 1 year has passed since last update.

DIというか Auto Wiring Container って書いたけど。
https://github.com/nishimura/simple-container

今まで何度かフレームワークを作っていて、その全てで必須だった機能だけ抜き出した感じなので作ったというか削ってまとめたというか。

使い方

composer require nish/simple-container
<?php
use Nish\Container\Container;
require_once 'vendor/autoload.php';
$c = Container::getInstance();

パッケージにしてるけど200行のソース1ファイル。
各フレームワークのコンポーネントやDI単体のパッケージがいくつもあることは知っているけれど、使わない機能のために何十ファイルとか何千行とかのプログラムを入れるのは好きじゃないので、ファイルを一つ置けば使えるレベルのものにした。

コンストラクタインジェクション。よくあるやつ。

// auto wiring
class Foo
{
    private $obj;
    public function __construct(stdClass $obj)
    {
        $this->obj = $obj;
    }
}

$c = Container::getInstance();
$foo = $c->get(Foo::class);

あらかじめ登録する場合と、ファクトリー的なやつ。

interface Uri {}
class MyUri implements Uri {}

$c->set(Uri::class, new MyUri);
$uri = $c->get(Uri::class);
// こちらはsingleton

// こちらは毎回newする
$c->setFactory(Uri::class, function($c){
    return new MyUri();
});

// 自分でnewしつつsingletonにしたい場合
$c->setFactory(Uri::class, function($c){
    $myUri = new MyUri();
    $c->set(Uri::class, $myUri);
    return $myUri;
});

あとはauto wiringしつつメソッドを実行する機能も個人的には必須だったので。

// メソッド実行
$c->call(Foo::class, 'staticMethod');
$foo = new Foo();
$c->call($foo, 'method');

こういうものと、それから型で一意に特定できない場合の対応

// 引数名によるインジェクション
namespace MyProject;
class Db {
    private $dsn;
    public function __construct(string $dsn){
        $this->dsn = $dsn;
    }
    // ...
}
$c->set('MyProject\\Db#__construct.dsn', 'mysql://dbname...');
$db = $c->get(MyProject\\Db::class);

引数の名前でインジェクションする機能も使う。urlのidを取得して担当クラスのメソッドを呼び出す、みたいなことを自動でするときに使う。

ここまで来れば、あとは FastRoute などを使って

class MyUserAction
{
    public static function index(Db $db, MyForm $myForm, $userId)
    {
        // ここがアプリの処理
    }
}

// ルーティング設定ファイル的な
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/user/{userId:\d+}', ['myuser.html', MyUserAction::class, 'index']);
});


// ここからフレームワーク的な処理
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        // ... 404 Not Found
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        $allowedMethods = $routeInfo[1];
        // ... 405 Method Not Allowed
        break;
    case FastRoute\Dispatcher::FOUND:
        [$template, $cls, $method] = $routeInfo[1];
        $vars = $routeInfo[2];
        $result = $c->call($cls, $method, $vars);
        break;
}

$te = new TemplateEngine();
$te->output($template, $result);

フロントコントローラーからアクションを呼び出してテンプレートエンジンに渡すというよくある処理がすぐできる。

この辺の処理は 12年ぐらい前 にやっていて、多機能を求めたり シンプルを求めたり行ったり来たりしている。
アノテーションで色んなことをする版は、composer update でアノテーション解析が究極に遅くなったのでその次から止めた。

最近はとにかくシンプルにしたくてクラスじゃなくて関数にしたいんだけど、autoloadが働かないから仕方なくstaticメソッドにしている。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした