PHPで最小限のDIの仕組みを作る

2020-11-15

  • タイプヒントができるのはReflectionClassのおかげ。
  • 抽象クラスと具体クラスのペアを配列に入れてるよ

ググったら似たような記事は結構出てくる。
頭の良い人はLaravelのIlluminate\Container\Containerをいっぱいみてください。

ソース

<?php
//従業員の抽象
interface EmployeeInterface
{
    public function work() :string;
}

class Engineer implements EmployeeInterface
{
    public function work() :string
    {
        return "create products";
    }
}

class Salesman implements EmployeeInterface
{
    public function work() :string
    {
        return "sale products";
    }
}

// 会社クラス
class Company {
    private $employee;

    public function __construct(EmployeeInterface $employee)
    {
        $this->employee = $employee;
    }

    public function makeEmployeeWork()
    {
        return $this->employee->work();
    }
}

//コンテナ
class Container {
    //バインドした具象の配列
    private $binding;

    //抽象クラスと生成した具体クラスとをセットで登録
    public function bind($abstract, $concrete)
    {
        $this->binding[$abstract] = new $concrete;
    }

    //コンテナを経由してDIしながらインスタンスを生成する
    public function make($className) :object
    {
        $abstracts = $this->getAbstract($className);
        $concretes = [];
        foreach($abstracts as $abstract){
            $concretes[] = $this->binding[$abstract];
        }

        $class = new $className(...$concretes);
        return $class;
    }

    //コンテナ経由で生成するインスタンスのコンストラクタの引数のinterfaceを取得
    private function getAbstract($className) :array
    {
        $reflectionClass = new ReflectionClass($className);
        $reflectionMethod = $reflectionClass->getConstructor();
        $reflectionParameters =  $reflectionMethod->getParameters();
        //ReflectionParameterクラスを取得

        $abstracts = [];
        foreach($reflectionParameters as $parameter){
            $abstracts[] = $parameter->getClass()->getName();
            //抽象クラス名取得
        }
        return $abstracts;
    }
}

// コンテナを操るクラス。別にコンテナをnewしてもいいけどLaravelを模倣して作っといた。
class Provider
{
    public $app; //コンテナ
    public function __construct($container){
        $this->app = $container;
    }
}

class Application
{
    public $provider;

    public function __construct()
    {
        ini_set('display_errors', "On");
        $this->provider = new Provider(new Container());
        //プロバイダー生成
    }

    public function freeZone(){
        // $this->provider->app->bind(EmployeeInterface::class, Engineer::class);
        $this->provider->app->bind(EmployeeInterface::class, Salesman::class);
        //具体クラスを変更するときはバインドを変更するのみ

        $company =  $this->provider->app->make("Company");
        //コンテナ経由でcompanyインスタンスを生成する

        echo $company->makeEmployeeWork();
        //労働させる
    }

}

(new Application())->freeZone();
//コード実行

?>

所感

  • 会社と従業員の関係は例として不適切だからミスった。ゲームのハードとソフトとかにすればよかった。

参考