CleverStyle CMS — обзор для разработчика

    ВНИМАНИЕ: Статья актуальна на время написания, текущее положение вещей может отличаться чуть менее чем полностью.

    Почему ещё одна CMS?


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

    Моей целью было сделать баланс функциональности, сложности и удобства как можно более гармоничным.
    Система имеет определённое количество велосипедов, немного магии, и большие планы.
    На данный момент CleverStyle CMS может быть интересной игрушкой именно для разработчиков, так как позволяет быстро начать разработку и получать результаты, предоставляет сложным и/или рутинным задачам простой интерфейс, потому и статья об некоторых технических особенностях с примерами в общем.

    Системные требования


    • Unix-подобная ОС
    • Apache2 (Nginx не тестировался, но точно будет поддерживаться в будущем)
    • PHP 5.4+
    • MySQL

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

    С чего начать


    Начинается всё с установки. Она предельно простая: один установочный файл (автономный), одна страничка, несколько полей. Так, как чаще всего используется БД MySQL, а хост localhost — они используются по умолчанию, но в режиме эксперта это можно изменить.



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

    Что дальше


    Результатом первого шага является готовая пустая CMS c гостем и корневым администратором (что-то вроде root в Linux).
    Если вы — разработчик, вам захочется что-то написать. Начать можно с простого файла index.html c содержимым:

    Привет, мой друг!
    

    Затем зайти в components/modules от корня сайта, создать директорию Hello и бросить туда index.html

    Это готовый модуль, чтобы его активировать нужно зайти в админку Компоненты/Модули, нажать Обновить список модулей. Появившийся модуль нужно установить и включить, после чего он появится в меню:



    Вот так просто.

    Немного более сложный вариант — использовать вместо index.html файл index.php.
    В этом случае простым выводом уже не обойтись:

    $Page = \cs\Page::instance();
    $Page->content('Привет, мой друг!');
    

    Впрочем, не на много сложнее. Таких как $Page глобальных системных объектов несколько:
    $Cache, $Config, $Core, $db, $Error, $Index, $Key, $L, $Mail, $Page, $Storage, $Text, $User
    Все они доступны практически всегда, и отвечают каждый за свою часть общей функциональности. В общем название объекта соответствует названию класса (с некоторыми исключениями).
    Все методы и некоторые (пока не все) свойства объектов имеют PhpDoc секции с объяснением типов входящих параметров, их описанием и даже некоторыми примерами входных данных. В wiki все объекты описаны, и многие имеют примеры готового кода, который можно скопировать 1 в 1 и он будет работать.
    Так же есть один системный класс h, который используется как статический для рендеринга HTML с помощью CSS-подобного синтаксиса:

    $Page = \cs\Page::instance();
    $Page->content(h::p('Параграф');
    $Page->content(h::{'h1#some_id.and-class.another-class[data-type=title]'}('Заголовок статьи');
    


    Простота и удобство разработчика


    Система поддерживает работу с несколькими БД и их зеркалами одновременно, поддерживает различные backend-ы для кеша, умеет работать с несколькими хранилищами статических файлов и прочими вещами, но предоставляет для разработчика простой интерфейс взаимодействия со всем этим.
    Работа с объектами сделана максимально натуральной и очевидной (вот тут немного магии):

    $Cache = \cs\Cache::instance();
    $Cache->item			= 5;
    $item					= $Cache->item;
    unset($Cache->item);			//Хотя можно использовать и ::get() ::set() ::del() методы если вы правда считаете что эта магия будет для вас узким местом
    $Cache->{'Movies/1'}	= 1;
    $Cache->{'Movies/2'}	= 2;
    unset($Cache->Movies);			//Удаляет оба фильма из кеша
    

    С многоязычным интерфейсом работать тоже легко:

    \cs\Page::instance()->content(\cs\Language::instance()->hello);			//Здравствуйте
    

    Так же можно использовать следующую интересную конструкцию:

    $L = Language::instance();
    $Page = \cs\Page::instance();
    $Page->content($L->installation_of_module('Hello'));	//Установка модуля Hello
    

    Файлы перевода — JSON, а строки могут быть оформлены как для функции sprintf. Для предыдущего примера:

    "installation_of_module"	: "Установка модуля %s"
    

    Так можно делать персонализированные сообщения.

    Аналогичный формат используется при работе с БД:

    $db = DB::instance();
    $db->q(
    	"SELECT `login`
    	FROM `[prefix]users`
    	WHERE `id` = '%d'",
    	2
    );
    

    В этом примере перед подстановкой значение ещё будет обработано для защиты от SQL инъекций.
    В случае нескольких запросов за раз можно так:

    $db = DB::instance();
    $db->q(
    	[
    		"DELETE FROM `[prefix]users`
    		WHERE `id` = '%d'",
    		"DELETE FROM `[prefix]users_groups`
    		WHERE `id` = '%d'"
    	],
    	2
    );
    

    Кроме очевидности написанного, такой код красиво подсвечивает IDE.

    Структура сайта


    Сайт имеет обычные страницы для конечного польззователя, как было в первых примерах, административные, и странички API, которые не имеют интерфейса и отдают контент в формате JSON.

    Простой пример как создать API для нашего модуля Hello.


    Создаем внутри components/modules/Hello директорию api, а в ней файл index.php c содержимым:

    $Page = \cs\Page::instance();
    $Page->json([
    	'title'			=> 'Привет, хабр!',
    	'description'	=> 'Приветствие хабрахабру через API'
    ]);
    

    И обратившись с помощью jQuery по адресу api/Hello нам придет JSON строка с необходимым заголовком и телом.

    Что система вообще умеет:


    • Поддержка компонентов
      • Поддержка модулей (для отображения основного содержания страницы)
      • Поддержка плагинов (загружаются на каждой странице и обеспеивают дополнительную функциональность)
      • Поддержка блоков (размещаются вокруг страницы для отображения дополнительной информации)
    • Поддержка человекочитаемых адресов
    • Поддержка типов и групп пользователей
    • Поддержка прав и контроля доступа
    • Полная многоязычная поддержка (как интерфейса, так и контента)
      • Поддержка автоматического перевода контента
    • Поддержка минификации и компрессии CSS и JavaScript
    • Поддержка зекрал сайта
      • Поддержка зеркал доменов
      • Поддержка зеркал физических серверов для каждого домена
    • Поддержка тем и цветовых схем
    • Поддержка множественных баз данных
      • Поддержка множественных зеркал баз данных
    • Поддержка множественных хранилищ файлов
    • Поддержка системного кэширования
    • Фильтр и ограничения по IP
    • Поддержка внешнего API сайта
    • Поддержка IPv6

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

    Всё это открыто и под дружелюбной MIT лицензией доступно всем желающим на GitHub, там же в wiki документация на английском.
    https://github.com/nazar-pc/CleverStyle-CMS
    Часть независимых, но полезных функций вынесена и доступна отдельно, будет полезена любому PHP разработчику:
    https://github.com/nazar-pc/Useful-PHP-Functions

    Это самые простые вещи, под данную CMS можно писать как предельно просто, так и немного сложнее.

    Статья получилась не очень богатой на реальные примеры, если интересуют отдельные аспекты: разработка API сайта, работа с БД, роутингом, что-то ещё – напишу отдельную статью с простыми рабочими примерами.

    Спасибо за внимание, буду рад любой обоснованной критике и пожеланиям, в состоянии Alpha каждый может повлиять на то, какой система будет в будущем.

    UPD 09-07-2013: В следующей версии глобальные переменные объектов будут убраны
    UPD 19-07-2013: Глобальные переменные объектов больше не используются, кроме автозагрузчика классов ядра система может подхватывать автозагрузчик composer, если он есть, статья обновлена соответственно
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 42

      +8
      А без global никак?
        –1
        Почему же, можно, но: инициализацией системных объектов в определённой последовательности занимается единая точка входа (например, конфигурация не загрузится до инициализации БД). Классы находятся в пространстве имен cs, но точка входа проверяет пространство имен cs\custom, и если там есть нужный класс — использует его, это дает возможность влиять на поведения ядра, не редактируя системные файлы.

        Ну и большой разницы между

        $Page = Page::instance();
        

        и

        global $Page;
        

        В принципе, нет.
          0
          Разница в том что что «одиночку» нельзя переопределить вот так:
          global $Page;
          $Page = null;
          
            –3
            class Singleton {
            	static private $instance = false;
            	private function __construct() {}
            	private function __clone() {}
            	static function instance () {
            		if (self::$instance === false) {
            			self::$instance = new Singleton;
            		}
            		return self::$instance;
            	}
            }
            $obj	= Singleton::instance();
            var_dump($obj);
            $obj	= null;
            var_dump($obj);
            

            object(Singleton)#1 (0) {
            }
            NULL
              +1
              Хотя да, тут вы правы, немного не тот случай.
              0
              еще рекомендую __sleep и __wakeup сделать приватными, чтобы нельзя было копировать синглтоны при помощи сериализации
              0
              Удалено
          +7
          Предлагаю новое название: «Как не надо делать CMS»
          А вообще минусов настолько много, что и перечислять их нет смысла.
            0
            Напишите пожалуйста, даже в личку если не хотите тут, я постараюсь их учесть.
            А сказать «всё плохо» — это ничего не сказать.
            0
            Глобальные переменные конечно сильно смущают.
            $Cache->{'Movies/1'}	= 1;
            $Cache->{'Movies/2'}	= 2;
            unset($Cache->Movies);	
            

            Вот этот момент достаточно интересен.
              0
              Ещё одна CMS! Тысячи их…
              Хотелось бы написать о явных недочётах, видных невооруженным взглядом, но они полностью совпадают с перечнем «детских болезней» любой CMS.
              Это и глобальные переменные, и их имена. Это и имена методов, и то, что автор называет «CSS-подобным синтаксисом» — на самом деле являющееся то ли обёрткой DOMDocument, то ли портом PHP Simple HTML DOM Parser
              Ну и много-много-много всего остального, в том числе и багов.

              Я ни в коем случае не против, просто продукт на мой взгляд ещё очень и очень сырой и не надо его пока что показывать широкой общественности.
              Это может стать как CMS так и очередным популярным фреймворком. Разрабатывайте и да пребудет с Вами Сила!
                0
                Database Abstraction Layer сделайте какой-нибудь что ли. Как по мне, без него не то, чтобы использовать, даже смысла щупать нет.
                  0
                  Обертка над БД есть, внутри запросы обрабатывает в зависимости от конфигурации тот или иной движок БД.
                  А ORM нет, и не думаю, что будет. Это очень далеко выходит за рамки простоты, но интегрировать ORM при желании не составит большого труда.
                  +1
                  По ходе того как смотрю

                  1. Глобальные переменные.
                  2. Ну автозагрузку то можно было уже запилить.
                  3. Не используется единый стиль.
                  4. Мне кажется, что тут есть некоторое непонимание…
                  function __construct() {
                  if ($this->init) {
                  return;
                  }
                  


                  /**
                  * Cloning restriction
                  *
                  * @final
                  */
                  function __clone () {}
                  

                  5. Да, да, нет разницы между глобальными переменными и синглтонами.
                  /**
                  * For IDE
                  */
                  if (false) {
                  global $db;
                  $db = new DB;
                  }
                  

                  6. Имена: global ..., $L,…
                  7. Класс Core многостаночник: создаёт(удаляет) объекты, кодирует(декодирует), отправляет HTTP запросы
                  8. Не используются подготовленные выражения…


                  nazarpc, Вы остро нуждаетесь в обновлении своих знаний по PHP и архитектуре приложений.
                    0
                    Написал не туда
                    –1
                    2. Она есть
                    3. Я стараюсь, выходит не всегда
                    4. Уберу
                    5. Это для подсветки синтаксиса в IDE, никогда не выполняется.
                    Ещё один аргумент в пользу глобальных переменных — они называются везде одинаково, при синглтоне они могут называться по разному, это может усложнить понимание чужого кода.
                    6. Что с именами не так?
                    7. Есть такое, осталось c более старых времен, будет отрефакторено.
                    8.
                    В этом примере перед подстановкой значение ещё будет обработано для защиты от SQL инъекций.
                      +1
                      5. Что мешает использовать везде одно название для свойства класса? $this->db, по-моему — звучит =) Названия — это не проблема паттерна, а проблема фантазии программиста, как мне кажется. И что вы за IDE используете? У меня в SublimeText никогда проблем с подсветкой синтаксиса не возникало.
                      6. Они ужасны. Они должны быть понятными. Вот мне не понятно что за $L с первого взгляда (интуитивно) и я думаю, что не только мне не понятно.
                      8. При правильном подходе PDO решает эти проблемы без изобретения собственных великов.

                      И ещё, почему в качестве инструмента интернационализации был выбрал вариант с массивами в JSON файлах? Чем плох getText? Он быстрее, чем парсинг JSON файлов. Согласен, надо повозиться, но работать будет быстрее и соответственно будет «жрать» меньше ресурсов.
                        0
                        5. Не совсем понял, о чём вы. Блок, обернутый в false дает IDE (PhpStorm) понять, что такое global $db; в любом месте проекта (что, в прочем, в будущем будет не актуально)
                        6. $L исключение, очень лениво в каждом месте, где нужен перевод интерфейса писать $Language->word
                        8. А это по сути и не велик, всего лишь sprintf() и vsprintf(), массив подставляемых параметров предварительно прогоняется через функцию, которая делает строку безопасной

                        Чем-то мне не понравился он, уже не помню, чем. JSON предельно прост даже для тех, кто его не знает (30 сек на википедии — и можно делать перевод, легко переносится во фронтенд (доступно через аналогичное window.L). В ближайшее время кроме форматированных строк можно будет добавлять замыкания, которые будут обрабатывать склонения и прочие вещи. Возможно, в будущем я пересмотрю своё решение при наличии значительных аргументов «за» getText.
                          0
                          по поводу лени, можно же использовать простую функцию t('word');
                            0
                            Можно, но обращаться нужно всё равно к объекту, в котором переводы, настройки и правила обработки спрятаны за понятным интерфейсом.
                              0
                              Вы усложняете.
                              0
                              Тоже хорошее решение. Значительно лучше, чем global $L, на мой взгляд.
                                0
                                Вы можете себе для удобства в custom.php добавить строчку:

                                function t ($item) {global $L; return $L->$item;}
                                

                                И это будет работать. На счёт внедрения такого в ядре я хорошенько взвешу, и что-нибудь предприму.
                                0
                                5. Я пользовался PHPStorm года 3-4 назад. Тяжёлые были времена. С тех пор про него даже и не вспоминаю.

                                6. Если будете использовать getText, то там можно использовать функцию _('text'). В WP используют _e('text'), там тоже своё решение по переводам, но оно сильно похоже на getText, на сколько я помню, и оно реализовано не через JSON массивы.
                                Вообще в getText даже plural forms предусмотрены
                                ru.wikipedia.org/wiki/Gettext#.D0.9C.D0.BD.D0.BE.D0.B6.D0.B5.D1.81.D1.82.D0.B2.D0.B5.D0.BD.D0.BD.D1.8B.D0.B5_.D1.87.D0.B8.D1.81.D0.BB.D0.B0_2
                                Это довольно мощный и хорошо оптимизированный инструмент.

                                8. Всё равно PDOStatment = PDO::prepare и PDOStatement->bindParam значительно кошернее. Да и работает PDO, на сколько мне известно, быстрее, чем mysqli.
                                habrahabr.ru/post/137664/ — советую пробежаться по комментариям.
                                  0
                                  5. Сейчас PhpStorm, наверное, самая удобная IDE для PHP, посмотрите когда будет возможность, оно того стоит.
                                  6. Посмотрю ещё раз в сторону getText.
                                  8. Вполне возможно, но мне текущий вариант показался удобнее и проще в использовании.
                                    0
                                    5. Удобство — это дело привычки. К хорошему можно быстро привыкнуть. Не хочу в чём-то убеждать=)
                                    8. А вы почитайте статью ссылку на которую я привёл. И ваши сомнения сразу же исчезнут.
                                      0
                                      8. В том то и дело, что смотрел как во время публикации, так и после несколько раз. И не вижу преимущества в вызове 2-3 методов вместо просто $db->q() или $db->qf() если кроме запроса нужно ещё и данные забрать (qf = query+fetch)
                                        0
                                        Никто вам не мешает написать свой класс, который будет работать с PDO. Я обычно, использую что-то похожее на это:
                                        ...
                                        private function fastPrepare($sql, $params) {
                                        	$sth = $this->pdo->prepare($sql);
                                        	foreach ($params as $key => &$value) {
                                        		$sth->bindParam(":{$key}", $value);
                                        	}
                                        	return $sth;
                                        }
                                        
                                        public function fastFetchColumn($sql, $params) {
                                        	$sth = $this->fastPrepare($sql, $params);
                                        	return $sth->fetchColumn();
                                        }
                                        ...
                                        

                                        Это пример, а не реальный код. В реальности там ещё проверка типа данных в для bindParam, обработка ошибок, режим отладки и ещё кое что.
                                          0
                                          И ещё тут проблема в том, что благодаря тому, что вы берёте экземпляр класса для работы с БД (да и не только его) из global'а, то вы нарушаете инкапсуляцию… Может, это, конечно, паттерн такой, но необходимость его применения в вашем случае довольно спорный вопрос.
                              +4
                              Каждый php разработчик должен написать свою CMS/CMF/etc, потом понять на сколько это всё геморойно и забросить проект, в угоду старых (часто не менее фиговых и очень часто громоздких), хорошо документированых и часто обновляемых фреймворках/CMS/итд.
                              Я сам писал 3 CMS, 2 из которых забросил и одна была очень специфичная и закрытая, к сожалению не знаю её судьбу сейчас.
                                0
                                Согласен на 100%, даже в одном из черновиков такая фраза была.
                                Но несколько велосипедов из тысячи ведь выстреливают, ради этого стоит попробовать)
                                Тут даже не в системе как таковой дело, может кто-то найдет для себя интересную идею.
                                  0
                                  Я бы преложил писать пободные штуки ради самообразования, но обязательно в продакшене это использовать — иначе не понятно что удобно, а что нет. Я вот в каждый новым проектом понимаю что в прошлом какое то вещи можно было сделать умнее/удобнее/проще, и мне кажется нет этому предела =) Не знаю как дойти до момента, когда понимаешь что со временем лучше уже сделать не получится. Сейчас вот нынешний проект местами переписываю, просто для личного удобства, правда последний модуль какой то странный вышел, там 3 или 4 обертки друг над другом, выглядит запутано, хоть и просто, но добавление нового функционала невероятно простое теперь стало и никак не зависит от других модулей. Что что во всем проекте так не сделать технически или я пока не дорос до идеи, как реализовать.
                                  Резюмируя — главное делать и пихать в реальные проекты, а не только умные книжки читать и дядек слушать.
                                –1
                                Как-то странно.
                                Если бы переменные состояли не из одного слова, то я бы подумал, что вы используете стиль UpperCamelCase. До этого я встречал стиль UpperCamelCase только у C# программистов. Среди PHP разработчиков, на сколько мне известно, популярен camelCase.
                                Ещё global $L меня просто убил… Нельзя же так…

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

                                И почему для работы с БД был выбран не PDO?
                                  –1
                                  В целом стиль обозначен в соответствующем разделе wiki: github.com/nazar-pc/CleverStyle-CMS/wiki/Code-style#identifiers-and-case
                                  camelCase убивает мои глаза, подчеркивание намного удобнее читать. А названия объектов соответствуют названиям классов, которые именуются с заглавной буквы.
                                  $L — это только из-за частого использования, лень писать $Language каждый раз, так и $db ещё одно исключение от класса DB, всё это тоже описано в wiki: github.com/nazar-pc/CleverStyle-CMS/wiki/Classes

                                  Получилось не идеально, но это был вынужденный (с моей точки зрения) компромисс.
                                  Вскоре можно будет использовать что-то вроде Language::instance() с любой переменной.

                                  PDO мне показался избыточным, стандартные правила sprintf() вполне подходят для подготовленных выражений, а набор методов для получения одной записи, массива и т.д. вполне достаточен для большинства случаев.
                                  Но ничто не мешает добавить свой движок БД, который будет основан на PDO с сохранением стандартного интерфейса, CMS это позволяет.
                                  +1
                                  /**
                                  * Special function for files including
                                  *
                                  * param string $file
                                  * param bool|Closure $show_errors If bool error will be processed, if Closure — only Closure will be called
                                  *
                                  * return bool
                                  */
                                  function _require_once ($file, $show_errors = true) {
                                  return _require($file, true, $show_errors);
                                  }

                                  Серьезно?
                                    –4
                                    Да, лень писать

                                    if (file_exists('some_file')) {
                                        require_once 'some_file';
                                    }
                                    

                                    просто добавляю прочерк и false

                                    _require_once('some_file', false);
                                    

                                    И функция делает проверку существования файла за меня.

                                    Иногда подключение файла может быть не обязательным, а только по наличию. Такая простая обертка экономит время и занимает меньше места.
                                      0
                                      Вы не уверены, что файлы, которые вы подключаете существуют?
                                        0
                                        Вы о чём? Если модуль может иметь файл триггера, а может не иметь — я использую эту функцию. Есть — подключится, нет — ничего страшного.
                                        Жизненно важные вещи, естественно, подключаются без обертки через require_once.
                                          0
                                          Просто как вариант:
                                          @include_once 'anything.php';
                                          
                                            0
                                            А логи кто будет чистить?
                                            Это костыль, который заставляет игнорировать ошибку, в случае же обертки ошибки не возникает, это более кошерный вариант.
                                              0
                                              Логи чистить не надо будет, т.к. ошибка подавляется. Нативный вариант всегда лучше костылей =) Приведённый мной вариант понятнее, локоничнее и выполняет свою работу. Что ещё нужно? =)

                                  Only users with full accounts can post comments. Log in, please.