Использование xPDO без MODX

Некоторое время назад подписался помочь хорошему проекту Критиканство в написании парсера для автоматической загрузки скриншотов игр.
«Критиканство» — это крупнейший в российском сегменте интернета агрегатор критических отзывов на фильмы и игры. В нашей базе более 150 печатных и интернет-изданий, а количество рецензий на момент запуска (на март 2013 года) составило более 60 000.
Этот проект был запущен ребятами с прекрасного сайта kino-govno.com, к которому я питаю очень теплые чувства. Шутка ли, его создали авторы из лучшего журнала про игры — Game.EXE, который я до дыр зачитывал еще в школе.

Админка Критиканства создавалась в разное время разными специалистами и выглядит немного разрозненной. Тоже касается и таблиц базы данных — каждый разработчик мудрил с ними по своему.

Сам сайт сделан очень олдскульно и «чисто для себя». То есть, работает быстро, но все что можно зашито прямо в php код. Никаких ORM, никаких шаблонов, только то, что нужно.

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

Запуск xPDO


Последняя версия xPDO доступна в репозитории на github. Нужно создать конфигурационный файл, прописать туда данные для коннекта к БД, определить константы и запустить xPDO.

Вот примерный конфиг:
$database_type = 'mysql';
$database_server = 'localhost';
$database_user = 'databaseadmin';
$database_password = 'databasepassword';
$database_connection_charset = 'utf8';
$dbase = 'mydatabase';
$table_prefix = '';
$database_dsn = 'mysql:host=localhost;dbname=mydatabase;charset=utf8';
$config_options = array();
$driver_options = array();

if (!defined('PROJECT_NAME')) {
	define('PROJECT_NAME', 'MyProject');
}
if (!defined('PROJECT_NAME_LOWER')) {
	define('PROJECT_NAME_LOWER', strtolower(PROJECT_NAME));
}

if (!defined('PROJECT_BASE_PATH')) {
	define('PROJECT_BASE_PATH', '/var/www/data/www/');
}
if (!defined('PROJECT_CORE_PATH')) {
	define('PROJECT_CORE_PATH', PROJECT_BASE_PATH . 'core/');
}
if (!defined('PROJECT_ELEMENTS_PATH')) {
	define('PROJECT_ELEMENTS_PATH', PROJECT_CORE_PATH . 'elements/');
}

А вот файл index.php
require 'core/config/config.inc.php';
require PROJECT_CORE_PATH . 'xpdo/xpdo.class.php';

$xpdo = new xPDO($database_dsn, $database_user, $database_password, $config_options, $driver_options);
$xpdo->setLogLevel(xPDO::LOG_LEVEL_ERROR);
$xpdo->setLogTarget('HTML');

xPDO подключен — можно писать схему, генерировать по ней модель таблиц и работать в привычном окружении. Продолжение файла будет ниже, а пока вот вам скрипт генерации модели:
<?php

require dirname(dirname(__FILE__)) . '/config/config.inc.php';
require PROJECT_CORE_PATH . 'xpdo/xpdo.class.php';

$xpdo = new xPDO($database_dsn, $database_user, $database_password, $config_options, $driver_options);
$xpdo->setLogLevel(xPDO::LOG_LEVEL_INFO);
$xpdo->setLogTarget('HTML');

$model = PROJECT_CORE_PATH . 'model/';
$map = $model . PROJECT_NAME_LOWER . '/mysql';
$xml = $model . '/schema/' . PROJECT_NAME_LOWER.'.mysql.schema.xml';

/** @var xPDOManager $manager */
$manager = $xpdo->getManager();
/** @var xPDOGenerator $generator */
$generator = $manager->getGenerator();

rrmdir($map);
$generator->parseSchema($xml, $model);

$xpdo->log(xPDO::LOG_LEVEL_INFO, 'Model generated.');

/**
 * Recursive directory remove
 *
 * @param $dir
 */
function rrmdir($dir) {
	if (is_dir($dir)) {
		$objects = scandir($dir);

		foreach ($objects as $object) {
			if ($object != "." && $object != "..") {
				if (filetype($dir . "/" . $object) == "dir") {
					rrmdir($dir . "/" . $object);
				}
				else {
					unlink($dir . "/" . $object);
				}
			}
		}

		reset($objects);
		rmdir($dir);
	}
}

Понятное дело, схема-файлы ничем не отличаются от таковых в MODX.

Роутинг


Так как modx.class.php у меня нет, пришлось написать основные методы в собственном классе. Логика простая, index.php получает запрос, определяет ajax это или нет, и в зависимости от этого передаёт дальше: в loadPage(), или в handleAjax().

Первый метод грузит нужный php файл из специальной директории, оформляет его шаблоном и вставляет пейсхолдеры, а второй обрабатывает ajax запрос и отдаёт json ответ. Короче говоря, MODXmini.

Итак, продолжение index.php
$isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';

require PROJECT_CORE_PATH . 'model/myproject/myproject.class.php';
/** @var MyProject $myproject */
$myproject = $xpdo->getService('MyProject', 'myproject', PROJECT_MODEL_PATH . 'myproject/', array());

$request = empty($_REQUEST['q'])
	? 'home'
	: $_REQUEST['q'];

