in Профессиональное

Silex – просто добавь воды?!

Мимоходом потребовалось портировать один “оглобельный” сайт на новую платформу. В принципе самое важное там было – это смена дизайна. HTML + SSI? Но сайт мультиязычный, зараза. Немного скрипта таки придется применить.

В качестве эксперимента портируем 5ти язычный сайт на Silex (который суть есть обезжиренный Symfony2).

Note: english version of this article published on the SymfonyGuru site.

Silex представляет собой PHAR архив. Более подробно можете почитать на оф. сайте проекта или же покурить мануал на php.net.

Мы же перво-наперво создаем .htaccess:

# .htaccess
 
    Options -MultiViews
    RewriteEngine On
    #RewriteBase /path/to/app
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php [QSA,L]

Адепты оптимизации могут сразу внести данные директивы в конфигурацию виртуального хоста (и установить AllowOverride в None).

Потом создадим мультиконтроллер index.php (здесь и далее всё располагается внутри docroot вебсервера):

// index.php
/**
 *
 * @package
 * @author Dmitry Bykadorov <dmitry.bykadorov@gmail.com>
 * @version SVN: $Id: $
 */
 
require_once 'lib/silex.phar';
 
// Init
$app = new Silex\Application();
 
// Debug
$app['debug'] = true;
 
// ... код приложения будет где-то здесь
 
$app->run();

Дальше начинается самое весёлое. В качестве шаблонов будем использовать Twig. В качестве базиса для поддержки мультиязычности будем использовать Translation component из Symfony2. И то и другое позиционируется как расширения Silex, однако же подключаются они по разному.

Инициализацию компонент будем производить в before методе (судя по всему аналог preExecute модуля symfony 1.4). Сначала twig:

// index.php
 
/**
 * Before action
 */
$app->before(function () use ($app) {
    // Registering Twig
    $app->register(new Silex\Extension\TwigExtension(), array(
        'twig.path' => realpath(__DIR__.'/resources/views'), // шаблоны будут располагаться в /resources/views (относительно docroot)
        'twig.class_path' => realpath(__DIR__.'/lib/vendor/twig/lib'), // сам Twig будет располагаться тут (относительно docroot): /lib/vendor/twig/lib
    ));
}

Действия (actions) как и before будут сплошь замыкания (closures). Прикольно, да? )

Ну да, Twig инициализировали, но будьте также добры первый раз добавить “воды” в наш yupi silex: развернуть Twig в /lib/vendor/twig (можно скачать прямо с сайта). Получится что-то такое:

/lib
    /vendor
        /twig
            /lib
                /Twig
                    /Error
                    /Extension
                    /...

Теперь можно сделать индекс-контроллер, а также его шаблон.

// index.php
/**
 * Root
 */
$app->get('/', function () use ($app) {
    return $app->redirect("/en/about"); // если / - по умолчанию открываем /en/about
});
$app->get('/{locale}', function () use ($app) {
    $locale = $app['request']->get('locale');
    return $app->redirect("/{$locale}/about"); // если /{locale} - по умолчанию открываем /{locale}/about
});
$app->get('/{locale}/', function () use ($app) {
    $locale = $app['request']->get('locale');
    return $app->redirect("/{$locale}/about"); // если /{locale}/ - по умолчанию открываем /{locale}/about
})->bind('homepage');

У нашего сайта homepage по сути это about – отсюда эти три действия. Метод $app->get(‘/’ … ) – обслуживает GET / и так далее. В итоге все редиректится на about (которого у нас еще нет). Но пожалуй нам нужно сначала инициализировать Translation extension – возвращаемся в метод $app->before().

