Разработка кастомного шаблона ProcessWire
Шаблон в ProcessWire — это PHP-файл в /site/templates/, имя которого совпадает с именем шаблона в базе. Файл получает объект $page с данными текущей страницы и полный доступ к $pages, $config, $user и остальному API. Никакого движка шаблонов по умолчанию — чистый PHP, хотя можно подключить Twig через модуль TemplateEngineTwig.
Архитектура шаблонов: варианты разделения
Вариант 1: _init.php + _main.php
Самый распространённый подход. ProcessWire автоматически включает _init.php перед каждым шаблоном и _main.php после, если включена опция «Prepend/Append template file» в настройках шаблона.
// _init.php — глобальные переменные, хелперы
$baseUrl = $config->urls->root;
$homePage = $pages->get("/");
$mainNav = $pages->find("parent=/,template!=error404,sort=sort");
function formatDate(int $ts): string {
return date("d.m.Y", $ts);
}
// _main.php — layout-обёртка
?><!DOCTYPE html>
<html lang="<?= $user->language->name ?>">
<head>
<meta charset="utf-8">
<title><?= $page->title ?> | <?= $homePage->title ?></title>
<link rel="stylesheet" href="<?= $config->urls->templates ?>css/main.css">
</head>
<body>
<?php include("partials/nav.php"); ?>
<main><?= $content ?></main>
<?php include("partials/footer.php"); ?>
</body>
</html>
В шаблоне страницы $content накапливается через буферизацию вывода:
// services.php — ProcessWire автоматически буферизует вывод
$items = $pages->find("template=service, parent={$page}, sort=sort");
foreach ($items as $item) {
echo "<article>";
echo "<h2><a href='{$item->url}'>{$item->title}</a></h2>";
echo "<p>{$item->summary}</p>";
echo "</article>";
}
Вариант 2: контроллер + вид
Для сложных шаблонов логику выносят в отдельный файл-контроллер:
templates/
controllers/
services.php # только логика, без echo
views/
services.view.php # только разметка
services.php # точка входа, связывает controller+view
// templates/services.php
require __DIR__ . '/controllers/services.php';
extract($viewData); // переменные из контроллера
require __DIR__ . '/views/services.view.php';
// templates/controllers/services.php
$viewData = [
'items' => $pages->find("template=service, sort=sort, limit=12"),
'categories' => $pages->find("template=service-category, sort=sort"),
'pagination' => $modules->get("MarkupPagerNav"),
];
Работа с изображениями
ProcessWire обрабатывает изображения на лету через метод size():
// Основное изображение страницы
$img = $page->images->first();
if ($img) {
// Ресайз с кадрированием 800×600
$thumb = $img->size(800, 600, ['cropping' => 'center', 'quality' => 85]);
echo "<img src='{$thumb->url}' alt='{$img->description}' loading='lazy'>";
// WebP через опцию suffix
$webp = $img->size(800, 600, ['suffix' => 'webp', 'webpAdd' => true]);
}
Вариантные размеры кэшируются в /site/assets/files/{id}/. Повторный запрос возвращает уже готовый файл.
Повторно используемые частичные шаблоны
// partials/card.php — принимает $item из include
?>
<div class="card">
<?php if ($item->image): ?>
<img src="<?= $item->image->size(400,300)->url ?>" alt="<?= $item->title ?>">
<?php endif; ?>
<h3><?= $item->title ?></h3>
<p><?= $item->summary ?></p>
<a href="<?= $item->url ?>">Подробнее</a>
</div>
// Использование в services.php
foreach ($items as $item) {
include("./partials/card.php");
}
Мета-данные и SEO
SEO-поля обычно добавляют на уровне шаблона через отдельный fieldset:
// В _main.php
$metaTitle = $page->meta_title ?: $page->title;
$metaDescription = $page->meta_description ?: $page->summary;
$ogImage = $page->og_image ? $page->og_image->size(1200, 630)->httpUrl : '';
?>
<title><?= htmlspecialchars($metaTitle) ?></title>
<meta name="description" content="<?= htmlspecialchars($metaDescription) ?>">
<meta property="og:image" content="<?= $ogImage ?>">
Кастомные 404 и редиректы
// templates/basic-page.php
// Редирект устаревшего URL
if ($page->redirect_url) {
$session->redirect($page->redirect_url, 301);
}
// Принудительный 404
if (!$user->isLoggedin() && $page->requires_login) {
throw new Wire404Exception();
}
Производительность: ленивая загрузка связей
По умолчанию FieldtypePage загружает связанные объекты при обращении. При больших списках используют $pages->findMany() — потоковая обработка без загрузки всего в память:
// findMany() для больших наборов (1000+ страниц)
foreach ($pages->findMany("template=product, sort=title") as $product) {
echo $product->title . "\n";
// ProcessWire автоматически освобождает память
}
Типовые сроки разработки шаблонов
| Задача | Оценка |
|---|---|
| Базовый шаблон (layout + nav + footer) | 4–8 ч |
| Шаблон списка с пагинацией и фильтром | 8–16 ч |
| Детальная страница со связями | 4–10 ч |
| Система шаблонов для 10+ типов контента | 3–6 дней |
| Интеграция Twig + компонентный подход | 2–4 дня |







