DC.CMS и причем тут казалось бы Уроборос?! (часть №2)

Решения.
DC.CMS система управления содержанием сайтаПомните, в первой статье я упомянул три вопроса, которые почти всегда встают при создании CMS системы: 1. дерево, 2. модули, 3. интерфейс. Ряд вопросов уже получили своё решение. На данный момент я считаю их оптимальными, но ждущими ряда вложений в развитие своих сил. Но эти решения работают и причем вполне успешно.

Если вопрос 2 почти уже выше озвучен, это HMVC возможностей которого мне пока хватает, но я уже присматриваюсь к новому расширению. Связь модуля с «типом страницы» для наследования повторяющихся модулей и индивидуальная связь с каждой новой страницей как возможность наполнять индивидуальное содержание для страницы. Проще говоря. Модуль содержит ряд действий, которые он может осуществлять во благо сайта. От нас зависит, сможем ли мы их правильно установить в нужные нам страницы, и есть ли такая возможность у действия.

То с первым вопросом было много размышлений. Как представлять в базе данных дерево сайта, как удобно будет доставать его, как можно поддерживать целостность, сортировать в нужном пользователю порядке ветки и т.д. и т.п. Решений предлагалось много, вплоть до хранить пути а-ля /ru/news/russia/ прям в базе, управлять регулярками и так же находить всё дерево для «карты сайта». Но слава Оптимусу Прайму до этого не дошло. Появилась рациональная идея хранить дерево в двойном виде. Когда это нужно и проще достать одним запросом, то дерево представляет из себя простой список смежности id | parent_id. Когда нужно дерево пересчитывается по номеру списка ord и ведется себя как Nested Sets, это очень удобно, так же одним запросом выдернуть всё дерево, получить например всех-всех предков текущей ветки или же всех родителей текущей ветки. Это очень гибко, главное пересчет дерева делается при вставке, удалении ветки и ощутимо только для администратора сайта.
Все методы управления деревом и его информацией были вынесены в отдельную модель Page чтобы она стала ядром системы.

Таким образом, у нас получилась простая в понимании система действий ядра:
1. проверить адрес страницы, права, определить тип страницы, достать для неё представление (View/layouts) связанное с типом, проверить авторизацию пользователя
2. найти все модули связанные с типом, выполнить их.
3. найти все модули связанные именно с самой страницей. Да, в содержимом страницы может выполняться много модулей последовательно, например, сперва выполняет текст, потом фотоальбом, потом снова текст. Это довольно удобно для формирования сложных страниц или с рядом зависимых действий.
4. отдать, всю собранную информацию пользователю

Роутинг и параметры.
Как я уже говорил, мне чисто эстетически важны красивые URL, считаю их не только полезными, но и очень информативными для навигации и понимания сайта. Потому адреса во всех моих система одна из составляющих всей работы и логики. Но в DC.CMS была одна заноза. Нужно было давать понять возможность удобно передавать параметры. Стандартная схема принятая в CI controller/method/param1/param2/…/paramXXX нас уже не устраивала. Писать под каждый модуль систему роутинг правил так же не хотелось, это бы ограничивала пользователя в создании страниц.
К примеру, у нас имеется раздел /ru/news/ у него правило, что /ru/news/:id/:country то это могло бы помешать администратору сайта создавать подразделы (детей страницы) у этого раздела, так как сложно было бы определить /ru/news/2010/Russia это правила роутинга или же созданные подразделы. Получилось бы двояко и решению бы приходилось постоянно проверять базу страниц.
Моим решением было просто разделение параметров в конце нашего URI знаком «точка». Это простое решение в целом и оставляет однозначность, так как было решено запретить использовать точки в названии латинском страниц и адреса не потеряли своей элеганности /ru/news/date.20-10-2010.html выглядит на мой взгляд вполне едабельно. А на входе нашего модуля у нас всегда есть ассоциативный массив date=>20-10-2010, html=>NIL

