Refactoring de code sous symfony Fabien Potencier. Refactoring de code sous symfony Fabien Potencier



Documents pareils
PARTAGER UN ANNUAIRE COLLECTIF DE SIGNETS AVEC DEL.ICIO.US

Sécurité des applications web. Daniel Boteanu

L envoi d un formulaire par courriel. Configuration requise Mail Texte Mail HTML Check-list

calls.paris-neuroscience.fr Tutoriel pour Candidatures en ligne *** Online Applications Tutorial

Supervision et infrastructure - Accès aux applications JAVA. Document FAQ. Page: 1 / 9 Dernière mise à jour: 15/04/12 16:14

Instructions Mozilla Thunderbird Page 1

PUPPET. Romain Bélorgey IR3 Ingénieurs 2000

Alfstore workflow framework Spécification technique

Compléter le formulaire «Demande de participation» et l envoyer aux bureaux de SGC* à l adresse suivante :

OpenPaaS Le réseau social d'entreprise

Flexible Identity. authentification multi-facteurs. authentification sans token. Version 1.0. Copyright Orange Business Services mai 2014.

Génie Logiciel avec Ada. 4 février 2013

Gestion centralisée d un réseau de sites discrets. Nicolas JEAN

Tutoriel de formation SurveyMonkey

How to Login to Career Page

Application Form/ Formulaire de demande

Soon_AdvancedCache. Module Magento SOON. Rédacteur. Relecture & validation technique. Historique des révisions

arcopole Studio Annexe 4 Intégration LDAP et processus d authentification Site du programme arcopole :

Création d objet imbriqué sous PowerShell.

DOCUMENTATION - FRANCAIS... 2

Réinvestir les scénarios de test de la plate-forme d'apprentissage Moodle pour stimuler les échanges sur ces fonctionnalités

Gestion Electronique de Document (ECM/GED)

Projet de programmation (IK3) : TP n 1 Correction

Formulaire de candidature pour les bourses de mobilité internationale niveau Master/ Application Form for International Master Scholarship Programme

PHP 4 PARTIE : BASE DE DONNEES

Utiliser un proxy sous linux

«Cachez-moi cette page!»

Corrigé des exercices sur les références

Technologies du Web. Ludovic DENOYER - ludovic.denoyer@lip6.fr. Février 2014 UPMC

PHP et mysql. Code: php_mysql. Olivier Clavel - Daniel K. Schneider - Patrick Jermann - Vivian Synteta Version: 0.9 (modifié le 13/3/01 par VS)

Editing and managing Systems engineering processes at Snecma

PDO : PHP Data Object 1/13

Extended communication server 4.1 : VoIP SIP service- Administration

Quelques patterns pour la persistance des objets avec DAO DAO. Principe de base. Utilité des DTOs. Le modèle de conception DTO (Data Transfer Object)

Extension SSO Java. Cette note technique décrit la configuration et la mise en œuvre du filtre de custom SSO Java.

Logitech Tablet Keyboard for Windows 8, Windows RT and Android 3.0+ Setup Guide Guide d installation

OUTIL DE TRAVAIL COLLABORATIF

INF2015 Développement de logiciels dans un environnement Agile. Examen intra 20 février :30 à 20:30

Contents Windows

Imprimantes et partage réseau sous Samba avec authentification Active Directory

25 mars. Tutoriel sur Laravel. Préparé par : Lydiane Beaulne-Bélisle. Ceci est un tutorial qui montre comment débuter avec le Framework PHP Laravel.

Rafraichissement conditionné d'une page en.net

Introduction à ElasticSearch

Paris Airports - Web API Airports Path finding

Once the installation is complete, you can delete the temporary Zip files..

Instructions pour mettre à jour un HFFv2 v1.x.yy v2.0.00

Modélisation PHP Orientée Objet pour les Projets Modèle MVC (Modèle Vue Contrôleur) Mini Framework

Olivier Mondet

Construire un réseau social avec Symfony Xavier Lacot Clever Age. Symfony Live 11 et 12 juin 2009 Clever Age Xavier Lacot

