读 Slim 框架代码(1)

<p>一直都想找个PHP 的 web 框架的代码来读读,好学习一下 web 框架都是怎么写的。但是一般的 PHP 框架好像都比较傲复杂。之前在 Mysapce 实习的时候用过一套自己写的非常简单的 MVC 框架,只是很薄的包了一层,把 Model,View 和 Controller 分开,我自己平时写 PHP 应用也是用这种方法写的。凭着记忆,把当时用的那套东西重现了一下(<a href="https://github.com/ljie-PI/php-mini-framework">github</a>)。</p>

<p>前几天在 github 上发现<a href="https://github.com/codeguy/Slim">一个叫 Slim 的框架</a>,star 和 fork 数都不算少,也号称非常轻量级,打算看一看它的源代码。</p>

<h2>Hello World</h2>

<p>Slim Framework 的文档第一个例子依然是 <a href="http://docs.slimframework.com/#Hello-World">Hello World</a>。先结合这个例子来看一看 Slim 的实现方法。</p>

<h3>第一步:创建 Slim 对象</h3>

<p>第一行代码是:</p> ```php $app = new \Slim\Slim(); ``` <p>这一行代码对应的 Slim 代码如下:</p> <!--more--> ```php public function __construct(array $userSettings = array()) { // Setup IoC container $this->container = new \Slim\Helper\Set(); $this->container['settings'] = array_merge(static::getDefaultSettings(), $userSettings);

    // Default environment
    $this->container->singleton('environment', function ($c) {
        return \Slim\Environment::getInstance();
    });

    // Default request
    $this->container->singleton('request', function ($c) {
        return new \Slim\Http\Request($c['environment']);
    });

    // Default response
    $this->container->singleton('response', function ($c) {
        return new \Slim\Http\Response();
    });

    // Default router
    $this->container->singleton('router', function ($c) {
        return new \Slim\Router();
    });

    // Default view
    $this->container->singleton('view', function ($c) {
        $viewClass = $c['settings']['view'];

        return ($viewClass instanceOf \Slim\View) ? $viewClass : new $viewClass;
    });

    // Default log writer
    $this->container->singleton('logWriter', function ($c) {
        $logWriter = $c['settings']['log.writer'];

        return is_object($logWriter) ? $logWriter : new \Slim\LogWriter($c['environment']['slim.errors']);
    });

    // Default log
    $this->container->singleton('log', function ($c) {
        $log = new \Slim\Log($c['logWriter']);
        $log->setEnabled($c['settings']['log.enabled']);
        $log->setLevel($c['settings']['log.level']);
        $env = $c['environment'];
        $env['slim.log'] = $log;

        return $log;
    });

    // Default mode
    $this->container['mode'] = function ($c) {
        $mode = $c['settings']['mode'];

        if (isset($_ENV['SLIM_MODE'])) {
            $mode = $_ENV['SLIM_MODE'];
        } else {
            $envMode = getenv('SLIM_MODE');
            if ($envMode !== false) {
                $mode = $envMode;
            }
        }

        return $mode;
    };

    // Define default middleware stack
    $this->middleware = array($this);
    $this->add(new \Slim\Middleware\Flash());
    $this->add(new \Slim\Middleware\MethodOverride());

    // Make default if first instance
    if (is_null(static::getInstance())) {
        $this->setName('default');
    }
}

<ul>
<li>Environment 对象中包含 REQUEST_METHO,REMOTE_ADD,REQUEST_URI 等各种 $_SERVER 环境变量</li>
<li>Request 对象中包含 REQUEST_METHOD,QUERY_STRING,HTTP_COOKIE 及各种 HTTP 请求头内容</li>
<li>Response 对象包含响应状态码,header 及 body</li>
<li>Router 对象会解析请求的 URI 并关联到对应的响应函数</li>
<li>View 对象可以从 HTML 模板渲染页面</li>
<li>LogWriter 和 Log 主要用来写 log</li>
<li>Middle 数组是一串 middle,每个请求过来会一次执行 Middle 对象的 call 方法</li>
</ul>

<h3>第二步:指定 route 和 callback</h3>

<p>这一步对应的代码是:</p>

```php
    $app->get('/hello/:name', function ($name) {
        echo "Hello, $name";
    });

<p>此时会执行 Slim 对象的 mapRoute 方法,并关联请求方法:</p>

    public function get()
    {
        $args = func_get_args();

        return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_GET, \Slim\Http\Request::METHOD_HEAD);
    }