Параметры модулей.
Порой модулям нужно больше свободы, как я понял создавать универсальный интерфейс для конфигурирования каждого модуля не самая целесообразная и быстрая задача. Потому простейшим решением было оставить конфигурирование простым текстовым полем в формате YAML. Это очень удобно и читабельно для людей, а модулю это уже будет преобразовано в понятный ему вид ассоциативных массивов.
url: info@mrak7.com
can_read:
  users: [1, 2, 3,4]
can_write:
  users: [6]


Пользовательские права.
Контроль нужен и в целом есть много подходов реализации его. Кто-то предпочитает задавать ACL для своих контроллеров, в случае какого-то индивидуального проекта это удобно. В случае CMS с динамически изменяемыми группами, пользователями и свойствами прав ACL становится динамическим и очень громоздким.
Полагаться число на «Роли» мне лично показалось не плохим решеним, простым и очень легко реализуемым. Так, что пока похвастать нечем, у нас простые групповые роли: admin, editor, user. И проверка вида $this->model(‘user’)->is(‘admin’) не редкость.
Но сразу скажу об одной интересной идеи, каркас, интерфейс и связи которой уже есть в системе и работаю со стороны администрирования. но пока еще было необходимости работать с фронт стороны сайта.
Это групповые права замещаемые индивидуально страничными и пользовательскими.
Есть три свойства: read ®, write (w), delete (d)
Их можно назначить как для группы, например Admin это все три пункта 111, Editor 101, Users 100 — так и для определенной страницы например /ru/news/ будут для групп admin 111, а для editor 110. Так например на этой странице редактор не сможет удалять новости, что он может делать на других страницах. Так же возможно индивидуальное назначение подобных масок прав для отдельных пользователей системы. В общем это удобно и на будущее будет очень полезно, тем более если нужно будет расширить кол-во таких свойств.

DC.CMS права доступа к страницам

В целом это получается на столько просто, что лозунг KISS выполнен на все 100 процентов с премиальными. Премиальные получились, кстати знатные, что я вижу как CMS система начинает перерастать сам Framework CodeIgniter.

Кэширование.
К примеру, в один из моментов была переписана полностью система кэширования фреймворка. Сам фреймворк позволяет кэшировать два вида информации. Запросы к базе данных, сериализуя их и складывая в файл-кэш. И полностью подготовленные на вывод страницы. Это две замечательных возможности, которые при больших нагрузках ваших проектов дадут очень весомую фору и передых серверу.
Но так как в CMS мы отошли от классического MVC кэширование страниц в папки вида controller/method как предполагает фреймворк нас не устраивает. Пришлось переписать для учета нашего URI полностью.
Файлы. Кэширование в файлы и разбитые по своим папочкам очень хорошо. Но если это не массовый хостинг или загруженный хотя бы десятком виртуальных систем VDS. Дисковый массив может быть и один, но чтение нагружает очень дико. Потому с удовольствием пришлось добавить возможность складывать результаты всех кэшей в Memcache – это просто прелесть такая.

DC.CMS кэш, оптимизация базы данных

Модули, контроллеры, наследование. Ряд расширений на будущее пришлось произвести с контроллерами, которые стали модулями. Несколько удобных методов пришлось привнести уже в их родителя DC_Module. Но всё же ряд вещей, которые сделаны в CodeIgniter не нравится до сих пор, почему не был к примеру взят подход именования конроллеров например NewsController, а модели бы получались именованными как $news = new News(); и легко бы садились на автозагрузку, здесь же наоборот наш контроллер именуется News, а модель с каким либо суффиксом NewsModel например. Не очень разительно, в своём форке я эту ошибку устранил.

Модели. Как раз они очень хорошо расширились, но всё же не желание CI наследовать их от ActiveRecord тоже сказывается, что пришлось придумать ряд магических методов как find, find_by_* и т.п. Зато код модулей-контроллеров стал удобнее и понятнее, причем автозагрузка и виртуализация срабатывает машинально, сейчас поясню, вот код:
$data[‘user’] = $this->model(‘user’)->find_by_login( ‘MpaK’, 1 );
return $this->tpl->( ‘index’, $data );


