Vous avez déjà travaillé avec de vieux projet PHP (3,4), du “include-ception” ou tout simplement un framework non PSR-0? Voici un retour sur les étapes employé dans différent cas de migration de “legacy” vers Symfony 2.
1. DE LEGACY À SYMFONY
PHP QUÉBEC
3 MARS 2016
Etienne Lachance
@elachance
2. QUI SUIS-JE
Sys admin de formation
Programmeur depuis ~10 ans
Propriétaire de elcweb.ca
Consultation en entreprise
Programmation
Hébergement spécialisé
Fin de l'auto promotion
3. CONTEXT
Evolution d'un projet "Legacy" sans affecter la productivité mais en
introduisant les bonne pratique d’un nouveau framework.
4. PAR LEGACY J'ENTEND
peu/pas de documentation
peu/pas de tests
Code procédural
Code spaghetti
Duplication de code (copier-coller)
Include-ception
Couplage de responsabilité
... Non SOLID
8. 1. PARALLEL
simple a implémenter (mod_rewrite)
aucune communication direct entre les 2 applications
utilisation de la BD ou Redis pour l'échange entre les 2 apps
peu/pas d'impacte sur l'application 1
9. 2. PROXY
l'utilisateur voie uniquement une application (Symfony)
necessite plus de travail pour la mise en place
"wrapper" pour les requêtes a l'application Legacy
authentification
sécurité entre les 2 applications
peu/pas d'impacte sur l'application Legacy
10. 3. INTÉGRATION
On veut changer la structure fondamental du code actuel.
une seule application
OPTION PRÉSENTÉ
11. QU'EST-CE QUE SYMFONY?
Symfony est *
une collection de composante
un framework applicatif
une philosophy
une communauté
Symfony est a la base un framework HTTP
GESTION DES REQUÊTE / RÉPONSE
source: http://symfony.com/what-is-symfony
16. EXEMPLE DE TYPE "INCLUDE-CEPTION"
<?php
// index.php
include("includes/common.php");
include("includes/config.php");
$mod = $_GET['mod'];
if ($mod=="" || !preg_match('/^[A-Za-z1-90_]+$/Ui',$mod))
$mod = "dashboard";
include ("modules/".$mod.".php");
aucun namespace
logique basé sur include() / require()
17. LEGACY CONTROLLER
namespace AppBundleController;
use ...;
class LegacyController extends Controller
{
/** @Route("/index.php") */
public function legacyAction()
{
// __DIR__ == 'src/AppBundle/Controller'
include __DIR__ . '/../includes/common.php';
include __DIR__ . '/../includes/config.php';
// @todo: renommé $mod pour $module
$mod = $_GET['mod'];
if ($mod=="" || !preg_match('/^[A-Za-z1-90_]+$/Ui', $mod)) {
$mod = "dashboard";
}
18. RÉCAPITULATION
déplacer les fichiers a l'extérieur du répertoire publique
vérifier si le module exist
si le module n'existe pas, retourne une erreur 404
encapsuler les "echo" du code legacy dans un objet Response
23. PROVIDER
Est responsable d'aller chercher l'utilisateur
Implement UserProviderInterface
<?php
namespace AppBundleSecurityUser;
use ...
class UserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
// aller chercher l'utilisateur
// dans la base de donnée, le webservice, ...
$userData = ...
// pretend it returns an array on success, false if there is no user
if ($userData) {
$password = '...';
24. ENCODER
Responsable d'encoder et de valider un mot de passe
<?php
namespace AppBundleSecurityEncoder;
use SymfonyComponentSecurityCoreEncoderBasePasswordEncoder;
class LegacyMd5Encoder extends BasePasswordEncoder
{
public function isPasswordValid($encoded, $raw, $salt = null) :bool
{
return $this->comparePasswords(strtolower($encoded), strtolower($this->encodePassword($raw, $
}
/**
* @param string $raw
* @param null|string $salt
*
* @return string
28. DOCTRINE / REPOSITORY
Dans le contexte de Doctrine, un Repository est utilisé pour allez chercher
l'information
Centraliser les requêtes SQL
Isoler les requêtes du "controlleur"
Classifier par context d'objet (Utilisateur/Produit/Client)
29. LEGACY
<?php
// ...
$conn = mysql_connect($db_host, $db_user, $db_password);
if (!$conn) {
echo "Unable to connect to DB: " . mysql_error();
exit;
}
if (!mysql_select_db($dbname)) {
echo "Unable to select mydbname: " . mysql_error();
exit;
}
$sql = "SELECT * FROM products WHERE category = ".mysql_real_escape_string($_GET['cat']).";"
$result = mysql_query($sql);
30. GÉNÉRATION D'ENTITÉ A PARTIR D'UNE BASE DE DONNÉ
EXISTANTE
$ php bin/console doctrine:mapping:import --force AcmeBlogBundle xml
$ php bin/console doctrine:mapping:convert annotation ./src
$ php bin/console doctrine:generate:entities AcmeBlogBundle
http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html
31. REPOSITORY
namespace AppBundleEntity;
use DoctrineORMEntityRepository;
class ProductRepository extends EntityRepository
{
public function findByCategory($categoryId)
{
$sql = "SELECT * FROM products WHERE category = ".mysql_real_escape_string($categoryId
$stmt = $this->getEntityManager()->getConnection()->prepare($sql);
$stmt->execute();
return $stmt->findAll();
}
}
RawSQLTrait: https://gist.github.com/estheban/3eae41271f6cf5f3180a
32. UTILISATION DANS UN CONTROLLEUR
class ProductController extends Controller
{
/**
* @Route("/product.php/category/{id}")
*/
public function productByCategory(Category $category)
{
// throw 404 si pas de Catégorie trouvée
$entityManager = $this->getDoctrine()->getManager();
return $entityManager
->getRepository("AppBundle:Product")
->findByCategory($category->getId());
}
}