Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge branch 'master' into api-versioning-symfony
  • Loading branch information
eko committed Mar 15, 2018
2 parents f316ffd + aa68203 commit bbf9cf6
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 9 deletions.
Expand Up @@ -14,14 +14,24 @@ L'objectif est alors de sortir en production notre nouvelle version, et que chaq

En revanche, si un client, lors de sa requête, spécifie une version (comme `1.1.0` par exemple), alors il doit continuer à récupérer le même modèle de données que précédemment.

D'un point de vu technique, notre API devra alors appliquer une transformation sur le modèle de sortie afin d'assurer la rétro-compatibilité sur cette version.
D'un point de vu technique, notre API devra alors appliquer une transformation sur le modèle de sortie afin d'assurer la rétro-compatibilité sur cette version. C'est vraiment la réponse de notre API qui sera versionnée.

# Gestion du numéro de version

Pour la suite de cet article, j'ai choisi de partir sur un numéro de version spécifié en header de requête, type `X-Accept-Version: 1.1.0`.

À vous de choisir ce qui vous conviendra le mieux mais je trouve la solution du header plus simple à maintenir et surtout, lorsque vous décidez de ne plus supporter une version, cela n'a pas d'impact sur les endpoints d'appel à votre API, vous renvoyez simplement la dernière version de votre API.

# Pré-requis