О чем он говорит?
Первое, что мы пытаемся просмотреть не загружена ли уже эта модель и вернуть её, если незагружена, то model(‘user’) пытается найти в текущем каталоге модуля нашу модель User_mod.php, если нет возможности найти, то поиск происходит выше, уже в ядре системы. Если найти так же не удалось, то создается виртуальная модель из нашего MY_Model класса с уже имеющимися методами. Потом проходит инициализация, при не определенности связи модели с таблицей её автоматически связывают с названием во множественном числе, у нас в пример это будет таблица users.
Дальше у объекта созданной модели вызывается магический метод, который пытается найти в нашей таблице одного пользователя по полю login, эдакий inspired by Ruby on Rails, примерно код можно представить себе так:
$this->where( ‘login’, ‘MpaK’ );
return $this->find( ‘’, 1 );

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

Конечно, все эти 7 страниц текста могут показать немного сумбурными, но пытался выжать из основных идей самое вкусное и краткое, чтобы показать примерное устройство обычной CMS системы на примере своего продукта. Конечно систем не идеальна, очень не хватает руки дизайнера и работы с юзабилити системы, но это как раз то, чем я хочу занять в ближайшее время, так же официальным сайтом, бесплатной лайт версией системы и конечно же документацией, которая порой может быть важнее даже самых мощных возможностей системы.
Это всё, если у кого есть какие-то пожелания, вопросы или конструктивная критика рад буду обсудить.

Ibragimov Renat
www.mrak7.com

20 комментариев

avatar
  • akhmetov
  • 0
Когда идея доведена почти до конца — это здорово. Поздравляю.

Необходимость отображать по одному адресу несколько модулей на странице (что противоречит MVC, да?), каждый из которых находится в своем блоке в выбранном шаблоне меня самого мучала года с 2004-2005. Но только в 2007 собрался сделать первую версию. Кстати, решив «вопрос номер 2» с типами страниц и наследованиями. Но сама система показалась слишком сложной в плане интерфейса, а проект под который все это делалось утопили менеджеры.

Но в 2007 я сделал более простую систему для небольших сайтов. В этот раз место в шаблоне только одно и нет типов страниц. Скриншоты:

ufacode.ru/uploads/images/00/00/04/2010/09/12/696953.png
ufacode.ru/uploads/images/00/00/04/2010/09/12/80f261.png

На первом видно дерево страниц.
На втором — правка/создание страницы. Когда несколько модулей — выбор какой из них править, если один — то в этом же месте вместо списка отображается область редактирования:
ufacode.ru/uploads/images/00/00/04/2010/09/12/acc177.png

А в 2008 окончательно забросил его. Меньше десятка сайтов на нем.
avatar
  • akhmetov
  • 0
К слову, с 2008 меня мучает уже другая идея ) Думаю, CMS должна объединиться с Document Workflow System. Не такое страшное как SharePoint, но в этом направлении.
avatar
  • akhmetov
  • 0
Ведь у Content Managment System и Document Workflow System много общего. Второе является источником данных для первого. Документ проходит по цепочке исполнителей для публикации — копирайтер, редактор, верстальщик, контент-менеджер.
Правда, спроса нет на такое в малом бизнесе.
avatar
  • MpaK
  • 0
По Workflow тоже были были идеи, но сделать, что-то универсальное на всю CMS это сложно и очень ограничит другие возможности.
В моём выборе скорее проще сделать тот же документооборот отдельным модулем, как раз на базе CMF возможностей, когда не прийдётся решать все иные вопросы, кроме как потока документов и как с ними работать.
avatar
  • MpaK
  • 0
Выглядит симпатично, делалось на голом PHP?
avatar
  • akhmetov
  • 0
Да, внутри ничего такого. Умных слов тогда еще не знал :) Как мы обходились без них? :)
avatar
  • akhmetov
  • 0
