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

в 14:19, , рубрики: api, cmf, cms, php, rest

В последнее время на хабре было несколько статей о 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, а в заголовке Allow будет список доступных методов.

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

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

$Config = csConfig::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 или вложенных.

Автозагрузчик классов устроен так, что класс csmodulesBlogPost будет искаться в файле Post.php модуля Blog — просто и логично.

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

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

Резюме

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

Планы

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

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

Автор: nazarpc

Источник

Поделиться

* - обязательные к заполнению поля