Skip to content

Запросы в шаблоне

Достать данные прямо из разметки можно двумя хелперами, и разница между ними не в синтаксисе, а в том, что вернётся:

ХелперЧто отдаётКогда брать
query('site_content')строки stdClassчисло, плоский список, произвольный join
model('Resource')модели Eloquentсвязи, скоупы, касты, поля конструктора

Оба — те же самые, что в контроллерах: словарь методов общий, здесь только то, что важно именно в шаблоне.

query(): конструктор запросов

query() отдаёт конструктор запросов Illuminate к базе MODX. Имя таблицы пишется без префикса и без имени класса xPDO — site_content, а не modResource и не modx_site_content.

📊 Подсчёт записей

fenom
{query('site_content')->whereNotNull('publishedon')->count()}

📋 Список ресурсов

fenom
{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() нет:

fenom
{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

fenom
{set $resource = query('site_content')->find(10)}
{if $resource}
    <h2>{$resource->pagetitle}</h2>
{/if}

🔧 value() и pluck()

fenom
{query('site_content')->where('id', 5)->value('pagetitle')}
fenom
{set $titles = query('site_content')->where('parent', 0)->pluck('pagetitle')}
<pre>{$titles|print}</pre>

📦 JSON-колонки

data, properties, sync_fields хранятся в JSON, и по умолчанию конструктор отдаёт их строками. withJsonColumns() разворачивает названные колонки в массивы — а массив шаблон читает точкой:

fenom
{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\ — по короткому имени класса, без пространства имён:

fenom
{model('Resource')->published()->count()}

Строки возвращаются моделями, а не stdClass, и вместе с ними — всё, чего у сырого конструктора нет: связи, скоупы, касты, аксессоры и поля конструктора из JSON-колонки.

Скоупы вместо условий

То, что в query() пришлось бы выписывать руками, у модели уже названо:

fenom
{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. У Useractive(), inactive(), sudo(), inGroup($id), searchByUsername($text), searchByEmail($text); последний ищет в профиле, до которого сырым query() пришлось бы добираться через join.

Связи

Связь читается свойством и подтягивается сама:

fenom
{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():

fenom
{foreach model('User')->with('profile')->active()->limit(20)->get() as $user}
    <p>{$user->username} — {$user->profile->fullname}</p>
{/foreach}

Поля конструктора

Поле, добавленное в менеджере, колонки в таблице не имеет — оно лежит в JSON-колонке data и разворачивается моделью. В шаблоне оно неотличимо от обычного:

fenom
{set $block = model('PbBlockData')->find(7)}
{$block->title}
{$block->seo_text}

Точка до полей конструктора не достаёт

У модели точечная запись работает — {$res.pagetitle} и {$res->pagetitle} дают одно и то же, потому что Eloquent реализует ArrayAccess. Но только для настоящих колонок. Поле конструктора живёт в extra-атрибутах и доступно лишь через __get:

fenom
{$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/, и становятся доступны по короткому имени автоматически.

Что выбрать

Готовые модификаторы закрывают самые частые списки и уже учитывают публикацию, контекст и порядок — начинайте с них:

fenom
{foreach resources(['parent' => 5, 'limit' => 10]) as $res}…{/foreach}
{foreach users(['group' => 2]) as $user}…{/foreach}

Не хватает — берите model(). И только когда нужен join по таблицам, у которых модели нет, или голое число, — query().

Запрос в цикле остаётся запросом в цикле

Шаблон не мешает написать {foreach} с query() или model() внутри — и получить сотню запросов на страницу. Выбирайте данные один раз до цикла, связи забирайте with().

© PageBlocks 2019-present