symfony_black_014

Symfony2: How to get json encoded request from Backbone.js

Once, during building small web application I’ve got an issue: How to get normally request data from backbone.js ?

On front-end I had marionette.js and on back-end Symfony2 application.

Thus,  in routing I had in /src/SST/UserBundle/Resources/config/routing.yml something like this:

  1. user.register:
  2.     path:       /user
  3.     defaults:   { _controller: UserBundle:Profile:register }
  4.     methods:    [POST]
user.register:
    path:       /user
    defaults:   { _controller: UserBundle:Profile:register }
    methods:    [POST]

My controller looked like:

  1. <?php
  2.  
  3. namespace SST\UserBundle\Controller;
  4.  
  5. use SST\MobAppBundle\Exceptions\EntityValidationException;
  6. use SST\MobAppBundle\Exceptions\ValidationException;
  7. use SST\UserBundle\Entity\User;
  8. use Symfony\Bundle\FrameworkBundle\Controller\Controller;
  9. use Symfony\Component\HttpFoundation\Request;
  10.  
  11. class ProfileController extends Controller
  12. {
  13.     public function registerAction(Request $request)
  14.     {
  15.         $user = (new User())
  16.             ->setEmail($request->get('email'))
  17.             ->setPassword($request->get('password'))
  18.             ->setUsername($request->get('username'))
  19.             ->setIsActive(User::inactive)
  20.        ;
  21.  
  22.        $validator = $this->get('validator');
  23.        $errors = $validator->validate($user);
  24.  
  25.       if($errors->count() > 0) throw new EntityValidationException($errors);
  26.       ......
<?php

namespace SST\UserBundle\Controller;

use SST\MobAppBundle\Exceptions\EntityValidationException;
use SST\MobAppBundle\Exceptions\ValidationException;
use SST\UserBundle\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class ProfileController extends Controller
{
    public function registerAction(Request $request)
    {
        $user = (new User())
            ->setEmail($request->get('email'))
            ->setPassword($request->get('password'))
            ->setUsername($request->get('username'))
            ->setIsActive(User::inactive)
       ;
 
       $validator = $this->get('validator');
       $errors = $validator->validate($user);
 
      if($errors->count() > 0) throw new EntityValidationException($errors);
      ......

I supposed this code should work, BUT I forgot one important point: Marionette.js, which uses models from Backbone.js sends saved data to server in JSON. Thus, in routing, restriction with POST passed successfully and its logical, but in registerAction I have empty email, username.

First idea, to make in controller something like:

  1.     public function registerAction(Request $request)
  2.     {
  3.         $requestData = json_decode($request->getContent(),true);
  4.         $user = (new User())
  5.             ->setEmail(isset($requestData['email']) ? $requestData['email'] : null)
  6.             ....
    public function registerAction(Request $request)
    {
        $requestData = json_decode($request->getContent(),true);
        $user = (new User())
            ->setEmail(isset($requestData['email']) ? $requestData['email'] : null)
            ....

Also I found also the same solutions on stackoverflow.com, BUT when I imagined, that this hardcode I have to add in all actions which I want to implement, I decided not to use this code — I really hate code copy-pasting: If you have to copy-paste code, it means in 99.99% cases that you are doing or thinking in incorrect way. Solution looks too ugly and if something is changed in backbone.js, or I decide to use the same API for applications, for example, which sends plain objects in request, I’ll have to add some other «dirty» hacks in my code in this case. I made more flexible and useful solution with Symfony requests listener.

How to solve this issue

The idea is very simple: if I have JSON in request, it means that I must have in request headers Content-Type='application/json'. If I have this headers, I must json_decode request body and inject in our Request object parsed data. If I do not have valid headers in request with «application/json», well…, its a problem of client side and I do not care about it…

Lets Implement:

Create Listener Service:

I keep all services in separate folder «Services». I already created simple version of «REST» service for my application, thus I have folder «Rest» in «Services». So, I had this path for my new service: src/SST/MobAppBundle/Services/Rest/RequestsListener.php . Surely you might have different paths, so DO NOT forget to change namespaces names. Lets add code in Listener:

  1. <?php
  2.  
  3. namespace SST\MobAppBundle\Services\Rest;
  4.  
  5.  
  6. use Symfony\Component\HttpKernel\Event\GetResponseEvent;
  7. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  8.  
  9. class RequestsListener
  10. {
  11.     public function onKernelRequest(GetResponseEvent $event)
  12.     {
  13.         $request     = $event->getRequest();
  14.         $contentType = $request->headers->get('content-type');
  15.        
  16.         switch($contentType)
  17.         {
  18.             case 'application/json':
  19.                 $params = json_decode($request->getContent(),true);
  20.                
  21.                 //maybe we had error in json_decode
  22.                 if($params === null)
  23.                 {
  24.                     $errorCode = json_last_error();
  25.                     if($errorCode !== JSON_ERROR_NONE)
  26.                     {
  27.                         throw new BadRequestHttpException(json_last_error_msg(),null,$errorCode);
  28.                     }
  29.                 }
  30.                
  31.                 //we have something in request
  32.                 if(is_array($params))
  33.                 {
  34.                     $request->request->add($params); //inject parsed data to request object
  35.                 }
  36.                 break;
  37.             //add here other content-Types handlers
  38.         }
  39.        
  40.     }
  41. }
<?php

namespace SST\MobAppBundle\Services\Rest;


use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class RequestsListener
{
    public function onKernelRequest(GetResponseEvent $event)
    {
        $request     = $event->getRequest();
        $contentType = $request->headers->get('content-type');
        
        switch($contentType)
        {
            case 'application/json':
                $params = json_decode($request->getContent(),true);
                
                //maybe we had error in json_decode
                if($params === null)
                {
                    $errorCode = json_last_error();
                    if($errorCode !== JSON_ERROR_NONE)
                    {
                        throw new BadRequestHttpException(json_last_error_msg(),null,$errorCode);
                    }
                }
                
                //we have something in request
                if(is_array($params))
                {
                    $request->request->add($params); //inject parsed data to request object
                }
                break;
            //add here other content-Types handlers
        }
        
    }
}

Register your service:

Open file with services in your Bundle, for me it was src/SST/MobAppBundle/Resources/config/services.yml And add there your new service:

  1. kernel.listener.onrequest:
  2.     class:                      SST\MobAppBundle\Services\Rest\RequestsListener
  3.     tags:
  4.         - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
kernel.listener.onrequest:
    class:                      SST\MobAppBundle\Services\Rest\RequestsListener
    tags:
        - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

Thats all. Now all requests with json encoded data and valid request headers will be correctly parsed with your listener before Action from Controller. In controller you can use Request object as you used it before.

Quite simple and can be easily modified. Of course you can add UnitTest to this service as I did, but I think it will be too much for this article. Basically we’ve managed to get response from frontend in normal way =).

It's only fair to share...Share on FacebookShare on Google+Tweet about this on TwitterEmail this to someoneShare on LinkedIn

Aboutalex

Вэб-программист. Занимаюсь разработкой cервисов, написанием API, вэб-приложений. Интересна разработка приложений для высоконагруженных систем, анализ данных..

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

14 − восемь =