SlideShare une entreprise Scribd logo
DE LEGACY À SYMFONY
SYMFONY MONTRÉAL
16 JUIN 2016
Etienne Lachance
@elachance
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
CONTEXT
Evolution d'un projet "Legacy" sans affecter la productivité mais en
introduisant les bonne pratique d’un nouveau framework.
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
OBJECTIF
Permet de refactoriser le code petit peu par petit peu mais d'avoir des
avantages rapidement.
ATTENTION!
IL EST FORTEMENT RECOMMANDÉ D’ÉCRIRE DES TESTS
AUTOMATISÉS.
PHPUnit
Behat
3 MÉTHODOLOGIES
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
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
authenti cation
sécurité entre les 2 applications
peu/pas d'impacte sur l'application Legacy
3. INTÉGRATION
On veut changer la structure fondamental du code actuel.
une seule application
OPTION PRÉSENTÉ
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
EXEMPLE
STRUCTURE DE FICHIER
BONNE PRATIQUE: PLACER LE CODE A L'EXTERIEUR DU RÉPERTOIRE PUBLIQUE.
STRUCTURE RÉVISÉ
MODIFIONS LE CODE
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()
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";
}
LEGACY CONTROLLER - SUITE
// Include module file
$filename = __DIR__ . '/../modules/' . $mod . '.php';
if (!file_exists($filename)) {
throw new NotFoundHttpException('Module ' . $mod . ' not found');
}
// ob_start: Turn on output buffering
ob_start();
include $filename;
return new Response(ob_get_clean());
}
}
RÉCAPITULATION
déplacer les chiers a l'extérieur du répertoire publique
véri er si le module exist
si le module n'existe pas, retourne une erreur 404
encapsuler les "echo" du code legacy dans un objet Response
PROCHAINE ÉTAPES
Authenti cation/Autorisation (incluant la session)
Isolation de la base de donnée (Repository)
Vue (Templates)
AUTHENTIFICATION/AUTORISATION
AUTHENTIFICATION
Qui es-tu ?
AUTORISATION
Quels sont les accès / droits
MAINTENANT DANS SYMFONY
UTILISATEUR
Implement UserInterface
<?php
namespace AppBundleSecurityUser;
use ...
class User implements UserInterface
{
private $username;
private $password;
private $salt
prirate $roles;
...
}
PROVIDER
Est responsable d'aller chercher l'utilisateur
Implement UserProviderInterface
namespace AppBundleSecurityUser;
use ...
class UserProvider implements UserProviderInterface
{
public function loadUserByUsername($username) { ... }
public function refreshUser(UserInterface $user) { ... }
public function supportsClass($class)
{
return $class === 'AppBundleSecurityUserUser';
}
}
ENCODER
Responsable d'encoder et de valider un mot de passe
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, $
}
public function encodePassword($raw, $salt = null) :string
{
return md5($raw . 'secret_global_a_l_application');
}
}
CONFIGURATION
app/con g/services.yml
services:
app.user_provider:
class: AppBundleSecurityUserUserProvider
app.security.encoder.md5:
class: AppBundleSecurityEncoderLegacyMd5Encoder
app/con g/security.yml
security:
encoders:
AppBundleSecurityUserUser:
id: app.security.encoder.md5
providers:
legacy:
id: app.user_provider
firewall:
main:
pattern: ^/
http_basic: ~
RECOMMANDATION
Utilisation d'un algorithme plus sécuritaire comme bcrypt ou sha512
Convertion des mots de passes "on the y"
DOCTRINE
Permet de représenter en Objet et non en tables.
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"
Classi er par context d'objet (Utilisateur/Produit/Client)
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']).";"
LEGACY - SUITE
$result = mysql_query($sql);
if (!$result) {
echo "Could not successfully run query ($sql) from DB: " . mysql_error();
exit;
}
if (mysql_num_rows($result) == 0) {
echo "No rows found, nothing to print so am exiting";
exit;
}
$data = array();
while ($row = mysql_fetch_assoc($result)) {
$data = $row;
}
mysql_free_result($result);
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
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
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());
}
}
Voir: @ParamConverter
TEMPLATES
Ne pas convertir les templates pour le "Fun"
On peut retourner:
une réponse directement (aucun template)
un template en php
un template en twig (natif)
un template en smarty
un template en ...
EXEMPLE D'UN CONTROLLEUR QUI RETOURNE UNE RÉPONSE
BASÉ SUR UN TEMPLATE
public function indexAction()
{
// some logic to retrieve the blogs
$blogs = ...;
$this->render(
'AcmeBlogBundle:Blog:index.html.twig',
array('blogs' => $blogs)
);
}
EXEMPLE D'UN TEMPLATE EN TWIG
{# app/Resources/views/blog/index.html.twig #}
{% extends 'blog/layout.html.twig' %}
{% block content %}
{% for blogPost in blogs %}
<h2>{{ blogPost.title }}</h2>
<p>{{ blogPost.body }}</p>
{% endfor %}
{% endblock %}
EXEMPLE DE SMARTY ET TWIG
protected function renderSmarty($template, $outputData)
{
$smarty = new SmartyEngine();
$smarty->assign($outputData);
return $this->render(
'@ElcwebLegacy/Default/default.html.twig',
['output' => $smarty->fetch($template)]
);
}
public function bobAction()
{
$outputData = ['foo' => 'bar'];
return $this->renderSmarty('bob.tpl', $outputData);
}
QUESTIONS ?
MERCI!
http://elcweb.ca
http://etiennelachance.com
@elachance
https://github.com/estheban
https://www.linkedin.com/in/elachance

Contenu connexe

PDF
Créer une barre de progression grâce à PHP 5.4
PDF
Pots de Miel, Honeypot informatique - Sécurité informatique
PDF
Php 2 - Approfondissement MySQL, PDO et MVC
PPT
Php mysql cours
 
PDF
Cours php & Mysql - 5éme partie
ODP
Formation PHP avancé - Cake PHP
PDF
Un exemple élémentaire d'application MVC en PHP
PDF
Programmation orientée objet en PHP 5
Créer une barre de progression grâce à PHP 5.4
Pots de Miel, Honeypot informatique - Sécurité informatique
Php 2 - Approfondissement MySQL, PDO et MVC
Php mysql cours
 
Cours php & Mysql - 5éme partie
Formation PHP avancé - Cake PHP
Un exemple élémentaire d'application MVC en PHP
Programmation orientée objet en PHP 5

Tendances (18)

PDF
Cours php & Mysql - 4éme partie
PDF
Trucs et astuces PHP et MySQL
PDF
Bases de PHP - Partie 1
PDF
Cours php & Mysql - 2éme partie
PDF
Examen principal- php - correction
PDF
Application web php5 html5 css3 bootstrap
PPTX
php2 : formulaire-session-PDO
PDF
Utilisation optimale et professionnelle de PHP
PPT
PHP5 - POO
ZIP
Epitech securite-2012.key
PDF
Cours php & Mysql - 3éme partie
PDF
Nouveautés php 7
PDF
PHP 7 et Symfony 3
PPTX
Php & My Sql
ODP
Formation PHP
PPT
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
ODP
Solution Linux 2009 - JavaScript
PDF
Qui a laissé son mot de passe dans le code
Cours php & Mysql - 4éme partie
Trucs et astuces PHP et MySQL
Bases de PHP - Partie 1
Cours php & Mysql - 2éme partie
Examen principal- php - correction
Application web php5 html5 css3 bootstrap
php2 : formulaire-session-PDO
Utilisation optimale et professionnelle de PHP
PHP5 - POO
Epitech securite-2012.key
Cours php & Mysql - 3éme partie
Nouveautés php 7
PHP 7 et Symfony 3
Php & My Sql
Formation PHP
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
Solution Linux 2009 - JavaScript
Qui a laissé son mot de passe dans le code
Publicité

Similaire à De legacy à symfony (20)

PDF
De Legacy à Symfony
PDF
Les bonnes pratiques de l'architecture en général
PPTX
PPTX
"Un module Prestashop, comment ca marche?"
PDF
Quelle place pour le framework Rails dans le développement d'application web
PPT
Soutenance Zend Framework vs Symfony
PDF
Java 9 modulo les modules devoxx fr 2017
PPT
Les principes de base de PHP
PPT
Php cours
DOCX
démonstration code source site web ecole.docx
PDF
Open close principle, on a dit étendre, pas extends !
PDF
Qualité logicielle
ODP
Patterns and OOP in PHP
PDF
Synchroniser ses applis simplement avec akeneo/batch
PPT
20111006 bonnes pratiques-gi_g_v1
PPTX
Retour d'expérience sur PowerShell
PDF
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
PPS
Comment écrire du code testable ?
PDF
Des tests modernes pour Drupal
PDF
Gitlab CI : Integration et Déploiement Continue
De Legacy à Symfony
Les bonnes pratiques de l'architecture en général
"Un module Prestashop, comment ca marche?"
Quelle place pour le framework Rails dans le développement d'application web
Soutenance Zend Framework vs Symfony
Java 9 modulo les modules devoxx fr 2017
Les principes de base de PHP
Php cours
démonstration code source site web ecole.docx
Open close principle, on a dit étendre, pas extends !
Qualité logicielle
Patterns and OOP in PHP
Synchroniser ses applis simplement avec akeneo/batch
20111006 bonnes pratiques-gi_g_v1
Retour d'expérience sur PowerShell
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
Comment écrire du code testable ?
Des tests modernes pour Drupal
Gitlab CI : Integration et Déploiement Continue
Publicité

De legacy à symfony

  • 1. DE LEGACY À SYMFONY SYMFONY MONTRÉAL 16 JUIN 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
  • 5. OBJECTIF Permet de refactoriser le code petit peu par petit peu mais d'avoir des avantages rapidement.
  • 6. ATTENTION! IL EST FORTEMENT RECOMMANDÉ D’ÉCRIRE DES TESTS AUTOMATISÉS. PHPUnit Behat
  • 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 authenti cation 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
  • 13. STRUCTURE DE FICHIER BONNE PRATIQUE: PLACER LE CODE A L'EXTERIEUR DU RÉPERTOIRE PUBLIQUE.
  • 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. LEGACY CONTROLLER - SUITE // Include module file $filename = __DIR__ . '/../modules/' . $mod . '.php'; if (!file_exists($filename)) { throw new NotFoundHttpException('Module ' . $mod . ' not found'); } // ob_start: Turn on output buffering ob_start(); include $filename; return new Response(ob_get_clean()); } }
  • 19. RÉCAPITULATION déplacer les chiers a l'extérieur du répertoire publique véri er si le module exist si le module n'existe pas, retourne une erreur 404 encapsuler les "echo" du code legacy dans un objet Response
  • 20. PROCHAINE ÉTAPES Authenti cation/Autorisation (incluant la session) Isolation de la base de donnée (Repository) Vue (Templates)
  • 23. UTILISATEUR Implement UserInterface <?php namespace AppBundleSecurityUser; use ... class User implements UserInterface { private $username; private $password; private $salt prirate $roles; ... }
  • 24. PROVIDER Est responsable d'aller chercher l'utilisateur Implement UserProviderInterface namespace AppBundleSecurityUser; use ... class UserProvider implements UserProviderInterface { public function loadUserByUsername($username) { ... } public function refreshUser(UserInterface $user) { ... } public function supportsClass($class) { return $class === 'AppBundleSecurityUserUser'; } }
  • 25. ENCODER Responsable d'encoder et de valider un mot de passe 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, $ } public function encodePassword($raw, $salt = null) :string { return md5($raw . 'secret_global_a_l_application'); } }
  • 26. CONFIGURATION app/con g/services.yml services: app.user_provider: class: AppBundleSecurityUserUserProvider app.security.encoder.md5: class: AppBundleSecurityEncoderLegacyMd5Encoder app/con g/security.yml security: encoders: AppBundleSecurityUserUser: id: app.security.encoder.md5 providers: legacy: id: app.user_provider firewall: main: pattern: ^/ http_basic: ~
  • 27. RECOMMANDATION Utilisation d'un algorithme plus sécuritaire comme bcrypt ou sha512 Convertion des mots de passes "on the y"
  • 28. DOCTRINE Permet de représenter en Objet et non en tables.
  • 29. 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" Classi er par context d'objet (Utilisateur/Produit/Client)
  • 30. 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']).";"
  • 31. LEGACY - SUITE $result = mysql_query($sql); if (!$result) { echo "Could not successfully run query ($sql) from DB: " . mysql_error(); exit; } if (mysql_num_rows($result) == 0) { echo "No rows found, nothing to print so am exiting"; exit; } $data = array(); while ($row = mysql_fetch_assoc($result)) { $data = $row; } mysql_free_result($result);
  • 32. 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
  • 33. 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
  • 34. 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()); } } Voir: @ParamConverter
  • 35. TEMPLATES Ne pas convertir les templates pour le "Fun" On peut retourner: une réponse directement (aucun template) un template en php un template en twig (natif) un template en smarty un template en ...
  • 36. EXEMPLE D'UN CONTROLLEUR QUI RETOURNE UNE RÉPONSE BASÉ SUR UN TEMPLATE public function indexAction() { // some logic to retrieve the blogs $blogs = ...; $this->render( 'AcmeBlogBundle:Blog:index.html.twig', array('blogs' => $blogs) ); }
  • 37. EXEMPLE D'UN TEMPLATE EN TWIG {# app/Resources/views/blog/index.html.twig #} {% extends 'blog/layout.html.twig' %} {% block content %} {% for blogPost in blogs %} <h2>{{ blogPost.title }}</h2> <p>{{ blogPost.body }}</p> {% endfor %} {% endblock %}
  • 38. EXEMPLE DE SMARTY ET TWIG protected function renderSmarty($template, $outputData) { $smarty = new SmartyEngine(); $smarty->assign($outputData); return $this->render( '@ElcwebLegacy/Default/default.html.twig', ['output' => $smarty->fetch($template)] ); } public function bobAction() { $outputData = ['foo' => 'bar']; return $this->renderSmarty('bob.tpl', $outputData); }