Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Symfony CMF. Часть 2 и последняя

Anna | 29.05.2014 | нет комментариев
Продолжим разглядывать Symfony CMF, реализующую доктрину платформы для построения CMS из слабосвязанных компонентов. В первой части статьи мы детально разглядели схему хранения и доступа к данным, во 2-й части нас ожидает все остальное.

Продолжение статьи выходит со существенной задержкой из-за моей лени, задач со здоровьем и интернетом. За эти пару месяцев система доросла до версии 1.0.0, и все дальнейшие правки в master-ветке для чего-то ломают работу системы, не будучи документированными. На случай, если кто захочет ставить систему руками, помните — опирайтесь на стабильные версии, помеченные тегами.

Самые нетерпеливые могут промотать вниз, скачать виртуальную машину с установленной системой (понадобится VirtualBox) и пощупать все самому, но для полноты навыка я бы рекомендовал вначале прочитать статью.

Выходит. Что у нас по плану позже хранения данных?

Hello worldСкриншот основной страницы демо-плана

Шаблонизатор

Тут все знакомо для многих — применяется Twig. Эластичный, сильный, дюже стремительный и лаконичный. Поддерживает распределение на блоки, наследование и компиляцию образцов в PHP-код. Образец основной страницы выглядит так:

{% extends "SandboxMainBundle::skeleton.html.twig" %}

{% block content %}
    <p><em>We are on the homepage which uses a special template</em></p>

    {% createphp cmfMainContent as="rdf" %}
    {{ rdf|raw }}
    {% endcreatephp %}

    <hr/>

    {{ sonata_block_render({ 'name': 'additionalInfoBlock' }, {
        'divisible_by': 3,
        'divisible_class': 'row',
        'child_class': 'span3'
    }) }}

    <div>
        <div>
            <h2>Some additional links:</h2>
            <ul>
                {% for child in cmf_children(cmf_find('/cms/simple')) %}
                    <li>
                        <a href="{{ path(child) }}">{{ child.title|striptags }}</a>
                    </li>
                {% endfor %}
            </ul>
        </div>

        <div>
        {{ sonata_block_render({
            'name': 'rssBlock'
        }) }}
        </div>
    </div>
{% endblock %}

В составе CoreBundle идет пачка растяжений для Twig, которые упрощают работу с CMF и обход PHPCR-дерева, скажем, такие функции как cmf_prevcmf_nextcmf_children и другие.

Огромнее здесь исключительно глядеть не на что, Twig – он и в Африке Twig.

Пара слов об админке

Sonata
Основная страница админки

Известный генератор админок SonataAdminBundle исполняет ровно ту же самую функцию и в Symfony CMF, но через особую прослойку в виде SonataDoctrinePhpcrAdminBundle. Сделано это, Дабы подлинный бандл мог отвлекаться от хранилища данных.

Для работы с древовидными конструкциями предуготовлен TreeBrowserBundle, работающий на jsTree.

Имеющие админ-часть компоненты, описанные ниже, непременно подключают свои панели именно сюда. Следственно детально останавливаться на этом не вижу смысла, детальные скриншоты будут дальше.

Статический контент

Статический контент в CMS — основа каждого. В Symfony CMF за статический контент отвечает ContentBundle, тот, что обеспечивает базовую реализацию классов статических документов, включая многоязычность и связь с маршрутами.

Основой бандла является класс StaticContent, состав которого окажется приятелем многим — говорящие сами за себя поля типа titlebody, ссылка на родительский документ и так дальше. Помимо того, он реализует два интерфейса:

  • RouteReferrersInterface, обеспечивает связку с маршрутами
  • PublishWorkflowInterface, помогает показывать либо скрывать контент с поддержкой заданных дат публикации

publish workflow

Для мультиязычных документов предусмотрен MultilangStaticContent — все то же самое, но добавлен перевод полей и объявление локали. Как делается перевод — мы теснее видели в первой части статьи.

Бандлу полагается контроллер. ContentController состоит из исключительного indexAction, тот, что на входе принимает желамый документ и рендерит его на надобном языке, если с параметрами публикации все в порядке. Опционально дозволено задать образец, с которым будет выводиться страница. Если не задать — будет взят тот, что указан по умолчанию.

Роутинг

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

Какие требования предъявляются к роутингу в таком случае?

  • URL задается пользователем
  • помощь многосайтовости
  • помощь многоязычности
  • древовидная конструкция
  • контент, меню и маршруты обязаны быть поделены

