Slim框架学习——简介中,简单介绍了如何用Slim构造一个项目。看起来Slim好像没有提供过多的功能,非常的简单。但是麻雀虽小五脏俱全,Slim中对于中间件、路由以及依赖注入都做了很好的实现,非常值得学习。

基本使用

我们先回顾下Slim的基本使用:

<?php
// 配置
$config['db']['host']   = 'localhost';

// 初始化slim app
$app = new \Slim\App(['settings' => $config]);

// 依赖注入
$container = $app->getContainer();
$container['logger'] = function ($c) {
    $logger = new Logger;
    return $logger;
};

// 中间件
$app->add(function ($request, $response, $next) {
    $response->getBody()->write(' BEFORE ');
    $response = $next($request, $response);
    $response->getBody()->write(' AFTER ');
    return $response;
});

// 定义路由
$app->get('/', function ($request, $response, $args) {
    $response->getBody()->write(' Hello ');
    return $response;
});

// 启动app
$app->run();

可以看到,简单的十几行代码,就做到了这么多事,很了不起吧。我们先从初始化开始分析。

初始化Slim app

初始化app的代码:

<?php
$app = new \Slim\App($config);

我们再来看下构造函数:

<?php
// App构造函数
public function __construct($container = [])
{
    if (is_array($container)) {
        $container = new Container($container);
    }
    if (!$container instanceof ContainerInterface) {
        throw new InvalidArgumentException('Expected a ContainerInterface');
    }
    $this->container = $container;
}

Slim\App的构造函数就做了一件事,就是初始化App的依赖容器container。接受的参数是一个数组或者是实现了ContainerInterface接口的对象。在$container = new Container($container);这行,初始化的Container的也做了一些事情:

<?php
// Container构造函数
public function __construct(array $values = [])
{
    parent::__construct($values);

    $userSettings = isset($values['settings']) ? $values['settings'] : [];
    $this->registerDefaultServices($userSettings);
}

Container初始化,调用父类初始化把接收的参数都放入容器中,然后获取我们初始化传入的settings,通过settings中的一些配置来注册服务,即把一些默认的依赖服务放入容器中。如果你继续的深入代码,就会发现这些默认的服务包括Request、Response、路由、errorHandler等基础服务。但是你并不一定要依赖于这些默认的服务,只要你实现了Slim\Interfaces中的借口,一样可以替换掉默认的服务,这给了我们非常大的自由度。

依赖服务添加到容器中

我们通过$app->getContainer()来获取容器,slim里面getContainer的实现如下:

<?php
public function getContainer()
{
    return $this->container;
}

很简单吧!就是把初始化的那个容器取出来。因为Container是继承于Pimple\Container,而Pimple\Container又实现了ArrayAccess接口,所以可以直接把container当做一个数组来使用,添加依赖服务也可以直接用[]符号来操作。

中间件

Slim的中间件分为应用中间件和路由中间件。应用中间件直接用$app->add()来添加,而路由中间件在定义路由后添加。关于这些区别和如何使用,可以参考Middleware。我们还是直接来分析源码吧。

Slim里中间件的添加和调用是通过Slim\MiddlewareAwareTrait来实现的。里面的addMiddlewarecallMiddlewareStack两个方法是核心。 添加中间件与addMiddleware有关,其中包含了代码:

<?php
$next = $this->stack->top();
$this->stack[] = function (ServerRequestInterface $req, ResponseInterface $res) use ($callable, $next) {
    $result = call_user_func($callable, $req, $res, $next);
    if ($result instanceof ResponseInterface === false) {
        throw new UnexpectedValueException(
            'Middleware must return instance of \Psr\Http\Message\ResponseInterface'
        );
    }
    return $result;
};

可以看到,中间件是用一个栈来维护的。每添加一个中间件,就会在栈顶中间件闭包的基础上再包裹一个中间件闭包,然后继续加入到栈顶。这也就解释了Slim里面的中间件的调用顺序,就是后添加的中间件是先调用的。

Slim\MiddlewareAwareTrait还有一个方法seedMiddlewareStack,这个方法是定义第一个中间件并加入栈中的。这个方法里面有如下代码:

<?php
if ($kernel === null) {
    $kernel = $this;
}
$this->stack = new SplStack;
$this->stack->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);
$this->stack[] = $kernel;

所以,这个栈使用SplStack实现的,然后第一个中间件就是$this,因为Slim\MiddlewareAwareTraitSlim\App使用的,所以也就是我们定义的$app,即第一个闭包就是Slim\App__invoke方法。在这个__invoke方法里,包括了路由查询,以及调用路由的run,路由中的run又会调用路由的中间件,所以也就确定了路由的中间件是在应用中间件调用完之后再调用的。具体感兴趣的可以查看下源代码。

路由

Slim的路由使用的是nikic/fast-route,然后做了Route和Router应该实现的接口以及对FastRoute的封装。不管是get、post还是其他的http方法,Slim\App通过自己的map方法,来调用Slim\Router的map方法添加对应的Slim\Route。每个Slim\Route对应这一个路由配置。同样的,每个Slim\Route也有__invoke方法,来作为路由最后一个中间件。

运行app

Slim\App中最主要的run,看起来也很简单:

<?php
public function run($silent = false)
{
    $request = $this->container->get('request');
    $response = $this->container->get('response');

    $response = $this->process($request, $response);

    if (!$silent) {
        $this->respond($response);
    }

    return $response;
}

主要逻辑都在process里面了,在process里面最主要的一行代码就是:

$response = $this->callMiddlewareStack($request, $response);

也就是一个$request请求进来,通过一个callMiddlewareStack,就得到了我们的$response。这里调用了Slim\MiddlewareAwareTraitcallMiddlewareStack

<?php
public function callMiddlewareStack(ServerRequestInterface $req, ResponseInterface $res)
{
    if (is_null($this->stack)) {
        $this->seedMiddlewareStack();
    }
    /** @var callable $start */
    $start = $this->stack->top();
    $this->middlewareLock = true;
    $resp = $start($req, $res);
    $this->middlewareLock = false;
    return $resp;
}

也很简单,从中间件栈顶取出闭包,调用就是了。具体逻辑就不用代码分析了,可以用一张图来表示: Slim中间件流程

总结

这就是Slim的大概的相应请求的流程了,也不是特别的复杂,但是能用这种方式实现,也是十分厉害了。代码结构清晰、规范,非常适合学习,建议大家多看看里面的具体的实现,毕竟我这里总结的也不是很全面,也有可能有些错误。