2017-10-21 15 views
2

Je suis en train de construire une API REST Zend Expressive 2 (JSON) et je souhaite directement la version de mon API. J'utilise Zend ServiceManager + FastRoute pour le routage.Zend Expressive 2 - API REST (JSON) - Versioning (en en-tête) - FastRoute?

J'ai trouvé ce liens utiles pour versioning API REST et décident d'utiliser versioning l'intérieur en-tête de la demande:

Question:

Comment mettre en œuvre versioning api; en détail le routage à l'action middleware; dans zend expressive 2? (En utilisant FastRoute)

tête Accept (API JSON avec la version):

Accept: application/vnd.api+json;version=2 

structure souhaitée (application):

/ 
config/ 
data/ 
public/ 
src/ 
    App/ 
     V1/ 
      Action/ 
        SomeResource.php  // <- in Version 1 
        ... 
     V2/ 
      Action/ 
        SomeResource.php  // <- in Version 2 
        ... 
     ... 
vendor/ 
... 

Mes fragments de code: (fonctionne la détection de version, mais comment acheminer ?)

pipeline.php

<?php 
// ... 
// The error handler should be the first (most outer) middleware to catch 
// all Exceptions. 
$app->pipe(ErrorHandler::class); 
$app->pipe(ContentTypeJsonApiVersioning::class); // <-- detect version work quite well 
$app->pipe(ServerUrlMiddleware::class); 

routes.php

<?php 
// ... 
// 
$app->route('/api/some-resource[/{id:\d+}]', 
    [ 
     Auth\Action\AuthAction::class, 
     Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware::class, 
     App\Action\SomeResourceAction::class 
    ], 
    ['GET', 'POST', 'PUT', 'DELETE'], 
    'api.route.name' 
); 

ContentTypeJsonApiVersioning.php

<?php 

namespace App\Middleware; 

use Fig\Http\Message\StatusCodeInterface; 
use Psr\Http\Message\ServerRequestInterface; 
use Psr\Http\Message\ResponseInterface; 


/** 
* Middleware to detect accept is JSON API (application/vnd.api+json) and separate version 
*/ 
class ContentTypeJsonApiVersioning 
{ 

    /** 
    * @const string 
    */ 
    const EXPECTED_TYPE_JSON_API = 'application/vnd.api+json'; 


    /** 
    * Execute the middleware. 
    * 
    * @param ServerRequestInterface $request 
    * @param ResponseInterface  $response 
    * @param callable    $next 
    * 
    * @throws \InvalidArgumentException 
    * 
    * @return ResponseInterface 
    */ 
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) 
    { 
     // error: return response with code: 415 
     $return  = $response->withStatus(StatusCodeInterface::STATUS_UNSUPPORTED_MEDIA_TYPE); 
     $acceptHeader = $request->getHeader('Accept'); 

     if (isset($acceptHeader[0])) { 

      $data = $this->_processAcceptHeader($acceptHeader[0]); 

      if (self::EXPECTED_TYPE_JSON_API === $data['accept']) { 

       // continue processing 
       $return = $next($request->withAttribute('version', $data['version']), $response); 
      } 
     } 

     return $return; 
    } 


    /** 
    * @param string $acceptHeader 
    * @return array 
    */ 
    protected function _processAcceptHeader(string $acceptHeader) : array 
    { 
     // expected: "application/vnd.api+json; version=2.1" 
     $data = \explode(';', $acceptHeader); 
     $return = [ 
      'accept' => $data[0], 
      'version' => '1' 
     ]; 

     // on 2 items, 2nd is version parameter 
     if (2 === \count($data)) { 

      // split: "version=2.1" to "2.1" 
      list(,$return['version']) = \explode('=', \trim($data[1])); 
     } 

     return $return; 
    } 

} 

Répondre

0

Qu'est-ce que FastRoute fait est de lancer une regex à l'adresse et l'analyser. Passer un attribut de requête ne fonctionne donc pas. Je peux penser à quelques façons de le faire fonctionner:

  • Ne pas utiliser le versionnement dans l'en-tête mais l'utiliser dans l'URL. Mais puisque vous le demandez spécifiquement, je suppose que ce n'est pas une option.
  • Réécrivez l'URL dans ContentTypeJsonApiVersioning et mettez à jour la demande avant qu'elle ne soit transmise au routeur. Donc, fondamentalement, réécrire à /api/v1/resource/id
  • Passez toutes les demandes api à APiMiddlewareAction et là, vérifiez la version qui est passée dans la demande et chargez l'action appropriée.

Dans le dernier cas, vous pouvez avoir un seul itinéraire semblable à:

[ 
    'name'   => 'api', 
    'path'   => '/api/{resource:\w+}[/{resourceId:\d+}[/{relation:\w+}[/{relationId:\d+}]]]', 
    'middleware'  => ApiMiddleware::class, 
    'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], 
    'options'   => [ 
     'defaults' => [ 
     ], 
    ], 
], 

Il y a probablement plus de solutions.