de legacy à symfony

Post on 22-Feb-2017

418 Views

Category:

Software

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

DE LEGACY À SYMFONYPHP QUÉBEC

3 MARS 2016Etienne Lachance

@elachance

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

top related