SlideShare uma empresa Scribd logo
1 de 57
Baixar para ler offline
Refactoring de code sous symfony
Fabien Potencier
             Refactoring de code sous symfony | Fabien Potencier
C’est quoi le refactoring ?

           Refactoring de code sous symfony | Fabien Potencier
Le réusinage



             Ok, refactoring, c’est le terme anglais

     Refactorisation, c’est le terme français apparemment



           Refactoring de code sous symfony | Fabien Potencier
Le réusinage



      hmmm, Wikipedia suggère même « réusinage » !

        Je pense que je vais garder le mot anglais ;)



          Refactoring de code sous symfony | Fabien Potencier
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é. »
          Refactoring de code sous symfony | Fabien Potencier
Les applications cibles

           Refactoring de code sous symfony | Fabien Potencier
Avant de commencer



                    Applications Open-Source

          Tout le monde pourra refaire l’exercice



         Refactoring de code sous symfony | Fabien Potencier
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

            Refactoring de code sous symfony | Fabien Potencier
Siwapp



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



         Refactoring de code sous symfony | Fabien Potencier
Siwapp

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



             Refactoring de code sous symfony | Fabien Potencier
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

             Refactoring de code sous symfony | Fabien Potencier
Juste pour rire

           Refactoring de code sous symfony | Fabien Potencier
Ullright
// in plugins/ullFlowPlugin/modules/ullFlow/lib/BaseUllFlowActions.class.php
//                                 .       .
//                                / `. .' 
//                        .---. <     > <    > .---.
//                        |      - ~ ~ - / /       |
//                         ~-..-~             ~-..-~
//                     ~~~.'                    `./~~~/
//           .-~~^-.    __/                        __/
//         .' O          /               /        
//        (_____,    `._.'               |         } /~~~/
//         `----.          /       }     |        /     __/
//               `-.      |       /      |       /       `. ,~~|
//                   ~-.__|      /_ - ~ ^|      /- _       `..-' f: f:
//                        |     /        |     /      ~-.     `-. _||_||_
//                        |_____|        |_____|          ~ - . _ _ _ _ _>

               Refactoring de code sous symfony | Fabien Potencier
Ullright


//   in plugins/ullFlowPlugin/modules/ullFlow/lib/BaseUllFlowActions.class.php
//      _______ _______ _______ _______ _______
//    ( ____ ( ____ ( ___ )( ____ )( ____ |           /|
//    | (    /| (    /| ( ) || (      )|| (    /| ) ( |
//    | (_____ | (__    | (___) || (____)|| |      | (___) |
//    (_____ )| __) | ___ ||           __)| |      | ___ |
//          ) || (      | ( ) || ( ( | |          | ( ) |
//    /____) || (____/| ) ( || )  __| (____/| ) ( |
//    _______)(_______/|/     ||/ __/(_______/|/       |




                 Refactoring de code sous symfony | Fabien Potencier
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
 */


                Refactoring de code sous symfony | Fabien Potencier
C’est parti

              Refactoring de code sous symfony | Fabien Potencier
Siwapp




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




         Refactoring de code sous symfony | Fabien Potencier
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

             Refactoring de code sous symfony | Fabien Potencier
… 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




              Refactoring de code sous symfony | Fabien Potencier
… avoir confiance




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




         Refactoring de code sous symfony | Fabien Potencier
… avoir confiance



  – perte de temps pour les écrire

  – faux sentiment de confiance et de robustesse

  – juste pour la bonne conscience ?



             Refactoring de code sous symfony | Fabien Potencier
… 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')->


              Refactoring de code sous symfony | Fabien Potencier
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();
               Refactoring de code sous symfony | Fabien Potencier
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();



               Refactoring de code sous symfony | Fabien Potencier
Ne jamais écrire trop de code




           Refactoring de code sous symfony | Fabien Potencier
… le refactoring ultime est la suppression




                  Oui, un script vide suffit…
                  Pourquoi tester sfGuard ?




          Refactoring de code sous symfony | Fabien Potencier
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)
;

            Refactoring de code sous symfony | Fabien Potencier
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()
        ;
    }
}



                    Refactoring de code sous symfony | Fabien Potencier
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()');




               Refactoring de code sous symfony | Fabien Potencier
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()');

                   Refactoring de code sous symfony | Fabien Potencier
La classe utilisateur




           Refactoring de code sous symfony | Fabien Potencier
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;
    }
}




                    Refactoring de code sous symfony | Fabien Potencier
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);
    }


                     Refactoring de code sous symfony | Fabien Potencier
