Accept application/x-www-form-urlencoded Form Data

API Platform only supports raw documents as request input (encoded in JSON, XML, YAML...). This has many advantages including support of types and the ability to send back to the API documents originally retrieved through a GET request. However, sometimes - for instance, to support legacy clients - it is necessary to accept inputs encoded in the traditional application/x-www-form-urlencoded format (HTML form content type). This can easily be done using the powerful event system of the framework.

API プラットフォームは、未加工のドキュメントのみをリクエスト入力としてサポートします (JSON、XML、YAML でエンコードされています...)。これには、タイプのサポートや、GET 要求によって最初に取得された API ドキュメントに送り返す機能など、多くの利点があります。 -www-form-urlencoded 形式 (HTML フォーム コンテンツ タイプ)。これは、フレームワークの強力なイベント システムを使用して簡単に実行できます。

⚠ Adding support for application/x-www-form-urlencoded makes your API vulnerable to CSRF attacks. Be sure to enable proper countermeasures such as DunglasAngularCsrfBundle.

⚠ application/x-www-form-urlencoded のサポートを追加すると、API が CSRF 攻撃に対して脆弱になります。 DunglasAngularCsrfBundle などの適切な対策を有効にしてください。

In this tutorial, we will decorate the default DeserializeListener class to handle form data if applicable, and delegate to the built-in listener for other cases.

このチュートリアルでは、デフォルトの DeserializeListener クラスを装飾して、該当する場合はフォーム データを処理し、その他の場合は組み込みリスナーに委譲します。

Create your DeserializeListener Decorator

This decorator is able to denormalize posted form data to the target object. In case of other format, it fallbacks to the original DeserializeListener.

このデコレータは、ポストされたフォーム データをターゲット オブジェクトに非正規化できます。それ以外の形式の場合は元の DeserializeListener にフォールバックします。

<?php
// api/src/EventListener/DeserializeListener.php

namespace App\EventListener;

use ApiPlatform\Serializer\SerializerContextBuilderInterface;
use ApiPlatform\Symfony\EventListener\DeserializeListener as DecoratedListener;
use ApiPlatform\Util\RequestAttributesExtractor;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

final class DeserializeListener
{
    private $decorated;
    private $denormalizer;
    private $serializerContextBuilder;

    public function __construct(DenormalizerInterface $denormalizer, SerializerContextBuilderInterface $serializerContextBuilder, DecoratedListener $decorated)
    {
        $this->denormalizer = $denormalizer;
        $this->serializerContextBuilder = $serializerContextBuilder;
        $this->decorated = $decorated;
    }

    public function onKernelRequest(RequestEvent $event): void {
        $request = $event->getRequest();
        if ($request->isMethodCacheable(false) || $request->isMethod(Request::METHOD_DELETE)) {
            return;
        }

        if ('form' === $request->getContentType()) {
            $this->denormalizeFormRequest($request);
        } else {
            $this->decorated->onKernelRequest($event);
        }
    }

    private function denormalizeFormRequest(Request $request): void
    {
        if (!$attributes = RequestAttributesExtractor::extractAttributes($request)) {
            return;
        }

        $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes);
        $populated = $request->attributes->get('data');
        if (null !== $populated) {
            $context['object_to_populate'] = $populated;
        }

        $data = $request->request->all();
        $object = $this->denormalizer->denormalize($data, $attributes['resource_class'], null, $context);
        $request->attributes->set('data', $object);
    }
}

Creating the Service Definition

# api/config/services.yaml
services:
    # ...
    'App\EventListener\DeserializeListener':
        tags:
            - { name: 'kernel.event_listener', event: 'kernel.request', method: 'onKernelRequest', priority: 2 }
        # Autoconfiguration must be disabled to set a custom priority
        autoconfigure: false
        decorates: 'api_platform.listener.request.deserialize'
        arguments:
            $decorated: '@App\EventListener\DeserializeListener.inner'