对Slim 框架进行总结 一

导读:今天我们来看看中间件的相关功能

中间件

Slim中的中间件分两种:应用级中间件、路由级中间件。

应用中间件基于Rack协议实现,可以在应用对象调用之前或之后检查、分析、或修改应用环境变量、请求对象、响应对象。

每个中间件类都继承自抽象类Middleware,且需要实现其抽象方法call。所有注册的中间件组成一个中间件栈,其结构类似于一个洋葱,先注册的中间件在里层,后注册的在外层,最里层的是应用对象自身,请求从外到里逐层进行处理,任何一层都可以根据条件直接响应请求或递归调用往里一层/下一个中间件。

以中间件SessionCookie与MethodOverride为例,其call方法实现如下所示:

// 中间件SessionCookie
public function call()
{
    // 加载session数据
    $this->loadSession();
    // 调用下一个中间件
    $this->next->call();
    // 保存session数据
    $this->saveSession();
}

// 中间件MethodOverride
public function call()
{
    $env = $this->app->environment();
    if (isset($env['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
        // Header commonly used by Backbone.js and others
        $env['slim.method_override.original_method'] = $env['REQUEST_METHOD'];
        $env['REQUEST_METHOD'] = strtoupper($env['HTTP_X_HTTP_METHOD_OVERRIDE']);
    } elseif (isset($env['REQUEST_METHOD']) && $env['REQUEST_METHOD'] === 'POST') {
        // HTML Form Override
        $req = new \Slim\Http\Request($env);
        // $this->settings['key'] 默认为_METHOD
        $method = $req->post($this->settings['key']);
        if ($method) {
            $env['slim.method_override.original_method'] = $env['REQUEST_METHOD'];
            $env['REQUEST_METHOD'] = strtoupper($method);
        }
    }
    // 调用下一个中间件
    $this->next->call();
}

路由级中间件可以是任何可被调用的东西(is_callable返回true)。在相关路由的回调触发之前,会逐个调用绑定到这个路由的所有路由级中间件,代码实现如下所示:

// 路由类Route的dispatch方法:
/**
 * Dispatch route
 *
 * This method invokes the route object's callable. If middleware is
 * registered for the route, each callable middleware is invoked in
 * the order specified.
 *
 * @return bool
 */
public function dispatch()
{
    // 逐个调用绑定的路由级中间件,以当前路由对象作为参数传入
    foreach ($this->middleware as $mw) {
        call_user_func_array($mw, array($this));
    }

    // 调用路由回调,并将通过路由模式从URL中正则匹配到的参数传入回调
    $return = call_user_func_array($this->getCallable(), array_values($this->getParams()));
    return ($return === false) ? false : true;
}

路由匹配

先来看看Slim应用对象的call方法:

/**
 * Call
 *
 * This method finds and iterates all route objects that match the current request URI.
 */
public function call()
{
    try {
        if (isset($this->environment['slim.flash'])) {
            $this->view()->setData('flash', $this->environment['slim.flash']);
        }
        $this->applyHook('slim.before');
        ob_start();
        $this->applyHook('slim.before.router');
        $dispatched = false;
        // 路由匹配
        $matchedRoutes = $this->router->getMatchedRoutes($this->request->getMethod(), $this->request->getResourceUri());
        // 逐个路由分发执行
        foreach ($matchedRoutes as $route) {
            try {
                $this->applyHook('slim.before.dispatch');
                $dispatched = $route->dispatch();
                $this->applyHook('slim.after.dispatch');
                if ($dispatched) {
                    break;
                }
            } catch (\Slim\Exception\Pass $e) {
                continue;
            }
        }
        // 当路由的回调抛出非Pass异常时,则会响应404
        // 这貌似不好吧?
        if (!$dispatched) {
            $this->notFound();
        }
        $this->applyHook('slim.after.router');
        $this->stop();
    } catch (\Slim\Exception\Stop $e) {
        $this->response()->write(ob_get_clean());
    } catch (\Exception $e) {
        if ($this->config('debug')) {
            throw $e;
        } else {
            try {
                $this->response()->write(ob_get_clean());
                $this->error($e);
            } catch (\Slim\Exception\Stop $e) {
                // Do nothing
            }
        }
    }
}

其中用于路由匹配的Router类的getMatchedRoutes方法实现如下所示:

/**
 * Return route objects that match the given HTTP method and URI
 * @param  string               $httpMethod   The HTTP method to match against
 * @param  string               $resourceUri  The resource URI to match against
 * @param  bool                 $reload       Should matching routes be re-parsed?
 * @return array[\Slim\Route]
 */
public function getMatchedRoutes($httpMethod, $resourceUri, $reload = false)
{
    if ($reload || is_null($this->matchedRoutes)) {
        $this->matchedRoutes = array();
        foreach ($this->routes as $route) {
            // 如果当前请求的HTTP方法不被当前route支持且不是ANY,则跳过
            if (!$route->supportsHttpMethod($httpMethod) && !$route->supportsHttpMethod("ANY")) {
                continue;
            }

            // 否则继续匹配环境变量PATH_INFO
            if ($route->matches($resourceUri)) {
                $this->matchedRoutes[] = $route;
            }
        }
    }

    return $this->matchedRoutes;
}

其中用于PATH_INFO匹配的Route类的matches方法实现如下所示:

/**
 * Matches URI?
 *
 * Parse this route's pattern, and then compare it to an HTTP resource URI
 * This method was modeled after the techniques demonstrated by Dan Sosedoff at:
 *
 * http://blog.sosedoff.com/2009/09/20/rails-like-php-url-router/
 *
 * @param  string $resourceUri A Request URI
 * @return bool
 */
public function matches($resourceUri)
{
    //Convert URL params into regex patterns, construct a regex for this route, init params
    // preg_replace_callback — 执行一个正则表达式搜索并且使用一个回调进行替换
    $patternAsRegex = preg_replace_callback(
        '#:([\w]+)\+?#',
        array($this, 'matchesCallback'),
        // 括号中的部分表示可选
        // 如:/archive(/:year(/:month(/:day)))
        str_replace(')', ')?', (string)$this->pattern)
    );
    // 即使pattern最后有斜杠/,对于URL来说也是可选的
    if (substr($this->pattern, -1) === '/') {
        $patternAsRegex .= '?';
    }

    $regex = '#^' . $patternAsRegex . '$#';

    // 大小写不敏感
    if ($this->caseSensitive === false) {
        $regex .= 'i';
    }

    //Cache URL params' names and values if this route matches the current HTTP request
    // 正则匹配
    if (!preg_match($regex, $resourceUri, $paramValues)) {
        return false;
    }
    foreach ($this->paramNames as $name) {
        if (isset($paramValues[$name])) {
            if (isset($this->paramNamesPath[$name])) {
                $this->params[$name] = explode('/', urldecode($paramValues[$name]));
            } else {
                $this->params[$name] = urldecode($paramValues[$name]);
            }
        }
    }

    return true;
}

其中正则搜索替换的回调方法matchesCallback的实现如下所示:

/**
 * Convert a URL parameter (e.g. ":id", ":id+") into a regular expression
 * @param  array $m URL parameters
 * @return string       Regular expression for URL parameter
 */
protected function matchesCallback($m)
{
    $this->paramNames[] = $m[1];
    if (isset($this->conditions[$m[1]])) {
        return '(?P<' . $m[1] . '>' . $this->conditions[$m[1]] . ')';
    }
    if (substr($m[0], -1) === '+') {
        $this->paramNamesPath[$m[1]] = 1;

        return '(?P<' . $m[1] . '>.+)';
    }

    return '(?P<' . $m[1] . '>[^/]+)';
}

你可能感兴趣的:(Slim,Slim)