هندسة Hexagonal في PHP: كود نظيف بدون ألم

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

الكود الخاص بك هو حصن، وليس شعيرية

\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
\n\n

على عكس الهندسة النظيفة لروبرت مارتن، فإن Hexagonal أكثر مرونة: فهي لا تفرض عدد الطبقات، بل تقول ببساطة: "الداخل هو المجال، والخارج هو البنية التحتية، اربطهم عبر الواجهات".

\n\n

هيكل المشروع: من الفوضى إلى النظام

\n\n

دعنا ننتقل مباشرة إلى صلب الموضوع. إليك هيكل المجلدات النموذجي لهندسة Hexagonal في PHP:

\n\n
src/\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.

\n\n

الخطوة 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\\                

المدونات

توصيات الكتب