Обработка POST запросов AngularJs в Symfony2

http://labs.qandidate.com/blog/2014/08/13/handling-angularjs-post-requests-in-symfony/
  • Перевод
  • Tutorial
Примечание
Давненько уже читал пост на хабре, о сабже в контексте php, и все руки не доходили на Symfony2 привести это в какой-то красивый вид, а тут в недавнем дайджесте наткнулся на простое решение, которое здесь и представлено.


Использование Symfony2 и AngularJs в связке является хорошей идеей, но есть одна проблема — решение из коробки обладает проблемой в коммуникации. В этом посте будет рассказано о том, как автоматически декодировать JSON-запросы и использовать полученные данные при помощи Request Symfony используя библиотеку symfony-json-request-transformer (на самом деле всего-то один класс).

Идея

$http-сервис AngularJs автоматически отправляет данные с заголовком Content-Type: application/json в POST запросе, а Symfony в свою очередь ожидает application/x-www-form-urlencoded.

Для примера, отправим из нашего angular-приложения простой JSON-объект:
{
     "name": "John"
}

Теперь в контроллере получим эти данные:
public function postAction(Request $request)
{
    $data = json_decode($request->getContent(), true);
    echo $data['name']; // John
}

Довольно просто, верно? Но, к сожалению, мы не можем использовать ParameterBag интерфейс в объекте Request в данном случае.
Если name опционален и имеет значение по-умолчанию, хотелось бы получать данные вот так:
$name = $request->request->get('name', 'Ivan');

К счастью, используя метод replace мы можем заменить данные в ParameterBag на наш декодированный JSON.
public function postAction(Request $request)
{
    $data = json_decode($request->getContent(), true);
    $request->request->replace($data);

    echo $request->request->get('name', 'Ivan'); // John
}

Отлично, работает так, как нам хотелось. Но, ведь это только один контроллер…

Реализация

imageНо копирование кода в каждый контроллер нарушает DRY принцип, делая код мокрым (игра слов аббревиатур DRY и WET). Что если я скажу, что можно обрабатывать каждый JSON-запрос, совершенно не заботясь об этом? Используя обработчик событий, помеченный как kernel.event_listener, он:
  1. Проверит запрос на наличие заголовка Content-Type: application/json
  2. Если это так — декодирует его
  3. Заполнит объект Request::$request
  4. Вернет код ошибки HTTP 400 Bad Request, если что-то пошло не так


Можете увидеть полный код на Github.

Зарегистрировать обработчик события очень просто, просто определив новый сервис:
<service id="kernel.event_listener.json_request_transformer" class="Qandidate\Common\Symfony\HttpKernel\EventListener\JsonRequestTransformerListener">
    <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="100" />
</service>

Выход через сувенирную лавку

Чтобы показать приведенный код в действии, было создано демо-приложение, его код также на Github. На этом все, спасибо за внимание.
Поделиться публикацией

Похожие публикации

Комментарии 14

  • НЛО прилетело и опубликовало эту надпись здесь
      +2
      На то он и микрофреймворк. В Symfony другой уровень абстракции получается.
        +1
        Ну вообще можно сделать middleware для любых HttpKernel-based приложений и использовать что в Silex что в Symfony что в Laravel. Что до Silex, это конечно здорово, но только для небольших проектов подходит. Чем больше проект — тем больше бойлерплейт кода приходится использовать. Лично я отказался от идеи использовать маленький милый silex для REST и вернулся к Symfony.
        0
        В своем проекте написал транслятор симфонивской формы в json, в котором указываются типы полей и прочая муть + конечно же значения. А в ангуляре написал сервис, который этот json понимает и достает оттуда данные и конвертит обратно в application/x-www-form-urlencoded при сабмите. В итоге смог использовать старые описания форм + валидацию.
        По хорошему надо, что-то подобное, но сильно лайтовее. По модельке строить json с типами дынных + валидация.
          0
          В открытом доступе (на гитхаб, там) этого нет, посмотреть?
            +1
            Решение это временное и написано не очень. Но, если инетерсно, дома буду — оформлю в каком-нибудь виде.
            +2
            и конвертит обратно в application/x-www-form-urlencoded при сабмите

            Простите, но зачем? Это как бы считается плохой практикой. Не просто так как бы Angular не занимается такой чушью а шлет старый добрый JSON.

            Вообще для REST можно просто выкинуть symfony/form и оставить только jms serializer + symfony/validator. Выходит намного удобнее с десериализацией запросов в объекты, сразу же их можно валидировать и возвращать ошибки со статус кодом 400. В этом случае можно дописать обработчик ошибок для angularjs который будет все это добро обрабатывать корректно и реюзать из проекта в проект.
              +1
              Да практика плохая, но вот, то что я пытался использовать из досутпных бандлов не позволяло плавно переходить от стандарта form+twig к angular и rest. При этом ничего не переписывая.
                0
                Ну вот вы бы этот момент уточнили сразу. Хороший вариант перехода.
            +2
            Подобная штука есть в FOS rest bundle.

            Вообще с JSON запросами можно делать много вкусного, можно например десериализовать объекты напрямую в сущности (или в DTO) и передавать их с запросом, при этом существенно уменьшая объемы скучного и тупого кода. Словом много чего можно придумать забавного вооруживщись еще и JMS Serializer.
              –1
              Для того что бы не городить костыли в PHP проектах (не важно на каком фреймворке) достаточно указать content-type по умолчанию для всех post/put запросов отправляемых через $http:

              var app = angular.module('my_nifty_app', [...]).config(function($httpProvider) {
                  $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded';
                  $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
              });
                0
                маленькое НО, посылаете вы всеравно application/json, angular вроде как не умеет json в postdata конвертить, и к этим двум строчкам приходится еще и трансформер для запросов писать. И тут есть нюансы, так как в PHP том же например массивы немного по другому отрабатывают нежели в том же .NET. JSON в этом плане универсальнее, проще, да и не вижу никаких проблем с этим.
                  0
                  Используете jQuery.param(). Если не используете jQuery, то посмотрите как реализованна функция param() и скопируйте ее.
                    0
                    Да я знаю как это реализовывать, на этот случай у меня есть готовый трансформер который нужно только подключить. Мне лично как backend разработчика application/json полностью устраивает. Так же как это устраивает с точки зрения frontend. Если API не мое, то понятно, но лично мне удобнее на уровне обработки запроса сразу трансформировать json в сущности и работать уже с ними, еще больше упрощая код контроллеров и делая их максимально тонкими. Так как статья о Symfony и серверной части, то значит API наше и мы можем работать с JSON, так зачем тогда нам x-www-form-urlencoded?

                    Плюс angularjs в том что он предоставляет нам прозрачный сервис, который при помощи трансорматоров приводит запросы к нужному нам виду, и мы не заботимся об этом в коде нашего приложения. Плюс Symfony — абстракция над HTTP так же позволяющая нам изменять запросы по необходимости, что бы код приложения становился проще.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое