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

Symfony CMF. Часть 1, хранение данных

Anna | 31.05.2014 | нет комментариев

Я программирую на Yii теснее два года и в последнее время начал засматриваться на Symfony Framework 2. Отчасти меня привлекает продуманная зодчество, отчасти слабая связность компонентов, отчасти эластичность построенных приложений. Сразу позже того, как я разобрался с основным устройством нового фреймворка, мне стало увлекательно, допустимо ли на нем возвести CMS, а может быть, даже воспользоваться готовой.
Коробочного решения пока не придумали, впрочем, каким-то образом я забрел на сайт плана Symfony CMF и оказался сражен наповал методичным подходом к решению тех задач, с которыми я сталкивался в бытность работы на конвеере по натягиванию дизайна на какой-нибудь Друпал. На Прогре публикаций про именно CMF нет, да и сам план еще дюже сырой, впрочем в перспективе выглядит все увлекательно, хоть местами и есть к чему придраться.

Symfony CMF

План Symfony CMF призван упростить разработку функционала, принадлежащего CMS, для всех, кто использует в работе Symfony Framework 2.
Основные особенности плана:

  • слабая связность компонентов
  • масштабируемость
  • удобство
  • тестируемость

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

  • используете все, что хочется
  • заменяете то, что не нравится
  • игнорируете то, что не требуется

То есть, вам дан комплект модульных инструментов для разработки, а не готовое приложение «под ключ», правда теснее разработаны базовые бандлы, обеспечивающие CMS-функционал.

Для чего еще один CMF?

Не секрет, что на рынке существует довольно много готовых продуктов, как платных (1С-Битрикс, UMI), так и бесплатных (Drupal, MODx, WordPress, Joomla). Следственно абсолютно разумно, что при виде надписиWhatever CMS/CMF может появиться вопрос Для чего вообще делать еще одну CMS? Их же и так полно.
И я безусловно согласен. Как пользователь.

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

Из-за неудовлетворительно продуманной архитектуры при работе с готовыми решениями доводится сталкиваться с:

  • отсутствием ясного распределения логики, конфигурации, содержимого и представления. Довольно припомнить модули Drupal — куча файлов, мешанина из непостижимо как названных глобальных функций, хуков и прочего. Вот кстати недурная статья, в которой данный вопрос обсуждается
  • много легаси-кода, остающегося со ветхих версий. Периодично разработчиками предпринимаются попытки поправить это, обещаются переписывания ядра и прочие радости, но пока новая (переписанная) версия дойдет до стадии «дозволено пользоваться», времени может пройти дюже много
  • Зачастую в системе отсуствуют такие представления как development, testing, и отсутствуют инструменты для деплоя
  • задачи с кэшированием. Где-то его нет, где-то оно есть, но не дает довольную степень эластичности, либо есть задачи с инвалидацией, либо легко он не выручает и т. д.
  • низкая продуктивность на крупных (в данном случае это представление относительное) объемах данных
  • трудность в создании своих компонентов либо переопределении существующих
  • доводится выбирать между предварительно определенными типами данных, либо довольствоваться EAV-хранилищами поверх реляционных СУБД, либо еще что подряннее
  • неудобные шаблонизаторы-велосипеды, придуманные авторами CMS …
  • … и все это как следствие синдрома NIH.

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

  • хранение данных
  • система образцов
  • маршрутизация, ЧПУ, и то, как пользователь может все это контролировать
  • настройка меню
  • контент-менеджмент (редактируемые блоки на странице, фронтенд-правки на живом сайте, заливка файлов)
  • i18n
  • отличная админка

Начнем по порядку с задач.

Задача хранения данных

Исходя из собственно расшифровки представления CMS, становится ясно, что самая значимая составляющая CMS — это хранение данных. Даже огромнее — CMS должна обеспечивать хранение данных с различными свойствами. Скажем, для материалов типа BlogPost либо NewsItem дозволено сделать всеобщие поля title иbody, а дальше пойдут отличия — к новостям может потребуется прикреплять картинки.

Предположим себе интернет-магазин. Что хранится в базе данных? Как минимум — изложения товаров и история заказов. В различие от первого, для второго спроектировать схему хранения значительно проще, правда абсолютно видимо, что оба друг без друзья существовать не могут. Отсель следующее требование:CMS должна иметь вероятность ссылаться на контент как внутри CMS, так и в других частях системы.

Сам контент на сайте Почаще каждого организован в виде древовидной конструкции, в чем-то повторяющей файловую систему. Авторы сайта при этом хотят по-различному организовывать контент в зависимости от своих нужд, а также эластично настраивать меню и адреса материалов. Таким образом, CMS должна представлять данные в виде древовидной конструкции и уметь обслуживать несколько самостоятельных деревьев единовременно.

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