Все же думаю, систему стоит развивать в направлении документооборота. Считай, обновили документ «прайс-лист» — и в системе отображаются страницы сайта, которые перестали быть актуальными. Как-то так. Уверен, что так должно быть, но в организациях бардак с документами (лежат по компам сотрудников) и как бороться — не знаю.
avatar
  • Lapteuh
  • 0
Приятно, что Ваша CMS здравствует. :) Но мне вот интересно:
1) Я так понял по тексту, что Вы не использовали ORM, почему, если так?
2) И мне интересно почему отошли от MVC и смешали с модулями?
avatar
  • MpaK
  • 0
Ну не совсем так.

1. ORM используется, просто стандартный паттерн в CodeIgniter'е (фреймворке на базе которой работает CMS) это ActiveRecord, то есть максимально дистанцировались от «голых» SQL запросов. Но при этом нет каких-то огромных XML (YAML) схем и той самой монстроуозности присущей всяческих PHP ORM а-ля Propel или Doctrine.
Расшерение вида моделей я попытался сделать чуть более приближенным к ActiveRecord моделям из Ruby on Rails.

2. Немного описал по этому вопросу в пункте о модулях и почему они нужны. Вкратце, в идеологии MVC контроллер давлеет над всем. Он решает какие данные достать, какой view отобразить и это всё на одной странице. Одна страница — один контроллер.
В CMS системах, требует больше функионала, причем желательно не лезть в код каждый раз если желаешь к примеру «убрать меню из левой колонки и добавить текстовый блок на все страницы в подвале».
Потому получается, что в задаче CMS это определить по адресу страницы какие контроллеры нам нужны, дать им каждому поработать и вернуть результат в общем шаблоне.
В целом HMVC как раз и позволяет превратить связку Controller-Model-View в отдельную единицу — Модуль. На самом деле модуль выглядит как маленький кусочки MVC отстраненный от всех других. Своя структура, свои схожие методы и действия.
avatar
  • Lapteuh
  • 0
Никогда не делал свою CMS. ;)

Я как понимаю, у CMS есть две задачи:
* изменять динамически вид (отображение данных) в зависимости от прихоти пользователя (администратора сайта)
* и расширение функциональности (добавление каких дополнительных возможностей)
— обычно это простые страницы
— или что-то типа базы данных чего-то
— особая бизнес логика, над данными

Решения, как я вижу:
1) Первая проблема можно отделить от второй, просто сделать специализированные контролеры, которые изменяли только виды, т.е. у нас есть «модули», мы в каждом модуле управляем распределением и видимостью блоков.
2) Вторая проблема решается либо «в лоб», под каждый тип свой контроллер, либо можно сделать обобщенный контроллер, а специализация будет инжектироваться отдельно.
3) В идеале слой контроллера будет очень тонким, там будут лишь объекты сервисы, которые инкапсулируют работу с моделью.

По поводу ORM: я тут на вашей стороне, они слишком наворочены, но поскольку Вы используете объекты, то переход к MongoDb будет легким. :)

Плюс можно попробовать приспособить Ваш CMS к Google App Engine, там есть поддержка PHP в JVM через сторонний проект Quercus.
avatar
  • MpaK
  • 0
Попробуйте, писать CMS это действительно интересно. Конечно же если есть опыт это менее болезненно в плане создания велосипедов, но в любом случае даже приятно изучать чужой код с решением какой-то долго волновавшей проблемы.

На GAP я смотрел, пока правда нет задачь для использования таких возможностей, ну и запускать PHP по JVM это на мой взгляд как-то ужасно, уж лучше сразу тогда писать на JRuby или самой Java.

Проблем с MVC в CMS нет, просто не гибко это, придётся часто дергать разработчика отвечающего за какой-то контроллер страницы. Из кирпичей собирать страницы всё же удобнее.
avatar
  • tokernel
  • 0
Вопрос роутинга и параметров, в случае MVC очень проблематичны для многих.

Вот я например имею свою собственную идеалогию роутинга в CMS. Предложу в кратце.

