Retour

API versioning et rétrocompatibilité avec Symfony

Tuto
28 mars 2018
26mn

Progression
4

Ajout de la factory pour instancier les changements

A présent implémentons la classe Acme\VersionChanges\ChangesFactory.

Ajout de la classe

Créez donc le fichier suivant :

<?php namespace Acme\VersionChanges; use Symfony\Component\HttpFoundation\RequestStack; class ChangesFactory { /** * @var array */ private $versions; /** * @var RequestStack */ private $request; /** * @param array $versions * @param RequestStack $requestStack */ public function __construct(array $versions, RequestStack $requestStack) { $this->versions = $versions; $this->requestStack = $requestStack; $this->prepare(); } /** * @param string $version * * @return bool */ public function has($version) { return isset($this->versions[$version]); } /** * @param string $version * * @return AbstractVersionChanges|null */ public function get($version) { if (!$this->has($version)) { return; } return $this->versions[$version]; } }

Cette classe prend donc en entrée le tableau de versions déclaré en tant que parameters Symfony ainsi que le RequestStack que nous irons injecter dans nos fichiers d'application de changements de versions.

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

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

Cependant, les yeux les plus aguerris auront remarqué 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.

La méthode à ajouter est la suivante :

/** * Prepares class instances from class name. * * @throws \RuntimeException When version changes class does not exist or does not implement VersionChangesInterface. */ protected function prepare() { foreach ($this->versions as $version => $class) { if (!class_exists($class)) { 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; } }

Enfin, le listener implémenté dans l'étape précédente avait besoin d'une méthode getHistory($version) qui avait pour objectif de nous retourner les fichiers de changements de version (instanciés) à jouer en fonction de la version courante.

Nous ajoutons donc la méthode :

/** * Returns compatibility changes history for a given version. * * @param string $version * * @return array|null */ public function getHistory($version) { if (!$this->has($version)) { return; } $index = array_search($version, array_keys($this->versions)); return array_slice($this->versions, 0, $index + 1); }

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 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]); } }

Pour rappel, n'oubliez pas de vous assurer du comportement de vos méthodes en écrivant des tests unitaires.

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 :

acme.version.changes_factory: class: Acme\VersionChanges\ChangesFactory arguments: ["%versions%", "@request_stack"]

Prochaine étape

Notre structure est prête, il ne nous reste plus qu'à implémenter les fichiers de changements, dans l'étape suivante.


Articles sur le même thème

Comment créer de la dette technique dès le début d’un nouveau projet ?

Quand on arrive sur un projet existant, on doit souvent subir une dette technique qui nous fait perdre du temps et qui nous rend fou au point de vérifier qui a fait le code. Vous aussi vous voulez entrer dans la postérité lors d’un git blame et mal concevoir votre produit ?

9 août 20238mnMarianne Joseph-Géhannin

Découvrez Eleven Labs

Notre site pour mieux nous connaître

J'y vais

Contact

Eleven Labs - Paris

102, rue du Faubourg Saint Honoré

75008 Paris

Eleven Labs - Nantes

42, rue la Tour d'Auvergne

44200 Nantes

Eleven Labs - Montréal

1155, Metcalfe St Suite 1500

Montréal, QC H3B 2V6, Canada

business@eleven-labs.com

01.82.83.11.75