Если припомнить типовой роутер Symfony 2, становится ясно, что такой эластичности там не достичь. Роуты очевидно прописаны в конфиге для всякого контроллера и пользователю менять их просто не дают. Максимум, на что дозволено рассчитывать — это какой-нибудь /page/{slug}, тот, что дозволено править из админки.

Давайте посмотрим, как выглядела схема функционирования на голом SF2:

Роутинг в SF2

Если не вдаваться в детали вероятностей конфигурирования параметров, все достаточно просто. Приходит запрос, роутер решает какой вызвать контроллер, контроллер дергает надобные данные и рендерит вьюшку, после этого выдает сокровенный Response.

Это довольно привычная схема.

Отчего такой вариант неудовлетворительно отменен для CMS?

Предположим, что у нас есть некоторый PageController, тот, что принимает в качестве довода URL-псевдоним страницы, сопоставляет его с тем, что хранится в базе данных и выдает страничку, либо 404.

В моей практике встречались случаи, когда среди неподвижного контента встречались различные формочки, которые слаженней смотрелись бы в составе раздела сайта, нежели как обособленный компонент. Скажем, на одном сайте банка в URL текстового раздела /credits/cash добавлялся кредитный /calculator, Дабы люди, прочитав нужную информацию о кредитах, на месте могли посчитать себе надобные циферки.

Возможен, PageController обработает первую часть URL, что делать с калькулятором, тот, что, видимо, будет выступать отдельным контроллером? Дописать в конфиге pattern: /credits/cash/calculator и указать обособленный контроллер/экшен? Как-то уродливо. Даже если расставить приоритеты между остальными маршрутами, абсолютно видимо, что гибкостью здесь не пахнет — если изменится псевдоним в базе, руками придется править и конфиг.

Необходимо что-то другое.

Резюмируем роутинг в SF2:

  • определяется, какой контроллер обслуживает запрос
  • парсятся параметры URL
  • если ничего не получилось, поведение по умолчанию базируется на предварительно конфигурированном комплекте роутов
    • либо из конфигурации приложения
    • либо из бандлов
  • пользователи редактировать роуты не могут
  • не масштабируется до дюже большого числа роутов
  • в CMS пользователь сам хочет решать, что по какому адресу должно лежать.
Доктрина маршрутизации в Symfony CMF

От очаровательного и сильного, но неудобного в случае с CMS роутера Symfony 2 пришлось отказаться в пользу новой доктрины:

  • необходимо отделять дерево контента от навигационного дерева
  • навигационное дерево состоит из ссылок на элементы дерева контента. За счет этого легко реализуются:
    • многосайтовость (настольная, планшетная, мобильные версии)
    • мультиязычность
  • переботка навигации требует клонирования навигационного дерева
  • по готовности итог вливается обратно

Сразу на ум приходит решение в лоб: создаем маршрут по умолчанию (/{url} с непременным параметромurl: .*), один контроллер для всех запросов и в зависимости от содержимого перенаправляем запрос в другие контроллеры. Но при этом никто не отменяет раздоров с другими роутами.

navigation:
    pattern: "/{url}"
    defaults: { _controller: service.controller:indexAction }
    requirements:
        url: .*

Звучит по-бывшему не дюже.

Решение получше предоставлял (пока его не пометили как устаревший со времен Symfony 2.1)DoctrineRouter. Он теснее значительно эластичнее, потому что искал маршруты по URL в базе данных, при этом была готова реализация для документов через PHPCR-ODM, а еще дозволено приделать всякую свою. Маршрут по желанию очевидно указывал контроллер, в отвратном случае применялся ControllerResolver, тот, что пытался сам решить, какой контроллер будет обрабатывать запрос. Были и встроенные распознаватели:

  • привязка узлов определенного типа к контроллеру
  • привязка узлов определенного типа к образцу и применение стандартного (generic) контроллера

До кучи — переадресация маршрутов (на другие роуты либо безусловные URL).

На данный момент для решения всех задач с роутингом в Symfony CMF применяются два компонента —ChainRouter и DynamicRouter. 1-й заменяет типовой SF2-роутер и, невзирая на наименование, работу роутера (определение контроллера для обработки запроса) на самом деле не исполняет. Взамен этого он дает вероятность добавлять свои роутеры в список-цепочку. В цепочке обработать запрос испробуют все сконфигурированные роутеры по очереди, в порядке приоритета. Сервисы роутеров ищутся по тегам.

cmf_routing:
    chain:
        routers_by_id:
            # включаем DynamicRouter с низким приоритетом
            # в этом случае нединамические маршруты сработают прежде
            # Дабы не допускать лишнего похода в базу данных
            cmf_routing.dynamic_router: 20

            # подключаем свой роутер
            acme_core.my_router: 50

            # дефолтный роутер включаем с высоким приоритетом
            router.default: 100

