Blackfire
Migration progressive
Retour d'expérience - Veolys vs ProwebCE
Clément Bertillon
Quelle type de migration ?
- From Scratch
- Progressive
Problématiques
- Comment migrer une application existante vers Symfony3 dans les meilleures conditions
- Comment faire cohabiter 2 applicatifs différents sur un même projet pendant un laps de temps plus ou moins long
- Comment exploiter des fonctionnalités ou modules de l'ancien système avec le nouveau sans pénaliser l’applicatif
- Comment développer des cycles courts et un niveau d'efficacité optimum tout en conservant un code de qualité
D'une migration progressive
Veolys
Un outils de gestion d'ensemble immobilier
BPM
Backoffice HM et refonte du SI ProwebCE
L'existant
La nouvelle application
La (petite) liste de features veolys
L'existant
La nouvelle application
Statistiques
1er commit git- Gilles 2007
(une précédente version éxistait)
Legacy: 200 000 lignes de codes
Symfony3: 60 000 lignes de codes
500 routes hors webservice/api
Phpmetrics
Veolys : 61674 lignes
BPM : 61730 lignes
Migration de la stack
PHP 5.3
Apache 2.2
Mysql 5.5
Memcache
PHP 7
Apache 2.4
Percona Server - Mysql 5.7
Redis
Mentions honorables
symfony 1.0 (beta) avec vendor commité
Mot de passe en clair parce que c'est plus facile à lire/modifier pour le client
Un webservice en SOAP, avec un seul point d'entré qui accepte un fichier XML sans règles de validation
clement.bertillon - Francoisgueguen - Jules Pietri - Jérémy DERUSSE - Jérémy Derussé - Loïc Chardonnet - Mathieu MARCHOIS - Michel ROCA D'HUYTEZA - Olga ROTACH - PaskR - Quentin GUILLEMINEAU - Romain GAUTIER - Salah MEHARGA - Sylvain Jaune - alexandre.salome - fabien - fred - fx.deguillebon - geoffrey.bachelet - gilles - gregoire.hubert - gregoire.pineau - illusionOfParadise - inal.djafar - itkg-nanne - jeremy.derusse - julien.levasseur - loic.chardonnet - mRoca - mathieu.cavanne - maxime.douailin - pierre.cahard - romain.dorgueil - romain.gautier - roupen.torossian - sensio - simon.franquet - sofany.ong - thibaut.prim - vincent.dravert - xavier.briand - yohann.giarelli
git shortlog --summary
Christophe NGUYEN, Philippe MARQUINE, Stephane THEVENOUD, Philippe BEAUVAIS, Thibault DANTIGNY, Hugues GOBET, akeriel, bkaciousalah, Jonathan LEQUIN, Damien OGIER-DENIS, stephane thevenoud, Patrick SIMON, Adrien MADET, Antonin CLAUZIER, fmartin, Catherine GROUBON, Patrick CLOUZEAU, camille hernoux, Jonathan Lequin, François MARTIN-BROSSAT, Camille HERNOUX, Stephane BIZOLLON, ppailley, vmasia, kevin renaud, Ludovic LASQUELLEC, Amandine THERY, Adrien Madet, Jean-Jacques Parrenin, Ousmane KANTE, Hugues HG. GOBET, Jean Jacques Parrenin, marecauxg, Franck Sun, Christophe Nguyen, DIDIER UNGAR, gstorchi, Ousmane KANTE, Hugues Gobet, laurenta, Jean-Jacques PARRENIN, Damien LORTHIOIS, Francois MARTIN-BROSSAT, Antonin Clauzier, bialoruckio, Jean-Marie Le Bonniec (externe - smile), Yani Makouf (ext), Kevin RENAUD, traissardjm, Kévin ARBOUIN, Linda Bouzid, Boris FOURNIER, Maxime BERNARD, Zakaria Harrou, Fabien PIRON, Kevin ARBOUIN, Franck SUN, Christophe PYREE, Mehdi Tebourbi, Zakaria HARROU, llasquellec, Linda BOUZID, Thomas TALBOT, Mahmoud CHARFEDDINE, Jenkins HM, Ulas ATILA, asabbagh-e, Julien VANDROMME, Christophe DENIVET, Anis SABBAGH, Thibault Dantigny, anefoussi-e, Laetitia MONTEFERRARIO, perassia, Tayeb CHIKHI, Hakim BOUCHEMA, Kevin Renaud, Hugo Mallaroni-Cosentino, Yani Makouf, Ali CANSEV, agkerros, adminweb, Hugo MALLARONI-COSENTINO, Clement Bertillon, Antoine DI BARTOLOMEO, Boris Fournier, Jean Jacques Parrenin, Seifallah Azzabi, Di Bartolomeo, administrateur, Stéphanie SAINT-MARCEL, Alexandre VINET, Leo RIGER, Administrator, Stephane BEKALE, Thomas Talbot, Smaine Milianni, Emilie BAUDUIN, Seifallah AZZABI, Lara DUFOUR, clement.bertillon, cvs2svn, ddelot, Pascal BUISSON, Julien Manganne, Aroussi NEFOUSSI, Julien Maulny, pmichalet, tchikhi-e, Thibault, Yani MAKOUF, Charlie ARTO, Smaine MILIANNI, Badr HAKKARI, Safouane RKIK, Clement Passevant, Safouane REKIK, apaixao, Romain Gautier, root, ZaKaRiA, Christophe DEVINET, Stephane BEKALE, Fabien PAPET
Christophe NGUYEN, Philippe MARQUINE, Stephane THEVENOUD, Philippe BEAUVAIS, Thibault DANTIGNY, Hugues GOBET, akeriel, bkaciousalah, Jonathan LEQUIN, Damien OGIER-DENIS, stephane thevenoud, Patrick SIMON, Adrien MADET, Antonin CLAUZIER, fmartin, Catherine GROUBON, Patrick CLOUZEAU, camille hernoux, Jonathan Lequin, François MARTIN-BROSSAT, Camille HERNOUX, Stephane BIZOLLON, ppailley, vmasia, kevin renaud, Ludovic LASQUELLEC, Amandine THERY, Adrien Madet, Jean-Jacques Parrenin, Ousmane KANTE, Hugues HG. GOBET, Jean Jacques Parrenin, marecauxg, Franck Sun, Christophe Nguyen, DIDIER UNGAR, gstorchi, Ousmane KANTE, Hugues Gobet, laurenta, Jean-Jacques PARRENIN, Damien LORTHIOIS, Francois MARTIN-BROSSAT, Antonin Clauzier, bialoruckio, Jean-Marie Le Bonniec (externe - smile), Yani Makouf (ext), Kevin RENAUD, traissardjm, Kévin ARBOUIN, Linda Bouzid, Boris FOURNIER, Maxime BERNARD, Zakaria Harrou, Fabien PIRON, Kevin ARBOUIN, Franck SUN, Christophe PYREE, Mehdi Tebourbi, Zakaria HARROU, llasquellec, Linda BOUZID, Thomas TALBOT, Mahmoud CHARFEDDINE, Jenkins HM, Ulas ATILA, asabbagh-e, Julien VANDROMME, Christophe DENIVET, Anis SABBAGH, Thibault Dantigny, anefoussi-e, Laetitia MONTEFERRARIO, perassia, Tayeb CHIKHI, Hakim BOUCHEMA, Kevin Renaud, Hugo Mallaroni-Cosentino, Yani Makouf, Ali CANSEV, agkerros, adminweb, Hugo MALLARONI-COSENTINO, Clement Bertillon, Antoine DI BARTOLOMEO, Boris Fournier, Jean Jacques Parrenin, Seifallah Azzabi, Di Bartolomeo, administrateur, Stéphanie SAINT-MARCEL, Alexandre VINET, Leo RIGER, Administrator, Stephane BEKALE, Thomas Talbot, Smaine Milianni, Emilie BAUDUIN, Seifallah AZZABI, Lara DUFOUR, clement.bertillon, cvs2svn, ddelot, Pascal BUISSON, Julien Manganne, Aroussi NEFOUSSI, Julien Maulny, pmichalet, tchikhi-e, Thibault, Yani MAKOUF, Charlie ARTO, Smaine MILIANNI, Badr HAKKARI, Safouane RKIK, Clement Passevant, Safouane REKIK, apaixao, Romain Gautier, root, ZaKaRiA, Christophe DEVINET, Stephane BEKALE, Fabien PAPET
Christophe NGUYEN, Philippe MARQUINE, Stephane THEVENOUD, Philippe BEAUVAIS, Thibault DANTIGNY, Hugues GOBET, akeriel, bkaciousalah, Jonathan LEQUIN, Damien OGIER-DENIS, stephane thevenoud, Patrick SIMON, Adrien MADET, Antonin CLAUZIER, fmartin, Catherine GROUBON, Patrick CLOUZEAU, camille hernoux, Jonathan Lequin, François MARTIN-BROSSAT, Camille HERNOUX, Stephane BIZOLLON, ppailley, vmasia, kevin renaud, Ludovic LASQUELLEC, Amandine THERY, Adrien Madet, Jean-Jacques Parrenin, Ousmane KANTE, Hugues HG. GOBET, Jean Jacques Parrenin, marecauxg, Franck Sun, Christophe Nguyen, DIDIER UNGAR, gstorchi, Ousmane KANTE, Hugues Gobet, laurenta, Jean-Jacques PARRENIN, Damien LORTHIOIS, Francois MARTIN-BROSSAT, Antonin Clauzier, bialoruckio, Jean-Marie Le Bonniec (externe - smile), Yani Makouf (ext), Kevin RENAUD, traissardjm, Kévin ARBOUIN, Linda Bouzid, Boris FOURNIER, Maxime BERNARD, Zakaria Harrou, Fabien PIRON, Kevin ARBOUIN, Franck SUN, Christophe PYREE, Mehdi Tebourbi, Zakaria HARROU, llasquellec, Linda BOUZID, Thomas TALBOT, Mahmoud CHARFEDDINE, Jenkins HM, Ulas ATILA, asabbagh-e, Julien VANDROMME, Christophe DENIVET, Anis SABBAGH, Thibault Dantigny, anefoussi-e, Laetitia MONTEFERRARIO, perassia, Tayeb CHIKHI, Hakim BOUCHEMA, Kevin Renaud, Hugo Mallaroni-Cosentino, Yani Makouf, Ali CANSEV, agkerros, adminweb, Hugo MALLARONI-COSENTINO, Clement Bertillon, Antoine DI BARTOLOMEO, Boris Fournier, Jean Jacques Parrenin, Seifallah Azzabi, Di Bartolomeo, administrateur, Stéphanie SAINT-MARCEL, Alexandre VINET, Leo RIGER, Administrator, Stephane BEKALE, Thomas Talbot, Smaine Milianni, Emilie BAUDUIN, Seifallah AZZABI, Lara DUFOUR, clement.bertillon, cvs2svn, ddelot, Pascal BUISSON, Julien Manganne, Aroussi NEFOUSSI, Julien Maulny, pmichalet, tchikhi-e, Thibault, Yani MAKOUF, Charlie ARTO, Smaine MILIANNI, Badr HAKKARI, Safouane RKIK, Clement Passevant, Safouane REKIK, apaixao, Romain Gautier, root, ZaKaRiA, Christophe DEVINET, Stephane BEKALE, Fabien PAPET
Christophe NGUYEN, Philippe MARQUINE, Stephane THEVENOUD, Philippe BEAUVAIS, Thibault DANTIGNY, Hugues GOBET, akeriel, bkaciousalah, Jonathan LEQUIN, Damien OGIER-DENIS, stephane thevenoud, Patrick SIMON, Adrien MADET, Antonin CLAUZIER, fmartin, Catherine GROUBON, Patrick CLOUZEAU, camille hernoux, Jonathan Lequin, François MARTIN-BROSSAT, Camille HERNOUX, Stephane BIZOLLON, ppailley, vmasia, kevin renaud, Ludovic LASQUELLEC, Amandine THERY, Adrien Madet, Jean-Jacques Parrenin, Ousmane KANTE, Hugues HG. GOBET, Jean Jacques Parrenin, marecauxg, Franck Sun, Christophe Nguyen, DIDIER UNGAR, gstorchi, Ousmane KANTE, Hugues Gobet, laurenta, Jean-Jacques PARRENIN, Damien LORTHIOIS, Francois MARTIN-BROSSAT, Antonin Clauzier, bialoruckio, Jean-Marie Le Bonniec (externe - smile), Yani Makouf (ext), Kevin RENAUD, traissardjm, Kévin ARBOUIN, Linda Bouzid, Boris FOURNIER, Maxime BERNARD, Zakaria Harrou, Fabien PIRON, Kevin ARBOUIN, Franck SUN, Christophe PYREE, Mehdi Tebourbi, Zakaria HARROU, llasquellec, Linda BOUZID, Thomas TALBOT, Mahmoud CHARFEDDINE, Jenkins HM, Ulas ATILA, asabbagh-e, Julien VANDROMME, Christophe DENIVET, Anis SABBAGH, Thibault Dantigny, anefoussi-e, Laetitia MONTEFERRARIO, perassia, Tayeb CHIKHI, Hakim BOUCHEMA, Kevin Renaud, Hugo Mallaroni-Cosentino, Yani Makouf, Ali CANSEV, agkerros, adminweb, Hugo MALLARONI-COSENTINO, Clement Bertillon, Antoine DI BARTOLOMEO, Boris Fournier, Jean Jacques Parrenin, Seifallah Azzabi, Di Bartolomeo, administrateur, Stéphanie SAINT-MARCEL, Alexandre VINET, Leo RIGER, Administrator, Stephane BEKALE, Thomas Talbot, Smaine Milianni, Emilie BAUDUIN, Seifallah AZZABI, Lara DUFOUR, clement.bertillon, cvs2svn, ddelot, Pascal BUISSON, Julien Manganne, Aroussi NEFOUSSI, Julien Maulny, pmichalet, tchikhi-e, Thibault, Yani MAKOUF, Charlie ARTO, Smaine MILIANNI, Badr HAKKARI, Safouane RKIK, Clement Passevant, Safouane REKIK, apaixao, Romain Gautier, root, ZaKaRiA, Christophe DEVINET, Stephane BEKALE, Fabien PAPET
Workflow
Analyse
Audit d'architecture
Etude de migration
progressive
On garde un ensemble de contraintes qui vont faciliter la migration
- Les mêmes URL
- La même version de PHP
- Le même server
- Le même domaine
Documentation officiel
Problématiques
- Modèle
- Tests
- Cohabitation
- Sécurité
- Modèle
- Tests
- Cohabitation
- Sécurité
Cohabitation
Maintenir une application PHP tiers au sein d'une application Symfony
Legacy
<?php
// web/index.php
require_once __DIR__.'/../Symfony2/app/bootstrap.php.cache';
require_once __DIR__.'/../Symfony2/app/StaticKernel.php';
$staticKernel = StaticKernel::getInstance();
$request = Request::createFromGlobals();
$matcher = $staticKernel->getUrlMatcher($request);
try {
$attributes = $matcher->match($request->getPathInfo());
} catch (ResourceNotFoundException $e) {
chdir(__DIR__.'/../symfony1/web');
require_once __DIR__.'/../symfony1/web/index.php';
return;
} catch (MethodNotAllowedException $e) {
}
$kernel = $staticKernel->getKernel();
$kernel->loadClassCache();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
V1 : Passer par le front controller
Symfony2
Symfony2
symfony1
Inconvénients
- Si on tombe sur une route Symfony4, on doit appeler le routing à deux reprises
- Les erreurs client/serveur ne peuvent pas être controlé
- On ajoute pleins de règles spécifiques dans un script PHP qui ne resemble plus à rien
- On peut difficilement partager un context ou des informations entre notre nouvelle application et le legacy
Solution
C'est la nouvelle application qui prend la main et qui va encapsuler le legacy
Veolys-reboot
Symfony3
TheodoEvolutionLegacyWrapperBundle
Supporte: symfony1.4 / symfony1.5 / CodeIgniter
et custom kernel
Comment ça marche ?
Symfony4 cherche un controller
qui match la request
HTTP Request
Si aucune route Sf4, on passe la main au legacy pour faire le même travail
/**
* @param GetResponseEvent $event
*
* @return GetResponseEvent
*/
public function onKernelRequest(GetResponseEvent $event)
{
try {
$this->routerListener->onKernelRequest($event);
} catch (NotFoundHttpException $e) {
// Log ...
$response = $this->legacyKernel->handle($event->getRequest(),
$event->getRequestType(), true);
if ($response->getStatusCode() !== 404) {
$event->setResponse($response);
return $event;
}
}
}
Router Listener
class Symfony10Kernel extends LegacyKernel
{
/** @var ContainerInterface */
private $container;
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
{
$context = $this->setUpContext($request);
ob_start();
// Dispatch request
try {
$context->getController()->dispatch();
} catch (\sfStopException $e) {
} catch (\Exception $e) {
throw $e;
} finally {
$context->shutdown();
}
$stdout = ob_get_contents();
ob_end_clean();
return $this->convertResponse($context->getResponse(), $stdout);
}
// ...
}
Custom kernel Veolys
// \sfContext
Custom kernel BPM
class ProwebLegacyKernel extends LegacyKernel
{
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
{
// There is 3 type of legacy: Zend, homemade Framework and procedural php
$legacyType = $this->getLegacyType($request->getUri());
// Start the buffer and call the right legacy
ob_start();
switch ($legacyType) {
case self::LEGACY_TYPE_HOMEPAGE:
require_once $this->getRootDir().'/../legacy/hm.php';
break;
case self::LEGACY_TYPE_ZEND:
require_once $this->getRootDir().'/../legacy/hm2/public/index.php';
break;
case self::LEGACY_TYPE_HOMEMADE_FRAMEWORK:
require_once $this->getRootDir().'/../legacy/front.php';
break;
case self::LEGACY_TYPE_PROCEDURAL_PHP:
putenv('PHP_SELF=/'.implode('/', $pathParts));
require_once $this->getRootDir().'/../legacy/'.implode('/', $pathParts);
break;
}
$response = ob_get_clean();
return new Response($response, 200, $header);
}
}
# app/config/config.yml
theodo_evolution_legacy_wrapper:
root_dir: %kernel.root_dir%/../legacy
kernel:
id: legacy.kernel.symfony10
options:
application: frontend
environment: '%kernel.environment%'
debug: '%kernel.debug%'
Configuration
Authentification cross application
Lecture de session: TheodoEvolutionSessionBundle
Authentification:
PreAuthenticatedListener custom
Authentification géré par l'application legacy
TheodoEvolutionSessionBundle
Supporte: symfony1.x / CodeIgniter
class BagConfiguration implements BagManagerConfigurationInterface
{
private $namespaces = array(
BagManagerConfigurationInterface::LAST_REQUEST_NAMESPACE => 'symfony/user/sfUser/lastRequest',
BagManagerConfigurationInterface::AUTH_NAMESPACE => 'symfony/user/sfUser/authenticated',
BagManagerConfigurationInterface::CREDENTIAL_NAMESPACE => 'symfony/user/sfUser/credentials',
BagManagerConfigurationInterface::ATTRIBUTE_NAMESPACE => 'symfony/user/sfUser/attributes',
BagManagerConfigurationInterface::CULTURE_NAMESPACE => 'symfony/user/sfUser/culture',
);
/**
* {@inheritdoc}
*/
public function getNamespaces()
{
return $this->namespaces;
}
/**
* {@inheritdoc}
*/
public function getNamespace($key)
{
return $this->namespaces[$key];
}
}
PreAuthenticatedListener
namespace Veolys\LegacyBundle\Security\Firewall;
/**
* Retrieves the user ID from the symfony1 session.
*/
class Symfony1SessionListener extends AbstractPreAuthenticatedListener
{
/**
* Gets the user and credentials from the Request.
*/
protected function getPreAuthenticatedData(Request $request)
{
if (!$this->veolysSession->isAuthenticated()) {
// For call the entryPoint and redirect the user to /
throw new BadCredentialsException();
}
return [(string) $this->veolysSession->getUserId(), null];
}
}
Security
=
Authentication
+
???
Authorization
mysql> select id, name from veolys_permission;
+-----+-------------------------------------------------+
| id | name |
+-----+-------------------------------------------------+
| 1 | Building/Edit |
| 3 | HotlineRequest/View |
| 5 | HotlineRequest/ViewAll |
| 6 | HotlineRequest/Post |
| 7 | HotlineRequest/PostForAll |
| 8 | HotlineRequest/DetailsChangeDescription |
| 9 | HotlineRequest/DetailsDecideConclusion |
| 16 | HotlineRequest/IsDispatcher |
# ...
| 158 | HotlineRequest/DelayAlert |
| 159 | HotlineRequest/Indicators |
| 160 | HotlineRequest/Lots |
+-----+-------------------------------------------------+
149 rows in set (0.00 sec)
Permissions utilisateurs
namespace Veolys\LegacyBundle\Security\Authentication\Provider;
/**
* Retrieves the user and his rights from the database
*/
class Symfony1SessionProvider implements AuthenticationProviderInterface
{
public function authenticate(TokenInterface $token)
{
$username = $token->getUsername();
$user = $this->userProvider
->loadUserByUsername($username) // l'Id qui vient du PreAuthenticatedListener
;
if (!$user) {
throw new AuthenticationException('The symfony1 session authentication failed.');
}
$roles = $this->em
->getRepository('VeolysApplicationBundle:VeolysUser')
->getRoles($user)
;
$user->setRoles($roles);
$authenticatedToken = new PreAuthenticatedToken($user, null, 'veolys', $roles);
return $authenticatedToken;
}
}
AuthenticationProvider
public function getRoles($user)
{
$permissions = $this->getUserPermissions();
$roles = array_map(
function ($permission) {
preg_match_all('#((?:^|[A-Z])[a-z]+)/?#', $permission['name'], $matches);
$name = strtoupper(implode('_', $matches[1]));
return 'ROLE_'.$name;
},
$permissions
);
if ($user->isSuperAdmin()) {
$roles[] = 'ROLE_SUPER_ADMIN';
}
$roles[] = 'ROLE_USER';
return $roles;
}
HotlineRequest/Post => ROLE_HOTLINE_REQUEST_POST
/**
* @Route("/edit", name="i_love_veolys")
*
* @Security("has_role('ROLE_HOTLINE_REQUEST_POST')")
*/
public function editAction(Request $request): Response
{
}
Base legacy
Nouvelle base
class RoleDecoder
{
/**
* Encode an array to a string
* [
* ROLE_CE_LISTE_READ_ONLY,
* ROLE_GESTION_PASSERELLE_UPDATE,
* ROLE_GESTION_MARQUE_BLANCHE_UPDATE,
* ].
*
* "|ce_liste=c|gestion_passerelle=m|gestion_marque_blanche=m|"
*/
public function encode(array $roles): string
{
$legacyRight = self::BEGIN_LEGACY_PERMISSION;
foreach ($roles as $role) {
$rolesToAdd[] = $role;
foreach ($rolesToAdd as $roleToAdd) {
$roleToAdd = str_replace('ROLE_', '', $roleToAdd);
if (0 < strpos($roleToAdd, '_READ_ONLY')) {
$legacyRight .= self::formatterLegacyPermission($roleToAdd, '_READ_ONLY',
strtolower('C'));
} elseif (0 < strpos($roleToAdd, '_UPDATE')) {
$legacyRight .= self::formatterLegacyPermission($roleToAdd, '_UPDATE',
strtolower('M'));
}
}
}
return $legacyRight;
}
}
// Legacy
"|onglet_parametres_acances=m|ce_liste=c|"
// Symfony4
['ROLE_ONGLET_PARAMETRES_AVANCES_UPDATE', 'ROLE_CE_LISTE_READ_ONLY']
Cas d'étude
Première migration
La migration, par où commence-t-on ?
Scrum, mais pas forcément de SPECS
Première étape
Reverse engineering du legacy (symfony1)
Legacy - Type de notification
Legacy - Permissions utilisateurs
Deux options
- On migre tout maintenant
Estimation: il faudra {{ random(20) + 20 }} jours
+ 3 jours de récup
- On migre pas, enfin on migrera plus tard...
Solution
Pas de migration, on va appeler le legacy
// src/LegacyBundle/Kernel/Symfony10Kernel.php
class Symfony10Kernel extends LegacyKernel
{
public function handle(Request $request)
{
}
public function notify($object)
{
}
// ...
}
Comment ?
Deuxième étape
Identifier le code legacy que l'on souhaite appeler
// legacy/plugins/appVeolysHotlineModule/lib/model/HotlineRequest.php
public function notifyChange()
{
// ...
$broadcastList = $this->getOrCreateUserBroadcastList();
$broadcastList->notify($this)
}
// src/LegacyBundle/Kernel/Symfony10Kernel.php
class Symfony10Kernel extends LegacyKernel
{
/**
* Call the notify method from the legacy UserBroadCastList model.
*/
public function notify($objectSF4)
{
// Map the doctrine entity to the propel model
$legacyObject = $this->transformToLegacy($objectSF4);
// Call the symfony1 method to get a UserBroadcastList
$userBroadCastList = $legacyObject->getOrCreateUserBroadcastList();
// Call the legacy method notify
try {
$broadCastList->notify($legacyObject);
} catch (\Exception $e) {
}
}
// ...
}
Prototype
Workflow
namespace AppBundle\Event;
class NotifyObjectEvent extends Event
{
const NAME = 'veolys.notify_object';
protected $object;
public function __construct(NotifiableObjectInterface $object)
{
$this->object = $object;
}
public function getObject()
{
return $this->object;
}
}
Evènement
class NotifyObjectListener implements EventSubscriberInterface
{
/** @var LoggerInterface */
protected $logger;
/** @var Symfony10Kernel */
protected $kernel;
/** @var Request */
protected $request;
public function notify(NotifyObjectEvent $event)
{
$object = $event->getObject();
if (!$this->supports($object)) {
return;
}
$this->kernel->notify($this->request, $object);
$this->logger->notice('Legacy notification', [
'object' => get_class($object),
]);
}
public function supports($object): bool
{
return $object instanceof NotifiableObjectInterface;
}
}
EventListener
Fin
Bonus
// src/LegacyBundle/Kernel/Symfony10Kernel.php
class Symfony10Kernel extends LegacyKernel
{
/**
* Call the notify method from the legacy UserBroadCastList model.
*/
public function notify(Request $request, NotifiableObjectInterface $object)
{
// Map the doctrine entity to the propel model
$legacyObject = $this->transformToLegacy($object);
// Call the symfony1 method to get a UserBroadcastList
$userBroadCastList = $legacyObject->getOrCreateUserBroadcastList();
// Call the legacy method notify
try {
$broadCastList->notify($legacyObject);
} catch (\Exception $e) {
}
}
// ...
}
Prototype (rappel)
/** @var NotifiableObjectInterface $object */
$id = $object->getId();
$legacyRepositoryName = sprintf('%sPeer', (new \ReflectionClass($object))->getShortName());
if (!class_exists($legacyRepositoryName)) {
throw new \Exception(sprintf('Invalid legacy repository peer : %s', $legacyRepositoryName));
}
$legacyObject = $legacyRepositoryName::retrieveByPK($id);
if (empty($legacyObject)) {
throw new \Exception(sprintf('Can\'t found the object with id: %s and legacy repository peer: %s',
$id, $legacyRepositoryName));
}
if (!$legacyObject instanceof \Notifiable) {
throw new \Exception(sprintf('The object % should implement Notifiable interface from legacy',
get_class($legacyObject)));
}
// Call the method with Reflection Api because the method getOrCreateUserBroadcastList can be private...
$method = new \ReflectionMethod(get_class($legacyObject), 'getOrCreateUserBroadcastList');
$method->setAccessible(true);
/** @var \UserBroadcastList $broadCastList */
$broadCastList = $method->invoke($legacyObject);
public function notify(Request $request, NotifiableObjectInterface $object)
Passer de Doctrine à Propel
Migration progressive 2020 Updated
By skigun
Migration progressive 2020 Updated
- 374