上节,我们迈出了前进的一小步—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容器探险之旅~~