php反射實現(xiàn)Ioc/Di及注解

? PHP5之后提供了完整的反射API撞羽,添加了對類阐斜、接口、函數(shù)放吩、方法和擴展進行反向工程的能力智听。此外,反射API提供了方法來取出

函數(shù)渡紫、類和方法的文檔注釋到推。

Ioc/Di大家應該都不陌生,但是對小白來說呢聽起來就挺高大上的惕澎,下面就用代碼來實現(xiàn):

<?php
/**
 * @author gaobinzhan <gaobinzhan@gmail.com>
 */

class Foo
{
    public function getClassName()
    {
        return 'this is Foo';
    }
}

class Bar
{
    public function getClassName()
    {
        return 'this is Bar';
    }
}

class Test
{
    public function __construct(Foo $foo, Bar $bar)
    {
        var_dump($foo->getClassName());
        var_dump($bar->getClassName());
    }

    public function index(Foo $foo)
    {
        var_dump($foo->getClassName());
    }
}

// 反射Test
$reflect = new ReflectionClass(Test::class);
// 獲取是否有構(gòu)造函數(shù)
$constructor = $reflect->getConstructor();
if ($constructor) {
    // 如果存在構(gòu)造 獲取參數(shù)
    $constructorParams = $constructor->getParameters();
    // 初始化注入的參數(shù)
    $args = [];
    // 循環(huán)去判斷參數(shù)
    foreach ($constructorParams as $param) {
        // 如果為class 就進行實例化
        if ($param->getClass()) {
            $args[] = $param->getClass()->newInstance();
        } else {
            $args[] = $param->getName();
        }
    }
    // 實例化注入?yún)?shù)
    $class = $reflect->newInstanceArgs($args);
} else {
    $class = $reflect->newInstance();
}


// 假設我們要調(diào)用index方法 在此之前自己判斷下方法是否存在 我省略了
$reflectMethod = new ReflectionMethod($class, 'index');
// 判斷方法修飾符是否為public
if ($reflectMethod->isPublic()) {
    // 以下代碼同等上面
    $args = [];
    $methodParams = $reflectMethod->getParameters();
    foreach ($methodParams as $param) {
        if ($param->getClass()) {
            $args[] = $param->getClass()->newInstance();
        } else {
            $args[] = $param->getName();
        }
    }
    $reflectMethod->invokeArgs($class, $args);
}

以上就簡單的實現(xiàn)了依賴注入莉测,下面我們接著封裝一下。

Ioc.php

<?php
/**
 * @author gaobinzhan <gaobinzhan@gmail.com>
 */

class Ioc
{
    public static function getInstance($className)
    {
        $args = self::getMethodParams($className);
        return (new ReflectionClass($className))->newInstanceArgs($args);
    }

    public static function make($className, $methodName, $params = []) {

        // 獲取類的實例
        $instance = self::getInstance($className);

        // 獲取該方法所需要依賴注入的參數(shù)
        $args = self::getMethodParams($className, $methodName);

        return $instance->{$methodName}(...array_merge($args, $params));
    }

    protected static function getMethodParams($className, $methodsName = '__construct')
    {

        // 通過反射獲得該類
        $class = new ReflectionClass($className);
        $args = []; // 記錄注入的參數(shù)

        // 判斷該類是否有構(gòu)造函數(shù)
        if ($class->hasMethod($methodsName)) {
            // 構(gòu)造函數(shù)存在 進行獲取
            $construct = $class->getMethod($methodsName);

            // 獲取構(gòu)造函數(shù)的參數(shù)
            $params = $construct->getParameters();

            // 構(gòu)造函數(shù)無參數(shù) 直接返回
            if (!$params) return $args;

            // 判斷參數(shù)類型
            foreach ($params as $param) {

                // 假設參數(shù)為類
                if ($paramClass = $param->getClass()) {

                    // 獲得參數(shù)類型名稱
                    $paramClassName = $paramClass->getName();
                    // 如果注入的這個參數(shù)也是個類 就要繼續(xù)判斷是否存在構(gòu)造函數(shù)
                    $methodArgs = self::getMethodParams($paramClassName);

                    // 存入數(shù)組中
                    $args[] = (new ReflectionClass($paramClass->getName()))->newInstanceArgs($methodArgs);
                }
            }
        }
        // 返回參數(shù)
        return $args;
    }
}

