حقن الاعتماديات التلقائي

في Webman، يُعتبر حقن الاعتماديات التلقائي ميزة اختيارية، وهذه الميزة مغلقة افتراضيًا. إذا كنت بحاجة إلى حقن الاعتماديات التلقائي، يوصى باستخدام php-di، وفيما يلي كيفية استخدام Webman مع php-di.

التثبيت

composer require psr/container ^1.1.1 php-di/php-di ^6.3 doctrine/annotations ^1.14

قم بتعديل الإعدادات في config/container.php، ليكون محتواها النهائي كما يلي:

$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();

config/container.php ترجع في النهاية مثالًا من حاوية متوافقة مع معيار PSR-11. إذا كنت لا ترغب في استخدام php-di، يمكنك هنا إنشاء وإرجاع مثال آخر لحاوية متوافقة مع معيار PSR-11.

حقن عبر المُنشئ

قم بإنشاء app/service/Mailer.php (إذا لم يكن الدليل موجودًا، الرجاء إنشاؤه بنفسك) بالمحتوى التالي:

<?php
namespace app\service;

class Mailer
{
    public function mail($email, $content)
    {
        // كود إرسال البريد الإلكتروني محذوف
    }
}

محتوى app/controller/UserController.php كما يلي:

<?php
namespace app\controller;

use support\Request;
use app\service\Mailer;