services:
    acme_core.my_router:
        class: %my_namespace.my_router_class%
        tags:
            - { name: cmf_routing.router, priority: 300 }

Ну вот, у нас есть безмерное число доступных для применения роутеров.

Сейчас припоминаем про поиск роутов в базе данных и DynamicRouter. Его задачей является загрузка маршрутов из провайдера, провайдером может быть (и как правило является) база данных. В стандартной поставке есть реализации провайдеров для Doctrine PHPCR-ODM, Doctrine ORM и разумеется, дозволено дополнить список провайдеров, реализовав RouteProviderInterface.

Что делают провайдеры? Провайдеры по запросу выдают упорядоченное подмножество маршрутов-кандидатов, которые могут подойти пришедшему запросу, а DynamicRouter принимает окончательное решение и сравнивает запрос с определенным объектом типа Route.

Роутинг 2.0

Маршрут определяет, какой контроллер будет обрабатывать определенный запрос. DynamicRouter использует несколько способов в порядке убывания приоритета:

  • очевидно: Route-документ сам верно объявляет финальный контроллер, если таковой возвращается из вызова getDefault('_controller').
  • по псевдониму: маршрут возвращается значение getDefault('type'), которое сопоставляется с конфигурацией из config.yml
  • по классу: Route-документ должен реализовать RouteObjectInterface и воротить объект дляgetContent(). Возвращаемый тип класса вновь же сопоставляется с конфигом
  • по умолчанию: будет применяться дефолтный контроллер, если таковой указан сконфигурирован

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

По желанию при помощи вышеупомянутого RouteObjectInterface дозволено обучить маршрут возвращать экземляр модели, ассоцированный с ним.

Поддерживаются и редиректы. Вообще есть интерфейс RedirectRouteInterface, но для PHPCR-ODM готова реализация в виде документа RedirectRoute. Он может перенаправлять на безусловный URI и на именованный машрут, сгенерированный любым роутером в цепочке.

Еще одна значимая фича, о которой может быть увлекательно узнать тем, кто с Symfony не работал — это двунаправленность роутера. Помимо распознавания маршрутов на основе заданных параметров, эти маршруты дозволено и генерировать, передавая параметры как доводы. В различие от стандартного роутера SF2, в качестве параметра для функции path() дозволено передавать не только заданное в конфиге имя маршрута, но и реализацию RouteObjectInterfaceRouteReferrersInterface (то есть объект-маршрут), либо ссылку на объект в репозитории, применяя его content_id:

