Search code examples
jsonrestslimfile-type

Slim and different response type based on url


So I want to allow users to either request .xml or .json responses when they run a rest API request to the server. (much like twitter)

But I don't belive that the following way is the best way, as it means duplicate code, surely there is a better way to allow .xml or .json response.

 $app->get('/books/:id.xml', function ($id) use ($app) {
        $app->render('/xml/books.xml', array('id' => $id));
    });

 $app->get('/books/:id.json', function ($id) use ($app) {
        $app->render('json/books.json', array('id' => $id));
    });

OR

// Define app routes
$app->get('/hello/{name}.{type}', function ($request, $response, $args) {
    //return $response->write("Hello " . $args['name']);
    if($args['type'] == 'xml')
    {
      return 'this is xml';
    }
    var_dump(parse_url($_SERVER['REQUEST_URI']));

});

if anyone knows how to do this, that would be great.


Solution

  • Consider using value of Accept HTTP header of request instead of file extension in the end of the URI.

    I'd say using header is more reliable and more "proper" way to determine the format in which the data should be returned. URI should be used to point to specific resource. Accept header should be sent by client to tell you what format the data should be returned. Apart from being standard way to implement a RESTful service, it removes the headache of altering routes (like in your second example).

    If you agree to such implementation, there's an excellent library to solve your problem.

    It has lots of uses, and here is an example for your use case:

    <?php
    use Negotiation\Negotiator;
    use Psr\Http\Message\ServerRequestInterface as Request;
    use Psr\Http\Message\ResponseInterface as Response;
    
    class YourController
    {
        public function __construct(Negotiator $negotiator, DataProvider $someDataProvider)
        {
            $this->negotiator = $negotiator;
            $this->someDataProvider = $someDataProvider;
        }
    
        /**
         * Processing request.
         * 
         * We get data, then we use Negotiator to detect what format the requestor prefers.
         * Then we return data in requested format or in case format is not supported,
         * fall back to JSON.
         * 
         * 
         * @param Request $request 
         * @param Response $response 
         * @return Response
         */
        public function __invoke(Request $request, Response $response)
        {
            $data = $this->someDataProvider->getSomeData();
    
            $mediaType = $this->determineMediaType($request);
    
            switch ($mediaType) {
                case 'application/json':
                default:
            // $data = $data->asJson();
                    // transform data to JSON...
                    break;
                case 'application/xml':
            $data = $data->asXml();
                    // transform data to XML...
                    break;
            }
    
            // Write data to body of response
            $response->getBody()->write($data);
    
            // Set appropriate response header
            return $response->withHeader('Content-Type', $mediaType);
        }
    
        /**
         * Find preferred data format from Accept header.
         * 
         * Uses Negotiator to determine whether JSON or XML should be returned.
         *
         * @param Request $request
         * @return string
         */
        private function determineMediaType(Request $request)
        {
            $acceptHeader = $this->extractAcceptHeader($request);
            // Set list of "known" formats, i.e. formats that your service supports
            $known = ['application/json', 'application/xml'];
            // Let negotiator determine what format should be used
            $mediaType = $this->negotiator->getBest($acceptHeader, $known);
            if ($mediaType) {
                return $mediaType->getValue();
            } else {
                return 'application/json'; # if request has unexpected value of accept header, default to JSON
            }
        }
    
        /**
         * Extract Accept header value from Request object
         * @param Request $request
         * @return string
         */
        private function extractAcceptHeader(Request $request)
        {
            return $request->getHeaderLine('Accept');
        }
    }
    

    This class is an example of a callback to a route. Such implementation allows you to easily extend the list of supported formats without tampering routes.