1. Все модули (articles, gallery, etc...) имеют свои так называемые блоки (виджеты).
2. Есть некий «Navigation» модуль (addon), с помощу которого мы строим фронтенд для сайта.
Состоит он из панелей в них же столбцы, в столбцах же виджеты. Почему так решено, будет описана ниже.
(screen пример панелей gnu.am/tmp/navigation-panels-screen.jpg)

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

Почему панели, столбцы, виджеты?

Дело в том, что у многих разроботчикав есть колько такое панятие как left | center | right. Это не хорошо. Ведь может быть случай, когда в «топ» — е нужна ставить лишь один видже, в центре три таких, потом баннер с левого грая до конца страницы до края середины, тоесть правую калонку надо оставить в покое.
(пример gnu.am/tmp/navigation-panels-example1.jpg).
Вот в этом случае этом метод по моему универсален. Ксате, все объекты (панели, столбцы, виджеты) можно перетаскивать на права, на лево, наверх и вниз. Есть еще трюки по поводу стилей, но об этом потом, есле вас поинтересует это тема.

С нетерпением жду ваших советов, вопросов и конечно же критики :)

П.С.
тут описана именна построение объектов а не навигация.
для навигации есть пара штучек. расскожу если вас поинтересует.
avatar
  • MpaK
  • 0
Это очень похоже, что есть как раз в этой системе, только с иными понятиями.

У нас тут так же есть модули News, Articles, Photos каждый модуль это объект со своими методами, которые можно связывать с некой желаемой переменной, например CONTENT и с типом страницы или с id страницы.

Как пример, что для главной страницы мы желаем связать с переменной
$content два модуля подряд News::last(3) и потому Photos::last(10), что как раз получается как бы в вашем случае колонка CONTENT где будут очередно запускаться и выдавать два модуля.

Разумеется методы модулей можно привязывать к разным переменным (колонкам), тасовать между собой и наследуемыми они получаются от ТИПА страницы. Чтобы не приходилось создавая новый раздел заново создавать к нему блоки меню, баннеров, контактов внизу и т.п.

В целом идеи у нас совпали почти по всему, только в разных названиях
переменна =~ колонка
метод модуля =~ виджет.

Вопрос только: как у вас связана вся эта коллекция колонок, с чем и какими параметрами?
Как происходит создание новой страницы, надо ли выбирать какой-то тип или формировать колонки?
avatar
  • tokernel
  • 0
Попробую изложить детально.

