Інтернет-магазин на 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 для вашого проєкту. Отримайте консультацію — оцінимо задачу та запропонуємо рішення.







