Skip to content

📘 pbQuery

pbQuery — это мощная и лаконичная обёртка над xPDOQuery, вдохновлённая стилем Laravel Query Builder. Она позволяет писать читаемые, гибкие и безопасные запросы к базе данных в MODX.

Работать с pbQuery можно двумя способами:

  • Через фасад:

    php
    pbQuery::table(modResource::class)
  • Или через глобальный хелпер (предпочтительный способ):

    php
    query(modResource::class)

Далее во всех примерах мы будем использовать именно хелпер query().

🏷 Изменение алиаса таблицы

По умолчанию pbQuery использует имя класса (например, modResource) как алиас таблицы во всех SQL-запросах:

php
query(modResource::class)

🔎 SQL:

sql
SELECT `modResource`.`id`, ... FROM `modx_site_content` AS `modResource`

Если тебе нужно задать свой собственный алиас — используй метод alias():

php
query(modResource::class)
    ->alias('r')
    ->select(['id', 'pagetitle'])
    ->fetchAll();

🔎 SQL:

sql
SELECT `r`.`id`, `r`.`pagetitle` FROM `modx_site_content` AS `r`

Алиас используется во всех частях запроса: SELECT, WHERE, JOIN, ORDER BY и т.д.

🔹 Получение всех записей

Чтобы получить все ресурсы из таблицы modResource:

php
$resources = query(modResource::class)->get();

Метод get() возвращает итератор (Traversable) — вы можете безопасно перебирать результаты в foreach, даже если записей будет очень много:

php
foreach ($resources as $resource) {
    echo $resource->pagetitle;
}

Этот способ аналогичен нативному MODX:

php
$resources = $modx->getIterator(modResource::class);

📌 get() vs all()

В pbQuery доступны два метода для получения множественных записей: get() и all(). Они оба возвращают список объектов, но отличаются тем, как они это делают.

МетодЧто возвращаетКогда использовать
get()Traversable (ленивый итератор)При большом объёме данных, для экономии памяти
all()array (массив объектов)Когда нужно быстро работать со всеми объектами

Пример с get() (итератор):

php
foreach (query(modResource::class)->get() as $resource) {
    echo $resource->id;
}

Пример с all() (массив):

php
$resources = query(modResource::class)->all();
echo count($resources); // Получаем количество

Разница важна: get() подгружает данные по мере итерации (экономия памяти), а all() сразу получает все данные в память. Если вы делаете сортировку, подсчёт или манипуляции с массивом — используйте all().

🔸 Получение одной записи

Первый результат

Чтобы получить первый найденный ресурс:

php
$resource = query(modResource::class)->first();

Этот метод возвращает null, если запись не найдена. Он аналогичен $modx->getObject(...).

Последний результат

Метод last() автоматически делает сортировку по убыванию ID и возвращает последний объект:

php
$resource = query(modResource::class)->last();

Случайный результат

Чтобы получить один случайный объект:

php
$resource = query(modResource::class)->rand();

Полезно, например, для случайного баннера, отзыва или товара.

🔍 Поиск конкретной записи

По ID (по умолчанию)

php
$resource = query(modResource::class)->find(3);

Этот код вернёт объект ресурса с id = 3.

По другому полю

Вы можете указать и другое поле:

php
$resource = query(modResource::class)->find('about', 'alias');

Здесь будет найден ресурс, у которого alias = 'about'.

📋 Получение данных в виде массива

Если вам не нужны объекты xPDOObject, вы можете запросить данные в виде обычных массивов.

Все записи:

php
$resources = query(modResource::class)->fetchAll();

Результат — массив, где каждая запись представлена как ассоциативный массив (аналогично PDO::FETCH_ASSOC).

Только одна запись:

php
$resource = query(modResource::class)->fetch();

Возвращает первую найденную строку как массив.

🧷 Извлечение отдельных значений

Метод value()

Чтобы получить одно конкретное значение одного поля:

php
$alias = query(modResource::class)
    ->where(['id' => 6])
    ->value('alias');

Возвращает, например: 'about'.

Метод pluck()

Метод pluck() позволяет получить массив значений из одного столбца:

php
$aliases = query(modResource::class)
    ->where(['published' => 1])
    ->pluck('alias');

Результат:

php
['home', 'about', 'contacts', ...]

Вы также можете указать, какое поле использовать в качестве ключа массива:

php
$aliases = query(modResource::class)
    ->pluck('alias', 'id');

Результат:

php
[
    1 => 'home',
    2 => 'about',
    3 => 'contacts',
]

📊 Агрегации

pbQuery предоставляет удобные методы для выполнения агрегатных запросов к базе данных. Эти методы работают аналогично SQL-функциям COUNT, MIN, MAX, AVG, SUM, EXISTS, и упрощают их вызов.

🔢 count()

Возвращает количество записей, соответствующих условиям запроса.

php
$count = query(modResource::class)
    ->where(['published' => 1])
    ->count();

👉 Вернёт, например: 42.

📉 min()

Получить минимальное значение поля:

php
$minId = query(modResource::class)->min('id');

👉 Вернёт самое маленькое значение поля id.

📈 max()

Получить максимальное значение поля:

php
$maxId = query(modResource::class)->max('id');

👉 Можно использовать, чтобы найти последний ID в таблице.

avg()

Рассчитать среднее значение поля:

php
$avgRank = query(modUser::class)->avg('rank');

👉 Вернёт среднее значение поля rank (например, 3.14).

sum()

Получить сумму значений определённого поля:

php
$totalViews = query(modResource::class)
    ->where(['published' => 1])
    ->sum('views');

👉 Вернёт, например: 15927.

📊 range()

Возвращает массив с минимальным и максимальным значением поля:

php
$range = query(modUser::class)->range('rank');

👉 Результат:

php
[
    'min' => 1,
    'max' => 5,
]

Полезно, например, чтобы построить слайдер с минимальным и максимальным значением.

exists()

Проверяет, существует ли хотя бы одна запись, удовлетворяющая условиям:

php
$hasUnpublished = query(modResource::class)
    ->where(['published' => 0])
    ->exists();

👉 Вернёт true или false.

🔗 Соединения таблиц

Методы соединения позволяют объединять текущую таблицу с другими таблицами или подзапросами по заданному условию.

📌 Методы

МетодОписание
join($class, $alias, $on)Выполняет INNER JOIN по условию ON.
leftJoin($class, $alias, $on)Выполняет LEFT JOIN.
rightJoin($class, $alias, $on)Выполняет RIGHT JOIN.
joinSub($callback, $alias, $on)Выполняет JOIN с подзапросом.
leftJoinSub($callback, $alias, $on)LEFT JOIN с подзапросом.
rightJoinSub($callback, $alias, $on)RIGHT JOIN с подзапросом.

📎 Пример 1. Простой LEFT JOIN с пользователем

