The Separation of Concerns

One down-side of our framework right now is that we need to copy and paste the code in front.php each time we create a new website. 60 lines of code is not that much, but it would be nice if we could wrap this code into a proper class. It would bring us better reusability and easier testing to name just a few benefits.

現在のフレームワークの欠点の 1 つは、新しい Web サイトを作成するたびにコードをコピーして front.php に貼り付ける必要があることです。 60 行のコードはそれほど多くありませんが、このコードを適切なクラスにラップできればよいでしょう。いくつかの利点を挙げると、再利用性が向上し、テストが容易になります。

If you have a closer look at the code, front.php has one input, the Request and one output, the Response. Our framework class will follow this simple principle: the logic is about creating the Response associated with a Request.

コードをよく見ると、front.php には 1 つの入力である theRequest と 1 つの出力である Response があります。私たちのフレームワーク クラスは、この単純な原則に従います。ロジックは、aRequest に関連付けられた Response を作成することです。

Let's create our very own namespace for our framework: Simplex. Move the request handling logic into its own Simplex\Framework class:

フレームワーク用に独自の名前空間を作成しましょう: Simplex.リクエスト処理ロジックを独自の Simplex\Framework クラスに移動します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 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
{
    private $matcher;
    private $controllerResolver;
    private $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 $exception) {
            return new Response('Not Found', 404);
        } catch (\Exception $exception) {
            return new Response('An error occurred', 500);
        }
    }
}

And update example.com/web/front.php accordingly:

それに応じて example.com/web/front.php を更新します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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();

To wrap up the refactoring, let's move everything but routes definition from example.com/src/app.php into yet another namespace: Calendar.

リファクタリングを締めくくるために、ルート定義以外のすべてを example.com/src/app.php からさらに別の名前空間である Calendar に移動しましょう。

For the classes defined under the Simplex and Calendar namespaces to be autoloaded, update the composer.json file:

Simplex および Calendar 名前空間で定義されたクラスを自動ロードするには、composer.json ファイルを更新します。
1
2
3
4
5
6
{
    "...": "...",
    "autoload": {
        "psr-4": { "": "src/" }
    }
}

Note

ノート

For the Composer autoloader to be updated, run composer dump-autoload.

Composer オートローダーを更新するには、composer dump-autoload を実行します。

Move the controller to Calendar\Controller\LeapYearController:

コントローラーを Calendar\Controller\LeapYearController に移動します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// example.com/src/Calendar/Controller/LeapYearController.php
namespace Calendar\Controller;

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

class LeapYearController
{
    public function index(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.');
    }
}

And move the is_leap_year() function to its own class too:

is_leap_year() 関数も独自のクラスに移動します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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);
    }
}

Don't forget to update the example.com/src/app.php file accordingly:

それに応じて example.com/src/app.php ファイルを更新することを忘れないでください。
1
2
3
4
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', [
    'year' => null,
    '_controller' => 'Calendar\Controller\LeapYearController::index',
]));

To sum up, here is the new file layout:

要約すると、新しいファイル レイアウトは次のとおりです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

That's it! Our application has now four different layers and each of them has a well-defined goal:

それでおしまい!私たちのアプリケーションには 4 つの異なるレイヤーがあり、それぞれに明確な目標があります。
  • web/front.php: The front controller; the only exposed PHP code that makes the interface with the client (it gets the Request and sends the Response) and provides the boiler-plate code to initialize the framework and our application;
    web/front.php: フロント コントローラー。クライアントとのインターフェイスを作成し(リクエストを取得してレスポンスを送信する)、フレームワークとアプリケーションを初期化する定型コードを提供する唯一の公開された PHP コード。
  • src/Simplex: The reusable framework code that abstracts the handling of incoming Requests (by the way, it makes your controllers/templates better testable -- more about that later on);
    src/Simplex: 着信リクエストの処理を抽象化する再利用可能なフレームワーク コード (ちなみに、これによりコントローラー/テンプレートのテストが容易になります。これについては後で詳しく説明します)。
  • src/Calendar: Our application specific code (the controllers and the model);
    src/Calendar: アプリケーション固有のコード (コントローラーとモデル)。
  • src/app.php: The application configuration/framework customization.
    src/app.php: アプリケーション構成/フレームワークのカスタマイズ。