Usando el patrón Singleton en el admin generator de symfony

22 julio 2010

Muchas veces es necesario utilizar clases singleton en nuestras aplicaciones symfony, por ejemplo, para mantener la configuración de la aplicación, etc..

Supongamos tenemos una aplicación que puede ser configurada dinámicamente. Lo que queremos es que esa configuración pueda realizarse en la aplicación misma, es decir, sin necesidad de tener que saber que archivo modificar, y donde.
La opción más simple para lograr éste objetivo es definir una clase AppConfiguration (por ejemplo), que contenga todos los parámetros de configuración de la aplicación. Pero no basta sólo con eso, ya que debemos tener una única instancia de ésta clase, de modo que podamos, con un par de modificaciones, utilizar el admin generator de symfony. Lo que debemos hacer entonces es aplicar el patrón singleton.

En primer lugar, lo que debemos hacer es agregar los siguientes métodos a la clase que querramos que siga el patrón singleton (en éste ejemplo, la clase AppConfiguration:

public static function getInstance()
{
  if (is_null(self::$instance))
  {
    self::$instance = ($i = AppConfigurationPeer::doSelectOne(new Criteria())) ? $i : new AppConfiguration();
  }

  return self::$instance;
}

public function __construct()
{
  $trace = debug_backtrace();
  $caller = $trace[1];

  /*
   * Prevent the method of being called from another method rather than getInstance,
   * populateObjects (in the peer class) or loadDataFromArray (when loading fixtures).
   */

  if ($caller['function'] != 'getInstance' && $caller['function'] != 'populateObjects' && $caller['function'] != 'loadDataFromArray')
  {
    throw new Exception("You can't create AppConfiguration instances directly.");
  }
}

El primer método, getInstance, es el que debemos llamar cada vez que queremos obtener la instancia de la clase AppConfiguration, de modo que devuelve una nueva instancia si no existe, de lo contrario, devuelve la instancia guardada en la base de datos.
El segundo método, __construct, es el constructor de la clase, que no puede ser privado (sería lo mejor), entonces debe controlar que el método no sea llamado fuera de la clase, cuando se popula el objeto (luego de un doSelectOne por ejemplo), o cuando se cargan los fixtures.

Entonces, si queremos obtener una instancia de la clase AppConfiguration debemos hacerlo de la siguiente manera:

AppConfiguration::getInstance();

Por ahora todo va bien, el problema es que si creamos varias configuraciones por base de datos (por ejemplo mediante consultas en mysql client), podemos obtenerlas todas usando los métodos doSelect y doSelectOne de la clase AppConfigurationPeer. Para evitar ésto, debemos sobreescribir el siguiente método en la clase AppConfigurationPeer:

public function doSelectStmt(Criteria $criteria, PropelPDO $con = null)
{
  $criteria->clearOrderByColumns();
  $criteria->addAscendingOrderByColumn(self::ID);
  $criteria->setLimit(1);
  parent::doSelectStmt($criteria, $con);
}

De modo que siempre que se haga una consulta, se retorne un único elemento (el primero creado).

Hasta ahora, sólo creamos la parte singleton del modelo, lo que queremos hacer ahora es que el admin generator pueda crear/editar sólo la instancia de AppConfiguration. Es decir, sólo habrá acción new / edit en el módulo.
Para ésto, debemos modificar las acciones executeIndex, executeNew y executeEdit para lograr ésto:

public function executeIndex(sfWebRequest $request)
{
  $this->app_configuration = AppConfiguration::getInstance();

  $this->redirect($this->app_configuration->isNew() ? '@app_configuration_new' : '@app_configuration_edit?id='.$this->app_configuration->getId());
}

public function executeNew(sfWebRequest $request)
{
  $this->app_configuration = AppConfiguration::getInstance();

  if ($this->app_configuration->isNew())
  {
    parent::executeNew($request);
  }
  else
  {
    $this->executeEdit($request);
  }
}

public function executeEdit(sfWebRequest $request)
{
  $this->app_configuration = AppConfiguration::getInstance();
  $this->form = $this->configuration->getForm($this->app_configuration);
}

Con ésto, lo que vamos a lograr es que siempre se pueda editar la configuración, evitando que se puedan listar configuraciones, y creando o editando la configuración según corresponda.

Share and Enjoy:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Twitter

Sin comentarios

Dejar un comentario

WP SlimStat