Avant de débuter l'implémentation technique, il vous faut disposer d'une instance Symfony. Vous pouvez vous rendre sur [http://symfony.com/download](http://symfony.com/download) pour installer une version de Symfony.

Cet article n'est pas spécifique à Symfony 4, cependant, si vous souhaitez installer cette dernière version, vous pouvez directement utiliser composer :

```
$ composer create-project symfony/skeleton api-versioning
```

# Prochaine étape

Une fois la logique claire, nous pouvons commencer à implémenter la configuration des changements en fonction du numéro de version dans notre application Symfony.
Expand Up @@ -17,6 +17,8 @@ parameters:

Nous spécifions une liste de la version la plus récente à la plus ancienne.

**Note** : La version actuelle (1.2.0) n'apparaît pas dans cette liste car il s'agit ici uniquement de la liste des versions sur lesquels nous souhaitons appliquer une rétro-compatibilité.

Les changements de rétro-compatibilité seront alors appliqués dans ce même ordre.

Ainsi, dans le cas ou un client ajoute un header `X-Accept-Version: 0.9.0` dans ses requêtes, alors, les changements de rétro-compatibilité des versions seront joués respectivement dans l'ordre `1.1.0`, `1.0.0` puis `0.9.0`.
Expand Down
Expand Up @@ -34,7 +34,8 @@ class VersionChangesListener
* @param RequestStack $requestStack
* @param ChangesFactory $changesFactory
*/
public function __construct(RequestStack $requestStack, ChangesFactory $changesFactory) {
public function __construct(RequestStack $requestStack, ChangesFactory $changesFactory)
{
$this->requestStack = $requestStack;
$this->changesFactory = $changesFactory;
}
Expand Down
Expand Up @@ -65,7 +65,7 @@ Cette classe prend donc en entrée le tableau de versions déclaré dans en tant

Notez que, par la suite, vous pourrez avoir besoin d'injecter Doctrine, par exemple, afin de récupérer des données en base de données, et pas simplement de les re-modeler.

Nous avons également écris deux méthodes `has($version)` et `get($version)` assez simples, pour retourner une version.
Nous avons également écrit deux méthodes `has($version)` et `get($version)` assez simples, pour retourner une version.

Cependant, les yeux les plus aguéris auront remarqués la présence dans le constructeur de l'appel à la méthode `prepare()` qui va nous permettre d'instancier les namespaces fournis dans la configuration en classes PHP utilisables.

Expand All @@ -74,8 +74,8 @@ La méthode à ajouter est la suivante :
```php
/**
* Prepares class instances from class name.
*
* @throws \RuntimeException When version changes class does not exist.
*
* @throws \RuntimeException When version changes class does not exist or does not implement VersionChangesInterface.
*/
protected function prepare()
{
Expand All @@ -84,6 +84,10 @@ La méthode à ajouter est la suivante :
throw new \RuntimeException(sprintf('Unable to find class "%s".', $class));
}

if (!$class instanceof VersionChangesInterface) {
throw new \RuntimeException(sprintf('Class "%s" does not implement VersionChangesInterface.', $class));
}

$instance = new $class($this->requestStack);

$this->versions[$version] = $instance;
Expand Down Expand Up @@ -117,6 +121,92 @@ Nous ajoutons donc la méthode :

Ainsi, dans le cas ou une version `1.0.0` est demandée, seuls les fichiers de changements `1.0.1` et `1.0.0` seront joués. Les versions précédentes tel que `0.0.9` seront ignorées.

Pour vous aider à mieux comprendre la façon dont cet historique de version est récupéré, voici comment serait testé unitairement (avec PHPUnit) cette méthode :

```php
<?php

namespace Tests\Acme\VersionChanges;

use Acme\VersionChanges\ChangesFactory;
use Symfony\Component\HttpFoundation\RequestStack;

class ChangesFactoryTest extends \PHPUnit_Framework_TestCase
{
/**
*
* @var array
*/
protected $versions;

/**
*
* @var RequestStack
*/
protected $requestStack;

/**
*
* @var ChangesFactory
*/
protected $changesFactory;

/**
* {@inheritdoc}
*/
protected function setUp()
{
$this->request = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')
->disableOriginalConstructor()
->getMock();

$this->versions = [
'1.1.0' => 'Acme\VersionChanges\VersionChange110',
'1.0.0' => 'Acme\VersionChanges\VersionChange100',
'0.9.0' => 'Acme\VersionChanges\VersionChange090',
'0.8.0' => 'Acme\VersionChanges\VersionChange080',
];

$this->changesFactory = new ChangesFactory($this->versions, $this->requestStack);
}

/**
* {@inheritdoc}
*/
protected function tearDown()
{
$this->request = null;
$this->versions = null;
$this->changesFactory = null;
}

/**
* Test getHistory() when version 1.1.0
*/
public function testGetHistoryWithVersion110()
{
$history = $this->versionChanges->getHistory('1.1.0');

$this->assertCount(1, $history);
$this->assertInstanceOf('Acme\VersionChanges\VersionChange110', $history[0]);
}

/**
* Test getHistory() when version 1.0.0
*/
public function testGetHistoryWithVersion100()
{
$history = $this->versionChanges->getHistory('1.0.0');

$this->assertCount(2, $history);
$this->assertInstanceOf('Acme\VersionChanges\VersionChange110', $history[0]);
$this->assertInstanceOf('Acme\VersionChanges\VersionChange100', $history[1]);
}
}
```

Aussi, pour rappel, même si nous n'écrivons pas de tests unitaires dans ce tutoriel, principalement afin d'en simplifier sa lecture, vous devriez en ajouter afin de vous assurer du comportement de vos méthodes.

# Ajout du service Symfony

Afin que ce service soit injecté par l'injection de dépendance de Symfony, nous devons également déclarer le service :
Expand Down
9 changes: 5 additions & 4 deletions src/assets/scss/objects/_course-page.scss
Expand Up @@ -52,7 +52,7 @@
width: em(70px, $context);
height: em(70px, $context);
.-current & {
background-color: #b8b8b8;
background-color: $brand-yellow;
}
}

Expand Down Expand Up @@ -86,9 +86,10 @@

&__button {
background-color: $brand-yellow;
border-radius: 50%;
width: em(110px);
height: em(110px);
border: none;
border-radius: 30%;
width: em(70px);
height: em(70px);
&[disabled] {
visibility: hidden;
}
Expand Down

0 comments on commit bbf9cf6

Please sign in to comment.