// index.php
$app->before(function () use ($app) {
    // ... инициализация Twig
 
    // Определяем локаль
    if ($locale = $app['request']->get('locale')) {
        $app['locale'] = $locale;
    } else {
        $app['locale'] = 'en';
    }
 
    // Регистрируем Translation
    $app->register(new Silex\Extension\TranslationExtension(), array(
        'locale' => $locale, // локаль
        'locale_fallback' => 'en', // локаль по умолчанию
        'translation.class_path' => realpath(__DIR__.'/lib/vendor/symfony/src'), // где искать классы компонента
    ));
 
    // Файлы переводов (будут в YAML формате)
    $app['translator.messages'] = array(
        'en' => realpath(__DIR__.'/resources/locales/en.yml'),
        'de' => realpath(__DIR__.'/resources/locales/de.yml'),
        'hu' => realpath(__DIR__.'/resources/locales/hu.yml'),
        'ru' => realpath(__DIR__.'/resources/locales/ru.yml'),
        'sk' => realpath(__DIR__.'/resources/locales/sk.yml'),
    );
    // Так как выбрали YAML то и лоадер не забудьте инициализировать
    $app['translator.loader'] = new Symfony\Component\Translation\Loader\YamlFileLoader();
 
    // Ну и напоследок инициализируем Twig translation extension (таг {% trans %} и фильтр | trans)
    $app['twig']->addExtension(new Symfony\Bridge\Twig\Extension\TranslationExtension($app['translator']));
}

Инициализировали? Фигушки. Теперь надо не просто добавить воды… а лить её как из ведра. Собственно создатели рекомендуют тупо скопировать библиотеки Symfony2 из стандартной дистрибьюции к нам в проект (нефигово так “воды налить”, да?!). На самом деле компонент для задачи больше пока не нужно, поэтому в конце я скажу что именно из Symfony2 можно будет оставить, остальное можно смело удалять. Пока же получится что-то типа:

/lib
    /vendor
        /symfony
            /src
                /Symfony
                    /Bridge
                        / ...
                    /Bundle
                        / ...
                    /Component
                        / ...

Добавляем about действие:

// index.php
/**
 * About
 */
$app->get('/{locale}/about', function () use ($app) {
    return $app['twig']->render('about.twig', array(
        'locale' => $app['request']->get('locale'),
    ));
})->bind('about');

В соответствии с нашими настройками, действие about рендерит шаблон /resources/views/about.twig. Но для начала, пожалуй, создадим layout. Он может выглядеть как-то так:

<!-- /resources/views/layout.twig -->
{% block content %}{% endblock %}

Ну и какой-то /resources/views/about.twig, который экстендит layout:

<!-- /resources/views/about.twig -->
{% extends "layout.twig" %}
 
{% block pageTitle %}
    {{ 'page.about.title' | trans }}
{% endblock %}
 
{% block content %}
    {{ 'page.about.title' | trans }}
{% endblock %}

Обратите внимание, что здесь у нас уже используется и Twig и Translation – собственно у нас появилась первая фраза для файла с переводами, поэтому создадим его для языка по-умолчанию (fallback locale дает нам возможность сначала заполнить файл для базового языка и после скопировать его и перевести на другие языки):

# /resources/locales/en.yml
page.about.title: About Us

Дальше уже дело техники. Создаем изменяемый контент для различных языков или берём его из базы данных (для этого надо будет дополнительно подключить Doctrine2), заполняем файлы переводов токенами, пишем нужные GET/POST методы (а также любые другие – Silex их поддерживает)…

Напоследок, как обещал, сообщаю структуру Symfony-директории в моем случае:

/Symfony
    /Bridge
        /Twig
    /Bundle
        /TwigBundle
    /Component
        /Config
        /Translation
        /Yaml

Это минимально необходимый набор (кроме Twig и Swiftmailer (опционально)) для комфортного создания небольшого сайтика без работы с базой данных.

Заключение: собственно зачем эта статья – немного обобщить мой опыт, полученный в ходе создания маленького, но с небольшими наворотами (5 языковых версий) сайта, который на чистом HTML делать геморно (много copy/paste, SSI с мультиязычностью слабо помог) а на чистом PHP “некошерно”. Опыт показал, что Silex для подобных работ использовать вполне можно, но вот метод “добавь воды (Twig), ещё воды (Swiftmailer), а потом и вовсе весь Symfony2 загрузи в Vendor” не совсем удобен, да и маленький сайтик по объему ресурсов и кода вполне себе быстро расползается как тесто на дрожжах. И чем же Silex удобнее Symfony2? – спросите вы. На мой взгляд он удобнее прежде всего конфигурированием (вернее отсутствием множества конфигурационных файлов Symfony2) – добавляем Twig и у нас в руках уже неплохой инструмент для создания сайта с layout, шаблонами, без copy/paste (ага, размечтался…). В теории вся мощь солнца симфони2 у нас в руках (возможно даже пакеты-bundles). Кроме того в Silex встроена возможность повторного использования кода приложений, да и чем чёрт не шутит – портировать потом на Symfony2 будет проще ))

Домашнее задание: подключить расширение Twig для маршрутизатора и использовать хелпер path() совместно с named routes )

Write a Comment

Comment

*

  1. о, давненько ты ниче не писал.

    1) не могу вспомнить свой логин-пароль для входа, восстановить тоже не могу – говорит ф-ция мейл отключена
    2) у тебя ж дабл эскейпинг в листингах

    • Да, лето как-то в ремонте провел, не до блога было )

      1. о, а я думаю чего у меня почта не ходит ) как разберусь – ресетну тебе пароль
      2. спасиб, не заметил ) сейчас подправлю

    • Проверь почту, там должно было несколько писем с конфирмом на смену майла упать (пока FQDN не настроил – MTA долго думал при отправке ))