Пример разработки простого блога на CleverStyle CMS

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

    Давно удивляюсь, как, бывает, усложняют разработку современные фреймворки. Конечно, у меня нет права сказать, что они плохие, но и хорошими я их называть не могу. А всё вот почему: их цель — упростить и ускорить разработку, а так же каким-то образом стандартизировать и структурировать проект. Но, по моему скромному субъективному мнению, с первой половиной порой получается прямо противоположный эффект, пишется много кода, который сам по себе ничего не делает, а только обслуживает основной код. Эта статья — пример иного подхода к задаче разработки простого блога, используя не Zend Framework 2, как это сделал rrromka, а собственную разработку CleverStyle CMS.

    Немного истории


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

    Окружение


    Очевидно, что нужен веб-сервер и БД с реквизитами доступа.
    Для установки не нужен composer или ещё какие-то инструменты, даже архиватором пользоваться не нужно. Бросаете дистрибутив в корень будущего сайта и открываете его через браузер. Получаете такое окошко:
    image
    Заполнив все поля, получаете готовое к использованию окружение. Дистрибутив сам себя распакует, выставит настройки по умолчанию где нужно и удалит сам себя в целях безопасности. Уже начиная с этого этапа можно заметить простоту, не нужно копировать несколько десятков тысяч мелких файлов по ftp/ssh.

    Структура будущего модуля


    Со структурой системы можно познакомиться в wiki, но мы будем знакомиться с используемыми частями по ходу статьи.
    Сам модуль будет состоять с класса Posts, который будет оберткой над БД и будет предоставлять простой интерфейс для управлением постами. Так же будут собственно страницы, доступные пользователю, API, и в конце концов будет несколько мета-файлов, которые будут объяснять движку некоторые детали работы и позволят собрать модуль в самостоятельно распространяемый дистрибутив.
    Для начала создадим директорию components/modules/MyBlog для нашего будущего модуля.

    БД


    Пост в блоге будет иметь название, содержимое, автора и дату написания. Как приверженец графических инструментов — готовлю структуру в PhpMyAdmin и экспортирую:

    CREATE TABLE IF NOT EXISTS `[prefix]myblog_posts` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `user` int(10) unsigned NOT NULL,
      `title` varchar(1024) NOT NULL,
      `text` text NOT NULL,
      `date` bigint(20) unsigned NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    Префикс таблицы заменен на [prefix] для универсальности, CMS подставит вместо него нужный сама.
    Создаем файл components/modules/MyBlog/meta/install_db/posts/MySQLi.sql и вставляем туда полученный SQL. Таким образом, при установке модуля в админке будет создана необходимая таблица в БД. Аналогично создадим файл components/modules/MyBlog/meta/uninstall_db/posts/MySQLi.sql:

    DROP TABLE `[prefix]myblog_posts`;
    

    MySQLi — название движка БД, он пока единственный,
    posts — произвольное название, с которым ассоциируется БД (может быть несколько для разных целей, вплоть до того, что часть таблиц будет на одном сервере в MySQL/MariaDB, а вторая — на другом сервере в PostgreSQL).
    Название posts пропишем в components/modules/MyBlog/meta/db.json:

    [
    	"posts"
    ]
    

    На этом с БД всё. Таблица будет создаваться при установке модуля и удаляться при удалении модуля.

    Класс Posts


    Класс разместим в файле components/modules/MyBlog/Posts.php и поместим в пространство имен cs\modules\MyBlog — это позволит CMS найти его в случае необходимости.
    Скрытый текст
    <?php
    /**
     * @package		MyBlog
     * @category	modules
     * @author		Nazar Mokrynskyi <nazar@mokrynskyi.com>
     * @copyright	Copyright (c) 2013, Nazar Mokrynskyi
     * @license		MIT License, see license.txt
     */
    namespace	cs\modules\MyBlog;
    use			cs\Config,
    			cs\Cache\Prefix,
    			cs\DB\Accessor,
    			cs\Language,
    			cs\User,
    			cs\CRUD,
    			cs\Singleton;
    
    /**
     * Class Posts for posts manipulation
     *
     * @method static \cs\modules\MyBlog\Posts instance($check = false)
     */
    class Posts {
    	use	CRUD,
    		Singleton;
    
    	/**
    	 * Cache object instance
    	 *
    	 * @var Prefix
    	 */
    	protected $cache;
    
    	protected $table		= '[prefix]myblog_posts';
    
    	protected $data_model	= [
    		'id'	=> 'int',
    		'user'	=> 'int',
    		'title'	=> 'text',
    		'text'	=> 'html',
    		'date'	=> 'int'
    	];
    
    	protected function construct () {
    		/**
    		 * Save instance of cache object with prefix MyBlog (will be added to every item)
    		 */
    		$this->cache	= new Prefix('MyBlog');
    	}
    	/**
    	 * Required by abstract Accessor class
    	 *
    	 * @return int	Database index
    	 */
    	protected function cdb () {
    		return Config::instance()->module('MyBlog')->db('posts');
    	}
    	/**
    	 * Get post
    	 *
    	 * @param int|int[]		$id
    	 *
    	 * @return array|bool
    	 */
    	function get ($id) {
    		if (is_array($id)) {
    			foreach ($id as &$i) {
    				$i	= $this->get($i);
    			}
    			return $id;
    		}
    		$id	= (int)$id;
    		/**
    		 * Try to get item from cache, if not found - get it from database and save in cache
    		 */
    		return $this->cache->get("posts/$id", function () use ($id) {
    			$data = $this->read_simple($id);
    			if ($data) {
    				$L					= Language::instance();
    				$data['datetime']	= $L->to_locale(date($L->_datetime_long, $data['date']));
    				$data['username']	= User::instance()->username($data['user']);
    			}
    			return $data;
    		});
    	}
    	/**
    	 * Add post
    	 *
    	 * @param string	$title
    	 * @param string	$text
    	 *
    	 * @return bool|int			Id of created post or <b>false</b> on failure
    	 */
    	function add ($title, $text) {
    		$id	= $this->create_simple([
    			User::instance()->id,
    			$title,
    			$text,
    			TIME
    		]);
    		if ($id) {
    			/**
    			 * Delete total count of posts
    			 */
    			unset($this->cache->total_count);
    			return $this->db_prime()->id();
    		}
    		return false;
    	}
    	/**
    	 * Edit post
    	 *
    	 * @param int		$id
    	 * @param string	$title
    	 * @param string	$text
    	 *
    	 * @return bool
    	 */
    	function set ($id, $title, $text) {
    		$data			= $this->get($id);
    		$data['title']	= $title;
    		$data['text']	= $text;
    		if ($this->update_simple($data)) {
    			/**
    			 * Delete cached item if any
    			 */
    			unset($this->cache->{"posts/$id"});
    			return true;
    		}
    		return false;
    	}
    	/**
    	 * Delete post
    	 *
    	 * @param int	$id
    	 *
    	 * @return bool
    	 */
    	function del ($id) {
    		if ($this->delete_simple($id)) {
    			/**
    			 * Delete cached item if any, and total count of posts
    			 */
    			unset(
    				$this->cache->{"posts/$id"},
    				$this->cache->total_count
    			);
    			return true;
    		}
    		return false;
    	}
    	/**
    	 * Get posts
    	 *
    	 * @param $page
    	 *
    	 * @return int[]
    	 */
    	function posts ($page = 1) {
    		$from	= ($page - 1) * 10 ?: 0;
    		return $this->db()->qfas(	//Readable database, Query, Fetch, Single, Array
    			"SELECT `id`
    			FROM `[prefix]myblog_posts`
    			ORDER BY `id` DESC
    			LIMIT $from, 10"
    		) ?: [];
    	}
    	/**
    	 * Get total count of posts
    	 *
    	 * @return int
    	 */
    	function total_count () {
    		return $this->cache->get('total_count', function () {
    			return $this->db()->qfs(	//Readable database, Query, Fetch, Single
    				"SELECT COUNT(`id`)
    				FROM `[prefix]myblog_posts`"
    			) ?: 0;
    		});
    	}
    }
    


    Класс является оберткой над БД с кэшированием постов и их общего количества. Это класс одиночка, и имеет такие публичные методы:
    • get
    • add
    • set
    • del
    • posts
    • total_count

    Сам класс не занимается проверкой прав доступа, а только корректностью вводимых данных.
    БД используется как в режиме чтения, так и в режиме записи
    в данном примере это не важно, но изначальное разделение операций позволит при использовании репликации использовать основную БД для записи, а остальные для чтения; это небольшое разделение сделано с перспективой

    Код достаточно просто написан и хорошо прокомментирован, а так же хорошо подхватывается IDE.

    Интерфейс пользователя


    В общем случае для маршрутизации используется простая json структура, которая описывает адреса страниц внутри модуля. Создадим файл components/modules/MyBlog/index.json который опишет маршрутизацию пользовательской части:

    {
    	"list"	: [],
    	"post"	: [
    		"view",
    		"add",
    		"edit",
    		"delete"
    	]
    }
    

    Таким образом, пути будут выглядеть так:
    • MyBlog/list
    • MyBlog/list/{page}
    • MyBlog/post/add
    • MyBlog/post/view/{id}

    Соответственно, в директории модуля создаем следующую структуру файлов:
    • list.php
    • post/add.php
    • post/delete.php
    • post/edit.php
    • post/view.php

    Благодаря тому, что они описаны выше — CMS их вызовет на соответствующих страницах.
    Пример файла list.php:
    Скрытый текст
    namespace	cs\modules\MyBlog;
    use			cs\Config,
    			cs\Page,
    			h;
    $rc				= Config::instance()->route;
    $page			= 1;
    if (isset($rc[1]) && $rc[1]) {
    	$page	= (int)$rc[1];
    }
    $Page			= Page::instance();
    $Posts			= Posts::instance();
    $total_count	= $Posts->total_count();
    $Page->content(
    	h::{'a.cs-button-compact'}(
    		h::icon('plus').' Добавить пост',
    		[
    			'href'	=> 'MyBlog/post/add'
    		]
    	)
    );
    if (!$total_count) {
    	$Page->content(
    		h::{'p.cs-center.uk-text-info'}('Пока нет постов')
    	);
    	return;
    }
    $Page->title('Мой блог');
    if ($page > 1) {
    	$Page->title("Страница $page");
    }
    $Page->content(
    	h::{'section article.cs-myblog-posts'}(
    		h::{'h1 a[href=MyBlog/post/$i[id]]'}('$i[title]').
    		h::div('$i[text]').
    		h::footer('$i[datetime], $i[username]'),
    		[
    			'insert'	=> $Posts->get($Posts->posts($page))
    		]
    	).
    	(
    		$total_count > 10 ? h::{'div.cs-center'}(pages($page, ceil($total_count / 10), function ($page) {
    			return $page < 2 ? 'MyBlog' : "MyBlog/list/$page";
    		})) : ''
    	)
    );
    


    Пространство имен у нас одно и то же практически во всех файлах модуля.
    Config::instance()->route — позволяет получить индексированный массив элементов пути страницы без учета названия модуля. В данном случае используется для того, чтобы определить какую страницу открывает пользователь, например для MyBlog/list/3 мы получим массив ['list', 3]. В целом, упомянутые выше файлы являют собой представления + проверку прав доступа.

    API


    Да, внешний API мы в самом модуле использовать не будем, но всё же сделаем его (может кому-то пригодится). Сделаем самое простое — управление конкретными постами (например, для редактирования постов без перезагрузки страницы). Создадим в components/modules/MyBlog/api несколько файлов:
    • index.delete.php
    • index.get.php
    • index.post.php
    • index.put.php

    Думаю, названия файлов рассказывают о себе достаточно. В самом простом случае у нас нет никакой структуры и index.json нам не нужен — мы просто создаем пачку index файлов для каждого типа запроса DELETE/GET/POST/PUT и CMS найдет эти файлы сама. Суффиксы можно использовать в API аналогичным образом и при вложенной структуре с index.json. Пример запроса к API:
    POST api/MyBlog
    {
    «title»: «Blog post title»,
    «text»: «Blog post content»
    }

    Можно отправлять JSON, если не забыть указать Content-type: application/json.
    В ответ придет либо:
    201 Created

    {
    «id»: «5»
    }

    Либо код и сообщение об ошибке.
    Вот файл index.post.php, который этим занимается:

    namespace	cs\modules\MyBlog;
    use			cs\Page;
    if (!isset($_POST['title'], $_POST['text'])) {
    	error_code(400);
    	return;
    }
    if ($post = Posts::instance()->add($_POST['title'], $_POST['text'])) {
    	code_header(201);
    	Page::instance()->json([
    		'id'	=> $post
    	]);
    } else {
    	error_code(500);
    }
    

    Page::instance()->json() позволяет отправлять данные как они есть (например массивы), а метод уже сам сделает JSON строку и добавит нужные заголовки. То же самое на счёт error_code(), просто передайте код ошибки — всё остальное будет сделано автоматически.

    Напоследок


    Для того, чтобы собрать установочный дистрибутив нашего модуля создадим файл components/modules/MyBlog/meta.json с некоторой служебной информацией:

    {
    	"package"				: "MyBlog",
    	"category"				: "modules",
    	"version"				: "0.0.2",
    	"description"			: "Simple demo blog module",
    	"author"				: "Nazar Mokrynskyi",
    	"website"				: "cleverstyle.org/cms",
    	"license"				: "MIT License",
    	"db_support"			: [
    		"MySQLi"
    	],
    	"provide"				: "myblog",
    	"optional"				: [
    		"editor"
    	],
    	"languages"				: [
    		"Русский"
    	]
    }
    

    Как положено — добавим components/modules/MyBlog/license.txt, а так же components/modules/MyBlog/prepare.php и components/modules/MyBlog/languages/Русский.json для того, чтобы сделать красивым заголовок страницы поста и локализировать название модуля.
    Ну вот, когда всё готово — берем из репозитория CleverStyle CMS и добавляем себе в корень сайта:
    • build
    • install
    • build.php
    • install.php

    Добавляем в .htaccess несколько строчек (чтобы движок не перехватывал обращения к этому файлу):
    <Files build.php>
    RewriteEngine Off
    </Files>

    Переходим по адресу build.php, выбираем Module, в списке выбираем MyBlog, кликаем Build.
    В корне сайта получаем файл MyBlog_0.0.2.phar.php, который можно использовать для установки на другой копии системы.

    Что можно ещё сделать


    Если нужна стилизация постов:
    • создать components/modules/MyBlog/includes/.htaccess
      Allow from all
      RewriteEngine Off

    • положить в components/modules/MyBlog/includes/css нужные файлы стилей, система их подхватит (аналогично JavaScript в components/modules/MyBlog/includes/js) и
      обработает
      файлы в production объединяются и сжимаются с помощью gzip, а в css файлах дополнительно очищаются комментарии, и встраиваются включения такие как импортированные css стили, шрифты, изображения, таким образом всё находится в одном файле


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

    UPD пара скриншотов:
    Скрытый текст




    UPD 11.11.2013: В новой версии движка появился трейт CRUD, класс Posts отредактирован, и стал проще с его использованием.
    Share post

    Similar posts

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

    More
    Ads

    Comments 38

      +5
      От меня будет несколько вопросов и лучей конструктивной критики :)

      1.
      … как, бывает, усложняют разработку современные фреймворки.
      Ладно, вам не нравятся фреймворки, но почему нельзя использовать сторонние библиотеки — готовые и протестированные.
      Зачем еще один core/engines/DB и core/engines/Cache и еще один класс для управления конфигами? То, на что у вас ушли недели, устанавливается composer-ом за две минуты.

      2. Не знаю, какую IDE вы используете, но код получается вырвиглазным. Вы же позиционируете свою CMS больше как инструмент для разработчиков — кто захочет ковыряться в таком?

      3. Свой HAML c блекджеком и всеми вытекающими?

      4. Своя реализация require_once? Велосипед 80-го левела.

      5. Своя реализация uniqid()?

      6. False_Class. Интересное решение.

      7. "CleverStyle CMS require PHP 5.4 or higher" 2013-й год. В проекте юзаются «global»-ы. Про кастомный автозагрузчик вместо composer-a даже нет смысла говорить.

      8. Зачем вы поддерживаете этого Франкенштейна, используете его в реальных проектах?
        0
        1. PHPMailer, HybridAuth, Plupload, jQuery — успешно используются
        2. У GitHub таб == 8 пробелов, в настройках проекта — таб == 4 пробела, всё выглядит очень даже красиво
        3. Нет, но мне так на самом деле удобнее. При желании — используйте любой шаблонизатор, будь то Twig, Smarty или ещё что-то
        4. Нет, просто лень писать if (file_exists(...)) require ...;
        5. Нет, у uniqid короче хеш, а мне ещё он нужен 100% уникальным, чтобы в таблице не было совпадений, uniqid() тоже успешно используется
        7. «global»-ы только два, это версии библиотек global $MySQLi, $mcrypt; required_verions.php, ещё одна переменная $fs используется при установке, не думаю, что вас это должно сильно волновать
        8. Ибо работает быстро, и разрабатывать под него быстро и легко
          +2
          1. Я имел ввиду базовые вещи типа HttpFoundation, Locale из Symfony Components, DBAL (не ORM) и Cache из Doctine, composer вместо _require_once.
          2. PSR-стандарты намекают, что лучше использовать пробелы.

          мне так на самом деле удобнее
          Понятно, что Open Source — дело добровольное: хочешь — пользуйся, не хочешь — не пользуйся. Но все-таки…

          не думаю, что вас это должно сильно волновать
          да, и я очень рад, что мне не придется поддерживать проекты на CleverStyle CMS.
            –1
            1. composer есть в директории core с системными зависимостями, которые комплектуются с дистрибутивом, если в корне проекта создать свой composer.json — система подхватит и его. Просто свой автозагрузчик удобен для работы с пространствами имен компонентов, которые можно устанавливать через админку из дистрибутивов, не объясняя пользователю что такое composer. На эти конкретные вещи посмотрю, может, что-то и возьму, спасибо вам!

            2. Я думаю, вы понимаете, что пробелы/табуляция это заведомо холиварная тема. Я отношусь к тем, кто предпочитает табуляцию, всего-то (к стати, странно, что в настройках проекта на GitHub нельзя задавать количество пробелов на символ табуляции)

            да, и я очень рад, что мне не придется поддерживать проекты на CleverStyle CMS

            Имеете полное право
            +2
            1) для jquery и Plupload (к слову не лучшее решение) есть bower. SwiftMailer круче. Почему бы не попробовать использовать AppKernel для обработки запросов? Почему бы не использовать doctrine для работы с базой? Почему бы не использовать уже готовые компоненты для кеширования? Да, свой написать просто но зачем?
            2) потому стоит вместо табов использовать пробелы. Да и вообще жутковатый coding style
            3) для тогочто бы заюзать twig тот же придется много чего переписать. Не хватает должного уровня абстракции.
            4) ну так и не пишите. За вас это сделает composer если вы будете делать все в соответствии с psr-0 или допишите правила загрузки классов. Более того, есть еще classloader, который для продакшена сможет конкатенировать все классы в один файл и при использовании apc все ускорится в разы.
            5) 23 символа это мало для хэша? Не думаю что cms-ку эту будут применять на проектах, где количество записей позволит добиться коллизии.
            7) глобалы это зло. почитайте про инверсию управления. Возьмите хотя бы pimple или еще чего для управления зависимостями.
            8) вам да, а другим не очень.
              0
              1) И зачем мне Bower? Просто чтобы было «круто и современно»? Библиотеки идут в комплекте, и обновляются вместе с системой, зачем добавлять лишние инструменты и прослойки? SwiftMailer посмотрю, что такое AppKernel не знаю, оно из той же оперы, что и bower в даном контексте? Doctrine не хочу, если вам она нужна — вы можете её легко подключить через composer, ничто не мешает.
              2) Не вижу смысла холиварить
              3) Не вижу никаких сложностей с этим. Возможно, в качестве доказательства сделаю. Абстракция позволяет переопределять многое, даже системные классы в случае надобности, просто положите класс в пространство имен cs\custom вместо cs.
              4) При использовании APC объединение файлов — экономия на спичках, я думаю, у вас будут совершенно иные узкие места если они появятся, и это будет не подключение файлов.
              5) Думайте как хотите, но почему это должно препятствовать тому, чтобы написать нормальный 100% надежный вариант сразу?
              7) Знаю, что зло, потому их всего две. Вы предлагаете для них создать класс по всем канонам ООП? На Pimple смотрел, не вижу в нём смысла. Это не фреймворк с полностью обособленными компонентами, то, что некоторые части знают немного друг о друге позволяет им работать оптимальнее, чем через общую абстракцию. Это компромисс, который рационален и нужен в данном случае.
              8) Я работаю над тем, чтобы было не только мне, потому и пишу сюда, читаю ваши ответы, и совершенствуюсь. Думаю, вам на любой новой системе будет работать не проще (по крайней мере в самом начале) чем на проверенной, этого не избежать.
                0
                1) речь шла в контексте composer. Мол сторонние готовые библиотеки именно для серверной части. jquery к ним не относится и если и использовать для него какие-то менеджеры пакетов то bower. Для cms-ки оно не нужно. AppKernel это микро ядро для работы с http запросами и ответами. На нем базируется symfony, silex, drupal с некоторых пор…
                4) прирост будет как минимум потому что не будет лишних вызовов file_exists. Причем прирост приличный (в зависимости от количества файлов).
                5) преждевременные оптимизации и решение еще не существующих проблем это не очень хорошо
                7) Возможно вам будет любопытно так же почитать это. А по поводу DiC — тут можно много чего помимо сделать. Вопервых это удобно. Вот захотите вы заменить уровень представления, подправите в одном месте и все. Или же можно через более сложные реализации dic, со скоупами, с тэгами и т.д. расширения ресолвить. Удобно поддерживать те же плагины, расширять их, да и устанавливать. Но это так, идеи.

                Мне вообще в последнее время для простеньких сайтов и сайтов визиток нравятся cms-ки на файлах. Есть так же довольно современные мини-cmsки. Например такие:
                github.com/bolt/bolt (тут правда не flat-file, но интересная)
                pico.dev7studios.com/index.html
                bolt80.com/piecrust/

                ну и много их еще.
                  0
                  1) Сомневаюсь, что найду ему применение, но всё же посмотрю.
                  4) Если вы точно знаете, какие классы будут загружены — подключите их в файле custom.php, тогда автозагрузчик даже не сработает
                  5) Это не оптимизация, а конкретное решение конкретной задачи. В случае uniqid оно могло быть не полным
                  7) Я посмотрю, спасибо
          +6
          Такого угара уже давненько не видел.

          Давно удивляюсь, как, бывает, усложняют разработку современные фреймворк…… А всё вот почему: их цель — упростить и ускорить разраа так же каким-то образом стандартизировать и структурировать проект. Но, по моему скромному субъективному мнению… ..., с первой половиной порой получается прямо противоположный эффект


          Simplest block

          Steps to create the simplest block:

          1. Create block file in components/modules with name block.{block_name_here}.php
          2. Add some HTML content or PHP code with direct output through echo, print(), etc.
          3. Go to Administration >> Components >> Blocks
          4. Click Add block
          5. Select created block in Block type, specify block title, click Save
          6. Drag block to desired place, click Save
          7. Block with content will appear on selected place of web-site
          8. That's it! You have created block for CleverStyle CMS


          То есть для создания простого… шаблона, нужно выполнить 7 действий?
            0
            Если считать

            Go to Administration >> Components >> Blocks

            За сложный шаг — то да.

            Это пример компонента типа блок, html блок со статическим контентом можно создать в админке заполнив два поля — название и содержимое.

            Вы считаете это сложным, где угар?
              +3
              Как бы философски ответить… угар это и документация, и архитектура, и взаимодействие с пользователем, и логика именования директорий.

              К примеру, у вас по всей иерархии разбросаны prepare.php. Допустим вы обновляете ядро до 0.9, где ломаете какую ни будь не понравившуюся или ошибочную архитектурную особенность. Что делать в старых проектах, которые крутятся на модулях от 0.8, к примеру? Обновлять все prepare.php файлы?

              Почему не сделать модуль, обернуть всю информацию, которая требуется для запуска модуля в class MyModule extends \base\Module? Таким образом можно запускать модуль в совершенно другой среде, сделав собственный \base\Module?

              Ну и чем же trigger_error лучше, чем Exception?

              И это мы только начали…
                0
                Когда переходил от глобальных переменных несколько месяцев назад, была одна версия, которая поддерживала и старый, и новый подход. Поэтому как бы да, если новая версия работает иначе — компоненты придется обновлять, разве не логично?

                Почему не сделать модуль, обернуть всю информацию, которая требуется для запуска модуля в class MyModule extends \base\Module? Таким образом можно запускать модуль в совершенно другой среде, сделав собственный \base\Module?


                Потому что это то, чем страдают фреймворки — избыточность. На кой мне сдался \base\Module если всё нужное для запуска уже есть в ядре, и не нужно ничего наследовать? Либо я не понял, о чём речь.

                Ну и чем же trigger_error лучше, чем Exception?

                Это разные вещи.

                И это мы только начали…

                Спасибо за обратную связь, можете продолжать, я в любом случае прислушиваюсь к каждому мнению.
                  +1
                  Потому что это то, чем страдают фреймворки — избыточность.

                  Хорошо, вот пример, фреймверк создан в 2008, около 1000-1500 строк кода. Поддерживает роутринг, «нормальные» модели, некое подобие ORM. Это куда луче, чем божественный класс User. И это далеко не самый лучший пример. Я уверен, что можно найти парочку добротных и простых фреймверков для не сложных задач.

                  Это разные вещи.

                  Хорошо, согласен, что разные. Так почему классы не выбрасывают Exception, а генерируют ошбики trigger_error? Что мне делать, если я вдруг захотел перехватить ошибку и обработать по-своему?

                  Ладно, пусть.

                  Почему везде синглтоны? Почему везде какое то извращение с подмешиванием? Любите подмешивать — берите JavaScript.

                  Ладно.

                  Core/classes директория ядра? Почему там валяется PHPMailer? Ведь мало того, что это сторонняя библиотека и ее лучше положить в vendors, так и к ядру она имеет минимальное отношение, тем более что директория то vendor существует.

                  Потом вы практически везде предлагаете разработчикам поиграть в шарады. Ну епты, что это означает?

                  	_require_once(CLASSES."/$class[namespace]/$class[name].php", false) ||
                  	_require_once(TRAITS."/$class[namespace]/$class[name].php", false) ||
                  	_require_once(ENGINES."/$class[namespace]/$class[name].php", false) ||
                  	(
                  		mb_strpos($class['namespace'], "modules/") === 0 && _require_once(MODULES."/../$class[namespace]/$class[name].php", false)
                  	) ||
                  	(
                  		mb_strpos($class['namespace'], "plugins/") === 0 && _require_once(PLUGINS."/../$class[namespace]/$class[name].php", false)
                  	);
                  


                  Нет, я понимаю, что можно почисать репу, почитать сильнее синтаксис и разобраться, но зачем запутывать на ровном месте? Код — это как книга, и чем проще она будет написана, тем больше людей ее прочитают и поймут.
                    0
                    божественный класс User

                    Не такой уже и божественный, ещё сессии скорее всего оттуда вынесу, смотрите upstream версию.

                    Что мне делать, если я вдруг захотел перехватить ошибку и обработать по-своему?

                    Повесить свой обработчик ошибок (set_error_handler)?
                    На самом деле там всего несколько, и резонность их кастомизации сомнительна.

                    Почему везде синглтоны?

                    Потому что это лучшее, что я нашел в данном случае, и оно хорошо работает с IDE.

                    Core/classes директория ядра? Почему там валяется PHPMailer? Ведь мало того, что это сторонняя библиотека и ее лучше положить в vendors, так и к ядру она имеет минимальное отношение, тем более что директория то vendor существует.

                    Это с одной стороны сложилось исторически, с другой стороны в классе PHPMailer есть публичный конструктор, который не дружит с трейтом cs\Singleton, и так как класс лежит с отрезанным конструктором (он на самом деле без параметров выставляет параметры, которые и так по умолчанию используются), очень не хочется используя composer редактировать что-то в папке vendor руками.

                    Потом вы практически везде предлагаете разработчикам поиграть в шарады. Ну епты, что это означает?

                    Автозагрузчик, ищет файлы в директориях, если возвращается false — файла нет, ищется дальше, очевидно же.
                    На самом деле в этом рыться изначально не обязательно, главное как оно работает, поэтому автозагрузку нужно будет описать в документации, спасибо.
                      +1
                      Нет, мне например, очень важно как оно работает. Потому что поддерживать такой код очень дорого.

                      Авторзагрузчик должен быть простым и понятным:

                      $plugin = new \plugins\MyPlugin();

                      Отсюда можно получить полную информацию что и откуда нужно грузить. И все встроено и придумывать ничего не нужно.
                        0
                        $Posts = \cs\modules\Blogs\Posts::instance();
                        

                        Что тут не понятно? Ясно и что за класс, и где лежит.
                          0
                          Хорошо, но тогда какохо черта вы используете require для подключения классов, если у вас уже есть автозагрузчик?
                            0
                            В каком месте? Пример кода выше — и есть автозагрузчик классов.
                        +1
                        Хорошо, я в одном хэндлере должен отлавливать ошибки всех классов и среди них выискивать ошибку от конкретного класса?

                        set_error_handler(function($errno) {
                            if ($errno === 1024) { // Окей вроде бы я поймал нужный код ошибки от класса MySuperClass
                                // А как же мне получить доступ к инстансу класса откуда выпала ошибка?
                                // А вдруг нужно еще какая то дополнительная информация?
                                // А вдруг мне нужно еще что то с классом сделать?
                            }
                        });
                        


                        Почему не:

                        $db = new Db();
                        
                        try {
                             $db->connect('192.168.0.1');
                        } catch (DbException $e) {
                             if ($e->getCode() === 1) { // Сервер БД не отвечает
                                 $db->connect('192.168.0.2');
                             }
                        }
                        


                        Как бы у вас получилось бы в хэндлере ошибки обработать конкретную ошибку конкретного класса и затем вернуться к инстансу этого класса и попробовать вызвать альтернативное подключение?
                          0
                          Это поведение

                          попробовать вызвать альтернативное подключение

                          уже заложено в ядре.

                          Если нужно изменить поведение — унаследуйте класс, и переопределите нужный метод, только оставьте исходный интерфейс в том же виде.
                            0
                            Я не хочу разбираться с документацией — я хочу чтобы это уже было из коробки, так как в PHP это уже работает из коробки. Тогда почему у вас этого нет?

                            То есть классы PHP умеют выбрасывать исключения, а ваши не умеют? Тогда выбросьте классы и используйте неймспейсы — все равно для вас что классы, что неймспейсы — вещи походу одинаковые.
                              –2
                              Если класс, который используется системой и у вас нет возможности обернуть его в try — он может и не выбрасывать исключение, вы его всё равно ловить не будете. А если хотите ловить (зачем? если можно не ловить, тем более, что в логах всё будет), то модифицируйте стандартное поведение для достижения цели.

                              В большинстве случаев в прокадшне всё равно, по какой причине вернулось false из $db->q('SELECT ...');, а для отладки можно и кастомизировать класс работы с БД, и в логи посмотреть.
                                0
                                PDO умеет выбрасывать исключения. Я не могу сходу назвать ни одного системного класса, который бы так же не умел этого делать.
                                  0
                                  Под системой я имею ввиду движок.
                          +1
                          По вашему класс User не божественный?

                          Он у вас имеет представление об окружении в котором находится — метод get, помимо этого он знает с какой БД он работает — работает он с SQL подобной БД. Он и модель, и в каком то смысле контроллер.
                            0
                            Ещё раз повторюсь, потому что это не фреймворк с кучей слоев абстракций и никакой связанности «чтобы красиво». Тут некоторые компоненты знают, где они и с кем работают, и это скорее плюс, чем минус. Это позволяет работать быстрее и оптимальнее.
                              +1
                              Можно конвертнуть php в байткод и все будет оптимально.

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

                              То есть если вы показываете свой продукт, то вам наверное хочется, что бы его приняли, но у вас внутри системы все вывернуто наизнанку: вроде бы модные штуки применяются, но используются не традиционно.
                        0
                        Либо я не понял, о чём речь.

                        Я имел ввиду, что нужно задать вопросы: что мне нужно от модуля? Например, страндартное:

                        • Название модуля
                        • Список зависимостей
                        • Минимальные требования к ядру
                        • Список контроллеров
                        • Инсталировать
                        • Деинсталировать


                        namespace modules;
                        
                        class MyModule extends \base\Module {
                            public $name = 'My Module Example';
                        
                            public $minCoreVersion = 10;
                        
                            public $maxCoreVersion = 20;
                        
                            public function requires() { return array('Mailer', 'CustomModule'); }
                        
                            public function controllers() { return array('PageController', 'SettingController'); }
                        
                            public function install() { ... }
                        
                            public function uninstall() {  ... }
                        }
                        


                        То есть модуль может не завязываться на вашу архитектуру — описательный класс модуля только рассказывает что ему нужно для работы, что он отдает, и предоставляет минимальные действия над собой. Другими словами — чтобы перенести модуль в другую среду — вам нужно воссоздать условия для его работы — класс \base\Module, и предоставить два модуля Mailer, CustomModule реализующие нужный кусок API для работы модуля. То есть грубо гвооря можно запустить модуль в режиме совместимости, сэмулировав для него подходящую среду.

                        Таким образом для ваших текущих модулей сэмулировать среду будт сложней.
                          0
                          Где такое можно увидеть в работе? Чтобы я, к примеру, написал свой \base\Module и использовал его в CleverStyle CMS?
                          Мне кажется, такое реиспользование в даном случае маловероятно, так как в качестве зависимости нужно будет указать все части ядра, которые используются, а значит переносимость если и будет — то очень неудобная, и никто этим заниматься не станет. Короче, это будет overengeneering.
                            +1
                            Мне кажется, такое реиспользование в даном случае маловероятно, так как в качестве зависимости нужно будет указать все части ядра.

                            Что считать модулем. Если есть зависимости только от БД — только одна зависисмость БД.

                            Вы упороты, увы в плохом смысле этого слова. Вместо того чтобы писать на хабр — почитайне книжки по проектированию, посмотрите на другие движки, а лучше поработайте с ними (больше — тем лучше).
                              0
                              То есть это вроде и есть, но его вроде и нет?
                              А что если зависимости от системной конфигурации, от маршрутизации и прочих вещей? По сути, придется реализовать половину интерфейсов движка чтобы перенести, я не думаю, что имеет смысл так всё пере усложнять.
                              Разве вы можете взять плагин Wordpress и вставить его в Joomla? Вот так просто — едва ли, вот и здесь не пойму почему вы вцепились в данную фичу. Сделать-то можно, но польза весьма сомнительная, а простой json файл читать и редактировать проще.
                  0
                  Как на счет Silex или Laravel?
                    –1
                    Как-то смотрел на Laravel, но подходы тут и там сильно отличаются, так как это скорее CMF, а не фреймворк, со всеми вытекающими.
                    +3
                    Почитайте «Чистый код» Боба Мартина. Код пишется не для машины, а для людей. Почему? Читайте книгу. На меня наибольшее впечатление оказала статистика по исследованию соотношения чтения/написания кода разработчиком при выполнении работы.

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

                    Ну и ещё. Велосипеды даже в PHP уже вышли из моды.
                      0
                      Почитаю обязательно. Стандарты PSR для меня вещь сомнительная, как и для многих, я не согласен с частью правил, и пробелы для отступов — одно из них.

                      На счёт велосипедов вы в корне не правы, ибо все новые штуки, которые приходят на смену старым и есть велосипеды. Или вы хотите застрять на текущих инструментах и не видеть ничего нового?
                        +2
                        Это конечно хорошо. Писать велосипед не понимая основных принципов, которые уже используются в языке не один год, либо писать велосипед, поработав с десятком других «велосипедов», создавая продукт осознанно и выверено. Разницу чувствуете?
                          0
                          Вопрос отступов решается настройкой IDE. Я не поскупился, купил себе PhpStorm. Но и NetBeans позволяет без проблем настроить размерность табуляции.
                          PSR-2 описывает не только отступы (https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
                          Стоит понимать причины возникновения стандартов. Это же не Вася Пупкин взял и придумал «давайте так делать». Множество людей обсуждали вопросы и пришли в итоге к определенному компромиссу, которые устроил большинство.
                          Тяга делать «как удобно мне» самоубийственна. Рано или поздно вы попадёте в команду профессиональных разработчиков. Уверяю, что вам придётся либо принять стандарты coding style, либо от вас избавятся. Никому не интересно тратить время на очередного «всезнайку».
                          У меня есть один бывший коллега, с которым расстались 2 года назад. Он удивительно схож с вами во многом. Успешной его карьеру назвать не могу, хоть у него и «горят глаза». Да даже если и не смотреть на карьеру, он топчется на месте в техническом плане.
                          Не повторяйте чужих ошибок.
                        +3
                        Судя по вашему профилю, я значительно раньше начал писать на php и в самом начале я допустил непростительную ошибку — я не написал, свою cms. И вот в прошлом году дернул меня черт предложил друг написать движок магазина и на его основе делать заказчикам магазины (прошу прощения за тавтологию).

                        Что я за этот год получил? 4 магазина, около 10к$ и огромную кучу геморроя с поддержкой.
                        При этом движок был был написан на Yii с достаточным (как я думал на то время) уровнем абстракции.

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

                        А вы, молодец! Движок ваш конечно не выстрелит, но за то поимеете опыт — что стоит делать и как, а что — нет.

                        Главное, помните — кроме написания кода, вам еще его и поддерживать.

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