搜尋
首頁後端開發php教程使用 Symfony 屬性驗證 DTO 的簡單方法

An easy way to validate DTO

介紹

DTO 是封裝資料屬性而不包含任何業務邏輯的簡單物件。它們通常用於將多個來源的資料聚合到單一物件中,從而更易於管理和傳輸。透過使用 DTO,開發人員可以減少方法呼叫的數量、提高效能並簡化資料處理,尤其是在分散式系統或 API 中。

舉個例子,我們可以使用 DTO 來對應透過 HTTP 請求接收到的資料。這些 DTO 會將接收到的有效負載值保存到其屬性中,我們可以在應用程式中使用它們,例如,透過建立一個條令實體對象,準備將 DTO 中保存的資料持久保存到資料庫中。由於 DTO 資料已經經過驗證,因此可以降低資料庫持久化過程中產生錯誤的機率。

MapQueryString 和 MapRequestPayload 屬性

MapQueryString 和 MapRequestPayload 屬性允許我們分別映射接收到的查詢字串和負載參數。讓我們看一下兩者的範例。

MapQueryString 範例

假設我們有一個 Symfony 路由,它可以在查詢字串中接收以下參數:

  • 來自:必填的起始日期
  • :必填日期
  • 年齡:可選年齡

基於上述參數,我們希望將它們對應到以下 dto:

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 
   ){}
}

要映射它們,我們只需按照以下方式使用 MapQueryString 屬性:

#[Route('/my-get-route', name: 'my_route_name', methods: ['GET'])]
public function myAction(#[MapQueryString] QueryInputDTO $queryInputDto) 
{
   // your code
}

如您所見,當symfony 偵測到參數$queryInputDto 已被標記為#[MapQueryString] 屬性時,它會自動將接收到的查詢字串參數對應到該參數,該參數是QueryInputDTO 類別。

MapRequestPayload 範例

在這種情況下,假設我們有一個 Symfony 路由,它接收在 JSON 請求負載中註冊新用戶所需的資料。這些參數如下:

  • 姓名:必填
  • 電子郵件:必填
  • 出生日期(dob):必填

基於上述參數,我們希望將它們對應到以下 dto:

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 
    ){}
 }

要映射它們,我們只需使用 MapRequestPayload 屬性,如下所示:

#[Route('/my-post-route', name: 'my_post_route', methods: ['POST'])]
public function myAction(#[MapRequestPayload] PayloadInputDTO $payloadInputDto) 
{
   // your code
}

如我們在上一節所看到的,當symfony 偵測到參數$payloadInputDto 已被標記為#[MapRequestPayload] 屬性時,它會自動將接收到的有效負載參數對應到在該參數中,即PayloadInputDTO 類別的實例。

MapRequestPayload 適用於 JSON 有效負載和表單 url 編碼的有效負載。

處理 DTO 驗證錯誤

如果在映射過程中驗證失敗(例如,強制電子郵件尚未發送),Symfony 會拋出 422 Unprocessable Content 異常。如果您想捕獲此類異常並將驗證錯誤以 json 等形式傳回給用戶端,您可以建立事件訂閱者並繼續監聽 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));
            }
        }
    }
}

觸發onException方法後,它會檢查事件異常是否是UnprocessableEntityHttpException的實例。如果是這樣,它還會檢查不可處理的錯誤是否來自驗證失敗,檢查先前的異常是否是 ValidationFailedException 類別的實例。如果是這樣,它將所有違規錯誤儲存在一個陣列中(僅屬性路徑作為鍵,違規訊息作為錯誤),根據這些錯誤建立 JSON 回應,並將新回應設為事件。

下圖顯示了由於電子郵件尚未發送而失敗的請求的 JSON 回應:

@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"
  }
]

以上的圖像請求是使用http檔產生。

建立您的自訂解析器

假設我們有一些路由將查詢字串參數接收到名為「f」的陣列中。像這樣的東西:

/my-get-route?f[from]=2024-08-20 16:24:08&f[to]=&f[age]=14

我們可以建立一個自訂解析器來檢查請求中的該數組,然後驗證資料。讓我們來編碼吧。

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

The constructor takes two dependencies: The Validator service for validating objects and a the Serializer service for denormalizing data into objects.

The getSubscribedEvents method

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