… pour définir une interface




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




          Refactoring de code sous symfony | Fabien Potencier
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);
 }

                    Refactoring de code sous symfony | Fabien Potencier
… 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();
 }




                Refactoring de code sous symfony | Fabien Potencier
… déplacer vers le modèle


public function executeForm(sfWebRequest $request)
{
  $this->form = new SearchForm($this->getUser()->getCurrentSeachParameters())
  $this->tags = TagPeer::getAll();
}




               Refactoring de code sous symfony | Fabien Potencier
… 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;
}




                Refactoring de code sous symfony | Fabien Potencier
… 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();
    }
}




                    Refactoring de code sous symfony | Fabien Potencier
… 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();
    }
}


                  Refactoring de code sous symfony | Fabien Potencier
… pour définir une interface




                   Le contrôleur fait régime
                   Le modèle est gourmand




          Refactoring de code sous symfony | Fabien Potencier
… pour définir une interface




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




          Refactoring de code sous symfony | Fabien Potencier
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();
    }
}

                                      Refactoring de code sous symfony | Fabien Potencier
Réfléchir à la bonne couche


class SearchFilter extends sfFilter
{
  public function execute($filterChain)
  {
    $this->context->getUser()->updateSearch($this->context->getRequest());

        $filterChain->execute();
    }
}




                   Refactoring de code sous symfony | Fabien Potencier
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));
 }

                         Refactoring de code sous symfony | Fabien Potencier
… 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');


                           Refactoring de code sous symfony | Fabien Potencier
… pour définir une interface




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




          Refactoring de code sous symfony | Fabien Potencier
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');




                    Refactoring de code sous symfony | Fabien Potencier
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');
}
                     Refactoring de code sous symfony | Fabien Potencier
… pour me faire plaisir ;)




                          A quoi ça sert que
                         Fabien se décarcasse ?




           Refactoring de code sous symfony | Fabien Potencier
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));!
  }
                    Refactoring de code sous symfony | Fabien Potencier
… ayez confiance



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



         Refactoring de code sous symfony | Fabien Potencier
Conclusions ?

          Refactoring de code sous symfony | Fabien Potencier
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

           Refactoring de code sous symfony | Fabien Potencier
Le refactoring est une activité de tous les jours

                        Ecrire du code
                       Tester son code
                    Documenter son code
                    Refactoriser son code
                       … et on recommence
            Refactoring de code sous symfony | Fabien Potencier
Questions?

         Refactoring de code sous symfony | Fabien Potencier
Sensio S.A.
                     92-98, boulevardVictor 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/



Refactoring de code sous symfony | Fabien Potencier

Mais conteúdo relacionado

Mais de Fabien Potencier

Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4Fabien Potencier
 
Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Fabien Potencier
 
Design patterns revisited with PHP 5.3
Design patterns revisited with PHP 5.3Design patterns revisited with PHP 5.3
Design patterns revisited with PHP 5.3Fabien Potencier
 
The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010Fabien Potencier
 
Dependency injection - phpday 2010
Dependency injection - phpday 2010Dependency injection - phpday 2010
Dependency injection - phpday 2010Fabien Potencier
 
Dependency Injection IPC 201
Dependency Injection IPC 201Dependency Injection IPC 201
Dependency Injection IPC 201Fabien Potencier
 
Caching on the Edge with Symfony2
Caching on the Edge with Symfony2Caching on the Edge with Symfony2
Caching on the Edge with Symfony2Fabien Potencier
 
Unit and Functional Testing with Symfony2
Unit and Functional Testing with Symfony2Unit and Functional Testing with Symfony2
Unit and Functional Testing with Symfony2Fabien Potencier
 
News of the Symfony2 World
News of the Symfony2 WorldNews of the Symfony2 World
News of the Symfony2 WorldFabien Potencier
 
Dependency Injection - ConFoo 2010
Dependency Injection - ConFoo 2010Dependency Injection - ConFoo 2010
Dependency Injection - ConFoo 2010Fabien Potencier
 
Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3Fabien Potencier
 

Mais de Fabien Potencier (20)

Look beyond PHP
Look beyond PHPLook beyond PHP
Look beyond PHP
 
Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4
 
Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Dependency injection-zendcon-2010
Dependency injection-zendcon-2010
 
Caching on the Edge
Caching on the EdgeCaching on the Edge
Caching on the Edge
 
Design patterns revisited with PHP 5.3
Design patterns revisited with PHP 5.3Design patterns revisited with PHP 5.3
Design patterns revisited with PHP 5.3
 
The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010
 
PhpBB meets Symfony2
PhpBB meets Symfony2PhpBB meets Symfony2
PhpBB meets Symfony2
 
