عكس الاعتماديات في التطبيق
صِل المحوّلات بالنواة باستخدام عكس الاعتماديات.
عكس الاعتماديات في التطبيق درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.
العكس مقابل الحقن
يخلط الناس بين فكرتين مختلفتين. حقن التبعية تقنية تتمثل في تمرير المتعاونين بدلاً من إنشائهم. أما عكس التبعية (الحرف D في SOLID) فهو مبدأ ينص على أن سياسة المستوى العالي والتفاصيل منخفضة المستوى تعتمدان كلتاهما على تجريد، وأن التجريد تملكه الوحدة عالية المستوى. ويركّز هذا الدرس على تطبيق الفكرة الثانية فعلياً، من خلال ربط المحوّلات الملموسة بنواة تعرّف الواجهات.
المبدأ بدقة
ينص DIP على ما يلي:
- يجب ألا تعتمد الوحدات عالية المستوى على الوحدات منخفضة المستوى؛ بل تعتمد كلتاهما على التجريدات.
- يجب ألا تعتمد التجريدات على التفاصيل؛ بل تعتمد التفاصيل على التجريدات.
أما الجزء الدقيق فهو أن الواجهة يملكها المستهلك لا المنفّذ. يعلن مجالك PaymentGateway، ويتوافق محوّل Stripe معها، وليس العكس.
تعريف التجريد داخل النواة
ضعوا الواجهة بجوار الشيفرة التي تحتاج إليها، وعبّروا عنها بمصطلحات المجال. ولا تسمحوا بتسرّب أنواع Stripe.
<?php
// src/Billing/Application/Port/PaymentGateway.php
interface PaymentGateway
{
public function charge(Money $amount, CardToken $token): ChargeId;
}
final class Money {
public function __construct(public readonly int $cents, public readonly string $currency) {}
}
final class CardToken { public function __construct(public readonly string $value) {} }
final class ChargeId { public function __construct(public readonly string $value) {} }اجعلوا المحوّل متوافقاً معها
ينفّذ محوّل البنية التحتية واجهة النواة ويترجمها إلى SDK الخاص بالمورّد. ويشير سهم التبعية من المحوّل (التفصيل) إلى التجريد (السياسة)، وبذلك يتحقق العكس.
<?php
final class StripePaymentGateway implements PaymentGateway
{
public function __construct(private \Stripe\StripeClient $stripe) {}
public function charge(Money $amount, CardToken $token): ChargeId {
$intent = $this->stripe->paymentIntents->create([
'amount' => $amount->cents,
'currency' => strtolower($amount->currency),
'payment_method' => $token->value,
'confirm' => true,
]);
return new ChargeId($intent->id);
}
}حقن المُنشئ هو الخيار الافتراضي
احقنوا التبعيات عبر المُنشئ، واستخدموا تلميح النوع للتجريد. فتصبح التبعيات واضحة وصريحة وغير قابلة للتغيير، ويستحيل نسيانها. وتجنبوا حقنها عبر دوال التعيين أو الخصائص للتبعيات المطلوبة، إذ يسمح ذلك بإنشاء كائنات غير مكتملة.
<?php
final class CheckoutService
{
public function __construct(
private PaymentGateway $payments, // abstraction, not StripeClient
private OrderRepository $orders,
) {}
public function pay(OrderId $id, CardToken $token): ChargeId {
$order = $this->orders->get($id);
$charge = $this->payments->charge($order->total(), $token);
$order->markPaid($charge);
$this->orders->save($order);
return $charge;
}
}جذر التركيب
يحدث كل ربط ملموس في مكان واحد تحديداً، هو جذر التركيب، ويكون أقرب ما يمكن إلى main()/نقطة الدخول. ولا ينشئ أي جزء آخر من التطبيق البنية التحتية باستخدام new. وهذا هو الموضع الوحيد الذي يعرف بوجود Stripe.
<?php
// public/index.php — composition root
$stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET'));
$gateway = new StripePaymentGateway($stripe);
$orders = new PdoOrderRepository(new PDO(getenv('DB_DSN')));
$checkout = new CheckoutService($gateway, $orders);
// Everything below depends only on abstractions
$controller = new CheckoutController($checkout);الربط باستخدام حاوية DI
في التطبيقات غير البسيطة، تؤتمت الحاوية (PHP-DI أو Symfony) عملية الربط. والخطوة الأساسية هي ربط الواجهات بالتطبيقات. يحل الربط التلقائي التبعيات في المُنشئات استناداً إلى الأنواع؛ ولا تحتاجون إلا إلى التصريح بخريطة الواجهة→الصنف.
<?php
use function DI\autowire;
use function DI\get;
return [
PaymentGateway::class => autowire(StripePaymentGateway::class),
OrderRepository::class => autowire(PdoOrderRepository::class),
\Stripe\StripeClient::class => fn() => new \Stripe\StripeClient(getenv('STRIPE_SECRET')),
PDO::class => fn() => new PDO(getenv('DB_DSN')),
];استبدال المحوّلات يثبت الفكرة
بما أن النواة تعتمد على PaymentGateway فقط، فإن تبديل المورّدين أو الاختبار دون اتصال لا يتطلب سوى تغيير في سطر واحد من الربط. إليكم بديلاً وهمياً مستخدماً في اختبار وحدة؛ إذ تظل CheckoutService دون تغيير، ولا تستورد Stripe مطلقاً.
<?php
final class FakeGateway implements PaymentGateway {
public array $charges = [];
public function charge(Money $a, CardToken $t): ChargeId {
$this->charges[] = $a;
return new ChargeId('ch_test_' . count($this->charges));
}
}
$fake = new FakeGateway();
$id = $fake->charge(new Money(2500, 'EUR'), new CardToken('tok_visa'));
echo $id->value, ' charges=', count($fake->charges), PHP_EOL; // ch_test_1 charges=1تجنبوا فخ محدد الخدمات
يُعد حقن الحاوية نفسها وسحب التبعيات داخل الطرق نمط محدد الخدمات المضاد. فهو يخفي التبعيات، ويعطّل التحقق من الأنواع، ويعيد ربط شيفرتكم بالحاوية. احقنوا ما تحتاجون إليه بشكل صريح.
<?php
// ANTI-PATTERN: hidden dependencies, container leaks everywhere
final class BadCheckout {
public function __construct(private ContainerInterface $c) {}
public function pay($id, $token) {
$gateway = $this->c->get(PaymentGateway::class); // hidden!
// ...
}
}أين يجوز ظهور الحاوية
تكون الحاوية مشروعة في طبقة واحدة تحديداً: جذر التركيب وطبقة الربط الخاصة بإطار العمل المحيطة به (مثل مصنع المتحكّمات). يجب أن تظل أصناف المجال والتطبيق مستقلة عن الحاوية، فتتلقى كائنات عادية عبر المُنشئات ويمكن إنشاء مثيلاتها يدوياً. والاختبار الجيد هو: هل يمكنكم ربط التطبيق بأكمله في ملف PHP واحد من دون حاوية؟ إذا كانت الإجابة نعم، فتبعياتكم صريحة وصادقة.
الربط الكسول من دون تحديد الخدمات
قد يكون إنشاء إحدى التبعيات مكلفاً أو قد لا تحتاجون إليها إلا في ظروف معينة. قاوموا إغراء حقن الحاوية، واحقنوا بدلاً منها دالة مصنع مجهولة. فتبقى التبعية واضحة ومحددة النوع، بينما يتأجل إنشاؤها حتى استخدامها فعلياً.
<?php
final class ReportService
{
/** @param Closure():PaymentGateway $gatewayFactory */
public function __construct(private Closure $gatewayFactory) {}
public function refundIfNeeded(bool $needed): void {
if (!$needed) return;
$gateway = ($this->gatewayFactory)(); // built only when required
// $gateway->charge(...) etc.
}
}
// Composition root supplies the factory, not the container
$svc = new ReportService(fn() => new StripePaymentGateway($stripe ?? null));
echo 'lazy dependency wired', PHP_EOL;تحقّق سريع
من الذي ينبغي أن يملك واجهة PaymentGateway؟
مراجعة
لقد ربطتم المحوّلات بالنواة بالطريقة الصحيحة:
- العكس لا يساوي الحقن: الحقن هو الآلية، أما العكس فيتمثل في امتلاك المستهلك للتجريد.
- تعرّف النواة الواجهة، وتتوافق محوّلات البنية التحتية معها.
- استخدموا حقن المُنشئ للتجريدات، وأنجزوا كل عمليات الربط الملموسة في جذر تركيب واحد (أو في إعداد حاوية يربط الواجهة→الصنف).
- يصبح استبدال المحوّلات واستخدام البدائل الوهمية في الاختبارات تغييراً في سطر واحد.
- تجنبوا نمط محدد الخدمات المضاد، وأبقوا الحاوية عند الحافة فقط.
الأسئلة الشائعة
هل درس «عكس الاعتماديات في التطبيق» مجاني؟
نعم — نص درس «عكس الاعتماديات في التطبيق» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.
ماذا ستتعلم في «عكس الاعتماديات في التطبيق»؟
صِل المحوّلات بالنواة باستخدام عكس الاعتماديات. تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟
لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «عكس الاعتماديات في التطبيق»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟
نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- من البنية متعددة الطبقات إلى البنية النظيفة
- شرح المنافذ والمحوّلات
- حالات الاستخدام وخدمات التطبيق
- عكس الاعتماديات في التطبيق