The resolve method is responsible for resolving the value of an argument based on the request and its metadata.

  • It checks if the argument has the MapQueryString attribute. If not, it returns an empty array.
  • If the argument is variadic, that is, it can accept a variable number of arguments, it throws a LogicException, indicating that mapping variadic arguments is not supported.
  • If the attribute is found, it sets the metadata property of the attribute and returns it as a php iterable.

The onKernelControllerArguments method

The onKernelControllerArguments method is called when the CONTROLLER_ARGUMENTS event is triggered.

  • It retrieves the current arguments and the request from the event.
  • It iterates over the arguments, checking for arguments flagged as MapQueryString
  • If found, it retrieves the query string parameters holded by the "f" array using $request->get('f', []).
  • If there are parameters, it denormalizes them into an object of the type specified in the argument's metadata (The Dto class).
  • It then validates the object using the validator. If there are validation violations, it throws an UnprocessableEntityHttpException which wraps a ValidationFailedException with the validation errors.
  • If validation passes, it replaces the original argument with the newly created object.

Using the resolver in the controller

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
}

Conclusion

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

以上是使用 Symfony 屬性驗證 DTO 的簡單方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
PHP的目的:構建動態網站PHP的目的:構建動態網站Apr 15, 2025 am 12:18 AM

PHP用於構建動態網站,其核心功能包括:1.生成動態內容,通過與數據庫對接實時生成網頁;2.處理用戶交互和表單提交,驗證輸入並響應操作;3.管理會話和用戶認證,提供個性化體驗;4.優化性能和遵循最佳實踐,提升網站效率和安全性。

PHP:處理數據庫和服務器端邏輯PHP:處理數據庫和服務器端邏輯Apr 15, 2025 am 12:15 AM

PHP在數據庫操作和服務器端邏輯處理中使用MySQLi和PDO擴展進行數據庫交互,並通過會話管理等功能處理服務器端邏輯。 1)使用MySQLi或PDO連接數據庫,執行SQL查詢。 2)通過會話管理等功能處理HTTP請求和用戶狀態。 3)使用事務確保數據庫操作的原子性。 4)防止SQL注入,使用異常處理和關閉連接來調試。 5)通過索引和緩存優化性能,編寫可讀性高的代碼並進行錯誤處理。

您如何防止PHP中的SQL注入? (準備的陳述,PDO)您如何防止PHP中的SQL注入? (準備的陳述,PDO)Apr 15, 2025 am 12:15 AM

在PHP中使用預處理語句和PDO可以有效防範SQL注入攻擊。 1)使用PDO連接數據庫並設置錯誤模式。 2)通過prepare方法創建預處理語句,使用佔位符和execute方法傳遞數據。 3)處理查詢結果並確保代碼的安全性和性能。

PHP和Python:代碼示例和比較PHP和Python:代碼示例和比較Apr 15, 2025 am 12:07 AM

PHP和Python各有優劣,選擇取決於項目需求和個人偏好。 1.PHP適合快速開發和維護大型Web應用。 2.Python在數據科學和機器學習領域佔據主導地位。

PHP行動:現實世界中的示例和應用程序PHP行動:現實世界中的示例和應用程序Apr 14, 2025 am 12:19 AM

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

PHP:輕鬆創建交互式Web內容PHP:輕鬆創建交互式Web內容Apr 14, 2025 am 12:15 AM

PHP可以輕鬆創建互動網頁內容。 1)通過嵌入HTML動態生成內容,根據用戶輸入或數據庫數據實時展示。 2)處理表單提交並生成動態輸出,確保使用htmlspecialchars防XSS。 3)結合MySQL創建用戶註冊系統,使用password_hash和預處理語句增強安全性。掌握這些技巧將提升Web開發效率。

PHP和Python:比較兩種流行的編程語言PHP和Python:比較兩種流行的編程語言Apr 14, 2025 am 12:13 AM

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP的持久相關性:它還活著嗎?PHP的持久相關性:它還活著嗎?Apr 14, 2025 am 12:12 AM

PHP仍然具有活力,其在現代編程領域中依然佔據重要地位。 1)PHP的簡單易學和強大社區支持使其在Web開發中廣泛應用;2)其靈活性和穩定性使其在處理Web表單、數據庫操作和文件處理等方面表現出色;3)PHP不斷進化和優化,適用於初學者和經驗豐富的開發者。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能