Laravel源碼閱讀(前奏-反射實例化對象)

說明

Laravel的核心容器,有一部分就是利用反射來實現依賴注入時類的實例化。這是一個簡化版示例。

代碼

測試用的類

/**
 * 坦克標準: 必須能開火
 */
interface Tank
{
    public function fire();
}


/**
 * 59坦克,使用100毫米炮
 */
class Tank59 implements Tank
{
    private $gun = 100;

    public function fire()
    {
        echo $this->gun . PHP_EOL;
    }
}

/**
 * 59增強版,可使用100毫米炮或120毫米炮,默認120毫米炮
 */
class Tank59Plus implements Tank
{
    private $gun;

    public function __construct($gun = 120)
    {
        if($gun != 100 && $gun != 120) {
            throw new Exception('口徑不合適');
        }

        $this->gun = $gun;
    }

    public function fire()
    {
        echo $this->gun . PHP_EOL;
    }
}

/**
 * 59超級版,可使用任意口徑的炮,必須提供一門火炮
 */
class Tank59Super implements Tank
{
    private $gun;

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

    public function fire()
    {
        echo $this->gun . PHP_EOL;
    }
}

/**
 * 坦克營,裝備59和59增強版,必須提供59和59增強版
 */
class TankArmy implements Tank
{
    private $tank59;
    private $tank59Plus;

    public function __construct(Tank59 $tank59, Tank59Plus $tank59Plus)
    {
        $this->tank59 = $tank59;
        $this->tank59Plus = $tank59Plus;
    }

    public function fire()
    {
        echo $this->tank59->fire();
        echo $this->tank59Plus->fire();
    }
}

容器

/**
 * 兵工廠
 */
class Factory
{
    /**
     * 建立實體對象
     */
    public static function build($blueprint)
    {
        //反射,獲取類細節
        $reflector = new ReflectionClass($blueprint);

        //沒法建立的類,好比接口,抽象類
        if(!$reflector->isInstantiable()) {
            throw new Exception('提供的不是坦克圖紙,不能製造');
        }

        //獲取類的構造方法
        $constructor = $reflector->getConstructor();
        if (is_null($constructor)) {
            //沒有構造方法,直接new
            return new $blueprint;
        }

        $parameters = [];
        //獲取構造方法依賴的參數
        $dependencies = $constructor->getParameters();
        foreach ($dependencies as $dependency) {
     
            if(is_null($dependency->getClass())) {
                //依賴參數不是對象
                //若是構造方法參數有默認值,獲取默認值
                if($dependency->isDefaultValueAvailable()) {
                    $parameters[] = $dependency->getDefaultValue();
                }
            }
            else {
                //依賴參數是對象,遞歸建立
                $parameters[] = self::build($dependency->getClass()->name);
            }
        }

        if(!$parameters) {
            //有構造方法且構造方法有參數,卻沒有能夠提供的實參,沒法建立
            throw new Exception('坦克缺乏必要的零件,不能製造');
        }
        else {
            //使用反射提供的方法建立對象,傳入參數
            return  $reflector->newInstanceArgs($parameters);
        }
    }
}

測試

Tank

$tank = Factory::build(Tank::class);
echo $tank->fire();

//輸出
//PHP Fatal error:  Uncaught Exception: 提供的不是坦克圖紙,不能製造

Tank59

$tank = Factory::build(Tank59::class);
echo $tank->fire();

//輸出
//100

Tank59Plus

$tank = Factory::build(Tank59Plus::class);
echo $tank->fire();

//輸出
//120

Tank59Super

$tank = Factory::build(Tank59Super::class);
echo $tank->fire();

//輸出
//Fatal error: Uncaught Exception: 坦克缺乏必要的零件,不能製造

TankArmy

$tank = Factory::build(TankArmy::class);
echo $tank->fire();

//輸出
//100
//120
相關文章
相關標籤/搜索