emrancu/dependency-injection-container

dev-master 2021-02-21 04:22 UTC

This package is auto-updated.

Last update: 2024-08-29 05:37:21 UTC


README

我们正在用Laravel构建应用程序,但许多人不知道如何使用依赖和参数Laravel初始化控制器和方法。

我们声明路由如下

Route::get('hello', 'TestController@index')

我们的控制器如下

 
 namespace App\Http\Controllers\Test;
  
 
 class TestController extends Controller
 {
      private $requests;
 
      public function __construct( Request $request)
         {
            $this->requests = $request;
         }
 
 
     /**
      * Show the application dashboard.
      */
     public function index(TestModel $testModel)
     {
       return $testModel->get();
     }
 
 
 }

或者路由如下

Route::get('hello', 'TestController')

或者控制器如下

 
 namespace App\Http\Controllers\Test;
  
 
 class TestController extends Controller
 {
      private $requests;
 
      public function __construct( Request $request)
         {
            $this->requests = $request;
         }
 
 
     /**
      * Show the application dashboard.
      */
     public function __invoke(TestModel $testModel)
     {
       return $testModel->get();
     }
 
 
 }

现在的问题

1.1 Laravel如何检测构造函数的依赖并注入?

1.2 Laravel如何使用构造函数的依赖初始化类,并调用带有依赖和参数的'index'方法?

2.1 我们没有传递方法名,但Laravel如何检测 __invoke 方法作为默认方法?

让我们构建一个依赖注入容器来理解前面的问题。

为什么我们应该了解?

  1. 作为Laravel开发者,我们需要了解Laravel是如何工作的。
  2. 这有助于我们以自己的方法构建新的框架。
  3. 我们可以使用这个容器来处理我们的大型项目或模块。
  4. 最后,我们将开始学习PHP Reflection(很棒!)

让我们开始

我们的步骤

  1. 创建一个类
  2. 创建一个用于初始化类以及注入构造函数的依赖和参数的方法(make)
  3. 创建一个用于提取类和方法以及注入方法依赖和参数的方法(call)

##1. 让我们创建一个类

class DependencyInjectionContainer
{

    /**
     * The container's  instance.
     *
     * @var static
     */
    protected static $instance;


    /**
     * the class name with namespace
     *
     * @var string
     */
    protected $callbackClass;

    /**
     * the method name of provided class
     *
     * @var string
     */
    protected $callbackMethod;

    /**
     * method separator of a class. when pass class and method as string
     */
    protected $methodSeparator = '@';

    /**
     * namespace  for  class. when pass class and method as string
     *
     * @var string
     */
    protected $namespace = "App\\controller\\";


    /**
     *   get Singleton instance of the class
     *
     * @return static
     */
    public static function instance()
    {

    }

    /**
     * @param $callable -- string class and method name with separator @
     * @param  array  $parameters
     */
    public function call($callable, $parameters = [])
    {

    }


    /**
     * separate class and method name
     * @param $callback
     */
    private function resolveCallback($callback)
    {

    }

    /**
     * instantiate class with dependency and return class instance
     * @param $class - class name
     * @param $parameters (optional) -- parameters as array . If constructor need any parameter
     */
    public function make($class, $parameters = [])
    {
    }

}

##创建一个用于初始化类以及注入构造函数的依赖和参数的方法(make)

我们将使用PHP PHP Reflection。在本节中,我们使用PHP ReflactionClass 对象。PHP ReflactionClass 有很多方法。在本节中,我们需要创建一个类实例。我们知道,如果我们创建任何类的实例,我们必须传递/注入所有依赖项和参数。

使用PHP ReflactionClass,我们需要检测所有依赖项。

public function make($class, $parameters = [])
    {
        $classReflection = new ReflectionClass($class);
        $constructorParams = $classReflection->getConstructor()->getParameters();
   }

在上述代码中,我们将构造函数的所有参数收集到 $constructorParams 变量中。

然后我们需要遍历 $constructorParams 并检测参数和依赖项。

public function make($class, $parameters = [])
    {

        $classReflection = new ReflectionClass($class);
        $constructorParams = $classReflection->getConstructor()->getParameters();
        $dependencies = [];

        /*
         * loop with constructor parameters or dependency
         */
        foreach ($constructorParams as $constructorParam) {

            $type = $constructorParam->getType();

            if ($type && $type instanceof ReflectionNamedType) {
                
                echo "It is a class and we need to instantiate the class and pass to constructor"

            } else {

                echo "This is a normal parameter and We need to find parameter value from $parameters . If not found value then need to check is this parameter optional or not. If not optional then through error"

            }

        } 
    }

要点

  1. 如果参数是一个类,我们需要实例化该类并将其传递给构造函数。
  2. 如果参数不是一个类,我们需要从 $parameters 中查找参数值。如果没有找到值,则需要检查该参数是否为可选参数。如果不是可选参数,则抛出错误。

让我们完善 make 方法