1. У нас есть Х «Addon» — ов, у которых есть свои собстенные виджеты, например — «last 5 news», или «show articles by category». (И еще, у нас нету «news», «publications» и.т.п… Есть некий «Articles» модуль, с помощю которого мы можем создать объект «news». Вот пример: Add new section (name = «news» have_categories=yes, и много других опции. Все это делается с помощю администратора CMS, как например в Joomla).

Ну вот, скахем есть у нас созданный «news» у которого есть по умолчанию свои виджеты.

2. Есть модуль «Navigation», который отвечает за навигацию сайта.
2.1. С его помощю мы саздаем группу ссылок, (например — группа ссылок для верхнего мену и группа ссылок для скажем левого навигационного блока). Таким образом, у нас есть возможность иметь не одну а слолько угодно «меню».

2.2. После того как мы саздали блок (группа ссылок), заходим и начинаем создание ссылок: «add new link» или «add new child link (для уже существуещего линка)» поскольку меню можно создовать с дочерными елементами. Линк — он же страница на сайте. У каждого линка есть свой уникальный url, в нашем случае «news». После того как мы создали линк, он еще не активен.

2.3. Нажимаем на «Manage panels» и начинаем строить панели для созданного линка (news). Как я уже говорил, Панели, в панелях колонки, в калонках же ставим виджеты сколько угодно.
Вот пример: gnu.am/tmp/navigation-panels-screen.jpg

2.4. Теперь у нас есть «меню» в котором есть ссылка «news».
Создовая для этой ссылки новые панели, колонки, начинаем включить для каждой колонки виджеты.

2.5. Как это происходит?
Как мы помним, у каждого модуля есть свои собственные виджеты.
На калонке, нажав на кнопку «add widget» мы переходим на строницу, где в списке представлены все активные виджеты всех модулей. Например:

Active widgets.
— [Articles]
show articles by category
news
publications
show last 5 articles
news
publications
[Picture gallery]
show pictures
my last gallery
gall_xxx
show slideshow
my last gallery
gall_xxx
— Все виджеты модулей могут модифицыроватся под нужды клиента.
Например капируем show_articles.php в папку customs с именим xxx_show_articles.php где xxx это уникальный текстовый id для текущего раздела, например news, и тогда автоматически будет загружен xxx_show_articles.php который был нами модифицирован.
этот метод работает для всех возможных виджетов.

И еще: после того как виджет преклеплен в колунке, мы можем изменить параметры по умолчанию. Например. если есть опции show last 5 articles, у администратора есть возможность изменить этот 5 на 7 (пряма в админке, без изминения кода в ручную).

3. И что у нас получается?

3.1 загрузка страницы.
Для url наш_сайт.com/news загружается ссылка news со своими панелями, колонками и веджетами. Виджеты сами знают что должны показывать по умолчанию.

Для url наш_сайт.com/news/categories/sport/page/5 загружается ссылка news. Опять же со всеми своими панелями, колонками и виджетами. Но здесь, виджет show_articles знает, что «categories/sport/5» это его параметры. В то время как другие виджеты работают со своими значениями по умолчанию.

4. Заключение.
У администратора всегда есть возможность переместить виджеты по желанию «шефа». Все виджеты можно адаптировать под свои нужды без изменения оригинальных файлов.

Только есть одно «но». Здесь как видети сами, логика «yoursite.com/module/method/param1/param2» не работает.
У меня другого выхода не нашлось. Но очень хотелась бы работать имменно с этой логикай.

Вопрос.
В вашем случае yoursite.com/news/show/last/20 как будет знать, что кроме того как загрузить модуль news и метод show должны загружатся и баннеры, и галерея с 5 картинами внизу и на правом верхнем углу login box?

Благодарю за внимание!
Я с удовольствием жду ваших версий и замечаний.
Если в тексте есть неграмотность, так прошу прощения.
avatar
  • MpaK
  • 0
Вот это не понял

Для url наш_сайт.com/news/categories/sport/page/5 загружается ссылка news. Опять же со всеми своими панелями, колонками и виджетами. Но здесь, виджет show_articles знает, что «categories/sport/5» это его параметры. В то время как другие виджеты работают со своими значениями по умолчанию.

почему он казалось бы при адресе news делает активным именно виджет show_articles?
и может ли быть у одного страницы несколько разных, активных виджетов?
avatar
  • tokernel
  • 0
Все виджеты на станице активны и каждый знает свою функцию. А про url я перепутал. Должно быть наш_сайт.com/news/articles/categories/sport/page/5 где articles это id мудуля. виджет show_articles знает, что «articles» после /news/ косается ему.
avatar
  • MpaK
  • 0
а если нужно на странице активировать например «раскрыть» одну новость передав ей параметр id/18 а ниже на этой же странице «раскрыть» модуль фотоальбома передав ему id/890 как быть?
avatar
  • tokernel
  • 0
Но как же? зачем такое делать?
У news — a есть свои картинки (как галлерея) если это для news. Если это какой то другой виджет, скажем ХХХ галлирея, то когда этот виджет был вблючен в колонку, у него уже есть свой скажем ID.
avatar
  • MpaK
  • 0
Ну очень удобно формировать страницу например из типовых элементов: новость, фотоальбом, текст, календарь и т.п. Как конструктор :)

Хотя согласен, параметры так передавать это вообще не частая задача, даже совсем не частая
avatar
  • tokernel
  • 0
И еще. хотел бы узнать ваше мнение про этот же момент :)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.