以上代碼實現(xiàn)了構(gòu)造函數(shù)的依賴注入及方法的依賴注入唧喉,下面進行測試捣卤。

<?php
class Bar
{
    public function getClassName()
    {
        return 'this is Bar';
    }
}

class Test
{
    public function __construct(Foo $foo, Bar $bar)
    {
        var_dump($foo->getClassName());
        var_dump($bar->getClassName());
    }

    public function index(Foo $foo)
    {
        var_dump($foo->getClassName());
    }
}
Ioc::getInstance(Test::class);
Ioc::make(Test::class,'index');

以上呢,就簡單的通過php的反射機制實現(xiàn)了依賴注入八孝。

繼基于swoole的微服務框架出現(xiàn)董朝,注解呢就開始慢慢出現(xiàn)在我們的視角里。據(jù)說php8也加入了注解支持:

use \Support\Attributes\ListensTo;

class ProductSubscriber
{
    <<ListensTo(ProductCreated::class)>>
    public function onProductCreated(ProductCreated $event) { /* … */ }

    <<ListensTo(ProductDeleted::class)>>
    public function onProductDeleted(ProductDeleted $event) { /* … */ }
}

就類似這樣的干跛,哈哈哈子姜。

而我們現(xiàn)在的注解則是通過反射拿到注釋去做到的解析。

接下來我們?nèi)ビ脛e人寫好的組件去實現(xiàn)annotations

編寫我們的composer.json

{
    "require": {
        "doctrine/annotations": "^1.8"
    },
    "autoload": {
        "psr-4": {
            "app\\": "app/",
            "library\\": "library/"
        }
    }
}

接下來要執(zhí)行啥楼入?哥捕?牧抽?這個你要是再不會,真的我勸你回家種地吧RW扬舒!哈哈哈 鬧著玩呢!

composer install

然后我們接下來去創(chuàng)建目錄:

- app // app目錄
    - Http
        - HomeController.php
- library // 核心注解庫
    - annotation
        - Mapping
            - Controller.php
            - RequestMapping.php
        - Parser
- vendor
- composer.json
- index.php // 測試文件
php-annotation目錄

害 圖片有點大凫佛。讲坎。。御蒲。衣赶。咋整。厚满。府瞄。算了,就這樣吧5夤俊W窆荨!

溫馨提示:在phpstrom里面丰榴,安裝插件PHP Annotation寫代碼會更友好盎醯恕!四濒!

創(chuàng)建library\Mapping\Controller.php

<?php
/**
 * @author gaobinzhan <gaobinzhan@gmail.com>
 */


namespace library\annotation\Mapping;


use Doctrine\Common\Annotations\Annotation\Attribute;
use Doctrine\Common\Annotations\Annotation\Attributes;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;

/**
 * Class Controller
 * @package library\annotation\Mapping
 * @Attributes({
 *     @Attribute("prefix", type="string"),
 * })
 * @Annotation
 * @Target("CLASS")
 */
final class Controller
{
    /**
     * @Required()
     * @var string
     */
    private $prefix = '';
    public function __construct(array $value)
    {
        if (isset($value['value'])) $this->prefix = $value['value'];
        if (isset($value['prefix'])) $this->prefix = $value['prefix'];
    }

    public function getPrefix(){
        return $this->prefix;
    }
}

@Annotation表示這是個注解類换况,讓IDE提示更加友好!

@Target表示這個注解類只能被類使用盗蟆!

@Required表示這個屬性是必須填寫的戈二!

@Attributes表示這個注解類有多少個屬性!

創(chuàng)建library\Mapping\RequestMapping.php

<?php
/**
 * @author gaobinzhan <gaobinzhan@gmail.com>
 */


namespace library\annotation\Mapping;


use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;

/**
 * Class RequestMapping
 * @package library\annotation\Mapping
 * @Annotation
 * @Attributes({
 *     @Attribute("route", type="string"),
 * })
 * @Target("METHOD")
 */
