关注点分离

现在我们框架的一个缺点是,每次我们要创建一个新网站的时候,我们都需要复制黏贴front.php的代码。60行的代码虽然不算多,但是如果我们将这段代码封装成一个类会更好。这会带给我们更好的可重用性,易测试等一系列的好处。

如果你仔细研究过代码,你能发现front.php有一个输入和一个输出,分别对应请求和响应。我们的框架类将会遵守一个简单的原则:代码逻辑就是创建一个与请求相关联的响应。

让我们给框架创建一个属于自己的命名空间:Simplex。将请求控制的逻辑放进Simplex\\Framework 类中:

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

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

class Framework
{
   protected $matcher;
   protected $controllerResolver;
   protected $argumentResolver;

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

   public function handle(Request $request)
   {
       $this->matcher->getContext()->fromRequest($request);

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

           $controller = $this->controllerResolver->getController($request);
           $arguments = $this->argumentResolver->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);
       }
   }
}

然后相应的更新 example.com/web/front.php :

// example.com/web/front.php

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

$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);

$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();

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

$response->send();

为了封装重构的代码,我们把除了路由定义的其他代码放进另一个命名空间:Calendar

为了使SimplexCalendar命名空间下定义的类可以被自动加载,更新 composer.json 文件:

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

运行composer dump-autoload更新Composer的自动加载器。

将控制器转移到 Calendar\Controller\LeapYearController:

// 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() 函数移动它自己的类中:

// 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
├── composer.lock
├── src
│   ├── app.php
│   └── Simplex
│       └── Framework.php
│   └── Calendar
│       └── Controller
│       │   └── LeapYearController.php
│       └── Model
│           └── LeapYear.php
├── vendor
│   └── autoload.php
└── web
    └── front.php

成了!现在我们的应用有了4个不同的层级,每一个层级都有一个明确的目标:

  • web/front.php: front控制器;唯一暴露出来的PHP代码,与客户端建立接口(获取请求并返回响应),并且提供了初始化框架和应用的样板式代码;

  • src/Simplex: 控制输入请求的抽象,可复用的框架代码(同时,它使你的控制器/模板易测试,稍后再谈);

  • src/Calendar: 应用的具体代码(控制器以及模型);

  • src/app.php: 应用配置/框架自定义信息。

results matching ""

    No results matching ""