Dependency injection - phpday 2010
Dependency injection - phpday 2010Dependency injection - phpday 2010
Dependency injection - phpday 2010
 
Symfony2 - WebExpo 2010
Symfony2 - WebExpo 2010Symfony2 - WebExpo 2010
Symfony2 - WebExpo 2010
 
Symfony2 - OSIDays 2010
Symfony2 - OSIDays 2010Symfony2 - OSIDays 2010
Symfony2 - OSIDays 2010
 
Dependency Injection IPC 201
Dependency Injection IPC 201Dependency Injection IPC 201
Dependency Injection IPC 201
 
Caching on the Edge with Symfony2
Caching on the Edge with Symfony2Caching on the Edge with Symfony2
Caching on the Edge with Symfony2
 
Unit and Functional Testing with Symfony2
Unit and Functional Testing with Symfony2Unit and Functional Testing with Symfony2
Unit and Functional Testing with Symfony2
 
News of the Symfony2 World
News of the Symfony2 WorldNews of the Symfony2 World
News of the Symfony2 World
 
Dependency Injection - ConFoo 2010
Dependency Injection - ConFoo 2010Dependency Injection - ConFoo 2010
Dependency Injection - ConFoo 2010
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
Symfony Components
Symfony ComponentsSymfony Components
Symfony Components
 
PHP 5.3 in practice
PHP 5.3 in practicePHP 5.3 in practice
PHP 5.3 in practice
 
Symfony2 revealed
Symfony2 revealedSymfony2 revealed
Symfony2 revealed
 
Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3
 

