Архітектура модуля CleverStyle CMS

Останнім часом на хабре було кілька статей про CMS написаних їх розробниками, от і я вирішив написати.
Про CleverStyle CMS я вже писав двічі (останній раз рік тому), і двічі отримував величезний спектр критики і велику пачку зауважень різного плану — спасибі за все, я витратив час на те, щоб врахувати помилки і виправити їх.

Стаття знову для розробників, в ній я спробую донести простою мовою переваги використання саме цього движка для розробки сайту, мова піде про один з найбільш важливих і функціональних компонентів — про модулі.

Трохи історії

Коли читаю про те, що движок був написаний однією людиною за рік у вільний час — виникає подив і повагу до виконаної роботи. Розробляючи систему три з хвостиком роки розумієш який це величезний пласт роботи, але ще більше, швидше за все, попереду. В більшості мені попадалася розробка сайтів з унікальною функціональністю, і движок в першу чергу писався як ядро в допомогу розробнику, незважаючи на CMS в назві, що позначається на майже всіх що у ньому є.

Філософія модуля

Модуль — такий компонент, який відповідає за відображення вмісту сторінки або обробку запитів до API. Модуль містить все в собі і не поширює свої файли по кутах системи (тому шляху у статті завжди будуть вказуватися щодо кореня папки модуля), а якщо треба щось таки покласти — після видалення модуль прибирає за собою все таким чином, щоб не залишити нічого. Це дозволяє зберігати продуктивність системи на належному рівні з плином часу.

Модуль може бути гранично простим (папка з одним файлом index.html або index.php), або скільки потрібно складним: з підтримкою декількох БД, сховищ файлів, залежностями, зовнішніх API, многоязычностью і купою різноманітних ресурсів. При всьому цьому ви використовуєте тільки те, що вам потрібно, і не більше того (мінімум шаблонного коду).

Ієрархія сторінок

Може бути три основних «частини модуля (в будь-якій комбінації): адміністрування, API, сторінки для кінцевого користувача. Всі три частини можуть складатися з одного index.php файлу (або додатково index.html у разі сторінки для користувача), або ієрархії сторінок, яка описана в admin/index.json, api/index.json index.json. Підтримуються сторінки першого і другого рівня, решта при необхідності реалізовуються розробником. Виглядає ось так (приклад з модуля системи, адміністрування):

{
"components" : [
"modules",
"plugins",
"blocks",
"databases",
"storages"
],
"general" : [
"site_info",
"system",
"optimization",
"appearance",
"languages",
"about_server"
],
"users" : [
"general",
"users",
"groups",
"permissions",
"security",
"mail"
]
}

Відповідно сторінки будуть виглядати так:

  • admin/System (буде обраний перший елемент на кожному рівні вкладеності index.json, аналогічно admin/System/components/modules)
  • admin/System/general/about_server
  • admin (буде обраний системний модуль)
Для обробки шляхів використовуються однойменні файли, що виконуються в такому логічному порядку:

  • admin/index.php (виконується при наявності завжди)
  • admin/components.php (за наявності)
  • admin/components/modules.php
Для API зручно використовувати REST, і в зв'язку з цим обробка запитів до API логічним і зручним чином відрізняється:

  • api/index.php
  • api/index.{method}.php
  • api/posts.php
  • api/posts.{method}.php
Таким чином можна направляти запити з допомогою різних HTTP методів в різні файли. Якщо потрібного файлу з методом немає, але є файл з іншим методом — у відповідь прилетить логічний 405 Method Not Allowed, а в заголовку Дозволити список доступних методів.

Так само варто зауважити, що при обробці запитів числові частини шляху ігноруються при розборі структури сторінок (це логічно, так як числа це зазвичай id якихось елементів, або номер сторінки).

Для ручних маніпуляцій можна отримати шлях сторінки без префікса admin або api таким нехитрим способом:

$Config = \cs\Config::instance();
$route = $Config->route; // ['components', 'modules'] для першого прикладу, або ['posts', 10] для прикладу з API

При бажанні можна залишити тільки index.php і розбиратися з маршрутом самому.

Ресурси (скрипти, стилі, зображення, шрифти, веб-компоненти)

Системою розрізняється три основних типи підтримуваних ресурсів: скрипти, стилі, веб-компоненти. Всі інші частіше за все прив'язані до згаданих. Розташовуються у відповідних папках:

  • includes/css
  • includes/js
  • includes/html (у випадку з Apache2 потрібно ще покласти сюди або рівнем вище .htaccess, який відкопає доступ до *.html файлів)
Всі файли усередині папок з відповідним розширенням будуть автоматично підхоплені системою в алфавітному порядку, файли можуть бути вкладені в підпапки — це не проблема. При бажанні виключити обробку папки ядром досить покласти в бажане місце порожній файл з назвою !include (читається як не підключати).

Все просто, а головне ефективно. Справа в тому, що при підключенні ресурсів ядро вміє розрулювати залежності компонентів, тобто є все необхідне даного компоненту буде автоматично підхоплено. Більш того, при виставленні опції в адмінці, скрипти, стилі і веб-компоненти будуть минифицированы, об'єднані (при бажанні для веб-компонентів можна додатково включити так звану вулканізацію), і запаковані з допомогою gzip, і при цьому всі операції робляться атомарно, тобто кожний самостійний набір ресурсів лежить в окремому файлі, і при підключенні минифицированных стислих версій все ще зберігається облік залежностей, а файли отримують унікальні префікси, щоб при оновленні певних файлів ресурсів їх стислі версії перейменовувалися і не висіли в кеші браузера викликаючи проблеми. Також варто зауважити, що всі відносні посилання на зображення, шрифти, вкладені імпорти стилів вбудовуються в результуючий css файл мінімізуючи кількість HTTP запитів (поки не прийде повсюдний HTTP2 або що вони там вирішать у підсумку), це так само справедливо для стилів, які використовуються у веб-компонентах.