{# myRoute это объект класса Symfony\Component\Routing\Route #}
<a href="{{ path(myRoute) }}">Read on</a>

{# Создает ссылку на / для этого сервера #}
<a href="{{ path('/cms/routes') }}">Home</a>

{# myContent реализует RouteReferrersInterface #}
<a href="{{ path(myContent) }}">Read on</a>

{# передаем ссылку на объект, тот, что реализует ContentRepositoryInterface #}
<a href="{{ path(null, {'content_id': '/cms/content/my-content'}) }}">
    Read on
</a>

Если для одного и того же материала подходит несколько маршрутов, предпочтительным будет считаться тот, локаль которого совпадает с локалью запроса.

Напоследок возвратимся к написанному чуть ранее, распределение навигационного дерева и дерева контента. Взгляните на схему, разветвленная навигация согласно определенным правилам передает управление нужным контроллерам и только позже этого запрашивает данные:

Связь между роутингом и контентом

В админке для маршрутов дозволено задать формат (чтоб URL заканчивался на .html, скажем) и финальный слэш.

Вдобавок ко каждому пока экспериментальный RoutingAutoBundle предлагает на основе предварительно заготовленных правил генерировать маршруты для контента. За счет генерации автомаршрутов достигается эластичность: для отдельных маршрутов легко переводить псевдонимы, генерировать карту сайта и менять класс документов, на которые маршрут может ссылаться.Но в большинстве случае для примитивных CMS данный бандл может и не потребоваться.

На этом с эластичной маршрутизацией завершим.

Меню

Ни одна CMS не обходится без системы меню. Правда конструкция меню традиционно повторяет конструкцию контента, ему может понадобиться собственная логика, не определенная контентом либо присутствующая в нескольких контекстах с различными опциями:

Пример меню

В состав Symfony CMF входит MenuBundle, инструмент, дозволяющий определять личные меню. Он расширяет знаменитый KnpMenuBundle, дополняя его иерархическими и мультиязычными элементами и инструментами для их записи в выбранное хранилище.

При итоге меню MenuBundle опирается на дефолтные для KnpMenuBundle рендереры и хелперы. Полную документацию почитать рекомендуется, но вообще в самом простейшем случае итог выглядит так:

{{ knp_menu_render('simple') }}

Переданное функции имя меню в свою очередь будет передано реализации MenuProviderInterface, которая будет решать, какое меню необходимо показать.

В основе бандла лежит PhpcrMenuProvider, реализация MenuProviderInterface, ответственная за динамическую загрузку меню из PHPCR-хранилища. По умолчанию сервис провайдера конфигурируется параметром menu_basepath, тот, что указывает, где искать меню в PHPCR-дереве. При рендеринге меню передается параметр name, тот, что должен быть прямым потомком указанного базового пути. Это разрешаетPhpcrMenuProvider трудиться с несколькими иерархиями меню, применяя цельный механизм хранения. Припоминая указанный выше пример применения, меню simple должно находиться по адресу/cms/menu/simple, если в конфигурации указано следующее:

cmf_menu:
    menu_basepath: /cms/menu

В бандле поддерживается два типа узлов: MenuNode и MultilangMenuNodeMenuNode содержит информацию об отдельном пункте меню: labeluri, список дочерних пунктов children, ссылку на маршут, связанный Content-элемент, плюс список признаков attributes, вследствие которому дозволено настраивать итог меню.

Класс MultilangMenuNode расширяет MenuNode для поддержки мультиязычности: добавлено поле locale для определения перевода, к которому принадлежит пункт и label с uri, помеченные как translated=true. Это исключительные поля, которые различаются между переводами.

Админка меню 1

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

Админка меню2

Конфигурируется бандл как традиционно, но все параметры опциональны.

Связь между роутингом, меню и контентом продемонстрирована здесь:

Роутинг, контент, меню

Блоки

Предусмотрен и балять уведомлениями, появляющимиcя в процессе редактирования

  • организовывать свои тулбары с необходимыми инструментами
  • вызывать пользовательские workflow-функции типа «удалить», «снять с публикации»

Каждый контент редактируется на месте, при этом за счет RDFa не доводится генерировать тонны вспомогательной HTML-разметки, как это делают некоторые CMS.

Редактор
ckEditor на службе добродушна

Ну и замыкает список CreatePHP, библиотека, объединяющая вызовы create.js и непринужденно бэкенд. Она отвечает за маппинг свойств модели на PHP к HTML-признакам и рендеринг сущности. Самые внимательные теснее видели, что для CreatePHP существует Twig-растяжение и его вызов красуется в первом же листинге этой статьи: передаем модель и указываем формат итога. Красота.

Последние два компонента объединены для комфорта в CreateBundle.

MediaBundle

Одним из бандлов самой минималистичной реализации является бандл для работы с медиа-объектами. Ими могут быть документы, двоичные файлы, MP3, видеоролики и еще чего душа пожелает. В нынешней версии поддерживается загрузка картинок и скачивание файлов, все остальное писать руками. SonataMediaBundleможет подмогнуть, тем больше что есть интеграция.

Бандл обеспечивает:

  • базовые документы для примитивных моделей;
  • базовые FormType для примитивных моделей;
  • контроллер для загрузки и скачивания файлов;
  • хелпер, дающий абстракцию от загрузки на сервер;
  • контроллер для отображения картинки.

А так же хелперы и адаптеры для интеграции:

  • медиа-браузеров (elFinder, ckFinder, MceFileManager, и т. п.);
  • библиотек для манипуляций с изображениями (Imagine, LiipImagineBundle).

эльфайндер

Есть целая россыпь интерфейсов для создания своих медиа-классов:

  • MediaInterface: базовый класс;
  • MetadataInterface: определение метаданных;
  • FileInterface: определяется как файл;
  • ImageInterface: определяется как картинка;
  • FileSystemInterface: файл хранится в файловой системе, как медиа-объект сохраняется путь к нему;
  • BinaryInterface: в основном применяется, когда файл сохранен внути медиа-объекта;
  • DirectoryInterface: определяется как директория;
  • HierarchyInterface: медиа-объекты хранят директории, путь к медиа: /path/to/file/filename.ext.

Увлекателен подход к файловым путям. В терминологии бандла под путем к медиа-объекту воспринимается, скажем, /path/to/my/media.jpg и отличия между путями в Windows и *nix-системах нивелируются. В PHPCR такой путь может применяться как идентификатор. Доступны несколько пригодных способов:

  • getPath получает путь к объекту, сохраненному в PHPCR, ORM либо ином Doctrine-хранилище;
  • getUrlSafePath превращает путь для неопасного применения в URL;
  • mapPathToId превращает путь в идентификатор, Дабы осуществлять поиск в Doctrine-хранилище;
  • mapUrlSafePathToId превращает URL обратно в идентификатор.

В Twig-растяжении доступны говорящие сами за себя функции:

<a href="{{ cmf_media_download_url(file) }}" title="Download">Download</a>
<img src="{{ cmf_media_display_url(image) }}" alt="" />

Прикрепить картинку к документу дозволено через предоставленный Form Type:

use Symfony\Component\Form\FormBuilderInterface;

protected function configureFormFields(FormBuilderInterface $formBuilder)
{
     $formBuilder
        ->add('image', 'cmf_media_image', array('required' => false))
     ;
}

Реализованы адаптеры для медиа-браузера elFinder, библиотеки Gaufrette, дающей слой абстракции над файловой системой и LiipImagine, которая упрощает манипуляции с картинками.

фейл

Как я и говорил ранее, реализация бандла минималистичная. Довольно сказать, что картинки скопом не загружаются, а Дабы физически удалить файл, прикрепленный к документу, нужно удалить и сам документ. Гм.

Перспективы

Планируется (а местами в какой-то степени даже готова) интеграция с модулями:

  • SymfonyCmfSearchBundle (полновесный поиск, расширяет LiipSearchBundle)
  • SymfonyCmfSimpleCms (простейшая CMS, поставляемая совместно с CMF)
  • LuneticsLocaleBundle (механическое определение локали)
  • другие бандлы от Sonata

Ну и безусловно, разработка новых фич и устранение нынешних недоработок.

Все ли так отлично?

Я здесь теснее на много килобайт текста распинаюсь, как все в Symfony CMF восхитительно, следственно разумно будет спросить, а где же критика.

Недостатков хватает.

Symfony CMF обновляется нечасто — на гитхабе указано, что процесс выпуска новых версий аналогичен релизной схеме SF2, то есть всякие полгода (четыре месяца пишем новые фичи, два месяца фиксим баги и готовим релиз). Безусловно, будут мелкие исправления, направленные на устранение уязвимостей, но в целом, если хочется новенького, придется довольно подождать. При этом теперь такой этап разработки, когда никто не обещает сохранение обратной совместимости между релизами всякий ценой. Это значит — что работало в 1.0, в 1.1 может запросто сломаться.

Страдает документация. В вики плана беспорядок, многие статьи теснее нужно бы и удалить, где-то написан устаревший код, да и в целом Symfony CMF Book не так дружелюбна и примитивна, как подобный альманах для SF2.

У CMF крайне высокий порог вступления. Дабы установить тестовую систему неудовлетворительно «распаковать все в webroot и запустить install.php» — необходимо отлично понимать связь между компонентами и уметь обращаться с всяким из них. Любая доработка либо внедрение своего кода затребуют вдумчивого постижения внутренностей. Правда, вероятно, используюших SF2 разработчиков это не напугает. А для пользователей документации нет вообще…

Итог, навязывающийся по теснее только по скриншотам — система сырая и пока далека от товарного вида. Юзабилити админки под вопросом — как бы и опрятный Bootstrap, а как бы и чувствуется, что рука дизайнера тут ничего не касалась. Невзирая на резкий фронтенд-редактор, для body-элементов в админке предусмотрен лишь ничтожный textarea высотой в две строки. Для типовых операций доводится делать слишком много телодвижений из-за непродуманной навигации.

Без любви

В документации Зачастую встречаются обещания сделать X либо Y потом. Если захочется кому-то порекламировать план — уговорить в рациональности применения получится с трудом, я думаю. Не будет модных ныне eye-candy-простынок, обещающих, как легка и весела станет ваша жизнь позже установки Symfony CMF. В всеобщем, «коробки», из которой дозволено достать симпатичную работающую систему, нет.И вероятно не будет

Отдельно подмечу, что примеров индустриального применения Symfony CMF пока нет. Неведомо, как система ведет себя под нагрузкой и что делать, если внезапно понадобится масштабирование (в том числе бэкенда) — эти вопросы не раскрыты в документации за исключением Cache-бандлов и установки APC.

Перейдем к делу

Дозволено сразу скачать подготовленный мною образ виртуальной машины дмечательный продукт?

На этом все. Маленький список пригодных ссылок:

 

 Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB