Best practice for client to choose response format (JSON / XML)

6

I would like to know the best coding practice that allows the client to define the response format for the request he made, which can also include filters, conditions, ordering, etc.

I made a small template for the answer, I do not know if it's the best practice, but it works (advice is welcome). I coded in the middleware after ();

PS: The format choice will be dynamic after building the request code.

Now about the requisition. I imagined coding in Middleware before (). What is the best way to do it?

Follow the code:

index.php

<?php

define('ROOT', dirname(__DIR__));
chdir(ROOT);

require 'vendor/autoload.php';
require 'src/Config/bootstrap.php';
require 'src/Config/routes.php';

$app->run();

bootstrap.php

<?php

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$app = new Application();

$app['serializer'] = function(){
    $encoders = array(new XmlEncoder(), new JsonEncoder());
    $normalizers = array(new ObjectNormalizer());
    return new Serializer($normalizers, $encoders);
};

$app['debug'] = true;

/*$app->before(function (Request $request) use ($app){
  $request->query->
});*/

$app->after(function (Request $request, Response $response) use ($app){
  //var_dump($response);
  $response->headers->set('Content-Type', 'application/xml');
  return $response;
});

return $app;

routes.php

<?php

$app->mount('/classificados', require 'src/App/Controllers/ClassificadosController.php');

ClassifiedsController.php

<?php

use Symfony\Component\HttpFoundation\Response;

$classificados = $app['controllers_factory'];

$classificados->get('/', function() use ($app) {
    $post = array(
        'title' => 'Titulo',
        'body'  => 'corpo',
    );

    $serializeContent = $app['serializer']->serialize($post, 'xml');
    return new Response($serializeContent, 200);
});

return $classificados;

What is the best way to build a logic to dynamize the response format (json or xml) for the client?

UPDATE

I refactored my code by @Guilherme Nascimento's response, I had the idea of always returning a json from Controller and after () if an xml was requested to deserialize the return and serialize to a new response in xml format, if it is requested a json would return the response itself without performing this procedure, with the intention of abstracting this procedure from each route? Is it too costly for the server?

It looks like this:

bootstrap.php

<?php

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$app = new Application();
$app['debug'] = true;

$app['formatSerialize'] = function($format){
  $app['formatSerialize'] = $format;
};

$app->before(function (Request $request) use ($app) {
  $app['formatSerialize'] = (array_key_exists('xml', $request->query->all()) == 1) ? 'xml' : 'json';
});

$app->after(function (Request $request, Response $response) use ($app){
  if ($app['formatSerialize'] == 'xml'){
    $serializer = new Serializer(array(new ObjectNormalizer()), array(new XmlEncoder(), new JsonEncoder()));

    $data = json_decode($response->getContent(), true);
    $serializeContent = $serializer->serialize($data, $app['formatSerialize']);

    $resp = new Response($serializeContent, $response->getStatusCode());
    $resp->headers->set('Content-Type', 'application/xml');
    return $resp;
  }
  $response->headers->set('Content-Type', 'application/json');
  return $response;
});

return $app;

ClassifiedsController.php

<?php

$classificados = $app['controllers_factory'];

$classificados->get('/', function() use ($app) {
    $post = array(
        'title' => 'Titulo',
        'body'  => 'corpo',
    );

    return $app->json($post, 200);
});

return $classificados;

Is this deserialization and re-serialization very costly?

    
asked by anonymous 16.01.2017 / 14:59

2 answers

4
Best practice I think "does not have" , depends a lot on the comfort of what you want to pass on to the client, however I recommend trying out some of these

  • You can define a route for each:

    $classificados->get('/xml', function() use ($app) {
    //...Coloque a execução para serializer como Xml aqui
    

    Json:

    $classificados->get('/json', function() use ($app) {
    //...Coloque a execução para serializer como Json aqui
    
  • Or you can use Accept: header, in this case it will depend on the client-side software to send the header Accept together with the request, for example Ajax, or a Mobile application, for direct access via browser will not work.

    To use just make sure your Ajax (if you use something like this), do this:

    xhr.open("GET", url, true);    // async
    xhr.setRequestHeader("Accept", "text/xml"); //Requisita XML
    xhr.onreadystatechange = OnStateChange;
    xhr.send(null);
    

    If it's a Mobile application that uses Java or C ++ (Android) or Object-c or Switft (iOS / Mac) will depend on how it does, however it's not hard to find, and in php it should do so: p>

    //$request se refere a classe 'Symfony\Component\HttpFoundation\Request'
    $contentType = $request->headers->get('Accept');
    
    if (strpos($contentType, 'application/json') === 0) {
        //Serialize Json
    } else if (strpos($contentType, '/xml') !== false) {
        //Serialize Xml
    }
    
  • Or you can even solve with a simple use of GET, something like http://site/?type=json or http://site/?type=xml :

    switch ($request->query->get('type')) {
        case 'json':
            //Serialize Json
        break;
        case 'xml':
            //Serialize Xml
        break;
    }
    
  • Or you can even create a route that simulates a file:

    //Acesse via http://site/api.xml
    $classificados->get('/api.xml', function() use ($app) {
        //...Coloque a execução para serializer como Xml aqui
    

    Json:

    //Acesse via http://site/api.json
    $classificados->get('/api.json', function() use ($app) {
       //...Coloque a execução para serializer como Json aqui
    

    Or you can also use something like:

    //Acesse via http://site/api.json ou http://site/api.xml
    $app->get('/api.{format}', function($format) use ($app) {
        switch ($format) {
            case 'json':
                //Serialize Json
            break;
            case 'xml':
                //Serialize Xml
            break;
        }
    }
    
  • 16.01.2017 / 16:04
    2

    According to Silex 2 Documentation , you can use the Silex\Application::view to set the data presentation configuration for the user.

    $app->view(function (array $controllerResult, Request $request) use ($app) {
    
        $acceptHeader = $request->headers->get('Accept');
    
        $bestFormat = $app['negotiator']->getBestFormat($acceptHeader, array('json', 'xml'));
    
        if ('json' === $bestFormat) {
            return new JsonResponse($controllerResult);
        }
    
        if ('xml' === $bestFormat) {
            return $app['serializer.xml']->renderResponse($controllerResult);
        }
    
        return $controllerResult;
    });
    

    This view method is a response interceptor. It will cause the response to be given to the user according to the Accept parameter in the headers, but you can customize it to do so via the GET parameter.

    I thought the way you did is not wrong, but since there is a specific method for dealing with presentation only, as shown above, I would choose to use it.

        
    14.04.2017 / 15:30