$tmp = explode('/', $request);
$page = current($tmp);
$params = array_splice($tmp, 1);

session_start();

if ($isAjax && !empty($_POST['action'])) {
	$response = $myproject->handleAjax($_POST['action']);
	$response = preg_replace('/\[\[.*?\]\]/', '', json_encode($response));
	@session_write_close();
	exit($response);
}
else {
	ob_start();
	$output = $myproject->loadPage($page, $params);

	echo preg_replace('/\[\[.*?\]\]/', '', $output);
	@session_write_close();
}
Метод loadPage:
public function loadPage($page, $params = array()) {
	$page = strtolower($page);
	if (!file_exists(PROJECT_ELEMENTS_PATH . 'page/' . $page . '.inc.php')) {
		$page = 'home';
	}

	switch ($page) {
		case 'game':
			$template = 'game';
			break;
		default:
			$template = 'base';
	}

	$_REQUEST['page'] = $page;
	$_REQUEST['request'] = $params;
	$xpdo = $this->xpdo;
	$myproject = $this;

	echo include PROJECT_ELEMENTS_PATH . 'page/' . $page . '.inc.php';
	$content = ob_get_clean();
	if (is_bool($content)) {
		$content = '';
	}
	$this->setPlaceholders(array(
		'content' => $content,
		'Header' => $this->getChunk('header'),
		'Navbar' => $this->getChunk('navbar'),
		'Footer' => $this->getChunk('footer'),
	));

	$tpl = $this->getChunk($template, $this->placeholders, PROJECT_ELEMENTS_PATH . 'templates/');
	$pls = $this->makePlaceholders($this->placeholders);

	$output = str_replace($pls['pl'], $pls['vl'], $tpl);

	$output = str_replace(array('[^t^]', '[^q^]', '[^qt^]'), array(
		sprintf("%2.4f s", microtime(true) - $this->startTime),
		$xpdo->executedQueries,
		sprintf("%2.4f s", $this->xpdo->queryTime)
	), $output);

	return $output;
}

Метод getChunk загружает чанки с жесткого диска, а не из БД. Внутри файлов-страниц можно использовать основной класс, xPDO и вообще делать там что угодно. В принципе, это как бы файловые сниппеты.

Заключение


Этой заметкой я хочу показать, что можно использовать основные возможность MODX без него самого.

Если вам не нужна админка, юзеры, права доступа, ресурсы, а нужна только быстрая и удобная работа с БД, вокруг которой вы строите свой проект — xPDO может помочь. Конечно, нужно написать роутинг самостоятельно, но в итоге вы получаете быстрый, легкий и MODX-совместимый движок.

Возможно, стоит потом сделать репозиторий с этим MODXmini проектом — вдруг еще понадобится. Ну и стало ясно, что pdoTools стоит немного доработать, чтобы он не так был завязан на переменные MODX.

Кстати говоря, вот скриншоты того, что у меня получилось:
Василий Наумкин
18 марта 2014, 16:10
modx.pro
3
5 959
+3

Комментарии: 20

Антон Соловьёв
18 марта 2014, 21:39
0
Кстати, была такая статья.
Пока не прочитал, все описанное здесь казалось сплошным шаманством.
    Виталий Князь
    18 марта 2014, 21:39
    0
    Иными словами (уточное для себя) можно сделать таким образом свою админку с только нужными элементами?
      Алексей Карташов
      18 марта 2014, 21:54
      0
      Тоже скоро будет задача подобного парсера. И весь modx там тоже, в общем-то, ни к чему.
      Так что, спасибо, пригодится!)
      Как всегда радуешь =)

      p.s. это поэтому ты стал частью КГ? =)
        Василий Наумкин
        18 марта 2014, 22:54
        0
        Ага, поэтому =)
          Алексей Карташов
          18 марта 2014, 22:56
          0
          Круто, поздравляю!

          А если не секрет, какие плюшки и обязанности появляются у участника администрации кг? Просто интересно :-)
            Василий Наумкин
            18 марта 2014, 22:59
            0
            Пока никаких, кроме малиновых штанов.

            Кстати говоря, за работу я денег тоже пока не брал — все по дружбе.
              Алексей Карташов
              18 марта 2014, 23:02
              0
              По крайней мере, первый шаг к тому, чтобы стать участником подкаста, уже сделан xD
        Іван Клімчук
        18 марта 2014, 22:49
        0
        Понятно, что делалось скорее всего под задачу, да и скрипт всего лишь, но я бы какой роутинг подкрутил бы толковый. Вообще интересно было бы сделать возможность загружать xpdo как пакет для compser.
          Aртур Чикин
          18 марта 2014, 23:45
          0
          www.kritikanstvo.ru/top/movies/best/alltime/genres/95/ прокручиваю в низ там остается очень большое поле пустое. Стоит только нажать f12 (консоль разработчика) оно исчезает.
          Иван Брежнев
          20 марта 2014, 02:13
          0
          Для роутинга, классная библиотека nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html
          Классная ORM, по мне так лучшая из тех с какими я работал github.com/laravel/framework/tree/master/src/Illuminate/Database Работает быстро, настраивается просто, схем не требует, поддерживает разнообразные связи, различные мутаторы и т.п. Советую просто обратить внимание
            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
            11