الكود الخاص بك هو حصن، وليس شعيرية
\n\nهل سبق لك أن أعدت كتابة مشروع من Laravel إلى Symfony أو من MySQL إلى PostgreSQL؟ إذا كان الأمر كذلك، فأنت تعرف هذا الألم: وحدات تحكم مليئة باستعلامات SQL، ونماذج تعرف كل شيء عن طلبات HTTP، ومنطق أعمال يموت عند تغيير الإطار. مألوف؟
\n\nمرحبًا بك في عالم هندسة Hexagonal في PHP (المعروفة أيضًا باسم Ports & Adapters). هذا ليس مجرد مصطلح رائج، بل هو طوق نجاة للمشاريع التي تريد أن تعيش طويلاً وبسعادة. في هذه المقالة، سنشرح كيفية بناء تطبيق PHP لا يخاف من التغيير: تغيير قاعدة البيانات، الانتقال إلى ذاكرة تخزين مؤقت أخرى، أو API جديد - كل هذا سيصبح مجرد استبدال روتيني للمحول، وليس إعادة كتابة نصف الكود.
\n\nما هي هندسة Hexagonal حقًا؟
\n\nتخيل أن تطبيقك هو نواة (core) لا تعرف من أين تأتي البيانات: من الويب، وحدة التحكم، بوت Telegram، أو الاختبارات. حول هذه النواة توجد المنافذ (واجهات)، ومن الخارج توجد المحولات (تطبيقات). الهندسة السداسية في PHP تسمح بفصل منطق الأعمال عن كود البنية التحتية. تكتب القواعد مرة واحدة وتستخدمها في كل مكان.
\n\nالمبادئ الأساسية:
\n- \n
- عكس التبعيات - الكود عالي المستوى لا يعتمد على الكود منخفض المستوى. كل شيء بالعكس. \n
- منطق الأعمال معزول - لا يتسلل أي إطار عمل إلى حالات الاستخدام الخاصة بك. \n
- قابلية الاختبار - يمكنك اختبار النواة بدون قاعدة بيانات و HTTP. \n
على عكس الهندسة النظيفة لروبرت مارتن، فإن Hexagonal أكثر مرونة: فهي لا تفرض عدد الطبقات، بل تقول ببساطة: "الداخل هو المجال، والخارج هو البنية التحتية، اربطهم عبر الواجهات".
\n\nهيكل المشروع: من الفوضى إلى النظام
\n\nدعنا ننتقل مباشرة إلى صلب الموضوع. إليك هيكل المجلدات النموذجي لهندسة Hexagonal في PHP:
\n\nsrc/\n├── Domain/\n│ ├── Entity/\n│ │ └── Order.php\n│ ├── Repository/\n│ │ └── OrderRepositoryInterface.php\n│ └── Service/\n│ └── OrderService.php\n├── Application/\n│ ├── UseCase/\n│ │ └── CreateOrderUseCase.php\n│ └── Port/\n│ ├── Input/\n│ │ └── CreateOrderInputPort.php\n│ └── Output/\n│ └── OrderOutputPort.php\n├── Infrastructure/\n│ ├── Persistence/\n│ │ └── DoctrineOrderRepository.php\n│ └── Http/\n│ └── OrderController.php\n└── Framework/\n └── Symfony/\n └── Kernel.php\n\nهل لاحظت؟ Domain و Application لا يعرفان بوجود Doctrine أو Symfony أو MySQL. إنهما يعملان فقط مع الواجهات. تعيش المحولات في Infrastructure ويتم توصيلها عبر Dependency Injection.
الخطوة 1: تحديد النواة - Domain
\n\nلنبدأ بكيان الطلب. لا توجد تعليقات توضيحية ORM، ولا واضعات تخالف الثوابت:
\n\n<?php\n// src/Domain/Entity/Order.php\nnamespace App\\Domain\\Entity;\n\nclass Order\n{\n public function __construct(\n private readonly string $id,\n private readonly string $productName,\n private float $totalAmount,\n private string $status = 'pending'\n ) {}\n\n public function approve(): void\n {\n if ($this->status !== 'pending') {\n throw new \\DomainException('Only pending orders can be approved.');\n }\n $this->status = 'approved';\n }\n\n public function getId(): string { return $this->id; }\n public function getProductName(): string { return $this->productName; }\n public function getTotalAmount(): float { return $this->totalAmount; }\n public function getStatus(): string { return $this->status; }\n}\n\nوإليك المنفذ (الواجهة) للمستودع. لاحظ: لا يعرف شيئًا عن Doctrine أو PDO:
\n\n<?php\n// src/Domain/Repository/OrderRepositoryInterface.php\nnamespace App\\Domain\\Repository;\n\nuse App\\