Продолжим разглядывать Symfony CMF, реализующую концепцию платформы для построения CMS из слабосвязанных компонентов. В первой части статьи мы подробно рассмотрели схему хранения и доступа к данным, во второй части нас ждет все остальное.
Продолжение статьи выходит со значительной задержкой из-за моей лени, проблем со здоровьем и интернетом. За эти пару месяцев система доросла до версии 1.0.0, и все последующие правки в master-ветке зачем-то ломают работу системы, не будучи документированными. На случай, если кто захочет ставить систему руками, помните — опирайтесь на стабильные версии, помеченные тегами.
Самые нетерпеливые могут промотать вниз, скачать виртуальную машину с установленной системой (потребуется VirtualBox) и пощупать все самому, но для полноты опыта я бы рекомендовал сначала прочитать статью.
Итак. Что у нас по плану после хранения данных?
Скриншот главной страницы демо-проекта
Шаблонизатор
Здесь все знакомо для многих — используется 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 class="row">
<div class="span3">
<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 class="span3">
{{ sonata_block_render({
'name': 'rssBlock'
}) }}
</div>
</div>
{% endblock %}
В составе CoreBundle идет пачка расширений для Twig, которые упрощают работу с CMF и обход PHPCR-дерева, например, такие функции как cmf_prev
, cmf_next
, cmf_children
и другие.
Больше тут особенно смотреть не на что, Twig – он и в Африке Twig.
Пара слов об админке
Главная страница админки
Знаменитый генератор админок SonataAdminBundle выполняет ровно ту же самую функцию и в Symfony CMF, но через специальную прослойку в виде SonataDoctrinePhpcrAdminBundle. Сделано это, чтобы оригинальный бандл мог абстрагироваться от хранилища данных.
Для работы с древовидными структурами предназначен TreeBrowserBundle, работающий на jsTree.
Имеющие админ-часть компоненты, описанные ниже, обязательно подключают свои панели именно сюда. Поэтому подробно останавливаться на этом не вижу смысла, детальные скриншоты будут дальше.
Статический контент
Статический контент в CMS — основа всего. В Symfony CMF за статический контент отвечает ContentBundle, который обеспечивает базовую реализацию классов статических документов, включая многоязычность и связь с маршрутами.
Основой бандла является класс StaticContent
, состав которого окажется знакомым многим — говорящие сами за себя поля типа title
, body
, ссылка на родительский документ и так далее. Кроме того, он реализует два интерфейса:
RouteReferrersInterface
, обеспечивает связку с маршрутамиPublishWorkflowInterface
, помогает показывать или скрывать контент с помощью заданных дат публикации
Для мультиязычных документов предусмотрен MultilangStaticContent
— все то же самое, но добавлен перевод полей и объявление локали. Как делается перевод — мы уже видели в первой части статьи.
Бандлу полагается контроллер. ContentController
состоит из единственного indexAction
, который на входе принимает желамый документ и рендерит его на нужном языке, если с параметрами публикации все в порядке. Опционально можно задать шаблон, с которым будет выводиться страница. Если не задать — будет взят тот, что указан по умолчанию.
Роутинг
В современных больших сайтах количество материалов может легко измеряться тысячами. Помножить на количество переводов. Добавить необходимость постоянных правок материалов и URL-ов к ним во имя поисковой оптимизации.
При этом, заметьте, такими вещами обычно занимается администратор сайта (вебмастер, контент-менеджер, сеошник), а не разработчик.
Какие требования предъявляются к роутингу в таком случае?
- URL задается пользователем
- поддержка многосайтовости
- поддержка многоязычности
- древовидная структура
- контент, меню и маршруты должны быть разделены
Если вспомнить стандартный роутер Symfony 2, становится понятно, что такой гибкости там не достичь. Роуты явно прописаны в конфиге для каждого контроллера и пользователю менять их попросту не дают. Максимум, на что можно рассчитывать — это какой-нибудь /page/{slug}
, который можно править из админки.
Давайте посмотрим, как выглядела схема функционирования на голом 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
. Первый заменяет стандартный SF2-роутер и, несмотря на название, работу роутера (определение контроллера для обработки запроса) на самом деле не выполняет. Вместо этого он дает возможность добавлять свои роутеры в список-цепочку. В цепочке обработать запрос попробуют все сконфигурированные роутеры по очереди, в порядке приоритета. Сервисы роутеров ищутся по тегам.
cmf_routing:
chain:
routers_by_id:
# включаем the 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
.
Маршрут определяет, какой контроллер будет обрабатывать определенный запрос. DynamicRouter
использует несколько методов в порядке убывания приоритета:
- явно:
Route
-документ сам точно объявляет конечный контроллер, если таковой возвращается из вызоваgetDefault('_controller')
. - по псевдониму: маршрут возвращается значение
getDefault('type')
, которое сопоставляется с конфигурацией изconfig.yml
- по классу:
Route
-документ должен реализоватьRouteObjectInterface
и вернуть объект дляgetContent()
. Возвращаемый тип класса опять же сопоставляется с конфигом - по умолчанию: будет использоваться дефолтный контроллер, если таковой указан сконфигурирован
Аналогично (явно или по классу) маршрут может задавать и шаблон, с которым должна рендериться страница.
По желанию при помощи вышеупомянутого RouteObjectInterface
можно научить маршрут возвращать экземляр модели, ассоцированный с ним.
Поддерживаются и редиректы. Вообще есть интерфейс RedirectRouteInterface
, но для PHPCR-ODM готова реализация в виде документа RedirectRoute
. Он может перенаправлять на абсолютный URI и на именованный машрут, сгенерированный любым роутером в цепочке.
Еще одна важная фича, о которой может быть интересно узнать тем, кто с Symfony не работал — это двунаправленность роутера. Помимо распознавания маршрутов на основе заданных параметров, эти маршруты можно и генерировать, передавая параметры как аргументы. В отличие от стандартного роутера SF2, в качестве параметра для функции path()
можно передавать не только заданное в конфиге имя маршрута, но и реализацию RouteObjectInterface
, RouteReferrersInterface
(то есть объект-маршрут), либо ссылку на объект в репозитории, используя его content_id:
{# myRoute это объект класса SymfonyComponentRoutingRoute #}
<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
и MultilangMenuNode
. MenuNode
содержит информацию об отдельном пункте меню: label
, uri
, список дочерних пунктов children
, ссылку на маршут, связанный Content-элемент, плюс список атрибутов attributes
, благодаря которому можно настраивать вывод меню.
Класс MultilangMenuNode
расширяет MenuNode
для поддержки мультиязычности: добавлено поле locale
для определения перевода, к которому принадлежит пункт и label
с uri
, помеченные как translated=true
. Это единственные поля, которые различаются между переводами.
Для интеграции с админкой предусмотрены панели и сервисы для SonataDoctrinePhpcrAdminBundle. Панели доступны сразу, но чтобы использовать их, надо явно добавить и в дашборд.
Конфигурируется бандл как обычно, но все параметры опциональны.
Связь между роутингом, меню и контентом продемонстрирована тут:
Блоки
Предусмотрен и бандл для работы с блоками. Блоки могут реализовывать какую-то логику или просто возвращать статичный контент, который можно вызвать в любом месте шаблона. BlockBundle основывается на SonataBlockBundle и где нужно, заменяет компоненты родительского бандла на свои, совместимые с PHPCR.
Типичные блоки с главной страницы
Внутри бандла представлены несколько типовых блоков:
StringBlock
— блок с единственным полемbody
, который просто рендерит строку в шаблоне, даже не окружая ее какими-либо тегамиSimpleBlock
— кbody
добавляетсяtitle
ContainerBlock
— рендерит заданный список блоков (включая другие блоки-контейнеры)ReferenceBlock
— может только ссылаться на другой блок. При вызове срабатывает так, как если бы вызывался блок, на который указывает ссылка.ActionBlock
— рендерит результат выполнения определенного экшена из контроллера, можно передать желаемые параметры запросаRssBlock
— показывает RSS-фид с указанным шаблономImagineBlock
— используется LiipImagineBundle, чтобы выводить картинки прямиком из PHPCRSlideshowBlock
— особая разновидность блока-контейнера, которая позволяет обернуть любые блоки в разметку, чтобы можно было организовать слайдшоу. Примечательно, что JS-библиотеку для этого нужно выбрать самому, в комплекте ее нет.
Можно создавать и свои блоки.
Механизм кэширования вывода блоков работает поверх SonataCacheBundle, правда, в BlockBundle отсутствуют адаптеры для MongoDB, Memcached и APC — придется довольствоваться Varnish или SSI.
Выводятся блоки при помощи Twig-функции sonata_block_render()
, только в отличие от оригинального бандла в качестве аргументов передается имя блока в PHPCR.
Frontend/Inline Editing
Редактирование-на-лету реализовано с помощью нескольких компонентов.
Первый — RDFa-разметка. Это способ описать метаданные в HTML в стиле микроформатов, но с помощью атрибутов.
<div id="myarticle" typeof="http://rdfs.org/sioc/ns#Post" about="http://example.net/blog/news_item">
<h1 property="dcterms:title">News item title</h1>
<div property="sioc:content">News item contents</div>
</div>
После этого код выше перестает быть «тупым» набором DOM-элементов, потому что информацию из атрибутов можно удобно извлечь в JS-код и связать ее с моделями и коллекциями Backbone.js при помощи VIE.js — это второй компонент.
Третьим в цепочке выступает create.js, который избавляет нас от необходимости придумывать интерфейс редактирования.
Схема работы create.js
create.js работает поверх VIE.js на jQuery-виджетах. Что он может?
- изменять содержимое RDF-размеченных элементов с помощью редакторов — Aloha, Hallo, Redactor, ckEditor
- используя localStorage, обеспечивать поддержку сохранения-восстановления правок до того, как они уйдут в CMS
- управлять уведомлениями, появляющими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 SymfonyComponentFormFormBuilderInterface;
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.
Перейдем к делу
Можно сразу скачать подготовленный мной образ виртуальной машины для VirtualBox, где установлено и настроено все, включая разные бэкенды. Для удобства можно прописать к себе в hosts-файл ip_виртуалки cmf-sandbox
и зайти туда через браузер, но вообще заходить можно и просто по айпишнику, который она попробует подсказать сразу после логина (дефолтные логин и пароль: symfony
).
Зеркала (.ova-файл, ≈ 1Gb):
Если по каким-то причинам вам не хочется с этим возиться, даю ссылку на онлайн-песочницу, но туда не залезешь поковыряться внутрь, хотя посмотреть на скорую руку тоже сгодится.
Вручную устанавливать немного муторно, поэтому подробно останавливаться на каждом шаге, описанном в инструкции я не буду. Просто кратко пройдусь по основным моментам.
Требования к машине для запуска CMF немного нестандартные, хотя никакого криминала.
Во-первых, нужно удовлетворить стандартные потребности для Symfony 2 (весьма вероятно, что с этим уже все в порядке):
- установить PHP 5.3.3+
- включить поддержку JSON
- включить поддержку ctype
- в
php.ini
корректно установитьdate.timezone
- поставить PDO-драйвера для Doctrine
Все остальное (APC и так далее) — по желанию.
Далее идут требования Symfony CMF. По умолчанию для хранения данных используется SQLite, поэтому проверьте, чтобы было установлено расширение pdo_sqlite
.
Чтобы использовать другие бэкенды, устанавливаем:
- Apache Jackrabbit и соответственно Java (для каждого дистрибутива свой способ установки). В корне установленной CMF должен быть скрипт
jack
, который скачает и поможет запустить Jackrabbit без лишних телодвижений. Можно воспользоваться им, но Java ставить все равно отдельно. - Midgard2 PHPCR и его расширение для PHP. К сожалению, пакетов для этого пока мало: они либо помечены, как нестабильные (я брал в sid в случае с Debian), либо собраны далеко не под все платформы, либо собраны, но для устаревших версий ОС. В целом, если поискать, можно найти и RPM, и deb-пакеты. На крайний случай расширение можно собрать из исходников, но дело бесполезное — поддержка Midgard2 в CMF все еще сломана.
Также в папке с сэндбоксом лежит написанный мной скрипт switch_backends.py
, который сам подменит конфиг на нужный (оригинальные файлы правятся в app/config/phpcr/
) и почистит production-кэш, чтоб все это взлетело. По понятным причинам я пока закомментировал midgard-варианты — все равно они не работают.
Хочу предостеречь от соблазна набрать в консоли git pull
или composer update
— как я уже говорил в начале статьи, правки в master-ветке нарушают работоспособность системы, ждите очередного стабильного релиза.
Резюме
Итак, несмотря на многочисленные «но», проект выглядит интересно. На удивление удачно решены некоторые фундаментальные проблемы контент-менеджмента (например многоязычность и маршрутизация/меню). Разработка медленно ведется крайне ограниченным кругом людей, у которых и без того есть работа, поэтому сейчас лучшая помощь — форк на гитхабе и полезный пулл-реквест, будь то правка документации или исправление ошибок. Популяризация CMF только впереди (попробуйте поискать в сети материалы, их практически нет), вся надежда только на опенсорс и коммьюнити.
Использовать сейчас CMF в продакшене может быть рисково, но тем менее, стоит быть в курсе. Кто его знает, может с тех миллионов евро что-нибудь сюда перепадет и через пару лет мы увидим замечательный продукт?
На этом все. Небольшой список полезных ссылок:
Автор: waitekk