final class RequestMapping
{
    /**
     * @Required()
     */
    private $route;
    public function __construct(array $value)
    {
        if (isset($value['value'])) $this->route = $value['value'];
        if (isset($value['route'])) $this->route = $value['route'];
    }

    public function getRoute(){
        return $this->route;
    }
}

這里的代碼就不用再重復解釋了吧喳资!我偏不解釋了觉吭!哈哈哈

創(chuàng)建app\Http\HomeController.php

<?php
/**
 * @author gaobinzhan <gaobinzhan@gmail.com>
 */


namespace app\Http;


use library\annotation\Mapping\Controller;
use library\annotation\Mapping\RequestMapping;

/**
 * Class HomeController
 * @package app\Http
 * @Controller(prefix="/home")
 */
class HomeController
{
    /**
     * @RequestMapping(route="/test")
     */
    public function test(){
        echo 111;
    }
}

這里代碼沒啥解釋的。仆邓。鲜滩。

哈哈,下面來測試我們的結(jié)果节值!

index.php

<?php
/**
 * @author gaobinzhan <gaobinzhan@gmail.com>
 */

$loader = require './vendor/autoload.php';

// 獲取一個反射類
$refClass = new \ReflectionClass('\app\Http\HomeController');

// 注冊load
\Doctrine\Common\Annotations\AnnotationRegistry::registerLoader([$loader, 'loadClass']);

// new 我們的注解讀取器
$reader = new \Doctrine\Common\Annotations\AnnotationReader();

// 獲取該類上的所有注解
$classAnnotations = $reader->getClassAnnotations($refClass);

// 這是個循環(huán) 說明$classAnnotations是個數(shù)組
foreach ($classAnnotations as $annotation){
    // 因為我們定義了Controller注解類 要判斷好啊
    if ($annotation instanceof \library\annotation\Mapping\Controller){
        // 獲取我們的 prefix 這地方能看懂吧徙硅。。搞疗。
        echo $routePrefix = $annotation->getPrefix().PHP_EOL;
        // 獲取類中所有方法
        $refMethods = $refClass->getMethods();
    }

    // 進行循環(huán)
    foreach ($refMethods as $method){
        // 獲取方法上的所有注解
        $methodAnnotations = $reader->getMethodAnnotations($method);
        // 循環(huán)
        foreach ($methodAnnotations as $methodAnnotation){
            if ($methodAnnotation instanceof \library\annotation\Mapping\RequestMapping){
                // 輸出我們的route
                echo $methodAnnotation->getRoute().PHP_EOL;
            }
        }
    }
}

執(zhí)行結(jié)果:

/home
/test

之前我們不是建立了個Parser的目錄嘛闷游,可以在里面創(chuàng)建對應的解析類,然后去解析贴汪,把它們封裝一下子F晖!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扳埂,一起剝皮案震驚了整個濱河市业簿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌阳懂,老刑警劉巖梅尤,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異岩调,居然都是意外死亡巷燥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門号枕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缰揪,“玉大人,你說我怎么就攤上這事葱淳《巯伲” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵赞厕,是天一觀的道長艳狐。 經(jīng)常有香客問我,道長皿桑,這世上最難降的妖魔是什么毫目? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮诲侮,結(jié)果婚禮上镀虐,老公的妹妹穿的比我還像新娘。我一直安慰自己浆西,他們只是感情好粉私,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著近零,像睡著了一般诺核。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上久信,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天窖杀,我揣著相機與錄音,去河邊找鬼裙士。 笑死入客,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桌硫,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼夭咬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铆隘?” 一聲冷哼從身側(cè)響起卓舵,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膀钠,沒想到半個月后掏湾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡肿嘲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年融击,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雳窟。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡尊浪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涩拙,到底是詐尸還是另有隱情际长,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布兴泥,位于F島的核電站工育,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏搓彻。R本人自食惡果不足惜如绸,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旭贬。 院中可真熱鬧怔接,春花似錦、人聲如沸稀轨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奋刽。三九已至瓦侮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佣谐,已是汗流浹背肚吏。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狭魂,地道東北人罚攀。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓党觅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親斋泄。 傳聞我的和親對象是個殘疾皇子杯瞻,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351