Refactoring de code sous symfony (Symfony Live 09)

  • 1. Refactoring de code sous symfony Fabien Potencier Refactoring de code sous symfony | Fabien Potencier
  • 2. C’est quoi le refactoring ? Refactoring de code sous symfony | Fabien Potencier
  • 3. Le réusinage Ok, refactoring, c’est le terme anglais Refactorisation, c’est le terme français apparemment Refactoring de code sous symfony | Fabien Potencier
  • 4. Le réusinage hmmm, Wikipedia suggère même « réusinage » ! Je pense que je vais garder le mot anglais ;) Refactoring de code sous symfony | Fabien Potencier
  • 5. 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é. » Refactoring de code sous symfony | Fabien Potencier
  • 6. Les applications cibles Refactoring de code sous symfony | Fabien Potencier
  • 7. Avant de commencer Applications Open-Source Tout le monde pourra refaire l’exercice Refactoring de code sous symfony | Fabien Potencier
  • 8. 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 Refactoring de code sous symfony | Fabien Potencier
  • 9. Siwapp Aucune application n’est parfaite… jamais… même les miennes… hmmm … surtout les miennes Refactoring de code sous symfony | Fabien Potencier
  • 10. Siwapp •  http://www.siwapp.org/ •  « Free online invoice system » •  Licence MIT •  symfony 1.2.7 •  Propel Refactoring de code sous symfony | Fabien Potencier
  • 11. 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 Refactoring de code sous symfony | Fabien Potencier
  • 12. Juste pour rire Refactoring de code sous symfony | Fabien Potencier
  • 13. Ullright // in plugins/ullFlowPlugin/modules/ullFlow/lib/BaseUllFlowActions.class.php // . . // / `. .' // .---. < > < > .---. // | - ~ ~ - / / | // ~-..-~ ~-..-~ // ~~~.' `./~~~/ // .-~~^-. __/ __/ // .' O / / // (_____, `._.' | } /~~~/ // `----. / } | / __/ // `-. | / | / `. ,~~| // ~-.__| /_ - ~ ^| /- _ `..-' f: f: // | / | / ~-. `-. _||_||_ // |_____| |_____| ~ - . _ _ _ _ _> Refactoring de code sous symfony | Fabien Potencier
  • 14. Ullright // in plugins/ullFlowPlugin/modules/ullFlow/lib/BaseUllFlowActions.class.php // _______ _______ _______ _______ _______ // ( ____ ( ____ ( ___ )( ____ )( ____ | /| // | ( /| ( /| ( ) || ( )|| ( /| ) ( | // | (_____ | (__ | (___) || (____)|| | | (___) | // (_____ )| __) | ___ || __)| | | ___ | // ) || ( | ( ) || ( ( | | | ( ) | // /____) || (____/| ) ( || ) __| (____/| ) ( | // _______)(_______/|/ ||/ __/(_______/|/ | Refactoring de code sous symfony | Fabien Potencier
  • 15. 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 */ Refactoring de code sous symfony | Fabien Potencier
  • 16. C’est parti Refactoring de code sous symfony | Fabien Potencier
  • 17. Siwapp Avant de commencer le refactoring, lançons les tests… Refactoring de code sous symfony | Fabien Potencier
  • 18. 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 Refactoring de code sous symfony | Fabien Potencier
  • 19. … 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 Refactoring de code sous symfony | Fabien Potencier
  • 20. … avoir confiance Il vaut mieux n’avoir aucun test que des tests non maintenus Refactoring de code sous symfony | Fabien Potencier
  • 21. … avoir confiance – perte de temps pour les écrire – faux sentiment de confiance et de robustesse – juste pour la bonne conscience ? Refactoring de code sous symfony | Fabien Potencier
  • 22. … 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')-> Refactoring de code sous symfony | Fabien Potencier
  • 23. 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(); Refactoring de code sous symfony | Fabien Potencier
  • 24. 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(); Refactoring de code sous symfony | Fabien Potencier
  • 25. Ne jamais écrire trop de code Refactoring de code sous symfony | Fabien Potencier
  • 26. … le refactoring ultime est la suppression Oui, un script vide suffit… Pourquoi tester sfGuard ? Refactoring de code sous symfony | Fabien Potencier
  • 27. 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) ; Refactoring de code sous symfony | Fabien Potencier
  • 28. 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() ; } } Refactoring de code sous symfony | Fabien Potencier
  • 29. 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()'); Refactoring de code sous symfony | Fabien Potencier
  • 30. 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()'); Refactoring de code sous symfony | Fabien Potencier
  • 31. La classe utilisateur Refactoring de code sous symfony | Fabien Potencier
  • 32. 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; } } Refactoring de code sous symfony | Fabien Potencier
  • 33. 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); } Refactoring de code sous symfony | Fabien Potencier
  • 34. … pour définir une interface Créez et utilisez une interface publique, documentée et testée Refactoring de code sous symfony | Fabien Potencier
  • 35. 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); } Refactoring de code sous symfony | Fabien Potencier
  • 36. … 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(); } Refactoring de code sous symfony | Fabien Potencier
  • 37. … déplacer vers le modèle public function executeForm(sfWebRequest $request) { $this->form = new SearchForm($this->getUser()->getCurrentSeachParameters()) $this->tags = TagPeer::getAll(); } Refactoring de code sous symfony | Fabien Potencier
  • 38. … 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; } Refactoring de code sous symfony | Fabien Potencier
  • 39. … 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(); } } Refactoring de code sous symfony | Fabien Potencier
  • 40. … 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(); } } Refactoring de code sous symfony | Fabien Potencier
  • 41. … pour définir une interface Le contrôleur fait régime Le modèle est gourmand Refactoring de code sous symfony | Fabien Potencier
  • 42. … pour définir une interface Passez du temps pour définir le nom de vos classes et méthodes Refactoring de code sous symfony | Fabien Potencier
  • 43. 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(); } } Refactoring de code sous symfony | Fabien Potencier
  • 44. Réfléchir à la bonne couche class SearchFilter extends sfFilter { public function execute($filterChain) { $this->context->getUser()->updateSearch($this->context->getRequest()); $filterChain->execute(); } } Refactoring de code sous symfony | Fabien Potencier
  • 45. 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)); } Refactoring de code sous symfony | Fabien Potencier
  • 46. … 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'); Refactoring de code sous symfony | Fabien Potencier
  • 47. … pour définir une interface Passez du temps pour définir le nom de vos classes et méthodes Refactoring de code sous symfony | Fabien Potencier
  • 48. 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'); Refactoring de code sous symfony | Fabien Potencier
  • 49. 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'); } Refactoring de code sous symfony | Fabien Potencier
  • 50. … pour me faire plaisir ;) A quoi ça sert que Fabien se décarcasse ? Refactoring de code sous symfony | Fabien Potencier
  • 51. 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));! } Refactoring de code sous symfony | Fabien Potencier
  • 52. … ayez confiance Utilisez un système de gestion de versions et faites lui confiance Refactoring de code sous symfony | Fabien Potencier
  • 53. Conclusions ? Refactoring de code sous symfony | Fabien Potencier
  • 54. 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 Refactoring de code sous symfony | Fabien Potencier
  • 55. Le refactoring est une activité de tous les jours Ecrire du code Tester son code Documenter son code Refactoriser son code … et on recommence Refactoring de code sous symfony | Fabien Potencier
  • 56. Questions? Refactoring de code sous symfony | Fabien Potencier
  • 57. Sensio S.A. 92-98, boulevardVictor 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/ Refactoring de code sous symfony | Fabien Potencier