強化版Macroable

milksoul發表於2020-09-25

在閱讀Laravel原始碼的過程中,發現了Macroable這個trait,覺得如果能將帶有依賴注入的方法也”揉雜”到指定類中就好了。於是經過嘗試,有了下面的強化版Macroable。

<?php

use Closure;
use Illuminate\Support\Traits\Macroable;
use ReflectionClass;
use ReflectionMethod;
use BadMethodCallException;

trait InjectMacroable
{
    use Macroable;

    /**
     * Register a custom macro.
     *
     * @param  object $mixin
     * @param  array  $methods
     *
     * @return void
     */
    public static function enhanceMacro($mixin, $methods)
    {
        if(! is_array($methods)) $methods = [$methods];
        if(! empty($methods)){
            $reflection_class = new ReflectionClass($mixin);
            foreach($methods as $method){
                if(!self::hasMacro($method) && $reflection_method = $reflection_class->getMethod($method)){
                    static::$macros[$reflection_method->name] = $reflection_method->getClosure($mixin);
                }
            }
        }
    }

    /**
     * Mix another object into the class.
     *
     * @param  object  $mixin
     * @return void
     *
     * @throws \ReflectionException
     */
    public static function mixin($mixin)
    {
        $methods = (new ReflectionClass($mixin))->getMethods(
            ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
        );

        foreach ($methods as $method) {
            if($method->isConstructor() || $method->isDestructor()) continue;
            $method->setAccessible(true);

            if(!self::hasMacro($method->name) && $closure=$method->getClosure($mixin)){
                static::macro($method->name, $closure);
            }
        }
    }

    /**
     * Dynamically handle calls to the class.
     *
     * @param  string  $method
     * @param  array   $parameters
     * @return mixed
     *
     * @throws \BadMethodCallException
     */
    public function __call($method, $parameters)
    {
        if (! static::hasMacro($method)) {
            throw new BadMethodCallException(sprintf(
                'Method %s::%s does not exist.', static::class, $method
            ));
        }

        $macro = static::$macros[$method];

        if ($macro instanceof Closure) {
            return app()->call($macro, $parameters);
        }

        return call_user_func_array($macro, $parameters);
    }
}

如果需要在AController中引入BController的Test方法:

class BController{
    public function Test(Request $request, Cache $cache)
    {
        //todo
    }
}
class AController{
    use InjectMacroable ;
    public function __construct()
    {
        self::enhanceMacro(new BController(), 'Test');
    }
}

如果想將BController裡的所有方法都引入,則:

self::mixin(new BController());

在路由中定義AController@Test即可訪問。
引入的普通方法也能正常訪問。
因為多數場景都是呼叫非靜態方法,所以這裡只實現了__call方法,有需要的同學可以自行嘗試實現__callStatic。
另外,想引入其他類的方法也可以通過繼承之類的方式實現,這裡只是做一些不同的嘗試。
最後,希望和大家一起享受程式碼的快樂。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章