Slim研讀筆記五之依賴注入容器(上)

上節,我們邁出了前進的一小步—Composer研讀,瞭解了Composer組件加載機制。從這節開始,我們學習Slim幾大核心模塊—依賴注入容器、路由、中間件等。

依賴注入容器可以注入一些服務,主要用於解決組件之間的依賴關係。Slim支持Container-Interop接口實現的容器,我們可以使用Slim內置容器Pimple或其他第三方容器,例如PHP-DI。這裏爲了方便研讀,我們使用Slim內置容器—Pimple
若你使用了其他依賴注入容器,你需要將容器實例注入到Slim程序主體的構造方法中。
// 聲明一個應用主體對象並加載配置文件
$container = new \Slim\Container
$app = new \Slim\App($container);
你可顯示或隱式地依賴容器中獲取服務。
如下示例是從Slim應用程序中獲取一個顯示實例:
/**
* Example GET route
*
* @param  \Psr\Http\Message\ServerRequestInterface $req  PSR7 request
* @param  \Psr\Http\Message\ResponseInterface      $res  PSR7 response
* @param  array                                    $args Route parameters
*
* @return \Psr\Http\Message\ResponseInterface
*/
$app->get('/foo', function ($req, $res, $args) {
    $myService = $this->get('myService');

    return $res;
});

你可以這樣隱式地從容器中取得服務:
/**
* Example GET route
*
* @param  \Psr\Http\Message\ServerRequestInterface $req  PSR7 request
* @param  \Psr\Http\Message\ResponseInterface      $res  PSR7 response
* @param  array                                    $args Route parameters
*
* @return \Psr\Http\Message\ResponseInterface
*/
$app->get('/foo', function ($req, $res, $args) {
    $myService = $this->myService;

    return $res;
});
上面是官網示例,若未看過源碼的人,可能會產生兩個疑惑點:
1.爲何\Slim\App傳入一個容器實例到構造函數纔可以使用容器?
2.容器的服務就是應用主體實例$app的屬性?這些是否與$app類的魔術方法__get()有關? 
點開\Slim\App查看其構造方法 
   /**
     * 創建一個應用主體(PS:應用主體是博主從Yii2框架應用主體概念得到,應用主體可理解爲貫穿應用程序執行生命週期最重要的那個類)
     * Create new application
     *
     * @param ContainerInterface|array $container Either a ContainerInterface or an associative array of app settings
     * @throws InvalidArgumentException when no container is provided that implements ContainerInterface
     */
    public function __construct($container = [])
    {
        // 傳入$container是一個數組,說明傳入了一些配置且使用了Slim默認的Pimple依賴注入容器
        if (is_array($container)) {
            $container = new Container($container);
        }

        // 容器不是實現自ContainerInterface接口,拋出異常
        if (!$container instanceof ContainerInterface) {
            throw new InvalidArgumentException('Expected a ContainerInterface');
        }

        // 依賴注入容器實例賦值爲應用主體$app的container屬性
        $this->container = $container;
    }

這裏$container參數若不是實現了ContainerInterface接口的類,會拋出異常。既然看到這裏,我們就簡單提一下這個ContainerInterface接口,該接口繼承了PsrContainerInterface接口,PsrContainerInterface是採用PSR-11規範的容器接口。

PSR-11 容器接口

PSR-11是依賴注入容器的通用接口,旨在規範框架或庫通過容器獲取對象與參數。

我們根據new Container($container)繼續深挖代碼...
/**
 * Slim默認的依賴注入容器是Pimple
 * Slim's default DI container is Pimple.
 * Slim\App要求容器必須實現Psr\Container\Containerface接口
 * 且若你使用第三方容器,如下這些服務也必須實現
 * Slim\App expects a container that implements Psr\Container\ContainerInterface
 * with these service keys configured and ready for use:
 *
 *  - settings: an array or instance of \ArrayAccess
 *  - environment: an instance of \Slim\Interfaces\Http\EnvironmentInterface
 *  - request: an instance of \Psr\Http\Message\ServerRequestInterface
 *  - response: an instance of \Psr\Http\Message\ResponseInterface
 *  - router: an instance of \Slim\Interfaces\RouterInterface
 *  - foundHandler: an instance of \Slim\Interfaces\InvocationStrategyInterface
 *  - errorHandler: a callable with the signature: function($request, $response, $exception)
 *  - notFoundHandler: a callable with the signature: function($request, $response)
 *  - notAllowedHandler: a callable with the signature: function($request, $response, $allowedHttpMethods)
 *  - callableResolver: an instance of \Slim\Interfaces\CallableResolverInterface
 *
 * @property-read array settings
 * @property-read \Slim\Interfaces\Http\EnvironmentInterface environment
 * @property-read \Psr\Http\Message\ServerRequestInterface request
 * @property-read \Psr\Http\Message\ResponseInterface response
 * @property-read \Slim\Interfaces\RouterInterface router
 * @property-read \Slim\Interfaces\InvocationStrategyInterface foundHandler
 * @property-read callable errorHandler
 * @property-read callable notFoundHandler
 * @property-read callable notAllowedHandler
 * @property-read \Slim\Interfaces\CallableResolverInterface callableResolver
 */
class Container extends PimpleContainer implements ContainerInterface
暫時可知,Container類繼承Pimple容器,且實現了ContainerInterface,由
interface ContainerInterface extends PsrContainerInterface可知,間接實現了PsrContainerInterface接口。

Container類是Pimple依賴注入容器的核心類,我們會詳細講解。首先,我們從構造方法__construct()說起。
    /**
     * 創建新容器
     * Create new container
     *
     * @param array $values The parameters or objects.
     */
    public function __construct(array $values = [])
    {
        // 繼承父類的構造方法
        parent::__construct($values);

        // 獲取配置文件中settings配置項的值
        $userSettings = isset($values['settings']) ? $values['settings'] : [];

        // 註冊Slim需要的一些默認服務
        $this->registerDefaultServices($userSettings);
    }
我們瞭解下Container父類PimpleContainer的構造方法。 
    /**
     * 實例化容器
     * Instantiates the container.
     *
     * Objects and parameters can be passed as argument to the constructor.
     *
     * @param array $values The parameters or objects
     */
    public function __construct(array $values = array())
    {
        // 實例化factories,protected屬性,且賦值爲\SplObjectStorage()實例化對象
        // SplObjectStorage提供了從對象到數據的映射,或者忽略數據提供對象集
        $this->factories = new \SplObjectStorage();
        $this->protected = new \SplObjectStorage();

        // 將傳入的配置項整合到容器對象
        foreach ($values as $key => $value) {
            $this->offsetSet($key, $value);
        }
    }
瞭解下offsetSet函數
    /**
     * 設置參數或閉包對象
     * Sets a parameter or an object.
     *
     * Objects must be defined as Closures.
     *
     * Allowing any PHP callable leads to difficult to debug problems
     * as function names (strings) are callable (creating a function with
     * the same name as an existing parameter would break your container).
     *
     * @param string $id    The unique identifier for the parameter or object
     * @param mixed  $value The value of the parameter or a closure to define an object
     *
     * @throws FrozenServiceException Prevent override of a frozen service
     */
    public function offsetSet($id, $value)
    {
        if (isset($this->frozen[$id])) {
            throw new FrozenServiceException($id);
        }
        // 將賦值給values數組,並用keys數組記錄
        $this->values[$id] = $value;
        $this->keys[$id] = true;
    }

今天是柚子第一天健身,略有疲乏,且天色已晚,就到此爲止吧。明天會繼續我們的DI容器探險之旅~~







發佈了47 篇原創文章 · 獲贊 5 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章