php
query(modResource::class)
    ->alias('r')
    ->leftJoin(modUser::class, 'u', 'r.createdby = u.id')
    ->select(['r.id', 'r.pagetitle', 'u.username'])
    ->fetchAll();

🟢 SQL:

sql
SELECT `r`.`id`, `r`.`pagetitle`, `u`.`username`
FROM `modx_site_content` AS `r`
LEFT JOIN `modx_users` AS `u` ON r.createdby = u.id

📎 Пример 2. Несколько JOIN подряд

php
query(modResource::class)
    ->alias('r')
    ->select(['r.id', 'creator.username as created_by', 'editor.username as edited_by'])
    ->join(modUser::class, 'creator', 'r.createdby = creator.id')
    ->leftJoin(modUser::class, 'editor', 'r.editedby = editor.id')
    ->fetchAll();

🟢 SQL:

sql
SELECT `r`.`id`, `creator`.`username` AS `created_by`, `editor`.`username` AS `edited_by`
FROM `modx_site_content` AS `r`
JOIN `modx_users` AS `creator` ON r.createdby = creator.id
LEFT JOIN `modx_users` AS `editor` ON r.editedby = editor.id

📎 Пример 3. LEFT JOIN с подзапросом (joinSub)

php
query(modUser::class)
    ->alias('u')
    ->select(['u.username', 'p.fullname'])
    ->leftJoinSub(function ($query) {
        $query->table(modUserProfile::class)
            ->select(['internalKey', 'fullname']);
    }, 'p', 'u.id = p.internalKey')
    ->fetchAll();

🟢 SQL:

sql
SELECT `u`.`username`, `p`.`fullname`
FROM `modx_users` AS `u`
LEFT JOIN (
    SELECT `modUserProfile`.`internalKey`, `modUserProfile`.`fullname`
    FROM `modx_user_attributes` AS `modUserProfile`
) AS p ON u.id = p.internalKey

🔀 Объединение запросов

Методы union и unionAll позволяют объединить результаты нескольких запросов с одинаковой структурой столбцов в один набор данных.

📌 Методы

МетодОписание
union($callback)Объединяет с другим запросом через UNION (уникальные строки).
unionAll($callback)Объединяет с другим запросом через UNION ALL (все строки).

📎 Пример 1. Объединение ресурсов с разными шаблонами

php
query(modResource::class)
    ->select(['id', 'pagetitle'])
    ->where(['template' => 1])
    ->union(function ($query) {
        $query->table(modResource::class)
            ->select(['id', 'pagetitle'])
            ->where(['template' => 2]);
    })
    ->fetchAll();

🟢 SQL:

sql
(SELECT `modResource`.`id`, `modResource`.`pagetitle`
 FROM `modx_site_content` AS `modResource`
 WHERE `modResource`.`template` = 1)
UNION
(SELECT `modResource`.`id`, `modResource`.`pagetitle`
 FROM `modx_site_content` AS `modResource`
 WHERE `modResource`.`template` = 2)

📎 Пример 2. Объединение пользователей и администраторов (unionAll)

php
query(modUser::class)
    ->select(['id', 'username'])
    ->where(['sudo' => 0]) // обычные пользователи
    ->unionAll(function ($query) {
        $query->table(modUser::class)
            ->select(['id', 'username'])
            ->where(['sudo' => 1]); // админы
    })
    ->fetchAll();

🟢 SQL:

sql
(SELECT `modUser`.`id`, `modUser`.`username`
 FROM `modx_users` AS `modUser`
 WHERE `modUser`.`sudo` = 0)
UNION ALL
(SELECT `modUser`.`id`, `modUser`.`username`
 FROM `modx_users` AS `modUser`
 WHERE `modUser`.`sudo` = 1)

📝 Особенности union

  • union и unionAll принимают Closure, в который передаётся экземпляр $query, с которым нужно работать напрямую.
  • Количество и порядок колонок должны совпадать в каждом запросе.
  • Все методы выборки поддерживаются: fetch(), fetchAll(), get(), count(), toSql() и другие.

🧩 Where: фильтрация записей

Метод where() позволяет накладывать условия на выборку.

Поддерживаются:

  • простые условия с массивом,
  • вложенные условия через Closure,
  • логические связки AND / OR (второй параметр),
  • расширенные операторы для обычных и JSON-полей.

Методы:

php
where(array|Closure $conditions, string $conjunction = 'AND'): self
orWhere(array|Closure $conditions): self
whereRaw(string $expression): self
orWhereRaw(string $expression): self
whereColumn(array $conditions, string $conjunction = 'AND'): self
orWhereColumn(array $conditions): self
whereExists(\Closure $callback): self
whereNotExists(\Closure $callback): self

✅ Пример

php
query(modResource::class)->where([
    'template' => 1,
]);
sql
WHERE `template` = 1

📘 Поддерживаемые операторы

ОператорПример в PHPSQL Result Example
= (по умолчанию)'template' => 1template = 1
!='template:!=' => 1template != 1
>'id:>' => 10id > 10
<'id:<' => 10id < 10
>='id:>=' => 5id >= 5
<='id:<=' => 5id <= 5
LIKE'pagetitle:LIKE' => 'demo'pagetitle LIKE '%demo%'
LIKE + массив'pagetitle:LIKE' => ['one', 'two'](pagetitle LIKE '%one%' OR pagetitle LIKE '%two%')
NOT LIKE'title:NOT LIKE' => 'test'title NOT LIKE '%test%'
NOT LIKE + массив'title:NOT LIKE' => ['a', 'b'](title NOT LIKE '%a%' AND title NOT LIKE '%b%')
IN'id:IN' => [1, 2, 3]id IN (1, 2, 3)
NOT IN'id:NOT IN' => [1, 2, 3]id NOT IN (1, 2, 3)
IS'deleted:IS' => nulldeleted IS NULL
IS NOT'deleted:IS NOT' => nulldeleted IS NOT NULL
BETWEEN'id:BETWEEN' => [10, 20]id >= 10 AND id <= 20
FIND_IN_SET'groups:FIND_IN_SET' => 3FIND_IN_SET(3, groups)
FIND_NOT_IN_SET'groups:FIND_NOT_IN_SET' => 3NOT FIND_IN_SET(3, groups)

✅ Простейшие условия

php
query(modResource::class)->where([
    'published' => 1,
    'template' => 2,
])->get();

SQL:

sql
SELECT * FROM modx_site_content WHERE published = 1 AND template = 2

🔁 OR вместо AND (через второй аргумент)

php
query(modResource::class)->where([
    'template' => 1,
    'parent' => 0,
], 'OR')->get();

SQL:

sql
SELECT * FROM modx_site_content WHERE template = 1 OR parent = 0

🧠 Смешанные ключи с OR: (внутри одного массива)