class UserController
{
    private $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function register(Request $request)
    {
        $this->mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

في الظروف العادية، تحتاج إلى الكود التالي لإكمال إنشاء مثال من app\controller\UserController:

$mailer = new Mailer;
$user = new UserController($mailer);

عند استخدام php-di، لن يحتاج المطور يدويًا إلى إنشاء Mailer في وحدة التحكم، حيث سيقوم Webman بإنشائها تلقائيًا لك. إذا كان هناك أي اعتماديات أخرى في عملية إنشاء Mailer، فإن Webman أيضًا سيقوم بإنشائها وحقنها تلقائيًا. لا يحتاج المطور إلى أي عمل إعداد.

ملاحظة
يجب أن تكون الأمثلة التي أنشأها الإطار أو php-di لتتمكن من إكمال حقن الاعتماديات التلقائي، لن تتمكن الأمثلة التي تم إنشاؤها يدويًا بواسطة new من إكمال حقن الاعتماديات، إذا كانت هناك حاجة إلى الحقن، فيجب استخدام واجهة support\Container بدلاً من عبارة new، على سبيل المثال:

use app\service\UserService;
use app\service\LogService;
use support\Container;

// الأمثلة التي تم إنشاؤها بواسطة new لن تتمكن من حقن الاعتماديات
$user_service = new UserService;
// الأمثلة التي تم إنشاؤها بواسطة new لن تتمكن من حقن الاعتماديات
$log_service = new LogService($path, $name);

// الأمثلة التي أنشأتها Container يمكن حقن اعتمادياتها
$user_service = Container::get(UserService::class);
// الأمثلة التي أنشأتها Container يمكن حقن اعتمادياتها
$log_service = Container::make(LogService::class, [$path, $name]);

حقن بواسطة التعليقات التوضيحية

بالإضافة إلى حقن الاعتماديات عبر المُنشئ، يمكننا أيضًا استخدام حقن التعليقات التوضيحية. نواصل مع المثال السابق، حيث يُغير app\controller\UserController ليصبح كما يلي:

<?php
namespace app\controller;

use support\Request;
use app\service\Mailer;
use DI\Annotation\Inject;

class UserController
{
    /**
     * @Inject
     * @var Mailer
     */
    private $mailer;

    public function register(Request $request)
    {
        $this->mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

هذا المثال يستخدم تعليق @Inject للحقن، والتي يتم الإعلان فيها عن نوع الكائن بواسطة التعليق @var. هذا المثال يعادل الحقن عبر المُنشئ ولكن الكود أكثر اختصارًا.

ملاحظة
لا يدعم Webman حقن معلمات وحدة التحكم قبل الإصدار 1.4.6، على سبيل المثال، الكود التالي غير مدعوم عندما يكون Webman <= 1.4.6

<?php
namespace app\controller;

use support\Request;
use app\service\Mailer;

class UserController
{
    // لا يدعم الإصدار 1.4.6 حقن معلمات وحدة التحكم
    public function register(Request $request, Mailer $mailer)
    {
        $mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

حقن مُخصص عبر المُنشئ

في بعض الأحيان، قد لا تكون المعلمات المرسلة إلى المُنشئ هي مثيلات من الكائنات، ولكنها سلاسل نصية أو أرقام أو مصفوفات أو أي بيانات أخرى. على سبيل المثال، قد يحتاج مُنشئ Mailer إلى تمرير عنوان IP لخادم SMTP ورقم المنفذ:

<?php
namespace app\service;

class Mailer
{
    private $smtpHost;

    private $smtpPort;

    public function __construct($smtp_host, $smtp_port)
    {
        $this->smtpHost = $smtp_host;
        $this->smtpPort = $smtp_port;
    }

    public function mail($email, $content)
    {
        // كود إرسال البريد الإلكتروني محذوف
    }
}

في هذه الحالة، لا يمكن استخدام حقن الاعتماديات عبر المُنشئ المبين سابقًا مباشرة، لأن php-di لا يمكنه تحديد قيم smtp_host و smtp_port. في هذه الحالة، يمكن محاولة الحقن المخصص.

في config/dependence.php (إذا كان الملف غير موجود، يرجى إنشاؤه بنفسك) بالإضافة إلى الكود التالي:

return [
    // ... هنا تم تجاهل إعدادات أخرى

    app\service\Mailer::class =>  new app\service\Mailer('192.168.1.11', 25);
];

عندها، ستستخدم الاعتماديات تلقائيًا مثال app\service\Mailer الذي تم إنشاؤه في هذا الإعداد عند الحاجة للاعتماد.

نلاحظ أن config/dependence.php استخدمت new لإنشاء مثيل من فئة Mailer، وهو أمر لا مشكلة فيه في هذا المثال، ولكن تخيل لو كانت فئة Mailer تعتمد على فئات أخرى أو استخدمت الحقن بواسطة التعليقات التوضيحية؛ فإن استخدام new للت初始化 لن يدعم حقن الاعتماديات. الحل هو استخدام حقن عبر واجهات مخصصة، من خلال Container::get(اسم_الفئة) أو Container::make(اسم_الفئة, [معلمات_المُنشئ]) لتهيئة الفئة.

حقن عبر واجهات مخصصة

في المشاريع الواقعية، نفضل أن نبرمج وفقًا للواجهات، وليس الفئات المحددة. على سبيل المثال، يجب أن تستورد app\controller\UserController واجهة app\service\MailerInterface بدلاً من app\service\Mailer.

تعريف واجهة MailerInterface.

<?php
namespace app\service;

interface MailerInterface
{
    public function mail($email, $content);
}

تعريف تنفيذ واجهة MailerInterface.

<?php
namespace app\service;

class Mailer implements MailerInterface
{
    private $smtpHost;

    private $smtpPort;

    public function __construct($smtp_host, $smtp_port)
    {
        $this->smtpHost = $smtp_host;
        $this->smtpPort = $smtp_port;
    }

    public function mail($email, $content)
    {
        // كود إرسال البريد الإلكتروني محذوف
    }
}

استيراد واجهة MailerInterface وليس التنفيذ المحدد.

<?php
namespace app\controller;

use support\Request;
use app\service\MailerInterface;
use DI\Annotation\Inject;

class UserController
{
    /**
     * @Inject
     * @var MailerInterface
     */
    private $mailer;

    public function register(Request $request)
    {
        $this->mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

config/dependence.php ستحدد واجهة MailerInterface كما يلي.

use Psr\Container\ContainerInterface;
return [
    app\service\MailerInterface::class => function(ContainerInterface $container) {
        return $container->make(app\service\Mailer::class, ['smtp_host' => '192.168.1.11', 'smtp_port' => 25]);
    }
];

عندما تحتاج الأعمال لاستخدام واجهة MailerInterface، سيتم استخدام تنفيذ Mailer تلقائيًا.

فائدة البرمجة على الواجهات هو أنه عند الحاجة لتغيير أحد المكونات، لا تحتاج إلى تغيير كود الأعمال، بل تحتاج فقط إلى تعديل التنفيذ المحدد في config/dependence.php. وهذا مفيد أيضًا في إجراء اختبارات الوحدة.

حقن مخصص آخر

يمكن أن تُعرّف config/dependence.php بالإضافة إلى اعتماديات الفئات، قيمًا أخرى مثل السلاسل النصية أو الأرقام أو المصفوفات.

على سبيل المثال، يمكن أن تُعرّف config/dependence.php كما يلي:

return [
    'smtp_host' => '192.168.1.11',
    'smtp_port' => 25
];

عندها يمكننا استخدام @Inject لحقن smtp_host و smtp_port في خصائص الفئة.

<?php
namespace app\service;

use DI\Annotation\Inject;

class Mailer
{
    /**
     * @Inject("smtp_host")
     */
    private $smtpHost;

    /**
     * @Inject("smtp_port")
     */
    private $smtpPort;

    public function mail($email, $content)
    {
        // كود إرسال البريد الإلكتروني محذوف
        echo "{$this->smtpHost}:{$this->smtpPort}\n"; // ستخرج 192.168.1.11:25
    }
}

ملاحظة: @Inject("key") تضع المفتاح داخل علامات الاقتباس المزدوجة

المزيد من المحتوى

يرجى الرجوع إلى دليل php-di