Інтернет-магазин на Craft CMS — товари надходять з 1С по CommerceML, але ціни оновлюються раз на добу. Клієнти скаржаться на застарілі акції, а редактору потрібен кастомний звіт по продажах з фільтрами за категоріями та періодами. Стандартні плагіни на кшталт CP Reports не дають потрібної гнучкості — доводиться лізти в ядро Craft або писати CRON-скрипти поверх. Вихід — розробити кастомний модуль на Yii2 (Yii Framework), який вирішує обидві задачі: консольна команда для синхронізації цін і Twig-розширення для звіту.
Ми створюємо модулі, які вирішують рівно одну задачу і не тягнуть зайвого. За п'ять років реалізували понад 20 проєктів: від простих Twig-розширень до повноцінних консольних синхронізаторів із зовнішніми API. У цій статті — як ми це робимо і що виходить у результаті.
Розробка кастомного модуля Craft CMS: як це робиться
Проблеми, які вирішуємо — розробка кастомного модуля
- Інтеграція із зовнішнім API: синхронізація товарів з 1С через REST API. Стандартні плагіни не підтримують кастомні поля, тому модуль реалізує сервіс
SyncServiceз методамиsyncPrices(),syncStocks(). Консольна команда запускається по крону кожні 15 хвилин, забезпечуючи актуальність даних. - Кастомні звіти та дашборди: для редактора потрібен звіт по продажах за тиждень з групуванням за категоріями. Через модуль підключаємо свій контролер
DashboardControllerі Twig-шаблони, використовуючи Yii2 GridView для таблиць. - Складні Twig-фільтри: форматування телефонів, цін з пробілами, SVG-іконок. Виносимо в Twig-розширення, щоб не дублювати код у шаблонах.
- Нестандартні поля: наприклад, поле «термін придатності» з автоматичним розрахунком. Створюємо поведінку (behavior) для елемента, яка валідує поле при збереженні.
Як ми це робимо
Використовуємо актуальні версії: Craft CMS 4/5, PHP 8.1+, Yii2 — основа Craft CMS. Основний стек — PHP, Yii2, Twig, MySQL/PostgreSQL. Для консольних команд — Symfony Console, вбудований у Craft.
Розберемо типовий кейс: інтернет-магазину знадобився кастомний блок «Товари зі знижкою» з динамічним оновленням цін кожні 15 хвилин. Ми реалізували:
- Сервіс
DiscountService— отримує товари зі знижкою з БД із кешуванням через Redis. - Twig-функцію
discountProducts()— виводить блок на сторінці. - Консольну команду
discount/update— оновлює дані по крону.
Завдяки оптимізації запитів час генерації складних звітів скорочується на 40%. Модуль не створює зайвого навантаження на сервер — всі сервіси завантажуються лише при необхідності.
Ключовий клас модуля — SiteCustomModule, який реєструє сервіси та Twig-розширення. Ось його структура:
// modules/sitecustom/SiteCustomModule.php
namespace modules\sitecustom;
use Craft;
use craft\events\RegisterComponentTypesEvent;
use craft\services\Elements;
use craft\web\twig\variables\CraftVariable;
use modules\sitecustom\services\ProductService;
use modules\sitecustom\variables\SiteVariable;
use modules\sitecustom\twigextensions\SiteTwigExtension;
use yii\base\Event;
use yii\base\Module;
class SiteCustomModule extends Module
{
public static SiteCustomModule $instance;
public function init(): void
{
parent::init();
self::$instance = $this;
Craft::setAlias('@modules/sitecustom', __DIR__);
$this->setComponents([
'products' => ProductService::class,
]);
Event::on(CraftVariable::class, CraftVariable::EVENT_INIT, function (Event $event) {
$event->sender->set('site', SiteVariable::class);
});
if (Craft::$app->request->getIsSiteRequest()) {
Craft::$app->view->registerTwigExtension(new SiteTwigExtension());
}
}
}
Реєстрація в config/app.php:
return [
'modules' => [
'site-custom' => \modules\sitecustom\SiteCustomModule::class,
],
'bootstrap' => ['site-custom'],
];
Приклад сервісу з бізнес-логікою
// modules/sitecustom/services/ProductService.php
namespace modules\sitecustom\services;
use craft\base\Component;
use craft\elements\Entry;
class ProductService extends Component
{
public function getFeaturedProducts(int $limit = 6): array
{
return Entry::find()
->section('products')
->featured(true)
->inStock(true)
->orderBy('sortOrder asc, postDate desc')
->limit($limit)
->with(['featuredImage', 'categories'])
->all();
}
public function updateStock(int $entryId, int $quantity): bool
{
$entry = Entry::find()->id($entryId)->one();
if (!$entry) return false;
$entry->setFieldValue('stockQuantity', $quantity);
return \Craft::$app->elements->saveElement($entry);
}
}
Приклад Twig-розширення
// modules/sitecustom/twigextensions/SiteTwigExtension.php
namespace modules\sitecustom\twigextensions;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class SiteTwigExtension extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFilter('formatPrice', [$this, 'formatPrice']),
new TwigFilter('phoneFormat', [$this, 'formatPhone']),
];
}
public function getFunctions(): array
{
return [
new TwigFunction('svg', [$this, 'inlineSvg'], ['is_safe' => ['html']]),
];
}
public function formatPrice(float $price, string $currency = 'RUB'): string
{
return number_format($price, 0, '.', ' ') . ' ' . $currency;
}
public function formatPhone(string $phone): string
{
$digits = preg_replace('/\D/', '', $phone);
return preg_replace('/(\d)(\d{3})(\d{3})(\d{2})(\d{2})/', '+$1 ($2) $3-$4-$5', $digits);
}
public function inlineSvg(string $name): string
{
$path = \Craft::getAlias('@webroot/icons/' . $name . '.svg');
return file_exists($path) ? file_get_contents($path) : '';
}
}
Приклад консольної команди
// modules/sitecustom/console/controllers/SyncController.php
namespace modules\sitecustom\console\controllers;
use craft\console\Controller;
use yii\console\ExitCode;
class SyncController extends Controller
{
public function actionProducts(): int
{
$this->stdout("Синхронізація товарів...\n");
$count = SiteCustomModule::$instance->products->syncFromExternalApi();
$this->stdout("Синхронізовано: {$count} товарів\n");
return ExitCode::OK;
}
}
Запуск: php craft site-custom/sync/products.
Чому модуль краще плагіна для унікальних завдань?
| Характеристика | Модуль | Плагін |
|---|---|---|
| Поширення | Тільки в проєкті | Composer-пакет |
| Ліцензія | Не потрібна | Є (MIT, GPL тощо) |
| Версіонування | Немає | CHANGELOG.md |
| Вартість розробки | На 30–40% нижча | Вища через універсальність |
| Швидкість розробки | В 2–3 рази швидше | Довше через універсальність |
Модуль краще, якщо функціональність прив'язана до конкретного сайту. Плагін — коли планується публікація або тиражування. Ми гарантуємо, що модуль не містить зайвих залежностей і суворо відповідає вашим вимогам. Замовте розробку модуля під ключ — заощадьте до 40% бюджету порівняно з самостійним впровадженням.
Склад кастомного модуля
Етапи розробки
| Етап | Тривалість | Результат |
|---|---|---|
| Аналіз вимог | 1–2 дні | ТЗ, проектування |
| Реалізація сервісів і розширень | 2–5 днів | Код модуля |
| Інтеграція та тестування | 1–2 дні | Працююча функціональність |
| Документування та здача | 0.5–1 день | Документація, доступи |
Загальний термін: від 3 до 7 днів залежно від складності.
Що входить у роботу
- Код модуля з коментарями українською/англійською
- Документація з установки та налаштування
- Консольні команди (за наявності)
- Інтеграція з шаблонами (Twig-розширення)
- 30 днів підтримки після здачі проєкту
Типові помилки при розробці модулів
- Плутанина з простором імен — модуль має бути в неймспейсі
modules\ім'япроєкту, інакше автозавантаження не спрацює. - Забули прописати alias — без
Craft::setAlias('@modules/...', __DIR__)не підвантажаться файли. - Дублювання логіки — часто намагаються копіювати код з плагінів, порушуючи принцип єдиної відповідальності.
Уникнути цих помилок допомагає наш досвід і code review. Сертифіковані розробники з 5+ роками досвіду гарантують якість коду.
Процес роботи та реєстрація модуля
- Створіть папку
modules/ваш-модульі додайте основний клас. - Визначте неймспейс
modules\ваш-модуль. - Зареєструйте модуль в
config/app.phpу секціях'modules'і'bootstrap'. - Пропишіть alias
Craft::setAlias('@modules/ваш-модуль', __DIR__)в методіinit()основного класу. - Запустіть
php craft project-config/applyдля синхронізації конфігурації.
Чому вигідно замовити модуль під ключ?
Розробка модуля під ключ економить час вашої команди: не потрібно розбиратися в тонкощах Yii2 та архітектури Craft. Ми беремо на себе проектування, реалізацію та тестування. Зв'яжіться з нами, щоб розробити кастомний модуль Craft CMS для вашого проєкту. Отримайте консультацію — оцінимо задачу та запропонуємо рішення.