php
query(modResource::class)
    ->select(['id', 'pagetitle'])
    ->where([
        'published' => 1,
        'OR:pagetitle:LIKE' => '%demo%',
        'OR:alias:LIKE' => '%demo%',
    ])->get();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle` 
FROM `modx_site_content` AS `modResource` 
WHERE (
    `modResource`.`published` = 1 
    OR `modResource`.`pagetitle` LIKE '%demo%' 
    OR `modResource`.`alias` LIKE '%demo%')

🧩 orWhere() как альтернатива

php
query(modResource::class)
    ->where(['published' => 1])
    ->orWhere([
        'template' => 1,
        'parent' => 0,
    ])
    ->get();

То же самое, что:

php
->where([
    'template' => 1,
    'parent' => 0,
], 'OR')

🔄 Составной массив условий

php
query(modResource::class)->orWhere([
    ['parent' => 12, 'published' => 1],
    ['template' => 7, 'published' => 1],
])->get();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle` 
FROM `modx_site_content` AS `modResource` 
WHERE  ( 
    (`modResource`.`parent` = 12 AND `modResource`.`published` = 1) OR 
    (`modResource`.`template` = 7 AND `modResource`.`published` = 1) 
)

🔗 Подзапросы в where

Метод where() поддерживает подзапросы через передачу замыкания (анонимной функции). Это удобно для условий с IN, NOT IN, EXISTS и NOT EXISTS.

📌 Поддерживаемые операторы

УсловиеПример ключа в whereSQL-результат
IN'id:IN' => fn ($q) => ...id IN (SELECT ...)
NOT IN'id:NOT IN' => fn ($q) => ...id NOT IN (SELECT ...)
EXISTS':EXISTS' => fn ($q) => ...EXISTS (SELECT ...)
NOT EXISTS':NOT EXISTS' => fn ($q) => ...NOT EXISTS (SELECT ...)

✅ Пример: найти ресурсы, у которых id входит в другой выбор

