Запросы в шаблоне
Достать данные прямо из разметки можно двумя хелперами, и разница между ними не в синтаксисе, а в том, что вернётся:
| Хелпер | Что отдаёт | Когда брать |
|---|---|---|
query('site_content') | строки stdClass | число, плоский список, произвольный join |
model('Resource') | модели Eloquent | связи, скоупы, касты, поля конструктора |
Оба — те же самые, что в контроллерах: словарь методов общий, здесь только то, что важно именно в шаблоне.
query(): конструктор запросов
query() отдаёт конструктор запросов Illuminate к базе MODX. Имя таблицы пишется без префикса и без имени класса xPDO — site_content, а не modResource и не modx_site_content.
📊 Подсчёт записей
{query('site_content')->whereNotNull('publishedon')->count()}📋 Список ресурсов
{set $resources = query('site_content')
->where('template', 4)
->orderBy('menuindex')
->limit(5)
->get()
}
<ul>
{foreach $resources as $res}
<li><a href="{$res->uri}">{$res->pagetitle}</a></li>
{/foreach}
</ul>Строки — объекты, поля читаются через ->
get() отдаёт PbCollection из stdClass. Точечная запись {$res.pagetitle} компилируется в $res['pagetitle'] и роняет рендер: «Cannot use object of type stdClass as array». Это фатал, то есть пустой 500, а не пустое место на странице.
🔀 join
Сигнатура — Illuminate, а не xPDO: таблица, левое поле, оператор, правое. Псевдоним пишется в самой строке таблицы (users as u), отдельного alias() нет:
{set $users = query('users as u')
->join('user_attributes as p', 'u.id', '=', 'p.internalKey')
->where('u.active', 1)
->select('u.username', 'p.fullname')
->orderBy('u.username')
->get()
}
{foreach $users as $user}
<p>{$user->username} ({$user->fullname})</p>
{/foreach}🔍 Запись по ID
{set $resource = query('site_content')->find(10)}
{if $resource}
<h2>{$resource->pagetitle}</h2>
{/if}🔧 value() и pluck()
{query('site_content')->where('id', 5)->value('pagetitle')}{set $titles = query('site_content')->where('parent', 0)->pluck('pagetitle')}
<pre>{$titles|print}</pre>📦 JSON-колонки
data, properties, sync_fields хранятся в JSON, и по умолчанию конструктор отдаёт их строками. withJsonColumns() разворачивает названные колонки в массивы — а массив шаблон читает точкой:
{set $rows = query('pb_block_data')->withJsonColumns(['data'])->where('model_id', 1)->get()}
{foreach $rows as $row}
{$row->data.title}
{/foreach}Без него то же самое делается модификатором на месте: {set $data = $row->data|fromJSON}.
model(): модели Eloquent
model() открывает запрос по модели из PageBlocks\App\Models\ — по короткому имени класса, без пространства имён:
{model('Resource')->published()->count()}Строки возвращаются моделями, а не stdClass, и вместе с ними — всё, чего у сырого конструктора нет: связи, скоупы, касты, аксессоры и поля конструктора из JSON-колонки.
Скоупы вместо условий
То, что в query() пришлось бы выписывать руками, у модели уже названо:
{foreach model('Resource')->published()->visible()->limit(5)->get() as $res}
<a href="{$res->uri}">{$res->pagetitle}</a>
{/foreach}У Resource это published() (published = 1), visible() (hidemenu = 0) и search($text) — поиск сразу по pagetitle, longtitle, description и content. У User — active(), inactive(), sudo(), inGroup($id), searchByUsername($text), searchByEmail($text); последний ищет в профиле, до которого сырым query() пришлось бы добираться через join.
Связи
Связь читается свойством и подтягивается сама:
{set $res = model('Resource')->find(1)}
<h1>{$res->pagetitle}</h1>
{if $res->parentResource}
<a href="{$res->parentResource->uri}">← {$res->parentResource->pagetitle}</a>
{/if}
{foreach $res->children as $child}
<li><a href="{$child->uri}">{$child->pagetitle}</a></li>
{/foreach}Связь в цикле — это запрос на каждой итерации. Когда список длинный, забирайте её заранее через with():
{foreach model('User')->with('profile')->active()->limit(20)->get() as $user}
<p>{$user->username} — {$user->profile->fullname}</p>
{/foreach}Поля конструктора
Поле, добавленное в менеджере, колонки в таблице не имеет — оно лежит в JSON-колонке data и разворачивается моделью. В шаблоне оно неотличимо от обычного:
{set $block = model('PbBlockData')->find(7)}
{$block->title}
{$block->seo_text}Точка до полей конструктора не достаёт
У модели точечная запись работает — {$res.pagetitle} и {$res->pagetitle} дают одно и то же, потому что Eloquent реализует ArrayAccess. Но только для настоящих колонок. Поле конструктора живёт в extra-атрибутах и доступно лишь через __get:
{$block->title} {* Международная биржа перевозок *}
{$block.title} {* пусто *}
{if $block.title} {* всегда ложь *}Причём молча: ни ошибки, ни записи в логе — просто пропавший текст и не сработавшее условие. В шаблонах, где встречаются поля конструктора, пишите -> везде, чтобы не держать в голове, какое поле настоящая колонка, а какое нет.
Какие модели есть
Resource, PbResource, PbBlockData, PbTableData, User, UserProfile, UserGroup, UserGroupMember, UserGroupRole, UserSetting, Template, Chunk, Snippet, Plugin, Event, Menu, Namespaces, Context, ContextSetting, SystemSetting, Source, Session.
Имя, которого в этом наборе нет, даёт фатал Class … not found — то есть пустой 500. Модели своего сайта кладутся туда же, в core/App/Models/, и становятся доступны по короткому имени автоматически.
Что выбрать
Готовые модификаторы закрывают самые частые списки и уже учитывают публикацию, контекст и порядок — начинайте с них:
{foreach resources(['parent' => 5, 'limit' => 10]) as $res}…{/foreach}
{foreach users(['group' => 2]) as $user}…{/foreach}Не хватает — берите model(). И только когда нужен join по таблицам, у которых модели нет, или голое число, — query().
Запрос в цикле остаётся запросом в цикле
Шаблон не мешает написать {foreach} с query() или model() внутри — и получить сотню запросов на страницу. Выбирайте данные один раз до цикла, связи забирайте with().