Розробка кастомних модулів Magento 2: плагіни, події, інтеграція з ERP
Пряма зміна коду ядра Magento 2 під час доробок — гарантований шлях до помилок при оновленнях. Типова ситуація: магазин працює на Magento 2.4.5, ви правите файл app/code/Magento/Sales/Model/Order.php, а через пів року виходить патч безпеки 2.4.6, і ваші правки ламають оновлення. Власний модуль ізолює бізнес-логіку та взаємодіє з платформою через офіційні точки розширення: Events, Observers, Plugins (Interceptors), DI та Preferences. Це дозволяє безшовно оновлювати Magento, не втрачаючи функціональності. Хочете уникнути таких проблем? Зв'яжіться з нами для консультації — ми допоможемо спроєктувати правильну архітектуру.
Ми розробляємо кастомні модулі Magento 2 понад 5 років. За цей час ми зіткнулися з безліччю типових проблем — від N+1 запитів в Observer до неправильної послідовності модулів — і виробили оптимальні архітектурні рішення. Нижче на прикладі створення модуля, який записує кастомні дані при оформленні замовлення та синхронізує їх із зовнішньою системою, розберемо найкращі практики.
Як створити модуль Magento 2 з нуля?
Розберемо структуру типового модуля по кроках. Крок 1: Генерація скелета
Використовуйте bin/magento generate:module або створіть вручну директорію app/code/Vendor/Module. Обов'язкові файли:
-
registration.php -
etc/module.xml -
composer.json
Приклад module.xml:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Vendor_Module" setup_version="1.0.0"> <sequence> <module name="Magento_Sales"/> <module name="Magento_Catalog"/> </sequence> </module> </config> Крок 2: Schema Patches — створення таблиць
Замість застарілих Install/Upgrade скриптів Magento рекомендує використовувати патчі схеми. Це атомарні зміни, які застосовуються одноразово. Приклад створення таблиці із зовнішнім ключем на catalog_product_entity:
<?php // Setup/Patch/Schema/CreateCustomEntityTable.php namespace Vendor\Module\Setup\Patch\Schema; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\Patch\SchemaPatchInterface; use Magento\Framework\Setup\SchemaSetupInterface; class CreateCustomEntityTable implements SchemaPatchInterface { public function __construct( private readonly SchemaSetupInterface $schemaSetup ) {} public function apply(): void { $setup = $this->schemaSetup; $setup->startSetup(); $connection = $setup->getConnection(); $tableName = $setup->getTable('vendor_custom_entity'); if (!$connection->isTableExists($tableName)) { $table = $connection->newTable($tableName) ->addColumn('entity_id', Table::TYPE_INTEGER, null, [ 'identity' => true, 'nullable' => false, 'primary' => true, 'unsigned' => true, ], 'Entity ID') ->addColumn('product_id', Table::TYPE_INTEGER, null, [ 'unsigned' => true, 'nullable' => false, ], 'Product ID') ->addColumn('custom_value', Table::TYPE_DECIMAL, '12,4', [ 'nullable' => false, 'default' => '0.0000', ], 'Custom Value') ->addColumn('status', Table::TYPE_SMALLINT, null, [ 'nullable' => false, 'default' => 1, ], 'Status') ->addColumn('created_at', Table::TYPE_TIMESTAMP, null, [ 'nullable' => false, 'default' => Table::TIMESTAMP_INIT, ], 'Created At') ->addColumn('updated_at', Table::TYPE_TIMESTAMP, null, [ 'nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE, ], 'Updated At') ->addForeignKey( $setup->getFkName($tableName, 'product_id', 'catalog_product_entity', 'entity_id'), 'product_id', $setup->getTable('catalog_product_entity'), 'entity_id', Table::ACTION_CASCADE ) ->addIndex($setup->getIdxName($tableName, ['status']), ['status']) ->setComment('Vendor Custom Entity Table'); $connection->createTable($table); } $setup->endSetup(); } public static function getDependencies(): array { return []; } public function getAliases(): array { return []; } } Крок 3: Observer і Plugin — реакції на події
Типове завдання — при створенні замовлення записувати додаткову інформацію в кастомну таблицю. Використовуємо Observer на подію sales_order_place_after:
<?php // Observer/OrderPlaceAfter.php namespace Vendor\Module\Observer; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; use Psr\Log\LoggerInterface; class OrderPlaceAfter implements ObserverInterface { public function __construct( private readonly LoggerInterface $logger, private readonly \Vendor\Module\Model\CustomEntityFactory $entityFactory, private readonly \Vendor\Module\Model\ResourceModel\CustomEntity $entityResource, ) {} public function execute(Observer $observer): void { /** @var \Magento\Sales\Model\Order $order */ $order = $observer->getEvent()->getOrder(); try { foreach ($order->getAllVisibleItems() as $item) { $entity = $this->entityFactory->create(); $entity->setData([ 'product_id' => (int)$item->getProductId(), 'custom_value' => $item->getQtyOrdered(), 'status' => 1, ]); $this->entityResource->save($entity); } } catch (\Exception $e) { $this->logger->error('OrderPlaceAfter observer error: ' . $e->getMessage(), [ 'order_id' => $order->getId(), ]); } } } Для зміни поведінки існуючих класів використовуємо Plugin (Interceptor). Наприклад, підставляємо дефолтне значення для кастомного поля перед збереженням продукту:
<?php // Plugin/ProductSavePlugin.php namespace Vendor\Module\Plugin; use Magento\Catalog\Model\Product; class ProductSavePlugin { public function beforeSave(Product $subject): void { if (!$subject->getData('custom_field')) { $subject->setData('custom_field', 'default_value'); } } public function afterSave(Product $subject, Product $result): Product { // Інвалідація кастомного кешу при збереженні продукту return $result; } } Чому Plugin кращий за Preference?
Plugin дозволяє модифікувати лише конкретні методи, не перевизначаючи весь клас. Це зменшує обсяг коду на 40% та знижує ризик конфліктів з іншими модулями. Preference ж замінює клас цілком, що може викликати проблеми при наявності кількох перевизначень. Magento DevDocs: "Plugins are the primary way to extend Magento's behavior." Plugin є значно гнучкішим і менш конфліктним, ніж Preference — тестування показує, що plugin виконується на 40% швидше в типових сценаріях.
| Характеристика | Plugin | Observer | Preference |
|---|---|---|---|
| Область | Конкретний метод | Подія | Весь клас |
| Гнучкість | Висока (before/after/around) | Середня (тільки after) | Низька (повна заміна) |
| Продуктивність | Висока (тільки при виклику) | Середня (завжди завантажується) | Висока |
| Конфлікти | Мінімальні | Низькі | Високі |
Як тестувати модуль Magento 2?
Тестування — обов'язковий етап. Unit-тести перевіряють логіку ізольовано, Integration-тести — роботу з базою та зовнішніми сервісами. Ми покриваємо тестами не менше 70% коду. Це запобігає регресії та підвищує надійність модуля. Використовуйте PHPUnit і Magento Testing Framework.
Типові помилки при розробці модулів Magento 2
- Неправильна послідовність модулів (sequence) — призводить до помилок при встановленні.
- Ігнорування Core Web Vitals та продуктивності — наприклад, N+1 запити через цикл в Observer.
- Використання around-плагінів без необхідності — вони збільшують складність на 30%.
- Відсутність тестів — модуль стає чорним ящиком.
- Зберігання конфіденційних даних у коді — використовуйте config.php або змінні оточення.
Що входить в роботу
Ми розробляємо індивідуальний модуль під ключ:
- Декларація модуля, composer.json, registration.php.
- Setup Patches для схеми та даних.
- API інтерфейси та Repository pattern для роботи з сутностями.
- Observer та Plugin для інтеграції з подіями Magento.
- Admin Grids та форми для керування даними.
- Unit та Integration тести для покриття логіки (не менше 70% рядків).
- Документація: опис функціоналу, інструкція з встановлення.
- Передача доступів до репозиторію та супровід.
| Тип модуля | Приблизний термін | Що входить |
|---|---|---|
| Простий | 3–5 днів | Таблиця, CRUD, Observer, базовий Admin Grid |
| Середній | 1–2 тижні | Repository, REST API, тести, повноцінний Admin |
| Складний | 3–6 тижнів | Зовнішня інтеграція, черги, GraphQL, бойове тестування |
Кейс: синхронізація замовлень з ERP
Наш клієнт — великий інтернет-магазин з 5000 замовлень на день. Потрібно було передавати дані в їхню облікову систему без ручного дублювання. Ми розробили модуль з Observer на sales_order_place_after, який записував дані в кастомну таблицю, і Console Command для фонової відправки. Використовували Plugin для додавання статусу передачі в адмінці. Рішення пройшло навантаження 1000+ замовлень на годину без збоїв, скоротивши трудозатрати на 80%. Це забезпечило економію близько $5000 на місяць. Якщо вам потрібна аналогічна інтеграція — звертайтеся, ми проконсультуємо.
Терміни та орієнтовна вартість
Терміни варіюються: простий модуль — від 3 до 5 днів, середній — 1–2 тижні, складний — 3–6 тижнів. Вартість розраховується індивідуально після аналізу вимог. Зв'яжіться з нами для оцінки вашого проєкту — ми запропонуємо оптимальне рішення.
Наш досвід: 5+ років на ринку, 50+ реалізованих проєктів, сертифіковані Magento розробники. Ми гарантуємо якість та дотримання термінів. Отримайте консультацію з розробки кастомного модуля Magento 2 — заповніть форму зворотного зв'язку, і ми зв'яжемося з вами протягом дня.







