Pull to refresh

Пользовательские запросы к БД в MODx Revolution

Reading time 5 min
Views 49K
Данный топик наверняка будет полезен тем, у кого довольно большие проекты на MODx Revolution, так как с обычными сайтами-визитками достаточно и стандартных методов работы а-ля $modx->getObject(), $modx->getCollection() и т.п. И данные методы по сути своей не просто работа с базой данных, а еще и с объектами MODx.

При работе с большими проектами методы а-ля $modx->getCollection() для нас не лучшее решение по двум причинам:
1. Перерасход ресурсов. Данные методы не просто получают данные из БД, но еще и создают инстанции получаемых объектов. В данном случае получая информацию о 10000 документов, мы получаем 10000 объектов modResource, что не очень круто.
2. Осложняется задача подсчета получаемых записей. Помимо прямых сложностей подсчета еще на уровне запроса, даже если вы получите 10 записей одного и того же документа (к примеру), MODx вернет вам как результат только один объект modResource. И хотя часто такое устроит многих программистов (они получили уникальные объекты и рады), кого-то это не устроит, так как опять же происходит перерасход ресурсов, а по конечному результату сразу и не видно, что запрос не оптимизированный.
К тому же при работе на крупных проектах нам чаще всего нужны не сами объекты, а только информация (записи из базы данных).

Описанные здесь методы работы с БД ставят 2 задачи:
1. Дать бОльшую гибкость в написании запросов к БД.
2. Придерживаться стандартных методов xPDO, то есть избежать чистого SQL, так как чистый SQL по некоторым причинам в фреймворках вообще не кашерно (хотя бы с точки зрения возможной миграции на другой тип БД, смены названий таблиц, префиксов или еще чего-нибудь)

Итак, к делу.

Для начала нам необходимо освоить важный метод
$modx->newQuery($class);


Для построения всех запросов в MODx всегда нужен хотя бы один базовый класс, от которого будет плясать весь запрос.

Вот более развернутый пример:
$q = $modx->newQuery('modResource');
$q->where(array(
	'context_key' => 'web'
));
$result = $modx->getCollection('modResource', $q);


В данном случае $q — это часто встречающаяся нам в документации так называемая criteria.
Это почти что тоже самое, что и where, когда мы передаем его в качестве второго параметра, только более мощный инструмент, так как у него много важных методов типа Sortby, leftJoin, innerJoin, Limit и другие.

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

Итак, несколько переделаем наш запрос.
$q = $modx->newQuery('modResource');
$q->where(array(
	'context_key' => 'web'
));
$q->prepare();
$sql = $q->toSQL();


Вот здесь мы уже получим чистый SQL, что наверняка много кому понадобится.
В данном примере мы увидели еще один важный метод
$q->prepare();

Он как раз и готовит конечный SQL.

Теперь же мы можем выполнить этот SQL
$q = $modx->newQuery('modResource');
$q->where(array(
	'context_key' => 'web'
));

$q->limit(10);    // Добавим лимит записей

$q->prepare();
$sql = $q->toSQL();

$query = $modx->prepare($sql);
$query->execute();

$result = $query->fetchAll(PDO::FETCH_ASSOC);
print_r($result);


UPD: этот пример оставлю в качестве демонстрашки $modx->prepare($sql);, но сразу за этим смотрите исправленный пример с одним вызовом ->prepare();
$q = $modx->newQuery('modResource');
$q->where(array(
	'context_key' => 'web'
));

$q->limit(10);    // Добавим лимит записей

$q->prepare();
 
$q->stmt->execute();

$result = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($result);


На выходе мы как раз и получим массив данных.
Но колонки будут иметь не совсем удачные названия а-ля
[modResource_id] => 0
[modResource_type] => document

Чтобы было понятней, добавим явный SELECT в запрос.
$q = $modx->newQuery('modResource');
$q->where(array(
	'context_key' => 'web'
));
$q->select(array(
   'modResource.*'
));
$q->limit(10); 

$q->prepare();
$q->stmt->execute();