La programmation orientée objet Gestion de Connexions HTTP Manipulation de fichiers Transmission des données PHP/MySQL. Le langage PHP (2)

lundi 3 août 2009 Choose your language What is Document Connection for Mac? Communautés Numériques L informatique à la portée du Grand Public

Sécurité Informatique. Description. Prérequis. Un petit test avant de commencer s embourber

Formulaire de candidature pour les bourses de mobilité internationale niveau Master/ Application Form for International Master Scholarship Program

DocOnline. Guide utilisateur

Configurer la supervision pour une base MS SQL Server Viadéis Services

Bases de données Oracle Virtual Private Database (VPD) pour la gestion des utilisateurs d applications

DOCUMENTATION - FRANCAIS... 2

Les Portfolios et Moodle Petit inventaire

Comment Accéder à des Bases de Données MySQL avec Windows lorqu'elles sont sur un Serveur Linux

AMENDMENT TO BILL 32 AMENDEMENT AU PROJET DE LOI 32

td3a correction session7az

PHOTO ROYAUME DE BELGIQUE /KINDOM OF BELGIUM /KONINKRIJK BELGIE. Données personnelles / personal data

VTP. LAN Switching and Wireless Chapitre 4

Guide d'installation rapide TFM-560X YO.13

Oauth : un protocole d'autorisation qui authentifie?

Utilisateur et administrateur

RAPPORT AUDIT SEO. Élaboré à l'attention de : Monsieur Greber Élaboré par : Cédric Peinado

Université de XY University of XY. Faculté XY Faculty of XY

ROYAUME DE BELGIQUE / KINGDOM OF BELGIUM / KONINKRIJK BELGIE

Export Permit (Steel Monitoring) Regulations. Règlement sur les licences d exportation (surveillance de l acier) CONSOLIDATION CODIFICATION

3 chapitre Groupe Eyrolles, 2008

Principales failles de sécurité des applications Web Principes, parades et bonnes pratiques de développement

Tous les logiciels cités dans ce document sont des marques déposées de leurs propriétaires respectifs

Projet Android (LI260) Cours 2

>>>>>>>>>> roger atasi <<<<<<<<<<<<<<< chez , boulevard Victor Hugo Clichy - Tél :

VXPERT SYSTEMES. CITRIX NETSCALER 10.1 et SMS PASSCODE 6.2. Guide d installation et de configuration pour Xenapp 6.5 avec SMS PASSCODE 6.

Guide de démarrage Intellipool Network Monitor

MapReduce. Nicolas Dugué M2 MIAGE Systèmes d information répartis

Bonnes Pratiques de Développement PHP. Pascal MARTIN SQLI Forum PHP 2009, Paris

HOWTO INSTALLATION DB2 et NSE (sous RedHat)

Guide Installation Serveur Extensive Testing

WordPress : principes et fonctionnement

SQL Parser XML Xquery : Approche de détection des injections SQL

Practice Direction. Class Proceedings

Lambda! Rémi Forax Univ Paris-Est Marne-la-Vallée

.NET - Classe de Log

Academic Project. B2- Web Development. Resit Project. Version 1.0 Last update: 24/05/2013 Use: Students Author: Samuel CUELLA

The new consumables catalogue from Medisoft is now updated. Please discover this full overview of all our consumables available to you.

TP JAVASCRIPT OMI4 TP5 SRC

SPECIFICATIONS TECHNIQUES : Gestion des Médicaments et des commandes de médicaments

Entity API. Alexandre Todorov, Felip Manyer i Ballester. Montpellier, le 17 septembre 2014

S7 Le top 10 des raisons d utiliser PHP pour moderniser votre existant IBM i

MELTING POTES, LA SECTION INTERNATIONALE DU BELLASSO (Association étudiante de lʼensaparis-belleville) PRESENTE :

Module pour la solution e-commerce Magento

Projet 2. Gestion des services enseignants CENTRE D ENSEIGNEMENT ET DE RECHERCHE EN INFORMATIQUE. G r o u p e :

