Pull to refresh

Создаем собственный фреймворк на основе Symfony2. (Часть 3)

Reading time4 min
Views4.3K
Original author: Fabien Potencier

Оглавление





До сих пор наше приложение было довольно простым, поскольку содержало только одну страницу.
Что бы немного все усложнить, добавим другую страницу, говорящую «goodbye»:
    <?php

    // framework/bye.php

    require_once __DIR__.'/autoload.php';

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

    $request = Request::createFromGlobals();

    $response = new Response('Goodbye!');
    $response->send();

Как видите, большая часть кода точно такая же как и для первой страницы.
Давайте отдельно выделим общий код для всех наших страниц.
«Выделение общего кода», звучит как отличный план для нашего первого «настоящего» фреймворка!

В рамках PHP рефакторинг будет состоять из создания подключаемого файла:
    <?php

    // framework/init.php

    require_once __DIR__.'/autoload.php';

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

    $request = Request::createFromGlobals();
    $response = new Response();

Посмотрим на это в действии:
    <?php

    // framework/index.php

    require_once __DIR__.'/init.php';

    $input = $request->get('name', 'World');

    $response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
    $response->send();

И для страницы "Goodbye":
    <?php

    // framework/bye.php

    require_once __DIR__.'/init.php';

    $response->setContent('Goodbye!');
    $response->send();

Итак, мы собрали общий код в одном месте. Но это не выглядит как хороший уровень абстракции, не так ли?
Во-первых, у нас все еще есть вызов метода send() на всех страницах, по-этому наши страницы не похожи на обычные шаблоны, и мы до сих пор не можем «правильно» тестировать код.

Другая серьёзная проблема заключается в том, что добавление новой страницы означает что мы должны создать новый PHP скрипт,
название которого будет передаваться в URL(http://example.com/bye.php) и вызываться веб-сервером.
Хорошей идеей будет перенести вызов нужного скрипта в наш код для лучшей гибкости.
Этого можно достичь перенаправляя все запросы на один PHP скрипт — front controller.

  • Перенаправление всех запросов на один скрипт это паттерн проектирования Front Controller


Сам скрипт может выглядеть так:
    <?php

    // framework/front.php

    require_once __DIR__.'/autoload.php';

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

    $request = Request::createFromGlobals();
    $response = new Response();

    $map = array(
        '/hello' => __DIR__.'/hello.php',
        '/bye'   => __DIR__.'/bye.php',
    );

    $path = $request->getPathInfo();
    if (isset($map[$path])) {
        require $map[$path];
    } else {
        $response->setStatusCode(404);
        $response->setContent('Not Found');
    }

    $response->send();

И пример нового hello.php:
    <?php

    // framework/hello.php

    $input = $request->get('name', 'World');
    $response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));

В скрипте front.php, $map связывает пути в URL соответсвующим путям к файлам скриптов.

Как бонус, если клиент запрашивает путь, который не определен в массиве соответсвий, мы возвращаем 404 ошибку.
Теперь вы сами контролируете сайт.

Теперь для доступа к страницам вы должны использовать скрипт front controller'а front.php:


/hello и /bye это «пути» страниц.

Подсказка.
Большинство веб-серверов, таких как Apache или nginx, способны переписывать
запрашиваемые URL страниц тем самым убирая из них название файла front controller'а (front.php),
так что вашим пользователям будет достаточно набрать example.com/hello?name=Fabien, что выглядит значительно лучше.


Итак, фокус в использовании метода Request::getPathInfo() который возвращает часть пути из "Request"
удаляя из него название файла front controller'а включая все поддиректории (только если потребуется — см. подсказку выше).

Вам даже не нужно настраивать веб-сервер. Просто
замените вызов $request = Request::createFromGlobals(); на что-то типа $request = Request::create('/hello?name=Fabien');,
где аргументом является запрос, который вы хотите имитировать.

Теперь, когда все запросы к нашим страницам идут через один скрипт (front.php),
мы можем способствовать безопасности перенеся все остальные PHP файлы вне корневой веб-директории:
    example.com
    +-- composer.json
    ¦   src
    ¦   +-- autoload.php
    ¦   L-- pages
    ¦       +-- hello.php
    ¦       L-- bye.php
    +-- vendor
    L-- web
        L-- front.php

Сейчас, настройте ваш веб сервер так, что бы директория web/ была корневой для него.
Все, другие файлы больше не доступны для клиента.

Для работы данной структуры директорий, вам нужно будет изменить пути в некоторых файлах проекта;
эти изменения будут домашней работой для читателя.

Последнее что повторяется на кажлой странице, это вызов setContent().
Сейчас мы можем превратить все наши страницы в «шаблоны» просто делая вывод контента
и вызывать setContent() прямо из скрипта front controller'а:
    <?php

    // example.com/web/front.php

    // ...

    $path = $request->getPathInfo();
    if (isset($map[$path])) {
        ob_start();
        include $map[$path];
        $response->setContent(ob_get_clean());
    } else {
        $response->setStatusCode(404);
        $response->setContent('Not Found');
    }

    // ...

Теперь скрипт hello.php можно переделать в шаблон:
    <!-- example.com/src/pages/hello.php -->

    <?php $name = $request->get('name', 'World') ?>

    Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>

На сегодняшний день наш фреймворк выглядит так:
    <?php

    // example.com/web/front.php

    require_once __DIR__.'/../src/autoload.php';

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

    $request = Request::createFromGlobals();
    $response = new Response();

    $map = array(
        '/hello' => __DIR__.'/../src/pages/hello.php',
        '/bye'   => __DIR__.'/../src/pages/bye.php',
    );

    $path = $request->getPathInfo();
    if (isset($map[$path])) {
        ob_start();
        include $map[$path];
        $response->setContent(ob_get_clean());
    } else {
        $response->setStatusCode(404);
        $response->setContent('Not Found');
    }

    $response->send();

Добавление страниц происходит в два шага: добавляем элемент в массив соответсвий и создаем
PHP шаблон в src/pages/. Из шаблона нам доступны данные "Request" посредством переменной $request
и мы можем манипулировать заголовками ответа("Response") через переменную $response.

Если вы решите остановиться сейчас, хорошей идеей будет вынести массив соответсвий URL в конфигурационный файл.
Tags:
Hubs:
+8
Comments8

Articles