En la última parte, hemos pasado algo de código a ProductForm. Hoy, vamos a mejorar las acciones aún más moviendo el código a la Vista y mediante el uso de algunos atajos útiles proporcionada por symfony para situaciones comunes.

En la parte 3, hemos refactorizado la accion index que ahora se ve como esto:

// apps/frontend/modules/product/actions.class.php
public function executeIndex()
{
  $this->products = ProductPeer::getAvailableProducts();

 
  $this->getResponse()->setTitle('All products');
  $this->getResponse()->addStylesheet('homepage.css');

 
  return sfView::SUCCESS;
}
 

En symfony, la vista predeterminada es sfView::SUCCESS, por lo que se puede omitir de la acción:

// apps/frontend/modules/product/actions.class.php

public function executeIndex()
{
  $this->products = ProductPeer::getAvailableProducts();

 
  $this->getResponse()->setTitle('All products');
  $this->getResponse()->addStylesheet('homepage.css');

}
 

En esta acción, hemos fijado el título de la página y añadido una hoja de estilo específica. Incluso si este código funciona como se espera, no es la manera correcta de hacerlo. Este código realmente pertenece a la vista. Así que vamos a pasar este código al template indexSuccess.php, de manera que nuestra acción se parezca a esto:

// apps/frontend/modules/product/actions.class.php
public function executeIndex()

{
  $this->products = ProductPeer::getAvailableProducts();
}
 

Para añadir una hoja de estilos en un template, podemos usar el helper use_stylesheet():

<!-- apps/frontend/modules/product/templates/indexSuccess.php -->
<?php use_stylesheet('homepage.css');
 
<h1>Our products</h1>
 
<?php foreach ($products as $product): ?>

<!-- ... -->
 

Para mover el título de la acción al template, tomará un poco mas. Tenemos que crear un slot en el layout que vamos usaremos en el template:

<!-- apps/frontend/modules/templates/layout.php -->
<html>
  <head>
    <title><?php include_slot('title') ?></title>

  </head>
  <body>
    <!-- ... -->
  </body>
</html>
 
<!-- apps/frontend/modules/product/templates/indexSuccess.php -->

<?php use_stylesheet('homepage.css');
<?php slot('title', 'All products') ?>

 
<h1>Our products</h1>
 
<?php foreach ($products as $product): ?>

<!-- ... -->
 

Los slots son una buena manera para añadir algunas partes dinámicas en el layout que puede ser sobreescritas por cada template.

Vamos a pasar a la accion edit. Aquí está el código actual:

public function executeEdit()

{
  $this->product = ProductPeer::retrieveByPk($this->getRequestParameter('id'));
  if (is_null($this->product))

  {
    $this->forward404();
  }
 
  $this->form = new ProductForm($this->product);

 
  if (sfRequest::POST == $this->getRequest()->getMethod())

  {
    $this->form->bind($this->getRequestParameter('product'), $this->getRequest()->getFiles('product'));
    if ($this->form->isValid())

    {
      $product = $this->form->save();
 
      $this->getUser()->setFlash('notice', sprintf('The product "%s" has been updated successfully.', $product->getTitle()));
      $this->redirect('product/index');
    }

  }
}
 

Como es muy común hacer un forward a una pagina 404 cuando se cumple una condición, symfony tiene algunos buenos métodos: forward404If() y forward404Unless():

public function executeEdit()

{
  $this->forward404Unless($this->product = ProductPeer::retrieveByPk($this->getRequestParameter('id')));

 
  // ...
}
 

A partir de symfony 1.1, symfony pasa el objeto de petición como primer argumento a los métodos execute*. Así que, en lugar de utilizar el metodo proxy getRequestParameter(), podemos utilizar el metodo getParameter() del objeto de petición. También podemos utilizar el nuevo metodo isMethod() para simplificar el chequeo POST.

Aquí está el metodo final executeEdit() para simplificar ladespués de realizados todos los cambios:

public function executeEdit($request)
{

  $this->forward404Unless($this->product = ProductPeer::retrieveByPk($request->getParameter('id')));

 
  $this->form = new ProductForm($this->product);
  if ($request->isMethod('POST'))

  {
    $this->form->bind($request->getParameter('product'), $request->getFiles('product'));
    if ($this->form->isValid())

    {
      $product = $this->form->save();
 
      $this->getUser()->setFlash('notice', sprintf('The product "%s" has been updated successfully.', $product->getTitle()));
      $this->redirect('product/index');
    }

  }
}
 

El código executeEdit() es ahora tan común, que puede utilizar un util atajo para el procesamiento del fomulario. El metodo bindAndSave() es un atajo que llama al metodo bind() y luego al save() del objeto si el fomulario es valido:

public function executeEdit($request)
{
  $this->forward404Unless($this->product = ProductPeer::retrieveByPk($request->getParameter('id')));

 
  $this->form = new ProductForm($this->product);
  if ($request->isMethod('POST') && $this->form->bindAndSave($request->getParameter('product'), $request->getFiles('product')))

  {
    $this->getUser()->setFlash('notice', sprintf('The product "%s" has been updated successfully.', $product->getTitle()));
    $this->redirect('product/index');
  }

}
 

El propósito del Controlador esta ahora espresado muy bien. Aquí está el código final para el modulo product :

// apps/frontend/modules/product/actions.class.php
public function executeIndex()

{
  $this->products = ProductPeer::getAvailableProducts();
}
 
public function executeEdit($request)

{
  $this->forward404Unless($this->product = ProductPeer::retrieveByPk($request->getParameter('id')));

 
  $this->form = new ProductForm($this->product);
  if ($request->isMethod('POST') && $this->form->bindAndSave($request->getParameter('product'), $request->getFiles('product')))

  {
    $this->getUser()->setFlash('notice', sprintf('The product "%s" has been updated successfully.', $product->getTitle()));
    $this->redirect('product/index');
  }

}
 
public function executeAddToFavorites($request)
{
  $this->forward404Unless($product = ProductPeer::retrieveByPk($request->getParameter('id')));

 
  $this->getUser()->addProductToFavorites($product);
 
  $this->redirect('product/index');

}
 
public function executeRemoveFromFavorites($request)
{
  $this->forward404Unless($product = ProductPeer::retrieveByPk($request->getParameter('id')));

 
  $this->getUser()->removeProductFromFavorites($product);
 
  $this->redirect('product/index');

}
 

La nueva clase productActions tiene ahora 31 lineas en lugar de 96. Hemos recortado el código en más de dos tercios!

Aquí está una tabla LOC para nuestro proyecto:

Layer Before After
Model
Product 0 9
User 0 19
View
Index 35 37
Controller
Actions 96 31
Forms 7 24
Total 138 120

Así que, después de la refactorización, tenemos un poco menos de código y, sobre todo, una mejor separación. También hemos creado objetos y métodos re-usables:

Los objetos reutilizables también significa que son auto-contenidos. Por lo tanto, puede incluso empaquetar tus clases del modelo y tus formularios como un plugin ahora.

Como una ventaja adicional, escribir pruebas unitarias para nuestro código es ahora mucho más fácil.

Los principales objetivos del proceso de refactorización son siempre las mismos:

Incluso si Vince esta muy cansado después de esta refactorización, quedo muy contento y comenzó a refactorizar otras partes de su sitio web inmediatamente. Espero que también hayan aprendido nuevos trucos que puedas utilizar en su próximo proyecto symfony.




La presente, es una traduccion al castellano realizada por Roberto G. Puentes Diaz, sobre el articulo refactoring story de Fabien Potencier.