Традиционная разработка плагинов для WordPress часто страдает от болезни монолитной связанности: глобальные переменные, прямое обращение к функциям ядра (get_post_meta, update_option), паттерн «божественного синглтона» (Plugin::get_instance()) и перемешивание HTML-разметки с бизнес-логикой. В enterprise-разработке 2026 года стандартом де-факто стала методология Software Design Description (SDD) в связке с принципами Чистой Архитектуры (Clean Architecture) и Domain-Driven Design (DDD).
При разработке с участием автономных AI-агентов SDD выступает в роли формального технического задания и строгого контракта. Агент сначала генерирует и верифицирует архитектурный документ (интерфейсы, схемы сущностей, сценарии использования), и лишь затем переходит к кодогенерации слоев:
add_action, add_filter), контроллеры REST API, репозитории к $wpdb или кастомным таблицам WooCommerce HPOS (High-Performance Order Storage), внешние HTTP-клиенты.Управление жизненным циклом и зависимостями делегируется PSR-11 совместимому контейнеру внедрения зависимостей (Dependency Injection Container), что делает код на 100% тестируемым через юнит-тесты без загрузки ядра WordPress.
В чистой архитектуре WordPress-плагина зависимости направлены строго снаружи внутрь: детали реализации (WordPress) зависят от абстракций (Domain), а не наоборот.
+------------------------------------------------------------------------+
| INFRASTRUCTURE LAYER |
| [ WordPress Hooks ] [ REST API Routes ] [ WooCommerce HPOS ] |
| [ WPDB Repositories ] [ Admin Pages UI ] [ External AI APIs ] |
+-----------------------------------+------------------------------------+
|
v (implements & calls)
+-----------------------------------+------------------------------------+
| APPLICATION LAYER |
| [ Use Cases: RegisterOrderRefund ] [ DTOs: RefundOrderRequest ] |
| [ Event Subscribers ] [ Service Interfaces ] |
+-----------------------------------+------------------------------------+
|
v (orchestrates)
+-----------------------------------+------------------------------------+
| DOMAIN LAYER |
| [ Entities: OrderAggregate ] [ Value Objects: Money, Tax ] |
| [ Domain Exceptions: InvalidOrder ] [ Repository Contracts ] |
+------------------------------------------------------------------------+ИИ проектирует схемы сущностей, OpenAPI/JSON-RPC контракты и интерфейсы репозиториев.
Создание строгих PHP 8.4 readonly классов и Value Objects без сторонних зависимостей.
Инкапсуляция бизнес-правил в легко тестируемые классы команд и запросов (CQRS).
Привязка к хукам WordPress, регистрация маршрутов REST API и интеграция с HPOS.
Декларативное связывание интерфейсов с их реализациями в сервис-провайдере.
dbDelta), где доступ к данным осуществляется строго через типизированные репозитории, а не прямые вызовы $wpdb.Ниже приведен архитектурный производственный артефакт корпоративного плагина обработки возвратов заказов с соблюдением Clean Architecture, интерфейсов и PHP 8.4.
<?php
declare(strict_types=1);
/**
* Author: Artur Yusupov (Fullstack Agentic Lab)
* Package: @agentic-lab/enterprise-commerce
* Module: OrderRefund
* Standard: Clean Architecture / DDD / PHP 8.4 Strict / WC HPOS
*/
namespace AgenticLab\Commerce\Domain\Model;
use InvalidArgumentException;
// ==========================================
// 1. DOMAIN LAYER: Value Objects & Contracts
// ==========================================
final readonly class CurrencyAmount
{
public function __construct(
public float $amount,
public string $currencyCode = 'EUR'
) {
if ($amount < 0.0) {
throw new InvalidArgumentException('Сумма не может быть отрицательной.');
}
if (strlen($currencyCode) !== 3) {
throw new InvalidArgumentException('Код валюты должен соответствовать ISO 4217.');
}
}
public function subtract(CurrencyAmount $other): self
{
if ($this->currencyCode !== $other->currencyCode) {
throw new InvalidArgumentException('Невозможно вычесть суммы в разных валютах.');
}
return new self($this->amount - $other->amount, $this->currencyCode);
}
}
interface OrderRepositoryInterface
{
public function findOrderTotal(int $orderId): ?CurrencyAmount;
public function applyRefund(int $orderId, CurrencyAmount $refundAmount, string $reason): bool;
}
// ==========================================
// 2. APPLICATION LAYER: Use Case & DTO
// ==========================================
namespace AgenticLab\Commerce\Application\UseCase;
use AgenticLab\Commerce\Domain\Model\CurrencyAmount;
use AgenticLab\Commerce\Domain\Model\OrderRepositoryInterface;
use DomainException;
final readonly class RefundOrderRequest
{
public function __construct(
public int $orderId,
public float $amount,
public string $currency,
public string $reason
) {}
}
final readonly class ProcessOrderRefundUseCase
{
public function __construct(
private OrderRepositoryInterface $orderRepository
) {}
public function execute(RefundOrderRequest $request): bool
{
$currentTotal = $this->orderRepository->findOrderTotal($request->orderId);
if ($currentTotal === null) {
throw new DomainException("Заказ #{$request->orderId} не найден в системе.");
}
$refundAmount = new CurrencyAmount($request->amount, $request->currency);
if ($refundAmount->amount > $currentTotal->amount) {
throw new DomainException('Сумма возврата превышает остаточную стоимость заказа.');
}
return $this->orderRepository->applyRefund(
$request->orderId,
$refundAmount,
$request->reason
);
}
}
// ==========================================
// 3. INFRASTRUCTURE LAYER: WooCommerce HPOS
// ==========================================
namespace AgenticLab\Commerce\Infrastructure\Persistence;
use AgenticLab\Commerce\Domain\Model\CurrencyAmount;
use AgenticLab\Commerce\Domain\Model\OrderRepositoryInterface;
use Automattic\WooCommerce\Utilities\OrderUtil;
use WC_Order;
use Exception;
final readonly class WooCommerceHposOrderRepository implements OrderRepositoryInterface
{
public function findOrderTotal(int $orderId): ?CurrencyAmount
{
$order = wc_get_order($orderId);
if (!$order instanceof WC_Order) {
return null;
}
return new CurrencyAmount(
(float) $order->get_total(),
$order->get_currency()
);
}
public function applyRefund(int $orderId, CurrencyAmount $refundAmount, string $reason): bool
{
$order = wc_get_order($orderId);
if (!$order instanceof WC_Order) {
return false;
}
try {
$refund = wc_create_refund([
'amount' => $refundAmount->amount,
'reason' => sanitize_text_field($reason),
'order_id' => $orderId,
'refund_id' => 0,
'restock_items' => true
]);
return !is_wp_error($refund);
} catch (Exception $e) {
error_log("[AgenticLab Refund] HPOS Error: " . $e->getMessage());
return false;
}
}
}
// ==========================================
// 4. INFRASTRUCTURE: Service Provider & Hook
// ==========================================
namespace AgenticLab\Commerce\Infrastructure\Bootstrap;
use AgenticLab\Commerce\Application\UseCase\ProcessOrderRefundUseCase;
use AgenticLab\Commerce\Application\UseCase\RefundOrderRequest;
use AgenticLab\Commerce\Infrastructure\Persistence\WooCommerceHposOrderRepository;
use WP_REST_Request;
use WP_REST_Response;
final class CommerceModulePlugin
{
private ProcessOrderRefundUseCase $refundUseCase;
public function __construct()
{
// Инициализация слоя внедрения зависимостей
$repository = new WooCommerceHposOrderRepository();
$this->refundUseCase = new ProcessOrderRefundUseCase($repository);
}
public function init(): void
{
add_action('rest_api_init', [$this, 'registerRestRoutes']);
}
public function registerRestRoutes(): void
{
register_rest_route('agentic-lab/v1', '/orders/(?P<id>\d+)/refund', [
'methods' => 'POST',
'callback' => [$this, 'handleRefundRestApi'],
'permission_callback' => static fn(): bool => current_user_can('manage_woocommerce'),
'args' => [
'amount' => ['required' => true, 'type' => 'number'],
'reason' => ['required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field'],
],
]);
}
public function handleRefundRestApi(WP_REST_Request $request): WP_REST_Response
{
$orderId = (int) $request->get_param('id');
$amount = (float) $request->get_param('amount');
$reason = (string) $request->get_param('reason');
try {
$dto = new RefundOrderRequest($orderId, $amount, get_woocommerce_currency(), $reason);
$success = $this->refundUseCase->execute($dto);
return new WP_REST_Response([
'success' => $success,
'message' => 'Возврат успешно оформлен через HPOS.',
], $success ? 200 : 400);
} catch (\Throwable $e) {
return new WP_REST_Response([
'success' => false,
'error' => $e->getMessage(),
], 422);
}
}
}Симптом: Невозможность запустить юнит-тесты без инициализации окружения WordPress (WP_UnitTestCase), падение тестов при вызове get_post_meta() или apply_filters().
Причина: Разработчик разместил вызов apply_filters() или $wpdb прямо внутри бизнес-логики класса Order.
Решение: Слой Domain должен состоять из чистых PHP-классов. Все взаимодействия с внешним миром должны происходить через интерфейсы, реализуемые в Infrastructure.
Симптом: Данные заказов в современных версиях WooCommerce не обновляются, запросы возвращают устаревшие значения, крах синхронизации.
Причина: Использование get_post_meta($order_id, '_order_total') вместо нативных методов объекта WC_Order или специализированного репозитория HPOS.
Решение: Использовать класс Automattic\WooCommerce\Utilities\OrderUtil для проверки статуса HPOS и работать строго через CRUD-методы WooCommerce.
Симптом: Невозможность параллельного изолированного тестирования, жесткая взаимная зависимость модулей, скрытое глобальное состояние.
Причина: Повсеместное использование конструкции MyPlugin::get_instance()->get_database()->do_something().
Решение: Использование Inversion of Control (IoC) и внедрения зависимостей через конструктор (Constructor Property Promotion).
Симптом: Передача сырых массивов $_POST или объектов WP_REST_Request вглубь сервисов, что приводит к ошибкам ненайденных ключей и нарушению строгой типизации.
Причина: Лень разработчика объявлять типизированные классы запросов.
Решение: Преобразовывать входящий запрос в строго типизированный Data Transfer Object (DTO) на уровне контроллера перед вызовом сценария использования.
ИИ формирует спецификацию bounded context, диаграммы классов и интерфейсы репозиториев.
Разработка сущностей и тестов бизнес-логики на базе Pest без загрузки ядра WP.
Написание репозиториев к HPOS/WPDB и REST-контроллеров.
Связывание компонентов через DI-контейнер и регистрация в хуках WordPress.