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容器探险之旅~~







你可能感兴趣的:(slim)