使用 Symfony 的组件创建自己的 PHP 框架(第六部分:控制器分析器)

使用 Symfony 的组件创建自己的 PHP 框架(第六部分:控制器分析器)

Chris Yue No Comment
Posts

英文原文地址:https://symfony.com/doc/current/create_framework/http_kernel_controller_resolver.html

可能你觉得我们的框架已经非常的稳定了,但其实我们仍然可以继续改进它。

目前,我们所有的例子,都是以面向过程的方式实现的,但是别忘了,我们的控制器可以是任何一种合法的 PHP 回调函数。让我们把控制器改写成一个类:

class LeapYearController
{
    public function indexAction($request)
    {
        if (is_leap_year($request->attributes->get('year'))) {
            return new Response('Yep, this is a leap year!');
        }

        return new Response('Nope, this is not a leap year.');
    }
}

再修改相应的路由配置:

$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
    'year' => null,
    '_controller' => array(new LeapYearController(), 'indexAction'),
)));

当你使用这种方法创建更多的新页面的时候,你会觉得更加的合理和直观,但是这种方式也有一个副作用:LeapYearController对象总是会被创建,无论当前的 url 是不是跟leap_year相匹配。这样做非常糟糕,因为性能受到很大影响。无论什么样的请求发送到服务器,所有的控制器类都会初始化一个对象。如果控制器会按照指定的路由匹配规则“迟载入(lazy-loaded)”,性能将会好很多。

要解决这个问题,我们再安装一个“Http 核心(HttpKernel)”组件:

{
    "require": {
        "symfony/class-loader": "2.1.*",
        "symfony/http-foundation": "2.1.*",
        "symfony/routing": "2.1.*",
        "symfony/http-kernel": "2.1.*"
    }
}

HttpKernel 组件有许多有意思的功能,但目前我们需要的是“控制器分析器(controller resolver)”。根据传过来的请求对象,controller resolver 知道那一个控制器将要被执行,以及将要传给它什么参数。所有的 controller resolver 需要实现以下接口:

namespace Symfony\Component\HttpKernel\Controller;

interface ControllerResolverInterface
{
    function getController(Request $request);

    function getArguments(Request $request, $controller);
}

getController() 方法实现依赖跟之前同样的方式:_controller 属性必然是跟某个请求对应关联的。除了 PHP 内置的回调函数方式,getController() 也支持像 "class::method" 这样的字符串作为合法的回调函数。

$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
    'year' => null,
    '_controller' => 'LeapYearController::indexAction',
)));

要让这段代码生效,我们要让框架代码使用HttpKernel组件的控制器分析器:

use Symfony\Component\HttpKernel;

$resolver = new HttpKernel\Controller\ControllerResolver();

$controller = $resolver->getController($request);
$arguments = $resolver->getArguments($request, $controller);

$response = call_user_func_array($controller, $arguments);

Controller resolver还有一个好处是,它将会为你合理的处理错误,比如说,如果你忘记了给路由规则设定_controller属性

现在我们来看看控制器所需要的参数是怎么被猜测出来的。getArguments() 方法将使用 PHP 的反射 来决定什么样的参数才需要传给控制器。

indexAction() 方法需要一个 Request 对象作为参数。getArguments() 知道如何根据参数类型(type-hint)来传入正确的参数:

public function indexAction(Request $request)

// 译者注:如果没有限定参数类型是Request类型,那么参数就不会被正确传递
// 所以以下代码不会正常运行
// public function indexAction($request)

更有趣的是,getArguments() 方法可以传递任何请求对象的属性值,只要参数的名字跟属性名称一样就行:

public function indexAction($year)

你甚至可以在传入属性指的同时也传入请求对象(因为已经通过参数类型和参数名称做好了匹配,所以参数顺序无所谓)

public function indexAction(Request $request, $year)

public function indexAction($year, Request $request)

最后,你可以使用方法参数默认值的方式,来给一个请求的可选参数设置默认值:

public function indexAction($year = 2012)

让我们把 $year 参数传递给控制器:

class LeapYearController
{
    public function indexAction($year)
    {
        if (is_leap_year($year)) {
            return new Response('Yep, this is a leap year!');
        }

        return new Response('Nope, this is not a leap year.');
    }
}

控制器分离器还有验证控制器回调函数以及其参数的功能,如果出现问题,它将抛出一个详细的意外来解释发生了什么错误(比如控制器类不存在,控制器方法不存在,某个参数没有相应的请求属性)

默认的控制器分析器已经非常的灵活,你可能会好奇怎么可能还有人去再实现一个分析器(为什么需要提供一个接口)?举两个(译者注:其他分析器的实现的)例子:在 sf 里,getController() 功能得到了进一步强化,让其可实现将控制器作为服务,而在 FrameworkExtraBundle 里,getArguments() 方法也得到了增强,让请求参数自动转化为对象。

让我们最后整理一下我们的新框架:

<?php

// example.com/web/front.php

require_once __DIR__.'/../vendor/.composer/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing;
use Symfony\Component\HttpKernel;

function render_template($request)
{
    extract($request->attributes->all());
    ob_start();
    include sprintf(__DIR__.'/../src/pages/%s.php', $_route);

    return new Response(ob_get_clean());
}

$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';

$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
$resolver = new HttpKernel\Controller\ControllerResolver();

try {
    $request->attributes->add($matcher->match($request->getPathInfo()));

    $controller = $resolver->getController($request);
    $arguments = $resolver->getArguments($request, $controller);

    $response = call_user_func_array($controller, $arguments);
} catch (Routing\Exception\ResourceNotFoundException $e) {
    $response = new Response('Not Found', 404);
} catch (Exception $e) {
    $response = new Response('An error occurred', 500);
}

$response->send();

现在我们框架现在更加的健壮,更加的灵活,而且实现它仍然没有超过 50 行代码。

返回阅读第五部分 | 继续阅读第七部分

使用 Symfony 的组件创建自己的 PHP 框架(第六部分:控制器分析器) by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

写作累,服务器也越来越贵
求分担,祝愿好人一生平安
天使打赏人

发表评论

74 − 70 =

2021年7月
 1234
567891011
12131415161718
19202122232425
262728293031  
Composer CSS Firefox industrial metal JavaScript LeetCode Linux MySQL NGINX nu metal OAuth PHP PHP7 Shell Symfony Ubuntu Vim 全栈 到底系列 命令行 安全 教程 框架 算法 翻译