Skip to content

Виртуальные страницы

Страница со своим адресом, которой нет в дереве ресурсов MODX. Маршрут, данные и шаблон ваши; MODX только приносит запрос.

Когда это нужно

  • Каталог на тысячи позиций, где ресурс на каждую позицию — абсурд
  • Профили пользователей — /user/boshnik
  • Статьи из своей таблицы, а не из site_content
  • Фильтры в адресе — /catalog/sale/smartphones
  • Эндпоинты API — /api/products/42

Страница товара, целиком

1. Маршрут

Файлы маршрутов лежат в core/App/routes/ и подключаются глобом по алфавиту. Адрес пишется без ведущего слэша:

php
<?php

use Boshnik\PageBlocks\Facades\Route;

Route::get('product/{alias}', 'ProductController@show')->name('product.show');

Порядок по алфавиту решает, кто победит

web.php обычно заканчивается Route::fallback(). Файл, который сортируется раньшеweb.php, будет проверен первым; тот, что после, не получит шанса вовсе. Назвать файл product.php — не косметика.

2. Контроллер

core/App/Http/Controllers/ProductController.php. Параметры маршрута идут первыми, запрос последним:

php
<?php

namespace PageBlocks\App\Http\Controllers;

use Boshnik\PageBlocks\Http\Request;
use PageBlocks\App\Models\PbProduct;

class ProductController extends BaseController
{
    public function show(string $alias, Request $request)
    {
        $product = PbProduct::query()
            ->where('alias', $alias)
            ->whereNotNull('published_at')
            ->first();

        if (!$product) {
            abort(404);
        }

        $this->shell($product->name, $product->description);

        return view('file:templates/product', [
            'product' => $product,
        ]);
    }
}

3. Шаблон

core/App/elements/templates/product.tpl:

html
<h1>{$product->name}</h1>
<div>{$product->description}</div>
<p>Цена: {$product->price}</p>

Какой базовый класс

Их два, и выбор решает, придётся ли вообще думать об оболочке:

Базовый классЧто делает за вас
ControllerРезолвит текущий ресурс MODX по адресу, алиасу и контексту и разворачивает его properties. Берите, когда странице соответствует ресурс.
BaseControllerНичего подобного. Берите для страницы, у которой ресурса нет, — то есть для виртуальной.

У виртуальной страницы резолвить нечего, поэтому честное сочетание — BaseController плюс оболочка ниже.

То, о чём никто не предупреждает: оболочка

У виртуальной страницы нет ресурса, а значит $modx->resource не ваш — и шапка, меню, хлебные крошки и все мета-теги в вёрстке читают именно из него. Отрендерите как есть — страница выйдет без шапки или с чужой.

Рабочий ответ — занять ресурс под оболочку и перезаписать поля, описывающие страницу:

php
private function shell(string $title, string $description = ''): void
{
    // Раздел, к которому относится страница. Запасной вариант — главная:
    // страница с чужой шапкой лучше страницы без шапки.
    $resource = Resource::find(4) ?: Resource::find((int) config('site_start', 1));

    if (!$resource) {
        return;
    }

    $this->modx->resource = $resource;
    $this->modx->resource->pagetitle   = $title;
    $this->modx->resource->seo_title   = $title;
    $this->modx->resource->longtitle   = $title;
    $this->modx->resource->description = $description;
    $this->modx->resource->seo_desc    = $description;
    $this->modx->resource->content     = '';
    $this->modx->resource->uri         = 'product/' . $alias;
}

uri перезаписывать обязательно

Оставите — и канонический адрес будет указывать на занятый ресурс: страница сама сообщает поисковику, что она копия раздела, и в индекс не попадает никогда. То же с description: страница берёт у раздела оболочку, но не его обещание.

Постраничность и 404

Виртуальный список обязан сам отвечать 404 за последней страницей. За вас этого не сделает никто:

php
if ($paginator->currentPage() > $paginator->lastPage()) {
    abort(404);
}

Без этого ?page=999 отдаёт 200 и пустую таблицу — для поискового робота это ещё одна пустая страница раздела.

Что вы получаете

  • Независимость от дерева ресурсов — структура ваша
  • Любую форму адреса, с параметрами
  • Контроль доступа, редиректы и обработку ошибок обычным PHP
  • Данные откуда угодно: свои таблицы, API, кэш

© PageBlocks 2019-present