В крупных организациях нередки обстановки, когда материал перед возникновением на сайте проходит несколько этапов проверки, взамен публикации одним кликом — CMS должна поддерживать перемещение и экспорт контента между деревьями. А для истории было бы недурно беречь версии контента, которые дозволено восстановить в всякий момент времени.

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

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

Content Repository

Становится ясно, что одним «мускулом» не отделаешься. Реляционные базы данных с такими задачами легко не совладают, правда существуют алгорифмы типа Materialized path либо Nested set, которые разрешают беречь в плоских базах данных конструкцию графа. Но даже если отдельно взятая реализация будет трудиться, она скорее каждого будет жестко привязана к определенному движку, а это теснее нехорошо, потому что лишает нас воли и эластичности. РСУБД винить не нужно — они задуманы для вовсе иных задач, им необходимы Отчетливо описанные данные, а не деревья, состоящие из слабо структурированных элементов.

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

JCR-170

Задача хранения данных для документо-ориентированных систем появилась много лет назад, следственно еще в первой половине двухтысячных люди из компании Day Software (а именно — David Ny: PHPCR presentation”></block></home><contact title=”Contact” content=”phpcr-users@groups.google.com”></contact></pages></cms></root>
Пока ничего неестественного.
Разглядим чуть подробнее, с чем придется трудиться.

Узлы

 

  • узел — именованный контейнер, у которого неизменно есть родитель
  • напоминает XML-элементы
  • узлы дозволено создавать, удалять, модифицировать, копировать
  • путь к узлу состоит из пути родительского узла и имени нынешнего узла:
  • Путь: /cms/pages/home
  • Родительский путь: /cms/pages
  • Имя узла: home

 

Свойства узлов

 

  • у узлов есть именованные свойства, хранящие значения
  • напоминают XML-признаки
  • типы данных: STRING, URI, BOOLEAN, LONG, DOUBLE, DECIMAL, BINARY, DATE, NAME, PATH, WEAKREFERENCE, REFERENCE
  • типы (WEAK)REFERENCE создают ссылки на другие узлы
  • узлы и свойства могут иметь пространства имен: jcr:createdjcr:mimeTypephpcr:class

 

Основные типы узлов

 

  • определяют разрешенные для применения имена, а также типы свойств и дочерних узлов
  • у всякого узла должен быть установлен стержневой тип
  • для хранения-чего-желательно применяется nt:unstructured
  • среди прочих встроенных типов есть nt:address, nt:folder, nt:file и другие
  • для создания своей схемы дозволено определять новые типы узлов

 

Mixin-типы узлов

 

  • у основных типов нет множественного наследования
  • но есть миксин-типы, которые добавляют узлам [trait](https://en.wikipedia.org/wiki/Trait_(computer_programming))-сходственную функциональность
  • миксин-типы могут быть назначены узлу во время его жизни

Пример: возможен, у нас есть качество jcr:uuid, в котором хранится неповторимый идентификатор. Зная uuid, мы можем сделать миксин mix:referenceable, а на его основе mix:versionable (но тогда нам еще понадобится иметь свойства jcr:versionHistoryjcr:predecessorsjcr:baseVersionjcr:isCheckedOut,jcr:mergeFailed и т. д.)

Рабочие пространства

 

  • рабочих пространств может быть несколько, всякое хранит в себе свое дерево узлов
  • напоминает файловую систему Unix и ветки в Git/SVN, всякую дозволено клонировать и исполнять слияния
  • могут применяться самостоятельно

Процесс получения данных в PHPCR

А сейчас немножко примеров того, как со всеми этим трудиться:

Создание сессии

use PHPCRSimpleCredentials;

// конфигурация, зависимая от определенной реализации бэкенда
use JackalopeRepositoryFactoryJackrabbit as Factory;
$parameters = array(
    'jackalope.jackrabbit_uri'
        => 'http://localhost:8080/server',
);
$repository = Factory::getRepository($parameters);

// а вот дальше все стандартно для всяких реализаций
$creds = new SimpleCredentials('admin','admin');
$session = $repository->login($creds, 'default');

CRUD-операции

$root = $session->getRootNode();

// узлы неизменно добавляются как дочерние для существующих
$node = $root->addNode('test', 'nt:unstructured');

// новейший узел сразу доступен в нынешней сессии
$node = $session->getNode('/test');

// создать/обновить качество
$node->setProperty('prop', 'value');

// сейчас узел доступен для всех сессий
$session->save();

// удалить узел и все дочерние узлы
$node->remove();

// выдаст ошибку, если узел кто-то редактировал в иной сессии
$session->save();

Обход дерева

$node = $session->getNode('/site/content');

foreach ($node->getNodes() as $child) {
    var_dump($child->getName());
}

// либо короче
foreach ($node as $child) {
    var_dump($child->getName());
}

// фильтр по имени
foreach ($node->getNodes('di*') as $child) {
    var_dump($child->getName());
}

Версионность

// включаем версионность
$node = $session->getNode('/site/content/about');
$node->addMixin('mix:versionable');
$session->save();
// создаем исходную версию
$node->setProperty('title', 'About');
$session->save();

// чек-ин (создаем версию)
// и чек-аут (подготовка к последующим обновлениям)
// итог этих операций доступен сразу же без вызова $session->save()
$vm = $session->getWorkspace()->getVersionManager();
$vm->checkpoint($node->getPath());

// обновляем узел
$node->setProperty('title', 'Ups');
$session->save();

// создаем еще одну версию, оставляем в состоянии «только для чтения»
$vm->checkin($node->getPath());

$base = $vm->getBaseVersion($node->getPath());
$current = $base->getLinearPredecessor();
$previous = $current->getLinearPredecessor();

// берем слепок ветхой версии
$frozenNode = $previous->getFrozenNode();
echo $frozenNode->getProperty('title'); // About

// восстанавливаем живые данные из нынешней версии
$vm->restore(true, $previous);

$node = $session->getNode('/site/content/about');
echo $node->getProperty('title'); // About

Поиск

$qm = $workspace->getQueryManager();

// в SQL2 оператор звездочки "*" не возвращает все столбцы
// а по крайней мере путь и степень соответствия
// (см. http://docs.jboss.org/exojcr/1.12.13-GA/developer/en-US/html/ch-jcr-query-usecases.html#d0e3332)
$sql = "SELECT * FROM [nt:unstructured]
    WHERE [nt:unstructured].type = 'nav'
    AND ISDESCENDANTNODE('/some/path')
    ORDER BY score, [nt:unstructured].title";
$query = $qm->createQuery($sql, 'JCR-SQL2');
$query->setLimit($limit);
$query->setOffset($offset);
$queryResult = $query->execute();

foreach ($queryResult->getNodes() as $node) {
    var_dump($node->getPath());
}

Другие примеры кода дозволено посмотреть в этой презентации.

Впрочем, возвратимся к манящей мысли о различных бэкендах.

Имеем мы на нынешний момент не так много реализаций, но и те теснее увлекательные:

  • Midgard2 PHPCR
  • Jackalope
  • поддерживает Jackrabbit
  • поддерживает Doctrine DBAL (хранение данных поверх реляционных БД)
  • поддерживает MongoDB (на самом деле нет)

 

Midgard2 PHPCR

Midgard2 — репозиторий контента с открытым начальным кодом с биндингами для C, Python и PHP.

Немножко отличаясь терминологией от JCR, Midgard2 предоставляет те же самые функции для доступа к контенту через Midgard2 PHPCR с поддержкой растяжения php5-midgard2. Будучи построенным поверх GNOME-библиотеки libgda, Midgard2 поддерживает значительный список реляционных баз данных, в которых дозволено поместить свой репозиторий.

Сразу скажу про ложку дегтя — PHP-растяжение собрано для довольно малого числа ОС:

  • под Debian 7 Wheezy пакет все еще находится в нестабильных ветках (и заслуженно — безмолвно роняет PHP-FPM в сегфолт).
  • для CentOS пакеты есть либо устаревшие, либо не для всех аама библиотека этого не требует) и не унаследованный от каких-то базовых абстрактных классов. Такая сущность не должна иметь в себе способы с ключевым словом final, реализовывать способы clone() и wakeup(), либо реализовывать, но делая это дюже осмотрительно. Сама по себе сущность состоит из свойств, фиксируемых в хранилище. От того что ODM работает поверх библиотеки Doctrine Common, реализующей базовый функционал (аннотации, кэширование и автозагрузка классов), маппинг свойств в хранилище данных к свойствам класса производится приятелем каждому путем — через аннотации в PHP-комментариях, либо YAML/XML-конфигах. У всякого документа есть заголовок (title) и содержимое (content). Все документы организованы в виде дерева и могут ссылаться на другие документы. Взгляните на пример документа:
    namespace Demo;
    
    use DoctrineODMPHPCRMappingAnnotations as PHPCRODM;
    
    /**
     * @PHPCRODMDocument
     */
    class MyDocument
    {
        /**
         * @PHPCRODMId
         */
        private $id;
        /**
         * @PHPCRODMParentDocument
         */
        private $parent;
        /**
         * @PHPCRODMNodename
         */
        private $name;
        /**
         * @PHPCRODMChildren
         */
        private $children;
        /**
         * @PHPCRODMString
         */
        private $title;
    
        /**
         * @PHPCRODMString
         */
        private $content;
    
      // и еще горстка геттеров и сеттеров для записи и чтения свойств
    }
    

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

    Для неизвестных с паттерном Data mapper может показаться, что такие классы немножко схожи наActive record (здравствуй, рельсовики и Yii-шники), впрочем таковыми они все равно не являются.

    Как трудиться с таким документом?

    require_once '../bootstrap.php';
    
    // вначале находим корневой узел
    $rootDocument = $documentManager->find(null, '/');
    
    // создаем новейший документ
    $doc = new DemoDocument();
    $doc->setParent($rootDocument);
    $doc->setName('doc');
    $doc->setTitle('My first document');
    $doc->setContent('The document content');
    
    // создаем 2-й, дочерний для первого
    $childDocument = new DemoDocument();
    $childDocument->setParent($doc);
    $childDocument->setName('child');
    $childDocument->setTitle('My child document');
    $childDocument->setContent('The child document content');
    
    // уведомляем администратору документов о том, какие документы у нас готовые для сохранения
    $documentManager->persist($doc);
    $documentManager->persist($childDocument);
    
    // отправляем все метаморфозы, вставки и т.д. в бэкенд
    $documentManager->flush();
    
    require_once '../bootstrap.php';
    
    $doc = $documentManager->find(null, "/doc");
    echo 'Found '.$doc->getId() ."n";
    echo 'Title: '.$doc->getTitle()."n";
    echo 'Content: '.$doc->getContent()."n";
    foreach($doc->getChildren() as $child) {
        if ($child instanceof DemoDocument) {
            echo 'Has child '.$child->getId() . "n";
        } else {
            echo 'Unexpected child '.get_class($child)."n";
        }
    }
    
    // удаляем документ
    $documentManager->remove($doc);
    
    $documentManager->flush();
    

    Малое примечание — в ORM привычно получать данные с поддержкой запросов. В ODM для этого нужно применять иерархию. Однако, дозволено и запросы делать, если крепко хочется.

    В PHPCR ODM теснее реализованы две дюже значимые функции — версионность и многоязычность. Начнем с первой.

    Версионность в PHPCR бывает 2-х видов — simpleVersionable и versionable. Для примитивный версионности предусмотрены checkin/checkout-способы и линейная история изменений. Чекин создает новую версию узла и делает доступной только для чтения, Дабы что-то записать, необходимо сделать чекаут.

    Полная версионность вдобавок к этому поддерживает нелинейную историю версий (для которой хелпер-способов у PHPCR-ODM пока нет) и метки (которые планируется добавить, как только их начнет поддерживать Jackalope). Для всякого узла дозволено добавить метку к версии, но за всю историю всякого узла метка не может повторяться двукратно (то есть, если хочется пометить иную версию, необходимо вначале снять ее со ветхой версии).

    Полная версионность соответсвует типу mix:versionable из PHPCR и разрешает создавать ветвление. К сожалению, PHPCR Version API не поддерживается PHPCR ODM целиком, для полновесной работы пока доводится трудиться с PHPCRVersionManager напрямую через PHPCR-сессию. Подробнее об этом здесь издесь.

    Имена версий генерируются PHPCR и не контролируются приложениям. Представление коммит-месседжа отсутствует (легко пока не понадобилось). В любом случае, никто не мешает завести в документе поле под это дело.

    Ветхие версии недоступы для модификации и сохранения изменений (исключение — при применении способов restoreVersion() и removeVersion().

    Дабы включить поддержку версионности для какого-то документа, необходимо очевидным образом указать это в аннотации:

    /**
     * @Document(versionable="simple")
     */
    class MyPersistentClass
    {
        /** @VersionName */
        private $versionName;
    
        /** @VersionCreated */
        private $versionCreated;
    }
    

    Документации по тому, как трудиться с версиями, фактически нет, авторы советуют обращаться в Phpdoc-комментарии в начальном коде. Следственно надолго задерживаться на таких высоких материях не будем, посмотрим маленький пример и пойдем дальше.

    $article = new Article();
    $article->id = '/test';
    $article->topic = 'Test';
    $dm->persist($article);
    $dm->flush();
    
    // создаем слепок версии документа на основе нынешнего состояния
    $dm->checkpoint($article);
    
    $article->topic = 'Newvalue';
    $dm->flush();
    
    // получаем информацию о версиях
    $versioninfos = $dm->getAllLinearVersions($article);
    $firstVersion = reset($versioninfos);
    // используем ее для приобретения слепка ветхой версии
    $oldVersion = $dm->findVersionByName(null, $article->id, $firstVersion['name']);
    
    echo $oldVersion->topic; // "Test"
    
    // ищем новейшую версию
    $article = $dm->find('/test');
    echo $article->topic; // "Newvalue"
    
    // устанавливаем ветхую версию в качестве последней
    $dm->restoreVersion($oldVersion);
    
    // документ обновился
    echo $article->topic; // "Test"
    
    // создаем еще одну версию, Дабы продемонстрировать удаление
    $article->topic = 'Newvalue';
    $dm->flush();
    $dm->checkpoint($article);
    
    // удаляем ветхую версию из истории (с последней так сделать не дадут)
    $dm->removeVersion($oldVersion);
    

    Сейчас про многоязычность. Всякое качество документа дозволено пометить как переводимое. Невзирая на то, что при переводе в дереве будет склонирован документ целиком, поля, которым перевод не требуется, копироваться почем напрасно не будут. Всякий раз указывать язык для работы очевидно не требутся — достаточно один раз сказать DocumentManager, какой язык нам необходим, и дальше для всех вызовов типа find() и создания новых документов будет применяться именно он. Комфортно:

    /**
     * @PHPCRODMDocument(translator="attribute")
     */
    class MyPersistentClass
    {
      /**
       * Нынешняя локаль документа
       * @Locale
       */
      private $locale;
    
      /**
       * Непереведенное качество
       * @Date
       */
      private $publishDate;
    
      /**
       * Переведенное качество
       * @String(translated=true)
       */
      private $topic;
    
      /**
       * Зависимая от языка картинка
       * @Binary(translated=true)
       */
      private $image;
    }
    

    И вот непринужденно работа с переводом полей:

    // предварительно загружаем DocumentManager (пример есть в документации)
    
    $localePrefs = array(
        'en' => array('fr'),
        'fr' => array('en'),
    );
    
    $dm = new DoctrineODMPHPCRDocumentManager($session, $config);
    $dm->setLocaleChooserStrategy(new LocaleChooser($localePrefs, 'en'));
    
    // после этого используем перевод:
    
    $doc = new Article();
    $doc->id = '/my_test_node';
    $doc->author = 'John Doe';
    $doc->topic = 'An interesting subject';
    $doc->text = 'Lorem ipsum...';
    
    // сберегаем документ на английском
    $dm->persist($doc);
    $dm->bindTranslation($doc, 'en');
    
    // изменяем содержимое одного из полей и сберегаем на французском
    $doc->topic = 'Un sujet intm->flush();
    
    // получаем документ на языке по умолчанию
    // (английский в данном случае)
    $doc = $dm->find(null, '/my_test_node');
    
    // получаем документ на французском
    $doc = $dm->findTranslation(null, '/my_test_node', 'fr');
    $doc->title = 'nouveau';
    $dm->flush(); // обновляем документ на французском, язык отслеживается администратором документов
    

    На нынешний момент для всех ссылок, включая родительские и дочерние, предусмотрен фоллбек к языку по умолчанию, от того что на время сессии дозволено отслеживать только один документ. Авторы пока не уверены, как поступить отменнее, причем так же открытым пока висит вопрос обработки незавершенных документов (когда поля, заявленные в документе, на деле не существуют). Однако, время покажет, как эти задачи будут решены.

    Помимо этого в грядущем улучшат логирование, кэширование итогов, начнут обновлять документацию (с этим пока дюже скромно), допустимо прикрутят поддержку Solr/ElasticSearch в связке с Doctrine DBAL и испробуют доделать реализацию для MongoDB. Теперь разработчики посматривают на следующую мажорную версию Jackrabbit (под кодовым именем Oak) и даже провели тесты на совместимость, впрочем первоочередной задачей все-таки является движение PHPCR и его включение в настоящие приложения.

    Подведем результат. При применении ODM стек выглядит дальнейшим образом:

    • PHP Content Repository с бэкендом в виде Jackalope либо Midgard2 (данные хранятся в Jackrabbit либо РСУБД)
    • PHPCR-ODM поверх Doctrine Common для комфортной работы самостоятельно от бэкенда
    • непринужденно код приложения.

    Продолжение во 2-й части статьи.

Источник: programmingmaster.ru

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