Un ordonnanceur stupide

L installation a quelque peu changée depuis les derniers tutos, voici une actualisation.

ETABLISSEMENT D ENSEIGNEMENT OU ORGANISME DE FORMATION / UNIVERSITY OR COLLEGE:

Transcription:

Refactoring de code sous symfony Fabien Potencier

C est quoi le refactoring?

Le réusinage Ok, refactoring, c est le terme anglais Refactorisation, c est le terme français apparemment

Le réusinage hmmm, Wikipedia suggère même «réusinage»! Je pense que je vais garder le mot anglais ;)

Le réusinage «Consiste à retravailler le code source non pas pour ajouter une fonctionnalité supplémentaire mais pour améliorer sa lisibilité, simplifier sa maintenance, ou changer sa généricité.»

Les applications cibles

Avant de commencer Applications Open-Source Tout le monde pourra refaire l exercice

Avant de commencer Attention, il n est pas question de critiquer ces applications, mais bien d essayer de les améliorer (but pédagogique) Ces applications sont globalement bien écrites Je transmettrais à chaque projet les conseils de ce refactoring

Siwapp Aucune application n est parfaite jamais même les miennes hmmm surtout les miennes

Siwapp http://www.siwapp.org/ «Free online invoice system» Licence MIT symfony 1.2.7 Propel

Ullright http://www.ullright.org/ «a framework and application suite providing helpful tools to support workflows and company-internal organisation in general» Licence GPL symfony 1.2.7 Doctrine

Juste pour rire

Ullright // in plugins/ullflowplugin/modules/ullflow/lib/baseullflowactions.class.php //.. // / `..' \ //.---. < > < >.---. // \ \ - ~ ~ - / / // ~-..-~ ~-..-~ // \~~~\.' `./~~~/ //.-~~^-. \ / \ / //.' O \ / / \ \ // (, `._.' \/~~~/ // `----. / / \ / // `-. / / `.,~~ // ~-. /_ - ~ ^ /- _ `..-' f: f: // / / ~-. `-. _ _ _ // ~ -. _>

Ullright // in plugins/ullflowplugin/modules/ullflow/lib/baseullflowactions.class.php // // ( \( \( )( )( \ \ / // ( \/ ( \/ ( ) ( ) ( \/ ) ( // ( ( ( ) ( ) ( ) // ( ) ) ) // ) ( ( ) (\ ( ( ) // /\ ) ( /\ ) ( ) \ \ ( /\ ) ( // \ )( / / \ / \ /( / / \

SteerCMS // in plugins/steercmsfoundationplugin/modules/steercmsauth/actions/actions.class.php /* * ============ * Please Note: * ============ * * That this code is acting as a proxy module to the awesome sfguardplugin. * We do this to provide some elegant over-rides and customizations. * A big thanks goes out to the developers of that great plugin :) * * http://trac.symfony-project.com/wiki/sfguardplugin */

C est parti

Siwapp Avant de commencer le refactoring, lançons les tests

Des tests? Pour quoi faire? Pourquoi? Refactoriser signifie qu on va déplacer et réécrire du code donc potentiellement introduire des régressions Les tests donnent la confiance nécessaire pour refactoriser sans crainte

pour qu ils passent Etats des lieux Bonne nouvelle : L application a des tests mais très peu Problème : ils ne passent pas vraiment

avoir confiance Il vaut mieux n avoir aucun test que des tests non maintenus

avoir confiance perte de temps pour les écrire faux sentiment de confiance et de robustesse juste pour la bonne conscience?

si on les maintient Problème 1 : Propel.php n est pas inclus, les fixtures ne sont donc pas chargées Problème 2 : Ils ne sont pas mis à jour au fur et à mesure http://dev.siwapp.org/projects/siwapp/changeset/572 Refactoring des CSS mais les tests n ont pas suivis #num-balance changé en #dashboard-balance-total mais pas dans les tests checkresponseelement('#num-balance', '273,029.83')->