public function make($class, $parameters = [])
    {

        $classReflection = new ReflectionClass($class);
        $constructorParams = $classReflection->getConstructor()->getParameters();
        $dependencies = [];

        /*
         * loop with constructor parameters or dependency
         */
        foreach ($constructorParams as $constructorParam) {

            $type = $constructorParam->getType();

            if ($type && $type instanceof ReflectionNamedType) {
                
                // make instance of this class :
                $paramInstance = $constructorParam->getClass()->newInstance()

                // push to $dependencies array
                array_push($dependencies, $paramInstance);

            } else {

                $name = $constructorParam->getName(); // get the name of param

                // check this param value exist in $parameters
                if (array_key_exists($name, $parameters)) { // if exist
                    
                    // push  value to $dependencies sequencially
                    array_push($dependencies, $parameters[$name]);

                } else { // if not exist
                
                    if (!$constructorParam->isOptional()) { // check if not optional
                        throw new Exception("Can not resolve parameters");
                    }

                }

            }

        }
        // finally pass dependancy and param to class instance 
        return $classReflection->newInstance(...$dependencies);
    }

我在代码中解释了所有内容。

好的,我们已经完成了一个用于类的依赖注入容器

让我们创建一个单例实例

  public static function instance()
    {

        if (is_null(static::$instance)) {
            static::$instance = new static;
        }

        return static::$instance;
    }

基本用法

$container =  DependencyInjectionContainer::instance();

定义一个类

class MyClass
{
    private $dependency;

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

不要使用 new MyClass,而是使用容器的 make() 方法

$instance = $container->make(MyClass::class);

容器将自动实例化依赖项,因此这在功能上等同于

$instance = new MyClass(new AnotherClass());

实际示例

以下是一个基于 PHP-DI 文档 的更实际的示例 - 将邮件功能从用户注册中分离出来

class Mailer
{
    public function mail($recipient, $content)
    {
        // Send an email to the recipient
        // ...
    }
}
class UserManager
{
    private $mailer;

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

    public function register($email, $password)
    {
        // Create the user account
       
    }
}
 

$container = DependencyInjectionContainer::instance();

$userManager = $container->make(UserManager::class);
$userManager->register('dave@davejamesmiller.com', 'MySuperSecurePassword!');

##3. 创建一个用于提取类与方法并注入方法依赖和参数的方法(调用)。在本节中,我们将了解Laravel如何解析控制器和方法,以及如何通过依赖/参数来初始化。

步骤

  1. 分离控制器和方法
  2. 检测方法依赖/参数并调用方法
  3. 使用我们的make()函数实例化类。

1. 分离控制器和方法

我们的类有一个属性$methodSeparator,用作分离类名和方法名的分隔符。

 private function resolveCallback($callable)
    {
        
        //separate class and method
        $segments = explode($this->methodSeparator, $callable);
    
        // set class name with namespace
        $this->callbackClass = $this->namespace.$segments[0]; 

        // set method name . if method name not provided then default method __invoke
        $this->callbackMethod = isset($segments[1]) ? $segments[1] : '__invoke'; 

    }

2. 检测方法依赖/参数并调用方法

 public function call($callable, $parameters = [])
    {
        
        // set class name with namespace and method name
        $this->resolveCallback($callable);

        // initiate ReflectionMethod with class and method 
        $methodReflection = new ReflectionMethod($this->callbackClass, $this->callbackMethod);

        // get all dependencies/parameters
        $methodParams = $methodReflection->getParameters();

        $dependencies = [];

        // loop with dependencies/parameters
        foreach ($methodParams as $param) {

            $type = $param->getType(); // check type

            if ($type && $type instanceof ReflectionNamedType) { /// if parameter is a class

                $name = $param->getClass()->newInstance(); // create insrance 
                array_push($dependencies, $name); // push  to $dependencies array

            } else {  /// Normal parameter  

                $name = $param->getName();

                if (array_key_exists($name, $parameters)) { // check exist in $parameters

                    array_push($dependencies, $parameters[$name]); // push  to $dependencies array

                } else { // if not exist

                    if (!$param->isOptional()) { // check if not optional
                        throw new Exception("Can not resolve parameters");
                    }
                }

            }

        }
    
        // make class instance
        $initClass = $this->make($this->callbackClass, $parameters);

        // call method with $dependencies/parameters
       return $methodReflection->invoke($initClass, ...$dependencies); 
    }

现在你可能会问,Laravel是如何收集参数以调用函数的。

当我们声明一个具有动态参数的路由时:

Route::get('hello/{name}', 'TestController@index')

Laravel路由系统会收集所有参数并传递给被调用的函数。

$parameters = ["name" => "AL EMRAN" ];
$container->call("TestController@index", $parameters)

用法

$container->call('TestController@index');
$container->call('TestController@show', ['id' => 4]);

$container->call('TestController');
$container->call('TestController', ['id' => 4]);

或者

示例

class TestController 
{
     protected $company;
     public function __construct(Company $company)
        {
            $this->company =  $company ;
        }

    /**
     * @param  Request  $request 
     */
    public function index(Request $request){

    $company =  $company->get();

     return view('admin.company', compact('company'));        

    }

}
 

我们可以使用

   $instance = DependencyInjectionContainer::instance();
   $instance->namespace = "App\\Http\Controllers\\Admin\\"; // change namespace

   $class = $instance->make(CompanyController::class); // make class instance

   $instance->call(["CompanyController", 'index']); // call method

   $instance->call([$class, 'index']); // or--call method

安装包

composer require emrancu/dependency-injection-container

免责声明

我不是PHP专家,只是尝试学习。

Laravel依赖注入容器非常庞大,其路由系统也是如此。我只是创建了这个小型类来帮助理解。

这是我的第一篇文章,所以请留下您的宝贵意见或通过“emrancu1@gmail.com”给我发邮件

灵感来源于这里

AL EMRAN

创始人,Babshaye.com(离线和在线业务解决方案)在Facebook上关注