$result = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($result);


Вот теперь все хорошо с именами колонок :-)

А теперь довольно полезный и наглядный пример: Получим 1/10 записей со сдвигом 1/20 и упорядочим по ID.
$q = $modx->newQuery('modResource');
$q->where(array(
	'context_key' => 'web'
));
$q->select(array(
   'modResource.*'
));

// Подсчитываем общее число записей
$total = $modx->getCount('modResource', $q);

// Устанавливаем лимит 1/10 от общего количества записей
// со сдвигом 1/20 (offset)

$q->limit($total / 10, $total / 20);   

// И сортируем по ID в обратном порядке
$q->sortby('id', 'DESC');

$q->prepare();
$q->stmt->execute();

$result = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($result);


Кстати, довольно легко эти примеры переделать так, чтобы получить конечные объекты.


$q = $modx->newQuery('modResource');
$q->where(array(
    'context_key' => 'web'
));
$q->select(array(
   'modResource.*'
));

// Подсчитываем общее число записей
$total = $modx->getCount('modResource', $q);

// Устанавливаем лимит 1/10 от общего количества записей
// со сдвигом 1/20 (offset)

$q->limit($total / 10, $total / 20);   

// И сортируем по ID в обратном порядке
$q->sortby('id', 'DESC');

$q->prepare();

// Получаем объекты
$docs = $modx->getCollection('modResource', $q);


Суть методов $modx->getObject() и $modx->getCollection() заключается в том, чтобы получив данные из БД, инициировать указанный класс и набить в него полученные данные методом $object->fromArray($array());

Кстати, настоятельно не советую играться с print_r($docs);, так как результат методов а-ля $modx->getCollection() — массив указанных объектов, каждый из которых является расширенным объектом xPDO и MODx вместе взятых, то есть это ооочень много информации.
Потому для вывода информации из объектов используйте метод $object->toArray();
В данном случае примерно так:
foreach($docs as $doc){
	print_r($doc->toArray());
}


Еще на заметку: элементы в массиве объектов MODx перечислены не по порядку, а каждый ключ — ID объекта (записи), потому вы не можете наверняка обратиться к 11-му элементу через $docs[10], так как документ с ID 10 может оказаться 1-ым, или 100-ым, или его может вообще не быть, хотя в массиве будет 100 документов.
Можете сами убедиться, переделав вывод как
foreach($docs as $id => $doc){
	print "<br />". $id;
}


Для работы с массивами объектов MODx очень полезно изучить методы работы с элементами массивов, описанных здесь.

end() — Устанавливает внутренний указатель массива на его последний элемент
key() — Выбирает ключ из массива
each() — Возвращает текущую пару ключ/значение из массива и смещает его указатель
prev() — Передвигает внутренний указатель массива на одну позицию назад
reset() — Устанавливает внутренний указатель массива на его первый элемент
next() — Передвигает внутренний указатель массива на одну позицию вперёд

К примеру, если мы хотим получить первый элемент из массива MODx, никак нельзя обращаться $doc = $docs[0]; В 99,9% вы ничего не получите, так как записи с ID = 0 практически никогда не используются.
Правильно обратиться так: $doc = current($doc);

Те, кому этот метод понравится, наверняка построят таким образом и более сложные запросы сразу из нескольких таблиц и т.п.

P.S. Небольшой сборный запрос с парой полезных фильтров.
Получим все настройки из контекстов WEB и MGR, значения которых IS NOT NULL и != ''

$q = $modx->newQuery('modContext');

$where = array(
   'modContext.key:in' => array('web', 'mgr'),
   'cs.value:!=' => NULL,
   'cs.value:!=' => '',
);
$q->select(array(
   'modContext.key',
   'cs.key as setting_key',
   'cs.value'
));

$q->innerJoin('modContextSetting', 'cs', 'cs.context_key = modContext.key');

$q->where($where);

$q->prepare();
$q->stmt->execute();

$result = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($result);
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+12
Comments 12
Comments Comments 12

Articles