ใช้คลาส Action แทน Controller ใน Symfony
ผมสานุศิษย์ของการดำเนินการชั้นวิธีใช้แทนการควบคุม คำอธิบายนั้นง่ายมาก: บ่อยครั้งที่Controllerมีการกระทำหลายอย่างเมื่อทำตามหลักการDependency Injectionเราจะต้องส่งการอ้างอิงที่จำเป็นทั้งหมดไปยังตัวสร้างและสิ่งนี้จะทำให้สถานการณ์เมื่อControllerมีการอ้างอิงจำนวนมาก แต่ในช่วงเวลาหนึ่ง (เช่นคำขอ) เราใช้การอ้างอิงเพียงบางส่วนเท่านั้น ยากที่จะรักษาและทดสอบรหัสสปาเก็ตตี้นั้น
ชี้แจงผมเคยใช้อยู่แล้วในการทำงานด้วยวิธีการว่าใน Zend Framework 2 แต่มีมันชื่อมิดเดิ้ล ฉันพบสิ่งที่คล้ายกันใน API-Platform โดยที่พวกเขาใช้คลาส Actionแทน Controller ด้วย แต่ปัญหาคือฉันไม่รู้วิธีปรุง
UPD: ฉันจะรับ Action Class ถัดไปและแทนที่ Controller มาตรฐานได้อย่างไรและควรเพิ่มการกำหนดค่าใดในโครงการ Symfony ปกติ
<?php
declare(strict_types=1);
namespace App\Action\Product;
use App\Entity\Product;
use Doctrine\ORM\EntityManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class SoftDeleteAction
{
/**
* @var EntityManager
*/
private $entityManager; /** * @param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager) { $this->entityManager = $entityManager; } /** * @Route( * name="app_product_delete", * path="products/{id}/delete" * ) * * @Method("DELETE") * * @param Product $product
*
* @return Response
*/
public function __invoke(Request $request, $id): Response
{
$product = $this->entityManager->find(Product::class, $id); $product->delete();
$this->entityManager->flush();
return new Response('', 204);
}
}
คำตอบ
คำถามค่อนข้างคลุมเครือสำหรับ stackoverflow แม้ว่ามันจะน่าสนใจเล็กน้อยก็ตาม ดังนั้นนี่คือรายละเอียดการกำหนดค่าบางส่วน
เริ่มต้นด้วยโครงการโครงกระดูก S4 นอกกรอบ:
symfony new --version=lts s4api
cd s4api
bin/console --version # 4.4.11
composer require orm-pack
เพิ่ม SoftDeleteAction
namespace App\Action\Product;
class SoftDeleteAction
{
private $entityManager; public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function __invoke(Request $request, int $id) : Response
{
return new Response('Product ' . $id);
}
}
และกำหนดเส้นทาง:
# config/routes.yaml
app_product_delete:
path: /products/{id}/delete
controller: App\Action\Product\SoftDeleteAction
ณ จุดนี้การเดินสายเกือบเสร็จสมบูรณ์ หากคุณไปที่ url คุณจะได้รับ:
The controller for URI "/products/42/delete" is not callable:
เหตุผลก็คือบริการเป็นส่วนตัวโดยปริยาย โดยปกติคุณจะขยายจาก AbstractController ซึ่งดูแลการทำให้บริการเป็นสาธารณะ แต่ในกรณีนี้วิธีที่เร็วที่สุดคือเพียงแค่แท็กการกระทำเป็นตัวควบคุม:
# config/services.yaml
App\Action\Product\SoftDeleteAction:
tags: ['controller.service_arguments']
ณ จุดนี้คุณควรมีการดำเนินการแบบมีสาย
แน่นอนว่ามีรูปแบบต่างๆมากมายและรายละเอียดเพิ่มเติมเล็กน้อย คุณจะต้อง จำกัด เส้นทางเป็น POST หรือ DELETE ปลอม
คุณอาจพิจารณาเพิ่ม ControllerServiceArgumentsInterface ที่ว่างเปล่าจากนั้นใช้ฟังก์ชันอินสแตนซ์บริการเพื่อใช้แท็กคอนโทรลเลอร์เพื่อให้คุณไม่จำเป็นต้องกำหนดบริการคอนโทรลเลอร์ด้วยตนเองอีกต่อไป
แต่ก็น่าจะเพียงพอสำหรับคุณเริ่มต้น
แนวทางที่ฉันพยายามใช้มีชื่อว่ารูปแบบ ADR (Action-Domain-Responder)และ Symfony ได้สนับสนุนสิ่งนี้แล้วโดยเริ่มจากเวอร์ชัน 3.3 คุณสามารถเรียกมันว่าควบคุม Invokable
จากเอกสารอย่างเป็นทางการ:
ตัวควบคุมยังสามารถกำหนดการดำเนินการเดียวโดยใช้เมธอด __invoke () ซึ่งเป็นแนวทางปฏิบัติทั่วไปเมื่อทำตามรูปแบบ ADR (Action-Domain-Responder):
// src/Controller/Hello.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/hello/{name}", name="hello")
*/
class Hello
{
public function __invoke($name = 'World') { return new Response(sprintf('Hello %s!', $name));
}
}