Розділення функціональності

Дата оновлення перекладу 2023-09-01

Розділення функціональності

Недоліком вашого фреймворку зараз є те, що нам треба копіювати та вставляти код у front.php кожний раз, коли ми створюємо новий сайт. 60 рядків коду - це не багато, але було б добре, якщо б ми могли огорнути цей код у відповідний клас. Це дало б нам кращу можливість повторного використання та полегшило б тестування, і це тільки декілька переваг.

Якщо ви уважніше подивитеся на код, front.php має одне введення - Запит, і одне виведення - Відповідь. Наш клас фреймворку дотримуватиметься цього простого принципу: логіка полягає у створенні Відповіді, повʼязаної з Запитом.

Давайте створимо наш власний простір імен для нашого фреймворку 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
// 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
{
    public function __construct(
        private UrlMatcher $matcher,
        private ControllerResolver $controllerResolver,
        private ArgumentResolver $argumentResolver,
    ) {
    }

    public function handle(Request $request): Response
    {
        $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);
        }
    }
}

Та відповідно оновіть 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();

Щоб закінчити з реорганізацією, давайте перемістимо все, окрім визначень маршрутів, з example.com/src/app.php в інший простір імен - Calendar.

Для автозавантаження класів, визначених у просторах імен Simplex і Calendar, оновіть файл composer.json:

1
2
3
4
5
6
{
    "...": "...",
    "autoload": {
        "psr-4": { "": "src/" }
    }
}

Note

Для оновлення автозавантажувача Composer, запустіть composer dump-autoload.

Перемістіть контролер в 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, int $year): Response
    {
        $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() в її власний клас:

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(int $year = null): bool
    {
        if (null === $year) {
            $year = date('Y');
        }

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

Не забудьте відповідно оновити файл example.com/src/app.php:

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

Щоб підсумувати, ось нова розмітка файлу:

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

Ось і все! Наш додаток тепер має чотири різних шари і кожний з них має чітко визначену ціль:

  • web/front.php: Фронт-контролер; єдиний відкритий PHP-код, який створює інтерфейс з клієнтом (отримує Запит та відправляє Відповідь) та надає шаблонний код для ініціалізації фремйворку та нашого додатку;
  • src/Simplex: Повторно використовуваний код фреймворку, який абстрагує обробку вхідних Запитів (до речі, він робить ваші контролери та шаблони легкотестованими - ви скоро дізнаєтеся про це більше);
  • src/Calendar: Наш специфічний для додатку код (контролери та модель);
  • src/app.php: Налаштування конфігурації/фреймворку додатку.