Post on 22-Feb-2017
QUI SUIS-JESys admin de formationProgrammeur depuis ~10 ansPropriétaire de elcweb.ca
Consultation en entrepriseProgrammationHébergement spécialiséFin de l'auto promotion
CONTEXTEvolution d'un projet "Legacy" sans affecter la productivité mais en
introduisant les bonne pratique d’un nouveau framework.
PAR LEGACY J'ENTENDpeu/pas de documentationpeu/pas de testsCode procéduralCode spaghettiDuplication de code (copier-coller)Include-ceptionCouplage de responsabilité... Non SOLID
OBJECTIFPermet 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.PHPUnitBehat
3 MÉTHODOLOGIES
1. PARALLEL
simple a implémenter (mod_rewrite)aucune communication direct entre les 2 applicationsutilisation de la BD ou Redis pour l'échange entre les 2 appspeu/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 Legacyauthentificationsé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 composanteun framework applicatifune philosophyune communauté
Symfony est a la base un framework HTTP
GESTION DES REQUÊTE / RÉPONSEsource: 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 namespacelogique basé sur include() / require()
LEGACY CONTROLLERnamespace AppBundle\Controller; 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"; }
RÉCAPITULATIONdéplacer les fichiers a l'extérieur du répertoire publiquevérifier si le module existsi le module n'existe pas, retourne une erreur 404encapsuler les "echo" du code legacy dans un objet Response
PROCHAINE ÉTAPESAuthentification/Autorisation (incluant la session)Isolation de la base de donnée (Repository)Vue (Templates)
AUTHENTIFICATION/AUTORISATIONAUTHENTIFICATION
Qui es-tu ?
AUTORISATIONQuels sont les accès / droits
MAINTENANT DANS SYMFONY
UTILISATEURImplement UserInterface
<?php
namespace AppBundle\Security\User;
use ...
class User implements UserInterface { private $username; private $password; private $salt prirate $roles;
... }
PROVIDEREst responsable d'aller chercher l'utilisateur
Implement UserProviderInterface
<?php
namespace AppBundle\Security\User;
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 = '...';
ENCODERResponsable d'encoder et de valider un mot de passe
<?php
namespace AppBundle\Security\Encoder;
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
class LegacyMd5Encoder extends BasePasswordEncoder { public function isPasswordValid($encoded, $raw, $salt = null) :bool { return $this->comparePasswords(strtolower($encoded), strtolower($this->encodePassword($raw, $salt))); }
/** * @param string $raw * @param null|string $salt * * @return string
CONFIGURATIONapp/config/services.yml
services: app.user_provider: class: AppBundle\Security\User\UserProvider app.security.encoder.md5: class: AppBundle\Security\Encoder\LegacyMd5Encoder
app/config/security.yml
security: encoders: AppBundle\Security\User\User: id: app.security.encoder.md5 providers: legacy: id: app.user_provider firewall: main: pattern: ̂/ http_basic: ~
RECOMMANDATIONUtilisation d'un algorithme plus sécuritaire comme bcrypt ou sha512
Convertion des mots de passes "on the fly"
DOCTRINEPermet de représenter en Objet et non en Base de donnée relationnel.
DOCTRINE / REPOSITORYDans le contexte de Doctrine, un Repository est utilisé pour allez chercher
l'information
Centraliser les requêtes SQLIsoler les requêtes du "controlleur"Classifier 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']).";"
$result = mysql_query($sql);
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
REPOSITORYnamespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
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 CONTROLLEURclass 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()); } }
QUESTIONS ?
MERCI!http://elcweb.cahttp://etiennelachance.com@elachancehttps://github.com/estheban