为什么我们需要 Laravel IoC 容器?

作者: summerbluet | 来源:发表于2019-04-15 11:06 被阅读12次
    image

    IOC 容器是一个实现依赖注入的便利机制 - Taylor Otwell

    文章转自:https://learnku.com/laravel/t/26922

    Laravel 是当今最流行、最常使用的开源现代 web 应用框架之一。它提供了一些独特的特性,比如 Eloquent ORM, Query 构造器,Homestead 等时髦的特性,这些特性只有 Laravel 中才有。

    我喜欢 Laravel 是由于它犹如建筑风格一样的独特设计。Laravel 的底层使用了多设计模式,比如单例、工厂、建造者、门面、策略、提供者、代理等模式。随着本人知识的增长,我越来越发现 Laravel 的美。Laravel 为开发者减少了苦恼,带来了更多的便利。

    学习 Laravel,不仅仅是学习如何使用不同的类,还要学习 Laravel 的哲学,学习它优雅的语法。Laravel 哲学的一个重要组成部分就是 IoC 容器,也可以称为服务容器。它是一个 Laravel 应用的核心部分,因此理解并使用 IoC 容器是我们必须掌握的一项重要技能。

    IoC 容器是一个非常强大的类管理工具。它可以自动解析类。接下来我会试着去说清楚它为什么如此重要,以及它的工作原理。

    首先,我想先谈下依赖反转原则,对它的了解会有助于我们更好地理解 IoC 容器的重要性。

    该原则规定:

    高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。

    抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

    一言以蔽之: 依赖于抽象而非具体

    class MySQLConnection
    {
    
       /**
       * 数据库连接
       */
       public function connect()
       {
          var_dump(‘MYSQL Connection’);
       }
    
    }
    
    
    class PasswordReminder
    {    
        /**
         * @var MySQLConnection
         */
         private $dbConnection;
    
        public function __construct(MySQLConnection $dbConnection) 
        {
          $this->dbConnection = $dbConnection;
        }
    }
    
    

    大家常常会有一个误解,那就是依赖反转就只是依赖注入的另一种说法。但其实二者是不同的。在上面的代码示例中,尽管在 PasswordReminder 类中注入了 MySQLConnection 类,但它还是依赖于 MySQLConnection 类。

    然而,高层次模块 PasswordReminder 是不应该依赖于低层次模块 MySQLConnection 的。

    如果我们想要把 MySQLConnection 改成 MongoDBConnection,那我们就还得手动修改 PasswordReminder 类构造函数里的依赖。

    PasswordReminder 类应该依赖于抽象接口,而非具体类。那我们要怎么做呢?请看下面的例子:

    interface ConnectionInterface
    {
       public function connect();
    }
    
    class DbConnection implements ConnectionInterface
    {
     /**
      * 数据库连接
      */
     public function connect()
     {
       var_dump(‘MYSQL Connection’);
     }
    }
    
    class PasswordReminder
    {
        /**
        * @var DBConnection
        */
        private $dbConnection;
        public function __construct(ConnectionInterface $dbConnection)
        {
          $this->dbConnection = $dbConnection;
        }
    }
    

    通过上面的代码,如果我们想把 MySQLConnection 改成 MongoDBConnection,根本不需要去修改 PasswordReminder 类构造函数里的依赖。因为现在 PasswordReminder 类依赖的是接口,而非具体类。

    如果你对接口的概念还不是很了解,可以看下 这篇文章 。它会帮助你理解依赖反转原则和 IoC 容器等。

    现在我要讲下 IoC 容器里到底发生了什么。我们可以把 IoC 容器简单地理解为就是一个容器,里面装的是类的依赖。

    OrderRepositoryInterface 接口:

    namespace App\Repositories;
    
    interface OrderRepositoryInterface 
    {
       public function getAll();
    }
    

    DbOrderRepository 类:

    namespace App\Repositories;
    
    class DbOrderRepository implements OrderRepositoryInterface
    {
    
      function getAll()
      {
        return 'Getting all from mysql';
      }
    }
    

    OrdersController 类:

    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    use App\Http\Requests;
    use App\Repositories\OrderRepositoryInterface;
    
    class OrdersController extends Controller
    {
        protected $order;
    
       function __construct(OrderRepositoryInterface $order)
       {
         $this->order = $order;
       }
    
       public function index()
       {
         dd($this->order->getAll());
         return View::make(orders.index);
       }
    }
    

    路由:

    Route::resource('orders', 'OrdersController');
    

    现在,在浏览器中输入这个地址 <http://localhost:8000/orders>

    报错了吧,错误的原因是服务容器正在尝试去实例化一个接口,而接口是不能被实例化的。解决这个问题,只需把接口绑定到一个具体的类上:

    image

    把下面这行代码加在路由文件里就搞定了:

    App::bind('App\Repositories\OrderRepositoryInterface', 'App\Repositories\DbOrderRepository');
    

    现在刷新浏览器看看:

    image

    我们可以这样定义一个容器类:

    class SimpleContainer
     {
        protected static $container = [];
    
        public static function bind($name, Callable $resolver)
        {   
            static::$container[$name] = $resolver;
        }
    
        public static function make($name)
        {
          if(isset(static::$container[$name])){
            $resolver = static::$container[$name] ;
            return $resolver();
        }
    
        throw new Exception("Binding does not exist in containeer");
    
       }
    }
    

    这里,我想告诉你服务容器解析依赖是多么简单的事。

    class LogToDatabase 
    {
        public function execute($message)
        {
           var_dump('log the message to a database :'.$message);
        }
    }
    
    class UsersController {
    
        protected $logger;
    
        public function __construct(LogToDatabase $logger)
        {
            $this->logger = $logger;
        }
    
        public function show()
        {
          $user = 'JohnDoe';
          $this->logger->execute($user);
        }
    }
    

    绑定依赖:

    SimpleContainer::bind('Foo', function()
     {
       return new UsersController(new LogToDatabase);
     });
    
    $foo = SimpleContainer::make('Foo');
    
    print_r($foo->show());
    

    输出:

    string(36) "Log the messages to a file : JohnDoe"
    

    Laravel 的服务容器源码:

      public function bind($abstract, $concrete = null, $shared = false)
        {
            $abstract = $this->normalize($abstract);
    
            $concrete = $this->normalize($concrete);
    
            if (is_array($abstract)) {
               list($abstract, $alias) = $this->extractAlias($abstract);
    
               $this->alias($abstract, $alias);
            }
    
            $this->dropStaleInstances($abstract);
    
            if (is_null($concrete)) {
                $concrete = $abstract;
            }
    
    
            if (! $concrete instanceof Closure) {
                $concrete = $this->getClosure($abstract, $concrete);
            }
    
            $this->bindings[$abstract] = compact('concrete', 'shared');
    
    
            if ($this->resolved($abstract)) {
                $this->rebound($abstract);
            }
        }
    
        public function make($abstract, array $parameters = [])
        {
            $abstract = $this->getAlias($this->normalize($abstract));
    
            if (isset($this->instances[$abstract])) {
                return $this->instances[$abstract];
            }
    
            $concrete = $this->getConcrete($abstract);
    
            if ($this->isBuildable($concrete, $abstract)) {
                $object = $this->build($concrete, $parameters);
            } else {
                $object = $this->make($concrete, $parameters);
            }
    
    
            foreach ($this->getExtenders($abstract) as $extender) {
                $object = $extender($object, $this);
            }
    
            if ($this->isShared($abstract)) {
                $this->instances[$abstract] = $object;
            }
    
           $this->fireResolvingCallbacks($abstract, $object);
    
           $this->resolved[$abstract] = true;
    
           return $object;
        }
    
        public function build($concrete, array $parameters = [])
        {
    
            if ($concrete instanceof Closure) {
                return $concrete($this, $parameters);
            }
    
           $reflector = new ReflectionClass($concrete);
    
            if (! $reflector->isInstantiable()) {
                if (! empty($this->buildStack)) {
                    $previous = implode(', ', $this->buildStack);
                    $message = "Target [$concrete] is not instantiable while building [$previous].";
                } else {
                    $message = "Target [$concrete] is not instantiable.";
                }
    
              throw new BindingResolutionException($message);
            }
    
             $this->buildStack[] = $concrete;
    
             $constructor = $reflector->getConstructor();
    
            if (is_null($constructor)) {
                array_pop($this->buildStack);
    
               return new $concrete;
            }
    
            $dependencies = $constructor->getParameters();
            $parameters = $this->keyParametersByArgument(
                $dependencies, $parameters
            );
    
             $instances = $this->getDependencies($dependencies,$parameters);
    
             array_pop($this->buildStack);
    
             return $reflector->newInstanceArgs($instances);
        }
    

    如果你想了解关于服务容器的更多内容,可以看下 vendor/laravel/framwork/src/Illuminate/Container/Container.php

    简单的绑定

    $this->app->bind('HelpSpot\API', function ($app) {
        return new HelpSpot\API($app->make('HttpClient'));
    });
    

    单例模式绑定

    通过 singleton 方法绑定到服务容器的类或接口,只会被解析一次。

    $this->app->singleton('HelpSpot\API', function ($app) {
        return new HelpSpot\API($app->make('HttpClient'));
    });
    

    绑定实例

    也可以通过 instance 方法把具体的实例绑定到服务容器中。之后,就会一直返回这个绑定的实例:

    $api = new HelpSpot\API(new HttpClient);
    
    $this->app->instance('HelpSpot\API', $api);
    

    如果没有绑定,PHP 会利用反射机制来解析实例和依赖。

    如果想了解更多细节,可以查看 官方文档

    关于 Laravel 服务容器的练习代码, 可以从我的 *GitHub *(如果喜欢,烦请不吝 star )仓库获取。

    感谢阅读。

    文章转自:https://learnku.com/laravel/t/26922

    更多文章:https://learnku.com/laravel/c/translations

    相关文章

      网友评论

        本文标题:为什么我们需要 Laravel IoC 容器?

        本文链接:https://www.haomeiwen.com/subject/jsaywqtx.html