การฉีดการพึ่งพาอัตโนมัติ

ใน 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 มีการพึ่งพาคลาสอื่น ๆ หรือใช้การฉีดด้วยคำอธิบายในคลาส 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 手册