爲何咱們須要 Laravel IoC 容器?

IOC 容器是一個實現依賴注入的便利機制 - Taylor Otwell

文章轉自:https://learnku.com/laravel/t...php

Laravel 是當今最流行、最常使用的開源現代 web 應用框架之一。它提供了一些獨特的特性,好比 Eloquent ORM, Query 構造器,Homestead 等時髦的特性,這些特性只有 Laravel 中才有。mysql

我喜歡 Laravel 是因爲它猶如建築風格同樣的獨特設計。Laravel 的底層使用了多設計模式,好比單例、工廠、建造者、門面、策略、提供者、代理等模式。隨着本人知識的增加,我愈來愈發現 Laravel 的美。Laravel 爲開發者減小了苦惱,帶來了更多的便利。laravel

學習 Laravel,不單單是學習如何使用不一樣的類,還要學習 Laravel 的哲學,學習它優雅的語法。Laravel 哲學的一個重要組成部分就是 IoC 容器,也能夠稱爲服務容器。它是一個 Laravel 應用的核心部分,所以理解並使用 IoC 容器是咱們必須掌握的一項重要技能。git

IoC 容器是一個很是強大的類管理工具。它能夠自動解析類。接下來我會試着去說清楚它爲何如此重要,以及它的工做原理。github

首先,我想先談下依賴反轉原則,對它的瞭解會有助於咱們更好地理解 IoC 容器的重要性。web

該原則規定:sql

高層次的模塊不該該依賴於低層次的模塊,二者都應該依賴於抽象接口。

抽象接口不該該依賴於具體實現。而具體實現則應該依賴於抽象接口。數據庫

一言以蔽之: 依賴於抽象而非具體設計模式

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 類。api

然而,高層次模塊 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>

報錯了吧,錯誤的緣由是服務容器正在嘗試去實例化一個接口,而接口是不能被實例化的。解決這個問題,只需把接口綁定到一個具體的類上:

把下面這行代碼加在路由文件裏就搞定了:

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

如今刷新瀏覽器看看:

咱們能夠這樣定義一個容器類:

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...

更多文章: https://learnku.com/laravel/c...
相關文章
相關標籤/搜索