DTO ialah objek ringkas yang merangkum atribut data tanpa mengandungi sebarang logik perniagaan. Ia sering digunakan untuk mengagregat data daripada pelbagai sumber ke dalam satu objek, menjadikannya lebih mudah untuk diurus dan dihantar. Dengan menggunakan DTO, pembangun boleh mengurangkan bilangan panggilan kaedah, meningkatkan prestasi dan memudahkan pengendalian data, terutamanya dalam sistem atau API teragih.
Sebagai contoh, kami boleh menggunakan DTO untuk memetakan data yang diterima melalui Permintaan HTTP. DTO tersebut akan menyimpan nilai muatan yang diterima pada harta mereka dan kami boleh menggunakannya dalam aplikasi, contohnya, dengan mencipta objek entiti doktrin yang sedia untuk disimpan ke pangkalan data daripada data yang disimpan dalam DTO. Memandangkan data DTO sudah pun disahkan, ia boleh mengurangkan kebarangkalian menjana ralat semasa pangkalan data berterusan.
Atribut MapQueryString dan MapRequestPayload membolehkan kami masing-masing memetakan rentetan pertanyaan dan parameter muatan yang diterima. Mari lihat contoh kedua-duanya.
Mari bayangkan kita mempunyai laluan Symfony yang boleh menerima parameter berikut dalam rentetan pertanyaan:
Berdasarkan parameter di atas, kami ingin memetakannya ke dto berikut:
readonly class QueryInputDto { public function __construct( #[Assert\Datetime(message: 'From must be a valid datetime')] #[Assert\NotBlank(message: 'From date cannot be empty')] public string $from, #[Assert\Datetime(message: 'To must be a valid datetime')] #[Assert\NotBlank(message: 'To date cannot be empty')] public string $to, public ?int $age = null ){} }
Untuk memetakannya, kita hanya perlu menggunakan atribut MapQueryString mengikut cara ini:
#[Route('/my-get-route', name: 'my_route_name', methods: ['GET'])] public function myAction(#[MapQueryString] QueryInputDTO $queryInputDto) { // your code }
Seperti yang anda lihat, apabila symfony mengesan bahawa argumen $queryInputDto telah dibenderakan dengan atribut #[MapQueryString], ia secara automatik memetakan rentetan pertanyaan yang diterima parameter ke dalam argumen itu yang merupakan contoh bagi Kelas QueryInputDTO.
Dalam kes ini, mari bayangkan kita mempunyai laluan Symfony yang menerima data yang diperlukan untuk mendaftarkan pengguna baharu dalam muatan permintaan JSON. Parameter tersebut adalah seperti berikut:
Berdasarkan parameter di atas, kami ingin memetakannya ke dto berikut:
readonly class PayloadInputDto { public function __construct( #[Assert\NotBlank(message: 'Name cannot be empty')] public string $name, #[Assert\NotBlank(message: 'Email cannot be empty')] #[Assert\Email(message: 'Email must be a valid email')] public string $email, #[Assert\NotBlank(message: 'From date cannot be empty')] #[Assert\Date(message: 'Dob must be a valid date')] public ?string $dob = null ){} }
Untuk memetakannya, kita hanya perlu menggunakan atribut MapRequestPayload mengikut cara ini:
#[Route('/my-post-route', name: 'my_post_route', methods: ['POST'])] public function myAction(#[MapRequestPayload] PayloadInputDTO $payloadInputDto) { // your code }
Seperti yang telah kita lihat dalam bahagian sebelumnya, apabila symfony mengesan bahawa argumen $payloadInputDto telah dibenderakan dengan atribut #[MapRequestPayload], ia secara automatik memetakan parameter muatan yang diterima ke dalam hujah tersebut iaitu contoh kelas PayloadInputDTO.
MapRequestPayload berfungsi untuk muatan JSON dan muatan berkod url borang.
Jika pengesahan gagal semasa proses pemetaan (contohnya, e-mel mandatori belum dihantar) Symfony membuang pengecualian 422 Kandungan Tidak Boleh Diproses. Jika anda ingin menangkap jenis pengecualian ini dan mengembalikan ralat pengesahan sebagai, contohnya, json kepada klien, anda boleh membuat pelanggan acara dan terus mendengar acara KernelException.
class KernelSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ KernelEvents::EXCEPTION => 'onException' ]; } public function onException(ExceptionEvent $event): void { $exception = $event->getThrowable(); if($exception instanceof UnprocessableEntityHttpException) { $previous = $exception->getPrevious(); if($previous instanceof ValidationFailedException) { $errors = []; foreach($previous->getViolations() as $violation) { $errors[] = [ 'path' => $violation->getPropertyPath(), 'error' => $violation->getMessage() ]; } $event->setResponse(new JsonResponse($errors)); } } } }
Selepas kaedah onException dicetuskan, ia menyemak sama ada pengecualian acara ialah tika UnprocessableEntityHttpException. Jika ya, ia juga menyemak sama ada ralat yang tidak boleh diproses datang daripada pengesahan yang gagal yang memeriksa sama ada pengecualian sebelumnya ialah contoh kelas ValidationFailedException. Jika ya, ia menyimpan semua ralat pelanggaran dalam tatasusunan (hanya laluan sifat sebagai kunci dan mesej pelanggaran sebagai ralat), mencipta respons JSON daripada ralat ini dan menetapkan respons baharu kepada acara tersebut.
Imej berikut menunjukkan respons JSON untuk permintaan yang gagal kerana e-mel belum dihantar:
@baseUrl = http://127.0.0.1:8000 POST {{baseUrl}}/my-post-route Content-Type: application/json { "name" : "Peter Parker", "email" : "", "dob" : "1990-06-28" } ------------------------------------------------------------- HTTP/1.1 422 Unprocessable Entity Cache-Control: no-cache, private Content-Type: application/json Date: Fri, 20 Sep 2024 16:44:20 GMT Host: 127.0.0.1:8000 X-Powered-By: PHP/8.2.23 X-Robots-Tag: noindex Transfer-Encoding: chunked [ { "path": "email", "error": "Email cannot be empty" } ]
Permintaan imej di atas telah dijana menggunakan fail http.
Mari bayangkan kita mempunyai beberapa laluan yang menerima parameter rentetan pertanyaan ke dalam tatasusunan bernama "f". Sesuatu seperti ini:
/my-get-route?f[from]=2024-08-20 16:24:08&f[to]=&f[age]=14
Kami boleh mencipta penyelesai tersuai untuk menyemak tatasusunan itu dalam permintaan dan kemudian mengesahkan data. Jom kodkan.
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\MapQueryString; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Exception\ValidationFailedException; use Symfony\Component\Validator\Validator\ValidatorInterface; class CustomQsValueResolver implements ValueResolverInterface, EventSubscriberInterface { public function __construct( private readonly ValidatorInterface $validator, private readonly SerializerInterface $serializer ){} public static function getSubscribedEvents(): array { return [ KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments', ]; } public function resolve(Request $request, ArgumentMetadata $argument): iterable { $attribute = $argument->getAttributesOfType(MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null; if (!$attribute) { return []; } if ($argument->isVariadic()) { throw new \LogicException(sprintf('Mapping variadic argument "$%s" is not supported.', $argument->getName())); } $attribute->metadata = $argument; return [$attribute]; } public function onKernelControllerArguments(ControllerArgumentsEvent $event): void { $arguments = $event->getArguments(); $request = $event->getRequest(); foreach ($arguments as $i => $argument) { if($argument instanceof MapQueryString ) { $qs = $request->get('f', []); if(count($qs) > 0) { $object = $this->serializer->denormalize($qs, $argument->metadata->getType()); $violations = $this->validator->validate($object); if($violations->count() > 0) { $validationFailedException = new ValidationFailedException(null, $violations); throw new UnprocessableEntityHttpException('Unale to process received data', $validationFailedException); } $arguments[$i] = $object; } } } $event->setArguments($arguments); } }
The CustomQsValueResolver implements the ValueResolverInterface and the EventSubscriberInterface, allowing it to resolve arguments for controller actions and listen to specific events in the Symfony event system. In this case, the resolver listens to the Kernel CONTROLLER_ARGUMENTS event.
Let's analyze it step by step:
The constructor takes two dependencies: The Validator service for validating objects and a the Serializer service for denormalizing data into objects.
The getSubscribedEvents method returns an array mapping the KernelEvents::CONTROLLER_ARGUMENTS symfony event to the onKernelControllerArguments method. This means that when the CONTROLLER_ARGUMENTS event is triggered (always a controller is reached), the onKernelControllerArguments method will be called.
The resolve method is responsible for resolving the value of an argument based on the request and its metadata.
The onKernelControllerArguments method is called when the CONTROLLER_ARGUMENTS event is triggered.
To instruct the MapQueryString attribute to use our recently created resolver instead of the default one, we must specify it with the attribute resolver value. Let's see how to do it:
#[Route('/my-get-route', name: 'my_route_name', methods: ['GET'])] public function myAction(#[MapQueryString(resolver: CustomQsValueResolver::class)] QueryInputDTO $queryInputDto) { // your code }
In this article, we have analized how symfony makes our lives easier by making common application tasks very simple, such as receiving and validating data from an API. To do that, it offers us the MapQueryString and MapRequestPayload attributes. In addition, it also offers us the possibility of creating our custom mapping resolvers for cases that require specific needs.
If you like my content and enjoy reading it and you are interested in learning more about PHP, you can read my ebook about how to create an operation-oriented API using PHP and the Symfony Framework. You can find it here: Building an Operation-Oriented Api using PHP and the Symfony Framework: A step-by-step guide
Atas ialah kandungan terperinci Cara mudah untuk mengesahkan DTO menggunakan atribut Symfony. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!