Ne jamais écrire trop de code $b-> get('/login')-> isstatuscode(401)-> isrequestparameter('module', 'sfguardauth')-> isrequestparameter('action', 'signin'); $dom = $b->getresponsedom(); $token = $dom->getelementsbytagname('input')->item(0)->getattribute('value'); $signin = array( 'username' => 'test', 'password' => 'test', '_csrf_token' => $token ); $b-> click('signin', array('signin' => $signin))-> isredirected()-> followredirect();

Ne jamais écrire trop de code $b-> get('/login')-> isstatuscode(401)-> isrequestparameter('module', 'sfguardauth')-> isrequestparameter('action', 'signin'); $signin = array('username' => 'test', 'password' => 'test'); $b-> click('signin', array('signin' => $signin))-> isredirected()-> followredirect();

Ne jamais écrire trop de code

le refactoring ultime est la suppression Oui, un script vide suffit Pourquoi tester sfguard?

OOP en PHP de A à Z // test/functional/siwapp/configurationactionstest.php $browser = new sftestbrowser(); signin($browser)->get('settings')-> isrequestparameter('module', 'configuration')-> isrequestparameter('action', 'settings')-> isstatuscode(200) ;

OOP en PHP de A à Z class SiwappBrowser extends sftestbrowser public function signin($username = 'test', $password = 'test') $signin = array('username' => $username, 'password' => $password); return $this-> get('/login')-> info(sprintf('signin user using username "%s" and password "%s"', $username, $pas click('signin', array('signin' => $signin))-> isredirected()-> followredirect() ;

Tester votre application $item = new InvoiceItem(); $item->setunitarycost(1234.214); $t->is($item->getunitarycost(), '1234.21', '->getunitarycost() rounds'); $item->setunitarycost(1234.216); $t->is($item->getunitarycost(), '1234.22', '->getunitarycost() rounds'); $item->setquantity(3); $t->is($item->getbaseamount(), 1234.22 * 3, '->getbaseamount()');

Tester votre application // first test if values on bbdd are ok $t->is($invoice->getbase(), 7198.85, '->getbase()'); $t->is($invoice->getdiscount(), 0, '->getdiscount()'); $t->is($invoice->getnet(), 7198.85, '->getnet()'); $t->is($invoice->gettaxes(), 1411.83, '->gettaxes()'); $t->is($invoice->getgross(), 8610.68, '->getgross()'); // reset calculated values, and recalculate $invoice->setbase(0); $invoice->setdiscount(0); $invoice->setnet(0); $invoice->settaxes(0); $invoice->setgross(0); $invoice->calculatetotals(); $t->is($invoice->getbase(), 7198.85, '->getbase()'); $t->is($invoice->getdiscount(), 0, '->getdiscount()'); $t->is($invoice->getnet(), 7198.85, '->getnet()'); $t->is($invoice->gettaxes(), 1411.83, '->gettaxes()'); $t->is($invoice->getgross(), 8610.68, '->getgross()');

La classe utilisateur

Masquer l implémentation class searchactions extends sfactions public function executeajaxtagstab($request) $showtags =!$this->getuser()->getattribute('showtags', false); $this->getuser()->setattribute('showtags', $showtags); return sfview::none;

Mettre le code à sa place public function executeajaxtagstab($request) $this->getuser()->toggletagcloud(); return sfview::none; class SiwappUser extends sfguardsecurityuser public function toogletagcloud() $this->setattribute('showtags',!$this->getattribute('showtags')); public function istagcloudvisible() return $this->getattribute('showtags', false);

pour définir une interface Créez et utilisez une interface publique, documentée et testée

Mettre le code à sa place public function executeform(sfwebrequest $request) $searchparams = $this->getuser()->getattribute('search'); //$this->getrequest()->getparameterholder()->set('page', 1); if (is_null($searchparams)) $usersearchfilter = $this->getuser()->getattribute('searchfilter', 'last_week'); $searchparams = array('quick_dates' => $usersearchfilter, 'tags' => ''); $this->form = new SearchForm($searchParams); $this->selected_tags = explode(',', $searchparams['tags']); $c = new Criteria(); $c->addascendingorderbycolumn(tagpeer::name); $this->tags = TagPeer::getAll($c); $this->showtags = $this->getuser()->getattribute('showtags', false);

notamment vers le modèle public function executeform(sfwebrequest $request) $searchparams = $this->getuser()->getcurrentseachparameters(); $this->form = new SearchForm($searchParams); $this->selected_tags = explode(',', $searchparams['tags']); $this->tags = TagPeer::getAll(); $this->showtags = $this->getuser()->istagcloudvisible();

déplacer vers le modèle public function executeform(sfwebrequest $request) $this->form = new SearchForm($this->getUser()->getCurrentSeachParameters()) $this->tags = TagPeer::getAll();

déplacer vers le modèle // plugins/steercmsfoundationplugin/modules/steercmsbookmark/actions/actions.class.php public function executedelete($bookmark) $c = new Criteria(); $c->add(steercmsbookmarkpeer::id, $this->getrequestparameter('id')); $c->add(steercmsbookmarkpeer::user_id, $this->getuser()->getguarduser()->getid()); $b = steercmsbookmarkpeer::doselectone($c); $b->delete(); exit;

déplacer vers le modèle public function executedelete($bookmark) if ($bk = steercmsbookmarkpeer::retrievebypk($this->getrequestparameter('id'))) if ($bk->getsfguarduser()!= $this->getuser()->getguarduser()) throw new Exception('You cannot delete this bookmark'); $bk->delete();

déplacer vers le modèle public function executedelete($bookmark) steercmsbookmarkpeer::deleteforuser($this->getrequestparameter('id'), $this->getuser( static public function deleteforuser($id, sfguarduser $user) $c = new Criteria(); $c->add(steercmsbookmarkpeer::id, $id); $c->add(steercmsbookmarkpeer::user_id, $user->getid()); if ($b = steercmsbookmarkpeer::doselectone($c)) $b->delete();

pour définir une interface Le contrôleur fait régime Le modèle est gourmand

pour définir une interface Passez du temps pour définir le nom de vos classes et méthodes

Réfléchir à la bonne couche class SearchFilter extends sffilter public function execute($filterchain) $request = $this->getcontext()->getrequest(); $user = $this->getcontext()->getuser(); $search_has_changed = false; if ($search = $request->getparameter('search')) if($user->getattribute('search')!= $search) $search_has_changed = true; $user->setattribute('search', $search); $prefix = substr($request->getpathinfo(), 1); if ($sort = $request->getparameter('sort')) $sort_array = array($sort, $request->getparameter('sort_type')); if($user->getattribute($prefix.'.sort')!= $sort_array) $search_has_changed = true; $user->setattribute($prefix.'.sort', $sort_array); if ($status = $request->getparameter('status')) if($user->getattribute($prefix.'.status')!= $status) $search_has_changed = true; $user->setattribute($prefix.'.status', $status); // at last we set the page. If the search has changed we reset page to 1 if ($search_has_changed) $request->setparameter('page', 1); if ($page = $request->getparameter('page')) $user->setattribute($prefix.'.page', $page); $filterchain->execute();

Réfléchir à la bonne couche class SearchFilter extends sffilter public function execute($filterchain) $this->context->getuser()->updatesearch($this->context->getrequest()); $filterchain->execute();

Réfléchir à la bonne couche public function updatesearch(sfwebrequest $request) $updated = false; $prefix = substr($request->getpathinfo(), 1); if (($search = $request->getparameter('search'))!= $this->getattribute('search')) $updated = true; $this->setattribute('search', $search); if ($sort = $request->getparameter('sort')) $sort_array = array($sort, $request->getparameter('sort_type')); if ($this->getattribute($prefix.'.sort')!= $sort_array) $updated = true; $this->setattribute($prefix.'.sort', $sort_array); if (($status = $request->getparameter('status'))!= $this->getattribute($prefix.'.status')) $updated = true; $this->setattribute($prefix.'.status', $status); if ($updated) $request->setparameter('page', 1); $this->setattribute($prefix.'.page', $request->getparameter('page', 1));

et le code devient testable include_once dirname( FILE ).'/../bootstrap/unit.php'; include_once sfconfig::get('sf_root_dir').'/apps/siwapp/lib/siwappuser.class.php'; $t = new lime_test(3, new lime_output_color()); class SiwappRequest extends sfwebrequest public function getpathinfo() return '/test'; $dispatcher = new sfeventdispatcher(); $request = new SiwappRequest($dispatcher); $user = new SiwappUser($dispatcher, new sfsessionteststorage(array('session_path' => '/tmp/'))); // ->updatesearch() $t->diag('->updatesearch()'); $user->updatesearch($request); $t->is($user->getattribute('test.page'), 1, '->updatesearch() sets the page to 1 if no search is given'); $request->setparameter('page', 2); $user->updatesearch($request); $t->is($user->getattribute('test.page'), 2, '->updatesearch() sets the page to request page parameter'); $request->setparameter('search', 'foo'); $user->updatesearch($request); $t->is($user->getattribute('test.page'), 1, '->updatesearch() resets the page to 1 if the search changes');

pour définir une interface Passez du temps pour définir le nom de vos classes et méthodes

Utilisez l API existante de symfony $mailbody = sfcontext::getinstance()->getcontroller()->getpresentationfor('print', 'InvoicePage'); $mailbody = $this->getcontext()->getcontroller()->getpresentationfor('print', 'InvoicePage'); $mailbody = $this->getcontroller()->getpresentationfor('print', 'InvoicePage');

Utilisez l API existante de symfony public function executehtml($request) $response = $this->getcontext()->getresponse(); $ids = (array) $request->getparameter('id'); $content = array(); $page = 0; foreach($ids as $id) $content[] = $this->getcontent($id, ++$page); $response->setcontent($this->decoratehtml(implode("\n", $content), $this->getdocumenttitle($ids), return sfview::none; private function decoratehtml($html, $title = null, $printdialog = false) return $this->getpartial('print/head', array('title' => $title, 'printdialog' => $printdialog)).$html.$this->getpartial('print/foot');

pour me faire plaisir ;) A quoi ça sert que Fabien se décarcasse?

Supprimer le code mort // plugins/ullventoryplugin/modules/ullventory/lib/baseullventoryactions.class.php! public function executeitemmodelsbymanufacturer($request)!! // var_dump($request->getparameterholder()->getall());! // $this->getresponse()->setcontenttype( application/json );! // $authors = DemoAuthorPeer::retrieveForSelect($request->getParameter( q ), $request->getpa $q = new Doctrine_Query;! $q! ->select( mo.id, mo.name )! ->from( UllVentoryItemModel mo )! ;! if ($id = $request->getparameter( ull_ventory_item_manufacturer_id ))!! $q->where( mo.ull_ventory_item_manufacturer_id =?,$request->getparameter( ull_ventory_i! // printquery($q->getquery());! // var_dump($q->getparams());! $result = $q->execute(array(), Doctrine::HYDRATE_ARRAY);! $models = array();! foreach ($result as $values)!! // $models[$values[ id ]] = $values[ name ];! $models[] = array( id => $values[ id ], name => $values[ name ]);!! // var_dump($models);die;! return $this->rendertext(json_encode($models));!

ayez confiance Utilisez un système de gestion de versions et faites lui confiance

Conclusions?

Coder est une course d endurance Les tutoriels sont très importants car le code et les pratiques sont globalement très suivies mais il est difficile de respecter les bonnes pratiques dans la durée

Le refactoring est une activité de tous les jours Ecrire du code Tester son code Documenter son code Refactoriser son code et on recommence

Questions?

Sensio S.A. 92-98, boulevard Victor Hugo 92 115 Clichy Cedex FRANCE Tél. : +33 1 40 99 80 80 Contact Fabien Potencier fabien.potencier at sensio.com http://www.sensiolabs.com/ http://www.symfony-project.org/ http://fabien.potencier.org/