Для того, щоб підключати ресурси на певних сторінках використовується файл includes/map.json, в якому вказується, на яких сторінках які ресурси потрібні. Якщо ресурс там не вказаний — він буде підключений на всіх сторінках сайту (саме так, не тільки в поточному модулі). Ось один із прикладів:

{
"admin/Blogs" : [
"admin.css"
],
"Blogs" : [
"general.css",
"general.js"
]
}

includes/css та аналогічні префікси вказувати не потрібно, з розширення файлу зрозуміло де він лежить.

Веб-компоненти
Насправді їх варто згадати трохи окремо. З ядром системи у комплекті йде Polymer Platform (полифилы веб-компонентів) і сам Polymer. Змусити їх працювати з jQuery і деякими іншими бібліотеками було зовсім не тривіально, але розробники оперативно прийняли патчі з виправленнями, так що Polymer і jQuery які йдуть в комплекті — це git версії після прийнятого патча з виправленням, так як релізу поки небуло. Також ядро движка патчіть jQuery.ready() так, щоб виконувати його після ініціалізації всіх веб-компонентів.

Якщо бути коротким — не знаю на поточний момент де ще є така комбінація патчів, яка дозволяє використовувати веб-компоненти в продакшені (трохи голосно звучить, але якщо ви пробували використовувати веб-компоненти в реальних проектах з jQuery — ви розумієте про що я).

Якщо ви не збираєтеся використовувати CleverStyle CMS
Хоча код досить монолітний, досить просто можна взяти певну функціональність, і використовувати у своєму проекті незалежно (ліцензія MIT це дозволяє) як приклад — об'єднання і минифицирование стилів і веб-компонентів (з підтримкою вулканізації).

Залежно

Модулі можуть залежати один від одного, і від плагінів (інший вид компонентів, не має власної сторінки для відображення).

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

На завершення можна вказати необов'язкові залежності (які розширюють функціональність, але не є обов'язковими), а так само можна вказати, починаючи з якої версії модуля його можна оновити до поточної (іноді бувають проблеми сумісності, і оновлення потрібно робити поступово, або просто оновлення застарілих версій припиняється для позбавлення від застарілих скриптів оновлення).

Приклад мета-файлу meta.json з описом деталей модуля і його залежностей:

{
"package" : "Blogs",
"category" : "modules",
"version" : "0.097.0+build-107",
"description" : "Adds blogging functionality. Comments module is required for comments functionality, Plupload or similar module is required for files uploading functionality.",
"author" : "Nazar Mokrynskyi",
"website" : "cleverstyle.org/cms",
"license" : "MIT License",
"db_support" : [
"MySQLi"
],
"provide" : "blogs",
"require" : "System=>0.574",
"optional" : [
"Comments",
"Plupload",
"TinyMCE",
"file_upload",
"editor",
"simple_editor",
"inline_editor"
],
"multilingual" : [
"interface",
"content"
],
"languages" : [
"English",
"Російський",
"Українська"
]
}

А як же код?

Всі класи, трейты, і подібні речі лежать в просторі імен cs або вкладених.

Автозавантажувач класів влаштований так, що клас cs\modules\Blog\Post буде шукатися у файлі Post.php модуля Blog — просто і логічно.

Для того щоб підписатися на події в системі (наприклад, очищення даних модуля при його видаленні) використовуються тригери, а їх оголошення найчастіше лежить у файлі trigger.php, який викликається на кожній сторінці не залежно від відображуваного модуля, і використовується спеціально для цих цілей.

Приклади коду не пишу, це займе багато місця, та і стаття про архітектурі і принципі роботи, ніж про конкретних прикладах.

Резюме

Це коротко про те, що являє собою модуль, як влаштований, і чому саме так. Сподіваюся, що з написаного зрозуміло, що система робить досить багато очевидних і корисних речей сама, але при цьому дозволяє розробнику змінити поведінку (як приклади була згадана можливість вручну обробляти адреси сторінок і підключення ресурсів). Це не вичерпна інформація, деякі речі втрачені для прості, так як покривають більш вузькі сценарії використання, але за наявності інтересу буду описувати більш специфічні деталі.

Плани

Останнім часом движок був істотно отрефакторен, і наближається перший за три роки реліз, версія 1.0.
У зв'язку з цим хотілося б отримати побільше зворотного зв'язку.
На даний момент більше всього мене турбує відсутність тестів (декілька існуючих не вважаються). В останні зміни зроблені кроки у напрямку до поліпшення тестируемости коду, але щось з тестами все одно не складається, буду дуже радий допомоги в цій галузі (тільки не треба DI пропонувати).

На цьому все, сподіваюся зумів викликати інтерес подивитися на все це в живу. Код покритий PhpDoc коментарями практично скрізь, що дуже добре підхоплюється IDE, так само як приклад можна дивитися готові модулі в репозиторії движка.
Сторінка проекту на GitHub, там же є wiki з документацією (переважно за Backend).

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.