การพึ่งพาการฉีดใน XOOPS
Dependency Injection (DI) คือรูปแบบการออกแบบที่ช่วยให้ส่วนประกอบต่างๆ ได้รับการพึ่งพาจากแหล่งภายนอก แทนที่จะสร้างขึ้นภายใน XOOPS 4.0 แนะนำการสนับสนุนคอนเทนเนอร์ PSR-11 DI ที่เข้ากันได้
ทำไมต้องพึ่งการฉีด?
หัวข้อที่มีชื่อว่า “ทำไมต้องพึ่งการฉีด?”ไม่มี DI (ข้อต่อแน่น)
หัวข้อที่มีชื่อว่า “ไม่มี DI (ข้อต่อแน่น)”class ArticleService{ private ArticleRepository $repository; private EventDispatcher $dispatcher;
public function __construct() { // Hard dependencies - difficult to test and modify $this->repository = new ArticleRepository(new XoopsDatabase()); $this->dispatcher = new EventDispatcher(); }}พร้อม DI (ข้อต่อหลวม)
หัวข้อที่มีชื่อว่า “พร้อม DI (ข้อต่อหลวม)”class ArticleService{ public function __construct( private readonly ArticleRepositoryInterface $repository, private readonly EventDispatcherInterface $dispatcher ) {}}PSR-11 คอนเทนเนอร์
หัวข้อที่มีชื่อว่า “PSR-11 คอนเทนเนอร์”การใช้งานพื้นฐาน
หัวข้อที่มีชื่อว่า “การใช้งานพื้นฐาน”use Psr\Container\ContainerInterface;
// Get the container$container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer();
// Retrieve a service$articleService = $container->get(ArticleService::class);
// Check if service existsif ($container->has(ArticleService::class)) { // Use the service}การกำหนดค่าคอนเทนเนอร์
หัวข้อที่มีชื่อว่า “การกำหนดค่าคอนเทนเนอร์”use Psr\Container\ContainerInterface;
return [ // Simple class instantiation ArticleRepository::class => ArticleRepository::class,
// Interface to implementation binding ArticleRepositoryInterface::class => ArticleRepository::class,
// Factory function ArticleService::class => function (ContainerInterface $c): ArticleService { return new ArticleService( $c->get(ArticleRepositoryInterface::class), $c->get(EventDispatcherInterface::class) ); },
// Shared instance (singleton) 'database' => function (): XoopsDatabase { return XoopsDatabaseFactory::getDatabaseConnection(); },];การลงทะเบียนบริการ
หัวข้อที่มีชื่อว่า “การลงทะเบียนบริการ”เดินสายไฟอัตโนมัติ
หัวข้อที่มีชื่อว่า “เดินสายไฟอัตโนมัติ”// The container automatically resolves dependencies// when type hints are available
class ArticleController{ public function __construct( private readonly ArticleService $service, private readonly ViewRenderer $renderer ) {}}
// Container creates ArticleController with its dependencies$controller = $container->get(ArticleController::class);การลงทะเบียนด้วยตนเอง
หัวข้อที่มีชื่อว่า “การลงทะเบียนด้วยตนเอง”return [ ArticleService::class => [ 'class' => ArticleService::class, 'arguments' => [ ArticleRepositoryInterface::class, EventDispatcherInterface::class, ], 'shared' => true, // Singleton ],
'article.handler' => [ 'factory' => [ArticleHandlerFactory::class, 'create'], 'arguments' => ['@database'], // Reference other service ],];การฉีดคอนสตรัคเตอร์
หัวข้อที่มีชื่อว่า “การฉีดคอนสตรัคเตอร์”แนวทางที่ต้องการ
หัวข้อที่มีชื่อว่า “แนวทางที่ต้องการ”final class ArticleService{ public function __construct( private readonly ArticleRepositoryInterface $repository, private readonly EventDispatcherInterface $dispatcher, private readonly LoggerInterface $logger ) {}
public function create(CreateArticleDTO $dto): Article { $this->logger->info('Creating article', ['title' => $dto->title]);
$article = Article::create($dto); $this->repository->save($article); $this->dispatcher->dispatch(new ArticleCreatedEvent($article));
return $article; }}วิธีการฉีด
หัวข้อที่มีชื่อว่า “วิธีการฉีด”สำหรับการพึ่งพาเพิ่มเติม
หัวข้อที่มีชื่อว่า “สำหรับการพึ่งพาเพิ่มเติม”class ArticleController{ public function __construct( private readonly ArticleService $service ) {}
public function show(int $id, ?CacheInterface $cache = null): Response { $cacheKey = "article_{$id}";
if ($cache && $cached = $cache->get($cacheKey)) { return $this->render($cached); }
$article = $this->service->findById($id);
$cache?->set($cacheKey, $article, 3600);
return $this->render($article); }}การผูกอินเทอร์เฟซ
หัวข้อที่มีชื่อว่า “การผูกอินเทอร์เฟซ”กำหนดอินเทอร์เฟซ
หัวข้อที่มีชื่อว่า “กำหนดอินเทอร์เฟซ”interface ArticleRepositoryInterface{ public function findById(int $id): ?Article; public function save(Article $article): void; public function delete(Article $article): void;}การดำเนินการผูกมัด
หัวข้อที่มีชื่อว่า “การดำเนินการผูกมัด”return [ ArticleRepositoryInterface::class => XoopsArticleRepository::class,
// Or with factory ArticleRepositoryInterface::class => function (ContainerInterface $c) { return new XoopsArticleRepository( $c->get('database') ); },];การทดสอบด้วย DI
หัวข้อที่มีชื่อว่า “การทดสอบด้วย DI”เยาะเย้ยง่าย
หัวข้อที่มีชื่อว่า “เยาะเย้ยง่าย”class ArticleServiceTest extends TestCase{ public function testCreateArticle(): void { // Create mocks $repository = $this->createMock(ArticleRepositoryInterface::class); $dispatcher = $this->createMock(EventDispatcherInterface::class); $logger = $this->createMock(LoggerInterface::class);
// Inject mocks $service = new ArticleService($repository, $dispatcher, $logger);
// Set expectations $repository->expects($this->once())->method('save'); $dispatcher->expects($this->once())->method('dispatch');
// Test $dto = new CreateArticleDTO('Title', 'Content'); $article = $service->create($dto);
$this->assertInstanceOf(Article::class, $article); }}XOOPS การบูรณาการแบบเดิม
หัวข้อที่มีชื่อว่า “XOOPS การบูรณาการแบบเดิม”เชื่อมโยงเก่าและใหม่
หัวข้อที่มีชื่อว่า “เชื่อมโยงเก่าและใหม่”// Get service from container in legacy codefunction mymodule_get_articles(int $limit): array{ $container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer(); $service = $container->get(ArticleService::class);
return $service->findRecent($limit);}การห่อตัวจัดการแบบเดิม
หัวข้อที่มีชื่อว่า “การห่อตัวจัดการแบบเดิม”return [ 'article.handler' => function () { return xoops_getModuleHandler('article', 'mymodule'); },
ArticleRepositoryInterface::class => function (ContainerInterface $c) { return new LegacyArticleRepository( $c->get('article.handler') ); },];แนวทางปฏิบัติที่ดีที่สุด
หัวข้อที่มีชื่อว่า “แนวทางปฏิบัติที่ดีที่สุด”- Inject Interfaces - ขึ้นอยู่กับนามธรรม ไม่ใช่การใช้งาน
- การฉีดคอนสตรัคเตอร์ - ชอบคอนสตรัคเตอร์มากกว่าการฉีดเซ็ตเตอร์
- ความรับผิดชอบเดี่ยว - แต่ละคลาสควรมีการพึ่งพาน้อย
- หลีกเลี่ยงการรับรู้คอนเทนเนอร์ - บริการไม่ควรรู้เกี่ยวกับคอนเทนเนอร์
- กำหนดค่า ไม่ต้องเขียนโค้ด - ใช้ไฟล์การกำหนดค่าสำหรับการเดินสาย
เอกสารที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เอกสารที่เกี่ยวข้อง”- ../07-XOOPS-4.0/Implementation-Guides/PSR-11-Dependency-Injection-Guide - PSR-11 การดำเนินการ
- ../03-Module-Development/Patterns/Service-Layer - รูปแบบการบริการ
- ../03-Module-Development/Best-Practices/testing - การทดสอบด้วย DI
- ../07-XOOPS-4.0/XOOPS-4.0-Architecture - ภาพรวมสถาปัตยกรรม