php
query(modResource::class
    ->select(['id', 'pagetitle'])
    ->where([
        'id:IN' => function ($query) {
            $query
                ->select('id')
                ->where(['template' => 7]);
        }
    ])
    ->fetchAll();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle` 
FROM `modx_site_content` AS `modResource` 
WHERE (`modResource`.`id` IN (
    SELECT `modResource`.`id` 
    FROM `modx_site_content` AS `modResource` 
    WHERE (`modResource`.`template` = 7) 
))

✅ Пример: ресурсы, у которых нет потомков

php
query(modResource::class)
    ->select(['id', 'pagetitle'])
    ->where([
        'id:NOT IN' => function ($query) {
            $query
                ->select('parent')
                ->where(['parent:!=' => 0]);
        }
    ])
    ->fetchAll();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle` 
FROM `modx_site_content` AS `modResource` 
WHERE (`modResource`.`id` NOT IN (
    SELECT `modResource`.`parent` 
    FROM `modx_site_content` AS `modResource` 
    WHERE (`modResource`.`parent` != 0)
))

✅ Пример: с EXISTS

php
query(\modResource::class)
    ->select('id,pagetitle')
    ->where([':EXISTS' => function ($query) {
        $query->table(\modTemplate::class)
            ->select('id')
            ->where(['category' => 53]);
    }
])->get();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle` 
FROM `modx_site_content` AS `modResource` 
WHERE EXISTS (
    SELECT `modTemplate`.`id` 
    FROM `modx_site_templates` AS `modTemplate` 
    WHERE (`modTemplate`.`category` = 53) )

🧨 Сырые SQL условия

php
query(modResource::class)
    ->select('id,pagetitle')
    ->whereRaw('pagetitle LIKE "%demo%"')
    ->orWhereRaw('alias LIKE "%demo%"')
    ->get();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle` 
FROM `modx_site_content` AS `modResource` 
WHERE ( pagetitle LIKE "%demo%" OR alias LIKE "%demo%" )

🔍 Поиск по JSON-полям

pbQuery поддерживает удобную работу с данными, хранящимися в JSON-формате — например, в поле values.

📦 Автоматический режим

Если вы указываете условие where, а поле отсутствует в таблице, pbQuery автоматически ищет его в JSON-поле (по умолчанию — values).

php
query(modResource::class)
    ->where(['priority:>' => 5]) // ищет в values->priority
    ->get();
  • Не нужно указывать values->prioritypbQuery сам это сделает.
  • Работают все стандартные операторы: =, LIKE, IN, >, <, != и др.

📌 Явное указание JSON-поля

Если вы хотите указать точный путь к JSON-ключу, используйте синтаксис с ->. Это может быть полезно, если вы работаете с несколькими JSON-полями или хотите задать глубину.

php
query(modResource::class)
    ->where([
        'values->brand' => 'adidas',
        'values->price:>=' => 100,
        'data->meta->color:LIKE' => '%red%',
    ])

В этом случае pbQuery понимает, что это доступ к JSON, и автоматически подставит JSON_EXTRACT и JSON_UNQUOTE.

🔢 Умное приведение типов

Если значение — число (int или float), то сравнение будет выполнено как числовое (CAST(...) AS DECIMAL), иначе как строка.

php
->where(['values->discount:>' => 0]) // сравнение как с числом
->where(['values->discount' => '0']) // сравнение как со строкой

🔎 Особые операторы

Некоторые операторы требуют особой обработки и дают больше гибкости:

Ищет подстроку в любом месте JSON независимо от вложенности. Используется через основной JSON-путь (values->...), но работает "вглубь".

php
->where(['meta:SEARCH' => 'поиск']) // внутри values->meta
  • Регистр не имеет значения.
  • Можно передать массив значений:
php
->where(['meta:SEARCH' => ['один', 'два']])
JSON_CONTAINS

Проверяет, что JSON-массив содержит указанное значение (или все значения из массива).

php
->where(['tags:JSON_CONTAINS' => 'news'])
->where(['tags:JSON_CONTAINS' => ['news', 'featured']])
NOT JSON_CONTAINS

Отрицание JSON_CONTAINS — значение не должно быть найдено в массиве.

php
->where(['tags:NOT JSON_CONTAINS' => 'old'])

💡 Примеры

php
query(modResource::class)
    // Автоматический JSON (values->priority)
    ->where(['priority:>' => 5])

    // Явное указание JSON-пути
    ->where(['values->brand' => 'nike'])

    // Числовое сравнение
    ->where(['values->discount:>' => 0])

    // Поиск по вложенным ключам
    ->where(['data->meta->price:<=' => 1000])

    // LIKE
    ->where(['title:LIKE' => '%тест%'])

    // IN
    ->where(['status:IN' => ['draft', 'published']])

    // JSON_CONTAINS
    ->where(['tags:JSON_CONTAINS' => 'special'])

    // SEARCH
    ->where(['meta:SEARCH' => 'текст'])

    // Комбинированный поиск
    ->where([
        ['meta:SEARCH' => 'один'],
        ['type:IN' => ['a', 'b']],
    ])

🔗 whereColumn() и orWhereColumn()

Методы whereColumn() и orWhereColumn() позволяют сравнивать одно поле с другим полем, а не со значением. Это полезно, когда тебе нужно сравнение между двумя колонками, например createdby = editedby или publishedon > editedon.

php
->whereColumn(array $conditions, string $conjunction = 'AND'): self
->orWhereColumn(array $conditions): self

📌 Синтаксис

php
query(modResource::class)
    ->whereColumn([
        'createdby' => 'editedby',
        'publishedon:>' => 'editedon',
    ])
    ->fetchAll();
  • Ключ — это имя поля (возможно с оператором, например publishedon:>).
  • Значение — поле, с которым нужно сравнивать.
  • Поддерживаются любые SQL-операторы: =, !=, <, >, <=, >=, LIKE, NOT LIKE, и т.д.

📌 orWhereColumn()

То же самое, но с использованием OR вместо AND.

php
query(modResource::class)
    ->orWhereColumn([
        'createdby' => 'editedby',
        'deletedon:<=' => 'publishedon'
    ])
    ->fetchAll();

🧠 Пример: сравнение с другой таблицей

Если ты используешь join(), можно сравнивать поля из разных таблиц:

php
query(modResource::class)
    ->select('id,pagetitle,createdby')
    ->join(modUser::class, 'User', 'User.id = modResource.createdby')
    ->whereColumn([
        'modResource.createdby' => 'User.id'
    ])
    ->fetchAll();

🔎 SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle`, `modResource`.`createdby` 
FROM `modx_site_content` AS `modResource` 
JOIN `modx_users` AS `User` 
    ON User.id = modResource.createdby 
    WHERE `modResource`.`createdby` = `User`.`id`

🔍 whereExists()

Метод whereExists() добавляет условие EXISTS (...) в SQL-запрос. Используется для вложенных запросов, которые проверяют наличие записей, соответствующих условиям.

php
query(modResource::class)
    ->select('id,pagetitle')
    ->whereExists(function ($q) {
        $q->table(\modTemplate::class)
            ->select('id')
            ->where(['category' => 53]);
    });
    ->fetchAll();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle` 
FROM `modx_site_content` AS `modResource` 
WHERE EXISTS (
    SELECT `modTemplate`.`id` 
    FROM `modx_site_templates` AS `modTemplate` 
    WHERE (`modTemplate`.`category` = 53) )

📌 whereNotExists()

Метод whereNotExists() добавляет в запрос условие NOT EXISTS (...). Он противоположен whereExists() и используется для исключения записей, у которых не существует связанных данных.

Пример:

php
query(modResource::class)
    ->select('id,pagetitle')
    ->whereNotExists(function ($q) {
        $q->table(modTemplate::class)
          ->select('id')
          ->where('category', 53);
    })
    ->fetchAll();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle`
FROM `modx_site_content` AS `modResource`
WHERE NOT EXISTS (
    SELECT `modTemplate`.`id`
    FROM `modx_site_templates` AS `modTemplate`
    WHERE (`modTemplate`.`category` = 53) )

🎯 Выбор полей

Методы select(), selectRaw() и distinct() позволяют гибко управлять выборкой полей в pbQuery.

📌 select()

Метод select() задаёт список полей, которые нужно получить из базы данных. Поддерживает строки, массивы, алиасы, JSON-поля, подзапросы (Closure) и множественные вызовы.

▪️ Строка

php
query(modResource::class)
    ->select('id, pagetitle')
    ->fetchAll();

▪️ Массив

php
query(modResource::class)
    ->select(['id', 'pagetitle'])
    ->fetchAll();

▪️ Алиасы (AS)

php
query(modResource::class)
    ->select(['pagetitle as title']) // или ['pagetitle' => 'title']
    ->fetchAll();

🧩 Поддержка JSON-полей

Если указанное поле не существует в таблице, оно будет автоматически извлечено из JSON-колонки values:

php
query(modResource::class)
    ->select(['id', 'price']) // => values->price
    ->fetchAll();

Можно явно указать JSON-путь через ->:

php
query(modResource::class)
    ->select('properties->title as prop_title')
    ->select('values->description')
    ->get();
php
query(modResource::class)
    ->select('id, pagetitle, properties->seosuite->uri as seouri')
    ->fetchAll();

🗂 JSON-ключи поддерживаются с алиасами (AS) и без.

🔁 Множественные вызовы

Метод select() можно вызывать несколько раз — поля объединяются:

php
query(modResource::class)
    ->select('id')
    ->select(['pagetitle', 'price'])
    ->get();

🔀 Подзапросы в select() через Closure

Метод select() поддерживает Closure, позволяя создавать подзапросы внутри SELECT:

php
query(modResource::class)
    ->alias('resource')
    ->select('id, pagetitle')
    ->select(['children_count' => function ($q) {
        $q->selectRaw('COUNT(*)')
          ->whereColumn(['parent' => 'resource.id']);
    }])
    ->fetchAll();

SQL:

sql
SELECT `resource`.`id`, `resource`.`pagetitle`, (
    SELECT COUNT(*) 
    FROM `modx_site_content` AS `modResource` 
    WHERE `modResource`.`parent` = `resource`.`id` ) as `children_count` 
FROM `modx_site_content` AS `resource`

🔢 Подсчёт через selectCount()

Упрощённая версия подзапроса COUNT(*) с условиями:

php
query(modResource::class)
    ->alias('resource')
    ->select('id, pagetitle')
    ->selectCount('children_count', [
        'parent' => 'resource.id'
    ])
    ->fetchAll();

🧪 Произвольный SQL: selectRaw()

Добавляет любое выражение или подзапрос вручную:

php
query(modResource::class)
    ->alias('resource')
    ->select('id, pagetitle')
    ->selectRaw('(
        SELECT COUNT(*)
        FROM modx_site_content
        WHERE parent = resource.id
    ) as children_count')
    ->fetchAll();

🔁 distinct()

Метод distinct() исключает повторяющиеся строки:

php
query(modResource::class)
    ->select('createdby')
    ->distinct()
    ->fetchAll();

SQL

sql
SELECT DISTINCT `modResource`.`createdby` 
FROM `modx_site_content` AS `modResource`

👉 Получим список всех уникальных авторов.

🔃 Сортировка

orderBy(field, direction = 'ASC')

Сортирует по указанному полю. Если поле отсутствует в таблице — предполагается, что оно находится в JSON-поле values.

php
query(modResource::class)
    ->orderBy('menuindex') // по умолчанию ASC
    ->get();
php
query(modResource::class)
    ->orderBy('price', 'DESC') // JSON-поле values->price
    ->get();

📌 Сортировка по id_order

Если в where() задан id:IN, метод:

php
query(modResource::class)
    ->where(['id:IN' => '3,7,1'])
    ->orderBy('id_order')
    ->get();

вернёт записи в том порядке, как указано: [3, 7, 1].

💡 Передача Closure в orderBy

Можно задать более сложную сортировку через замыкание:

php
query(modResource::class)
    ->select('id,pagetitle')
    ->orderBy(function ($q) {
        $q->table(\modUser::class)
            ->select('createdon')
            ->whereColumn([
                'modUser.id' => 'modResource.createdby'
            ]);
    }, 'DESC')
    ->fetchAll();

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle`
FROM `modx_site_content` AS `modResource` 
ORDER BY (
    SELECT `modUser`.`createdon` 
    FROM `modx_users` AS `modUser` 
    WHERE `modUser`.`id` = `modResource`.`createdby` 
) DESC

🔁 Сортировка по числу дочерних элементов:

php
query(\modResource::class)
    ->alias('resource')
    ->select('id,pagetitle')
    ->selectCount('children_count', [
        'parent' => 'resource.id'
    ])
    ->orderBy(function ($q) {
        $q->table(\modResource::class)
            ->selectRaw('COUNT(*)')
            ->whereColumn([
                'parent' => 'resource.id'
        ]);
    }, 'DESC')
    ->fetchAll();

SQL:

sql
SELECT `resource`.`id`, `resource`.`pagetitle`, (SELECT COUNT(*) 
    FROM `modx_site_content` AS `modResource` 
    WHERE `modResource`.`parent` = `resource`.`id` 
) as `children_count`
FROM `modx_site_content` AS `resource` 
ORDER BY (
    SELECT COUNT(*) 
    FROM `modx_site_content` AS `modResource` 
    WHERE `modResource`.`parent` = `resource`.`id` 
) DESC
html
Array
(
    [0] => Array
        (
            [id] => 47
            [pagetitle] => Очень большая
            [children_count] => 34
        )

    [1] => Array
        (
            [id] => 49
            [pagetitle] => Нецелые цены
            [children_count] => 24
        )

    [2] => Array
        (
            [id] => 12
            [pagetitle] => Женская одежда
            [children_count] => 23
        )

    [3] => Array
        (
            [id] => 1
            [pagetitle] => Детские товары
            [children_count] => 17
        )
)

🧪 orderByRaw(expression)

Позволяет передать произвольное SQL-выражение в ORDER BY:

php
query(modResource::class)
    ->orderByRaw("FIELD(id, 7, 3, 1)")
    ->get();
php
query(modResource::class)
    ->orderByRaw("CASE WHEN template = 1 THEN 0 ELSE 1 END")
    ->get();

🧮 Группировка

groupBy(field)

Группирует результаты по полю:

php
query(modResource::class)
    ->select(['id','template'])
    ->groupBy('template')
    ->fetchAll();

🎯 Фильтрация групп

having(string $expression)

php
query(modResource::class)
    ->alias('resource')
    ->select('template')
    ->selectRaw('COUNT(id) as total')
    ->groupBy('template')
    ->having('total > 5')
    ->fetchAll();

SQl:

sql
SELECT `resource`.`template`, COUNT(id) as total 
FROM `modx_site_content` AS `resource` 
GROUP BY template 
HAVING total > 5

Результат:

html
Array
(
    [0] => Array
        (
            [template] => 0
            [total] => 172
        )

    [1] => Array
        (
            [template] => 1
            [total] => 62
        )

    [2] => Array
        (
            [template] => 7
            [total] => 17
        )

)

Выберем шаблоны (template), у которых количество ресурсов находится в пределах от 5 до 10 включительно:

php
query(modResource::class)
    ->groupBy('template')
    ->having('COUNT(id) BETWEEN 5 AND 10')
    ->fetchAll();

📌 Ограничения

  • limit(n) — ограничивает количество результатов
  • offset(n) — пропускает первые n записей
php
query(modResource::class)
    ->limit(10)
    ->offset(20)
    ->get();

👉 Получим 10 записей, начиная с 21-й.

✨ Пагинация

paginate(int $perPage = 10, string $pageName = 'page'): Paginator

Метод возвращает объект Paginator для удобного постраничного вывода данных.

Параметры

  • $perPage (int, по умолчанию 10) Количество записей на одной странице.

  • $pageName (string, по умолчанию 'page') Название параметра в query string, который указывает текущую страницу. Например, если $pageName = 'p', то URL будет вида:

    html
    /products?p=2

Пример использования в шаблоне

php
{set $users = query('modUser')
    ->where(['active' => 1])
    ->paginate(15)}

<ul>
    {foreach $users->items() as $user}
        <li>{$user.username}</li>
    {/foreach}
</ul>

<div class="pb-pagination" pb-pagination>
    {$users->links()}
</div>

Дополнительно

Подробнее о доступных методах и возможностях смотрите в документации класса Paginator.

✏️ Изменение данных

Методы изменения данных в pbQuery:

МетодИспользуетОписание
createxPDOСоздаёт один или несколько объектов через newObject()->save()
updatexPDOОбновляет поля у найденных объектов
removexPDOУдаляет найденные объекты
incrementxPDOУвеличивает числовое поле
decrementxPDOУменьшает числовое поле
firstOrCreatexPDOЕсли запись существует — возвращает, иначе создаёт новую
updateOrCreatexPDOЕсли запись существует — обновляет, иначе создаёт новую
insertSQLВставляет данные напрямую в таблицу без использования моделей
command('UPDATE')SQLВыполняет массовое обновление через чистый SQL UPDATE
command('DELETE')SQLВыполняет массовое удаление через чистый SQL DELETE

✅ create(array $data)

Создаёт одну или несколько записей через xPDO. Возвращает:

  • xPDOObject при успешном создании одной записи,
  • null — если не удалось создать,
  • 7 - количество созданных записей — при массовом создании.
php
// Создание одной записи
query(modResource::class)->create([
    'pagetitle' => 'Новый ресурс',
    'context_key' => 'web'
]);

// Создание нескольких записей
query(modResource::class)->create([
    ['pagetitle' => 'Новый ресурс', 'context_key' => 'web'],
    ['pagetitle' => 'Новый ресурс2', 'context_key' => 'web'],
]);

💡 Использует $modx->newObject() и $object->save() для каждой записи.

🧩 insert(array $data): int

Вставка данных напрямую в таблицу через SQL (без xPDO). Работает быстрее create, но не вызывает save()-событий.

php
query(modResource::class)->insert([
    'pagetitle' => 'Новый ресурс',
    'context_key' => 'web'
]);

query(modResource::class)->insert([
    ['pagetitle' => 'Новый ресурс', 'context_key' => 'web'],
    ['pagetitle' => 'Новый ресурс2', 'context_key' => 'web'],
]);

Возвращает lastInsertId() при одной записи, либо rowCount() при множественной вставке.

🔄 update(array $data): int

Обновляет поля найденных объектов.

php
query(modResource::class)
    ->where(['parent' => 2])
    ->update(['deleted' => 1]);

Возвращает количество обновлённых записей.

🗑 remove(): int

Удаляет все найденные объекты.

php
query(modResource::class)
    ->where(['parent' => 2])
    ->remove();

Возвращает количество удалённых объектов.

📈 increment(string $column, int $count = 1): bool

Увеличивает значение числового поля.

php
pbQuery::table(modResource::class)
    ->where(['parent' => 2])
    ->increment('menuindex');

📉 decrement(string $column, int $count = 1): bool

Уменьшает значение поля. То же самое, что increment(..., -$count).

php
pbQuery::table(modUserProfile::class)
    ->where(['parent' => 2])
    ->decrement('menuindex');

🧩 firstOrCreate(array $values): ?\xPDOObject

Если по текущему запросу уже существует объект — он будет возвращён. Если нет — будет создан новый объект с указанными значениями.

php
$user = query(modUser::class)
    ->where(['username' => 'admin'])
    ->firstOrCreate([
        'username' => 'admin',
        'password' => md5('123456'),
    ]);

Если пользователь admin уже существует — вернётся он. Если нет — будет создан с заданным паролем.

🔁 updateOrCreate(array $data): int

Если существует запись по текущему запросу — обновляет, иначе создаёт.

php
pbQuery::table(modSystemSetting::class)
    ->where(['key' => 'site_name'])
    ->updateOrCreate(['value' => 'New Site Title']);

Возвращает количество обновлённых или созданных записей.

🛠 command(string $command = 'SELECT'): self

Позволяет выполнять низкоуровневые SQL-команды UPDATE и DELETE напрямую. Используется в связке с set() (для UPDATE) и where().

🔧 В отличие от update() и remove(), работает напрямую через SQL, минуя xPDO. Подходит для массовых изменений без вызова событий save()/remove().

✅ Пример: обновление записей через UPDATE

php
$result = query(modResource::class)
    ->command('UPDATE')
    ->set([
        'deleted' => 0,
        'published' => 1,
    ])
    ->where([
        'template' => 5,
        'parent' => 2,
    ])
    ->execute();

🔸 SQL:

sql
UPDATE `modx_site_content`
SET `deleted` = 0, `published` = 1
WHERE `template` = 5 AND `parent` = 2

🗑 Пример: удаление записей через DELETE

php
$result = query(modResource::class)
    ->command('DELETE')
    ->where([
        'published' => 0,
        'parent' => 10,
    ])
    ->execute();

🔸 SQL:

sql
DELETE FROM `modx_site_content`
WHERE `published` = 0 AND `parent` = 10

📌 Отличие от update() и remove()

МетодОсноваСобытияМассовоБыстродействие
update()xPDOsave()🐌 Медленно
command('UPDATE')SQL⚡ Быстро
remove()xPDOremove()🐌 Медленно
command('DELETE')SQL⚡ Быстро

🔁 Перебор и обработка записей

Методы map, each, и chunk позволяют удобно обрабатывать выборки — как целиком, так и порциями. Это особенно полезно, когда нужно изменить, перебрать или извлечь данные из множества записей.

🔹 map(callable $callback): array

Метод map перебирает объекты из выборки и возвращает массив результатов, полученных после обработки каждого элемента.

💡 Когда использовать: Когда тебе нужно преобразовать коллекцию объектов в простой массив, например для API, списка ID, имён, и т.д.

php
// Получить массив ID всех опубликованных ресурсов
$ids = query(modResource::class)
    ->where('published', 1)
    ->map(fn($resource) => $resource->get('id'));

// Получить массив URL ресурсов
$urls = query(modResource::class)
    ->where('published', 1)
    ->map(function ($resource) {
        return $resource->get('uri');
    });

🔹 each(callable $callback): self

Метод each перебирает объекты один за другим и передаёт их в коллбэк. Может использоваться для изменения данных, логирования, отправки писем и т.д.

💡 Когда использовать: Когда тебе нужно выполнить действие с каждым объектом, не создавая массив результата.

php
// Установить шаблон всем дочерним страницам
query(modResource::class)
    ->where(['parent' => 2])
    ->each(function ($resource) {
        $resource->set('template', 5);
        $resource->save();
    })
    ->get();

// Записать лог по каждому ресурсу
query(modResource::class)
    ->where('published', 1)
    ->each(function ($resource) {
        logger('Обработка ресурса: ' . $resource->get('pagetitle'));
    })
    ->get();

🔹 chunk(int $size, callable $callback): self

Метод chunk загружает объекты порциями и передаёт их в коллбэк. Это позволяет обрабатывать большие выборки без переполнения памяти.

💡 Когда использовать: При обработке тысяч записей, где важна экономия памяти и стабильность.

php
// Обработать ресурсы по 100 штук
query(modResource::class)
    ->where('template', 4)
    ->chunk(100, function ($resources) {
        foreach ($resources as $resource) {
            $resource->set('published', 0);
            $resource->save();
        }
    });

// Отправить письма всем пользователям по 50 за раз
query(modUser::class)
    ->chunk(50, function ($users) {
        foreach ($users as $user) {
            $user->sendEmail("Привет, {$user->username}", [
                'subject' => 'Есть кто живой?'
            ])
        }
    });

Сравнение

МетодЧто делаетВозвращаетКогда использовать
mapПеребирает объекты и возвращает результатМассивПреобразование данных
eachПеребирает объекты и выполняет действияpbQueryИзменение, логирование, вызов побочных эффектов
chunkДелит выборку на части и обрабатывает ихpbQueryМассовая обработка без перегрузки памяти

📦 Рендеринг шаблонов

Методы tpl(), tplWrapper(), tplParent, outputSeparator() и render() позволяют не только выполнить запрос, но и сразу отрендерить полученные данные через шаблоны. Это особенно удобно, если вы хотите сформировать HTML-вывод непосредственно из PHP без ручной вставки шаблонов в Fenom.

🔧 tpl(string $tpl): self

Устанавливает основной шаблон, который будет применяться к каждому элементу выборки.

php
$output = query(modResource::class)
    ->where(['published' => 1])
    ->tpl('tpl.resource.row')
    ->render();

Пример шаблона tpl.resource.row.tpl:

html
<div class="item">
    <h3>{$pagetitle}</h3>
    <p>{$introtext}</p>
</div>

🧩 tplWrapper(string $tpl, string $var = 'output'): self

Устанавливает шаблон-обёртку, которая применяется ко всему списку целиком. В обёртку передаётся переменная (по умолчанию output), содержащая весь отрендеренный список.

php
$output = pbQuery::table(modResource::class)
    ->tpl('tpl.resource.row')
    ->tplWrapper('tpl.resource.wrapper', 'list')
    ->render();

Пример шаблона tpl.resource.wrapper.tpl:

html
<section class="resources">
    {$list}
</section>

🔹 tplParent(string $name): self

Шаблон, который используется только для элементов, у которых есть потомки(children). Это позволяет задать другой шаблон для родительских узлов дерева.

php
->tplParent('parent-item')

Если tplParent не задан, то используется tplWrapper (если он задан) или основной tpl.

🔀 outputSeparator(string $separator): self

Позволяет задать разделитель между элементами списка. По умолчанию \n;

php
$output = pbQuery::table(modUser::class)
    ->tpl('tpl.user.row')
    ->outputSeparator("\n")
    ->render();

🧪 render(): string

Выполняет запрос, применяет шаблон к каждому элементу, добавляет системные переменные и возвращает итоговую HTML-строку.

Каждому элементу передаются дополнительные переменные:

ПеременнаяЗначение
_iterationПорядковый номер элемента, начиная с 1
_idxИндекс элемента, начиная с 0
_totalОбщее количество элементов
_firsttrue, если элемент первый
_lasttrue, если элемент последний
_eventrue, если индекс чётный (0, 2, 4…)
_oddtrue, если индекс нечётный (1, 3, 5…)

Пример шаблона tpl.user.row.tpl с использованием этих переменных:

html
<div class="user {if $_even}even{else}odd{/if}">
    <span class="number">#{$_iteration}</span>
    <strong>{$username}</strong>
</div>

💡 Полный пример использования

php
echo query(modResource::class)
    ->where(['template' => 3, 'published' => 1])
    ->orderBy('menuindex')
    ->tpl('tpl.resource.row')
    ->tplWrapper('tpl.resource.wrapper', 'content')
    ->outputSeparator(PHP_EOL)
    ->render();

📚 Метод menu

Метод menu() в pbQuery позволяет удобно строить древовидные структуры для вывода меню, категорий и других иерархических данных.

Пример использования

php
$menu = query(modResource::class)
    ->where(['published' => 1, 'hidemenu' => 0])
    ->tpl('tpl.menu.item')
    ->tplParent('tpl.menu.parent')
    ->tplWrapper('tpl.menu.wrapper')
    ->outputSeparator("\n")
    ->menu()
php
$menu = query(modResource::class)
    ->alias('resource')
    ->select('id,pagetitle,alias,parent,menuindex')
    ->select(['count' => function ($q) {
        $q->selectRaw('COUNT(*)')
            ->whereColumn(['parent' => 'resource.id']);
    }])
    ->tplWrapper('@INLINE <ul>{$output}</ul>')
    ->tplParent('@INLINE <li>{$pagetitle} ({$count})</li><ul>{$output}</ul>')
    ->tpl('@INLINE <li>{$pagetitle}</li>')
    ->menu(0,2);

Аргументы

php
menu(
    int $rootId = 0,
    int $maxLevel = 3,
    string $parentField = 'parent',
    string $primaryKey = 'id'
)
  • $rootId — ID корневого элемента.
  • $maxLevel — максимальная глубина вложенности.
  • $parentField — имя поля, указывающего на родителя.
  • $primaryKey — имя поля с первичным ключом.

Что делает

  1. Автоматически выставляет поля для выборки, если они ещё не заданы.
  2. Получает все элементы с помощью fetchAll().
  3. Группирует их по родительскому полю (parentField).
  4. Строит иерархию методом buildTree().
  5. Вызывает render() для вывода через Fenom-шаблоны.
  6. Если шаблон не задан — возвращает просто массив дерева.

Настройка шаблонов

Методы для шаблонов:

  • tpl($tpl) — шаблон обычного элемента.
  • tplParent($tpl, $var = 'output') — шаблон родительского элемента, $var — имя переменной с вложенным выводом.
  • tplWrapper($tpl, $var = 'output') — внешний обёрточный шаблон.
  • outputSeparator($sep) — разделитель между элементами.

Пример без шаблонов

Если не заданы шаблоны — метод menu() вернёт массив дерева:

php
$tree = query(modResource::class)
    ->where(['published' => 1])
    ->menu();

return $tree;

Структура выходного массива

php
[
    [
        'id' => 1,
        'pagetitle' => 'Главная',
        // ...
        'level' => 1
        'children' => [
            [
                'id' => 2,
                'pagetitle' => 'О нас',
                // ...
                'level' => 2
                'children' => []
            ]
        ]
    ]
]

🌐 Метод languages

Метод languages() в pbQuery используется для вывода доступных языков сайта, а также генерации соответствующих URL для каждого языка.

Пример использования

php
query(modResource::class)
    ->where(['id' => $modx->resource->id])
    ->tplWrapper('@INLINE <nav>{$output}</nav>')
    ->tpl('@INLINE <a href="{$url}" class="{if $active}active{/if}">{$language}</a>')
    ->languages();

Если шаблоны не заданы — вернёт массив с языками и ссылками.

Что делает

  • Получает текущий ресурс ($this->first()).

  • Загружает список контекстов из опции pageblocks_contexts (в JSON).

  • При активной опции pageblocks_context_fallback != 'default' исключает те языки, для которых нет перевода ресурса.

  • Формирует массив языков:

    • key — ключ контекста (web, uk, en и т.д.).
    • value — название языка.
    • active — активен ли текущий язык.
    • language — синоним value.
    • url — URL для переключения на язык.

Пример вывода без шаблонов

php
$languages = pbQuery::table(modResource::class)
    ->where(['id' => $modx->resource->id])
    ->languages();

Пример результата:

php
[
    [
        'key' => 'web',
        'value' => 'English',
        'active' => true,
        'language' => 'English',
        'url' => '/',
    ],
    [
        'key' => 'uk',
        'value' => 'Ukraine',
        'active' => false,
        'language' => 'Ukraine',
        'url' => '/uk/',
    ],
    [
        'key' => 'ru',
        'value' => 'Russian',
        'active' => false,
        'language' => 'Russian',
        'url' => '/ru/',
    ]
]

Настройка шаблонов

Метод languages() использует те же шаблонные методы, что и menu():

  • tpl($tpl) — шаблон одного элемента.
  • tplWrapper($tpl, $var = 'output') — внешний обёртка.
  • outputSeparator($sep) — разделитель между языками.

Требуемые настройки в MODX

В системных настройках должны быть заданы:

  • pageblocks_contexts — список контекстов в JSON:
json
[
    {"key": "web", "value": "English"},
    {"key": "uk", "value": "Ukraine"},
    {"key": "ru", "value": "Russian"}
]

🔍 Фильтрация результатов (filter)

Метод filter(callable $callback) позволяет отфильтровать уже полученные данные после выборки из базы, но до рендеринга шаблонов.

php
public function filter(callable $callback): self

📌 Где используется

Метод filter() можно использовать с любыми методами, возвращающими массивы: ->menu(), ->languages(), ->all(), ->fetchAll().

📥 Параметры

  • $callback — функция обратного вызова, в которую передаётся каждый элемент массива. Если функция возвращает true, элемент остаётся; если false — удаляется.

✅ Пример: фильтрация языков

Выводим список языков, кроме активного:

php
$languages = query('modResource')
    ->where(['id' => $modx->resource->id])
    ->tplWrapper('@INLINE <nav class="nav">{$output}</nav>')
    ->tpl('@INLINE <a class="nav-link" href="{$url}">{$language}</a>')
    ->filter(fn($item) => !$item['active'])
    ->languages();

✅ Пример: фильтрация дерева по template

Выводим дерево ресурсов, исключая те, у которых template = 3:

php
$tree = query('modResource')
    ->filter(fn($item) => $item['template'] != 3)
    ->menu();

⚙️ Доступ к объекту xPDOQuery

Иногда может понадобиться напрямую получить или подменить внутренний объект xPDOQuery, с которым работает pbQuery. Для этого предусмотрены методы getQuery() и setQuery().

🔹 getQuery(): xPDOQuery

Возвращает текущий экземпляр xPDOQuery, используемый внутри запроса. Это полезно, если ты хочешь выполнить низкоуровневые операции, которые не покрываются методом pbQuery.

💡 Когда использовать:

  • При необходимости добавить сложное условие или выражение.
  • При интеграции с внешними компонентами, которым нужен xPDOQuery.
php
$query = query(modResource::class)
    ->where(['parent' => 10])
    ->getQuery();

$query->where([
    'pagetitle:LIKE' => '%новость%',
]);

// Можно вручную получить результаты
$results = $query->prepare() && $query->stmt->execute()
    ? $query->stmt->fetchAll(PDO::FETCH_ASSOC)
    : [];

🔹 setQuery(xPDOQuery $query): self

Позволяет вручную установить объект xPDOQuery, заменив текущий. Это даёт полный контроль над логикой построения запроса — можно собрать его вне pbQuery, а затем использовать удобства класса (например, get(), first(), map() и т.д.).

💡 Когда использовать:

  • Когда нужно предварительно собрать xPDOQuery вручную или в другом месте.
  • Когда удобно сконструировать базовый запрос и передать его в pbQuery.
php
$xpdoQuery = $modx->newQuery(modUser::class);
$xpdoQuery->where(['active' => 1]);

$users = query(modUser::class)
    ->setQuery($xpdoQuery)
    ->map(fn($user) => $user->get('username'));

Эти методы дают возможность сочетать гибкость xPDO и удобство pbQuery. Их стоит использовать в продвинутых случаях, когда стандартных методов уже недостаточно.

🧠 Кеширование запросов

Метод cache() позволяет кешировать результат запроса и повторно использовать его при следующем вызове, избегая повторного выполнения SQL-запроса. Это особенно полезно для тяжёлых или часто повторяющихся операций.

🔹 cache(int $time = 3600, string $key = ''): self

  • $time — время хранения кеша в секундах. По умолчанию 1 час (3600 секунд).
  • $key — (необязательно) ключ кеша. Если не указан, будет сгенерирован автоматически на основе SQL.

Внутри используется метод Cache::remember(), который сохраняет результат выполнения, если его ещё нет в кеше, и возвращает сохранённый при следующем запросе.

php
$resources = query(modResource::class)
    ->where(['template' => 5])
    ->cache('', 600) // Кешируем на 10 минут
    ->fetchAll();

🧪 Пример: кеширование с map()

php
$titles = query(modResource::class)
    ->where(['published' => 1])
    ->cache('', 300) // 5 минут
    ->map(fn($res) => $res->get('pagetitle'));

💡 Зачем использовать кеширование

  • Снижение нагрузки на базу данных.
  • Ускорение времени ответа.
  • Удобство использования без необходимости вручную управлять кешем.

🐞 Отладка запросов

Чтобы увидеть, какой SQL-запрос выполняется, сколько он занял времени, сколько памяти использовано и была ли задействована система кеширования — используйте метод debug().

🔹 debug(): self

Включает вывод отладочной информации после выполнения запроса.

php
$resources = query(modResource::class)
    ->where(['published' => 1])
    ->debug()
    ->fetchAll();

После выполнения метода fetchAll() будет выведена подробная информация:

=== pbQuery Debug ===
CACHE: 0
SQL: SELECT ... FROM ...
Queries: 1
Query Time: 0.003210 s
PHP Memory: 4.21 MB
Peak Memory: 4.67 MB
PHP Time: 0.015826 s
======================

🔹 Что показывает debug

  • CACHE — результат получен из кеша (1 — да, 0 — нет).
  • SQL — сгенерированный SQL-запрос.
  • Queries — сколько SQL-запросов было выполнено.
  • Query Time — общее время выполнения SQL-запросов.
  • PHP Memory — объём текущего использования памяти.
  • Peak Memory — пик использования памяти во время выполнения.
  • PHP Time — общее время выполнения скрипта.

Метод debug() также использует getSQL(), чтобы отобразить итоговый SQL-запрос. Вы можете получить его отдельно через getSQL() — см. раздел Получение SQL-запроса.

💥 Быстрый вывод и остановка — dump()

Если нужно быстро посмотреть отладочную информацию и сразу завершить выполнение скрипта — используйте dump():

php
query(modResource::class)
    ->where(['template' => 2])
    ->dump();

Выведет ту же самую отладочную информацию, что debug(), но сразу остановит выполнение скрипта.

🧠 Полезно при:

  • Оптимизации запросов.
  • Диагностике кеширования.
  • Анализе потребления ресурсов.
  • Поиске узких мест при работе с большими выборками.

📄 Получение SQL-запроса

Иногда полезно получить сгенерированный SQL-запрос в виде строки — для логирования, ручной проверки или отладки. Для этого используйте метод getSQL().

🔹 getSQL(): string

Возвращает SQL-строку, сгенерированную на основе текущего состояния запроса.

php
$sql = query(modResource::class)
    ->select('id,pagetitle')
    ->where(['published' => 1, 'parent' => 0])
    ->getSQL();

return $sql;

SQL:

sql
SELECT `modResource`.`id`, `modResource`.`pagetitle`
FROM `modx_site_content` AS `modResource` 
WHERE `modResource`.`published` = 1 AND `modResource`.`parent` = 0

© PageBlocks 2019-present