<p>在 mapRoute 方法中,会创建一个 Route 对象,将 “/hello/:name” 作为 Route 对象的 pattern,将第二个参数(一个函数)作为 Route 对象的 callable,并将这个 Route 对象加入到 Router 对象的 routes 数组中。</p> ```php protected function mapRoute($args) { $pattern = array_shift($args); $callable = array_pop($args); $route = new \Slim\Route($pattern, $callable); $this->router->map($route); if (count($args) > 0) { $route->setMiddleware($args); }

    return $route;
}

<p>Route 对象的 via 方法</p>
```php
    public function via()
    {
        $args = func_get_args();
        $this->methods = array_merge($this->methods, $args);

        return $this;
    }

<h3>第三步:执行</h3>

<p>最后可以执行代码生成 reponse:</p> ```php $app->run(); ```

<p>这行代码在 Slim.php 中对应:</p> ```php public function run() { set_error_handler(array('\Slim\Slim', 'handleErrors'));

    //Apply final outer middleware layers
    if ($this->config('debug')) {
        //Apply pretty exceptions only in debug to avoid accidental information leakage in production
        $this->add(new \Slim\Middleware\PrettyExceptions());
    }

    //Invoke middleware and application stack
    $this->middleware[0]->call();

    //Fetch status, header, and body
    list($status, $headers, $body) = $this->response->finalize();

    // Serialize cookies (with optional encryption)
    \Slim\Http\Util::serializeCookies($headers, $this->response->cookies, $this->settings);

    //Send headers
    if (headers_sent() === false) {
        //Send status
        if (strpos(PHP_SAPI, 'cgi') === 0) {
            header(sprintf('Status: %s', \Slim\Http\Response::getMessageForCode($status)));
        } else {
            header(sprintf('HTTP/%s %s', $this->config('http.version'), \Slim\Http\Response::getMessageForCode($status)));
        }

        //Send headers
        foreach ($headers as $name => $value) {
            $hValues = explode("\n", $value);
            foreach ($hValues as $hVal) {
                header("$name: $hVal", false);
            }
        }
    }

    //Send body, but only if it isn't a HEAD request
    if (!$this->request->isHead()) {
        echo $body;
    }

    restore_error_handler();
}

<p>由于 Slim 对象的最后一个 Middle 是本身,所以在执行完一堆 Middle 的 call 方法后会执行自己的 call 方法:</p>
```php
    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;
                }
            }
            if (!$dispatched) {
                $this->notFound();
            }
            $this->applyHook('slim.after.router');
            $this->stop();
        } catch (\Slim\Exception\Stop $e) {
            $this->response()->write(ob_get_clean());
            $this->applyHook('slim.after');
        } catch (\Exception $e) {
            if ($this->config('debug')) {
                throw $e;
            } else {
                try {
                    $this->error($e);
                } catch (\Slim\Exception\Stop $e) {
                    // Do nothing
                }
            }
        }
    }

<p>在 Router 的 getMatchedRoutes 方法中会针对请求的 URI,用正则表达式匹配是否有合适的 Route 对象,并且匹配出其中传进来的参数</p> ```php public function getMatchedRoutes($httpMethod, $resourceUri, $reload = false) { if ($reload || is_null($this->matchedRoutes)) { $this->matchedRoutes = array(); foreach ($this->routes as $route) { if (!$route->supportsHttpMethod($httpMethod) && !$route->supportsHttpMethod("ANY")) { continue; }

            if ($route->matches($resourceUri)) {
                $this->matchedRoutes[] = $route;
            }
        }
    }

    return $this->matchedRoutes;
}
```php
    public function matches($resourceUri)
    {
        //Convert URL params into regex patterns, construct a regex for this route, init params
        $patternAsRegex = preg_replace_callback(
            '#:([\w]+)\+?#',
            array($this, 'matchesCallback'),
            str_replace(')', ')?', (string) $this->pattern)
        );
        if (substr($this->pattern, -1) === '/') {
            $patternAsRegex .= '?';
        }

        //Cache URL params' names and values if this route matches the current HTTP request
        if (!preg_match('#^' . $patternAsRegex . '$#', $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;
    }

<p>最后执行 Route 对象的 dispatch 方法,就是将匹配出来的参数传到 callable 然后执行。</p>

    public function dispatch()
    {
        foreach ($this->middleware as $mw) {
            call_user_func_array($mw, array($this));
        }

        $return = call_user_func_array($this->getCallable(), array_values($this->getParams()));
        return ($return === false)? false : true;
    }

转载于:https://my.oschina.net/u/1412485/blog/182562

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值