symfony framework

индекс
85,02

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

Оглавление





До сих пор наше приложение было довольно простым, поскольку содержало только одну страницу.
Что бы немного все усложнить, добавим другую страницу, говорящую «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 в конфигурационный файл.
+8
18 января 2012, 02:57
43

комментарии (8)

–1
shternberg #
Спасибо за перевод, но, хороший программист по определению знает английский на достаточном уровне, что бы прочитать данную статью в оригинале, а быдлокодерам от данного перевода мало толку.
0
Megas #
Если так рассуждать, то хороший программист и так знает как создать свой фреймворк.
0
assylbetti #
хороший программист уже создал все шаблоны, которые ему когда-либо понадобятся и всю работу делает за 3 клика
–1
icegreenberry #
Дело человек говорит. Зачем минусовать?
Английский в оригинальной статье очень даже простой, как и в большинстве документации в IT.
+3
avorobiev #
Чтобы развивать сообщество вокруг продукта, надо в него привлекать новых участников. А потенциальным участникам гораздо проще прочитать материал на родном языке, осмыслить, а уже потом, если заинтересует, отправиться за полноценным погружением к английской документации.
Для меня же этот перевод был полезен еще и тем, что обратил внимание на новый цикл публикаций в блоге Фабьена. Я ожидал, что будет новый учебник в блоге симфони, при том публикация его стартует в начале декабря и завершится к Рождеству, и так и не дождался…
Так что автору большое спасибо!
0
pentium133 #
спасибо!
–2
dezconnect #
Один вопрос нахрена городить новый фреймворк на другом фреймворке?
0
fozzy #
Статья не о том как сделать фреймворк на другом фреймворке. А как сделать свой, на основе уже существующих компонентов, при этом не факт что все компоненты будут от одного фреймворка. Например в этом проекте вы можете использовать части Symfony2, Zend или любого другого (практически любого компонента/библиотеки которая поддерживает спецификацию PSR-0). Все это для того что бы разработчик мог уйти от создания велосипедов и перейти сразу к разработке уникальной бизнес-логики для приложения.

При это ему не приходиться использовать громоздкие фремворки в целом, а только нужные вещи(компоненты), что положительно сказывается на скорости работы и сложности приложения в целом.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.