使用 Symfony 的组件创建自己的 PHP 框架(第七部分:封装框架代码)

使用 Symfony 的组件创建自己的 PHP 框架(第七部分:封装框架代码)

Chris Yue 10 comments
Posts

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

目前我们的框架还有一个不足之处:当我们要创建新的网站的时候,我们都需要将 front.php 的代码复制一份。虽然 40 行代码并不是很多,但是如果我们能将这些代码写成一个合适的类,将会更给力一些,比如更好的复用性以及更好的可测试性。

更进一步研究你会发现,front.php包含一个输入,即一个请求 Request,以及一个输出,即一个相应 Response。我们的框架将遵循一个简单的原则:生成与请求对应的响应。

我们可以为框架设置一个自己的命名空间:Simplex

将处理请求的逻辑代码移动到我们的框架类中:

<?php

// example.com/src/Simplex/Framework.php

namespace Simplex;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;

class Framework
{
    protected $matcher;
    protected $resolver;

    public function __construct(UrlMatcher $matcher, ControllerResolver $resolver)
    {
        $this->matcher = $matcher;
        $this->resolver = $resolver;
    }

    public function handle(Request $request)
    {
        try {
            $request->attributes->add($this->matcher->match($request->getPathInfo()));

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

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

相应的我们也需要更新一下 front.php 的代码:

<?php

// example.com/web/front.php

// ...

$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();

$framework = new Simplex\Framework($matcher, $resolver);
$response = $framework->handle($request);

$response->send();

让我们把除了路由定义的代码挪到另外一个命名空间 Calendar 下,以完成我们对重构代码的继续封装:

为了让在 Simplex 以及 Calendar 这两个命名空间下的代码文件能够自动加载,我们需要更新一下 composer.json 文件:

{
    "...": "...",
    "autoload": {
        "psr-4": { "": "src/" }
    }
}

为了让自动加载生效,需要运行

php composer.phar dump-autoload

将控制器代码挪到Calendar\Controller\LeapYearController

<?php

// example.com/src/Calendar/Controller/LeapYearController.php

namespace Calendar\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Calendar\Model\LeapYear;

class LeapYearController
{
    public function indexAction(Request $request, $year)
    {
        $leapyear = new LeapYear();
        if ($leapyear->isLeapYear($year)) {
            return new Response('Yep, this is a leap year!');
        }

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

然后把 is_leap_year() 方法挪到它应该存在的类中:

<?php

// example.com/src/Calendar/Model/LeapYear.php

namespace Calendar\Model;

class LeapYear
{
    public function isLeapYear($year = null)
    {
        if (null === $year) {
            $year = date('Y');
        }

        return 0 == $year % 400 || (0 == $year % 4 && 0 != $year % 100);
    }
}

别忘了example.com/src/app.php也需要在相应的地方做下更新:

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

最终,我们得到一个新的目录结构:

example.com/
    composer.json
    src/
        app.php
        Simplex/
            Framework.php
        Calendar/
            Controller/
                LeapYearController.php
            Model/
                LeapYear.php
    vendor/
        autoload.php
    web/
        front.php

没错!我们的应用程序目前有4个不同的部分,而且每一个部分都有自己特有的责任:

  • web/front.php:前段控制器,这是唯一一处没有被封装的php代码文件,并且唯一与客户端交互的接口(它接受请求并发送响应),并且提供了初始化框架的代码模版(boil-plate,译者注:这个单词很有意思,来源于印刷工业,指的是不能拆卸的一整块印刷母板,你可以想成是活字印刷发明之前用来印书的东西)
  • src/Simplex:可供复用的框架代码,作为处理请求的抽象化接口(另外,他使你的控制器/模板文件更容易测试,更多信息请看下一章)
  • src/Calendar:我们程序的特定代码(译者注:指一个程序的具体实现)
  • src/app.php:程序配置/框架自定义

返回阅读第六部分 | 继续阅读第八部分

使用 Symfony 的组件创建自己的 PHP 框架(第七部分:封装框架代码) by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

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

10 Comments

笔尖

5月 27, 2015 在 9:50 下午

博主你翻译的文章非常棒,我一直在看 不过是潜水的,其实关注你文章的人很多
非常感谢

 回复

lxrm

5月 20, 2013 在 3:08 下午

辛苦了,楼主,花了几天,看到这里,也完成了对sf2的基本了解和实验,对比原文,楼主翻译应该是比较用心了。赞一下!
另外,我目前的版本是2.1.* 版 autoload.php 位置是 直接放到了 vender 下面
require_once __DIR__.’/../vendor/autoload.php’;即可

 回复

md

6月 10, 2012 在 11:49 下午

有没有兴趣直接参与到我们这边?比如fork我们的网站,可以省了我过来翻阅的功夫,呵呵。国内用Symfony2的人星星点点的,如果能合在一处,用的人多了,各自应该能更省力,投入的时间精力应该也更划算吧。我的动机很简单,能分享的人,首先自己得有效率,想通过这个社区项目多结识一些“这种风格”的开发人员吧。我的Email:md@symfony.cn,如果有想法了,随时联系我。

 回复

    Chris Yue

    6月 22, 2012 在 1:48 下午

    因为我时间太不确定,所以直接参与这种事情我感觉我自己有点不太靠谱,毕竟参与也算是要承担了一份责任,不靠谱终归不好
    另外什么是fork?

     

    md

    7月 5, 2012 在 5:59 下午

    把“fork”理解成在github.com协作开发/写作的动词吧:)建议你有时间了解一下,其实通过GitHub参与像symfony.cn这样的项目,并不会对你的节奏有硬性要求。你可以在任意的时间做对你有意义的事情,通过GitHub来做同步就可以了。对你对做事负责的态度,我很赞同。

     

    Chris Yue

    7月 7, 2012 在 11:08 上午

    那我先fork着吧:)
    你们有qq群吗?

     

    md

    7月 8, 2012 在 5:22 下午

    qq群230078413,但没怎么管理;
    我打算逐渐把你翻译的这个系列转载到symfony.cn
    除了署名,有没有什么别的要求:)

     

    Chris Yue

    7月 11, 2012 在 7:28 下午

    注明原地址就行,没其他要求,我加群了哈

     

md

6月 1, 2012 在 5:32 下午

辛苦了,请问这个系列的翻译是否还在进行?我想在symfony.cn属性转载你的文章,是否允许?:)

 回复

    Chris Yue

    6月 6, 2012 在 8:38 下午

    既然有人看那就继续吧,哈哈:)
    没有,其实是工作原因导致没空更新。
    最近应该没这么忙了,会陆续把剩下5章全部翻译完

    转载没问题。

     

发表评论

66 + = 71

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 全栈 到底系列 命令行 安全 教程 框架 算法 翻译