Propel

RECU-0259 (Recurso Referencia)

Descripción

Propel es un librería de Mapeo Objeto-Relacional (ORM) de código abierto para PHP5. Le permite acceder a su base de datos mediante un conjunto de objetos, proporcionando una API sencilla para almacenar y recuperar datos. Propel ofrece al desarrollador de aplicaciones web las herramientas para trabajar con bases de datos de la misma manera que se trabaja con otras clases y objetos en PHP.

  • Propel le da a su base de datos de una API bien definida.
  • Propel utiliza los estándares PHP5 OO - Excepciones, carga automática, iteradores y amigos.

Características

A continuación vamos a resumir los principales aspectos funcionales del mapeo ORM utilizando Propel como proveedor del mismo.

Uso de arrays temporales

Cuando se utiliza Propel, los objetos creados ya contienen todos los datos, por lo que no es necesario crear un array temporal de datos para la plantilla. Los programadores que no están acostumbrados a trabajar con ORM suelen caer en este error. Estos programadores suelen preparar un array de cadenas de texto, o de números, para las plantillas, mientras que, en realidad, las plantillas pueden trabajar directamente con los arrays de objetos.

El problema es que, con el proceso de creación de objetos, hace que crear el array sea inútil, ya que el mismo código se puede reescribir utilizando this. De esta forma, el tiempo que se pierde creando el array se puede aprovechar para mejorar el rendimiento de la aplicación.

Si realmente es necesario crear un array temporal, porque se realiza cierto procesamiento con los objetos, la mejor solución es crear un nuevo método en la clase del modelo que devuelva directamente ese array. Si, por ejemplo, se necesita un array con los títulos de los artículos y el número de comentarios de cada artículo, la acción y la plantilla deberían ser similares a:

// En la acción
$this->articulos = ArticuloPeer::getArticuloTitulosConNumeroComentarios();
// En la plantilla
<ul>
<?php foreach ($articulos as $articulo): ?>
<li><?php echo $articulo[0] ?> (<?php echo $articulo[1] ?> comentarios)</li>
<?php endforeach; ?>
</ul>

Uso de validadores

El uso de validadores permitirá validar una entrada antes de realizar la persistencia a la base de datos. En Propel, los validadores son reglas que describen qué tipo de datos son aceptados por una columna. Los validadores se referencian en el archivo schema.xml, utilizando etiquetas <validator>.

Los validadores se aplican en el nivel de PHP, no se crean limitaciones a la propia base de datos. Esto significa que si también se utiliza otro lenguaje para trabajar con la base de datos, las reglas de validación no se harán cumplir. También puede aplicar varias entradas por la regla de validación en el archivo schema.xml.

<table name="user">
  <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  <column name="username" type="VARCHAR" size="34" required="true" />
  <validator column="username">
    <rule name="minLength"  value="4"  message="El nombre de usuario debe tener, al menos, ${value} caracteres !" />
  </validator>
</table>

En tiempo de ejecución se puede validar una instancia del modelo llamando al método validate() como en el ejemplo siguiente:

<?php
$objUser = new User();
$objUser->setUsername("foo"); // Solo 3 caracteres, que es demasiado corto...
if ($objUser->validate()) {
  // Si no hay errores de validación el dato puede ser persistido
  $objUser->save();
} else {
  // Si algo fue mal
  // Usamos validationFailures para saber qué falló
  $failures = $objUser->getValidationFailures();
  foreach($failures as $failure) {
    echo $objValidationFailure->getMessage() . "<br />\n";
  }
}

Los validadores que Propel proporciona son los siguientes:

  • MatchValidator: comprueba que se cumple la expresión regular
  • NotMatchValidator: comprueba que no se cumple con la expresión regular
  • MaxLengthValidator: comprueba que no se supera el máximo tamaño de una cadena que se inserta en una columna
  • MinLengthValidator: comprueba que no se supera el máximo tamaño de una cadena que se inserta en una columna
  • MaxValueValidator: comprobar que no se supera un máximo
  • MinValueValidator: comprobar que no se supera un mínimo
  • RequiredValidator: declara el atributo como obligatorio
  • UniqueValidator: comprueba que el valor existe en la tabla
  • ValidValuesValidator,
  • TypeValidator: comprueba que el tipo es aceptado por PHP

Transacciones

Las transacciones de bases de datos son la clave para asegurar la integridad de los datos y el rendimiento de las consultas de base de datos. Propel utiliza internamente las transacciones y proporciona un API simple para utilizarlos en su propio código. Propel usa PDO como capa de abstracción al acceso a la base de datos. Un ejemplo sería el siguiente:

<?php
public function transferMoney($fromAccountNumber, $toAccountNumber, $amount)
{
  // obtenemos el objeto de conexión PDO desde Propel
  $con = Propel::getConnection(AccountPeer::DATABASE_NAME);

  $fromAccount = AccountPeer::retrieveByPk($fromAccountNumber, $con);
  $toAccount   = AccountPeer::retrieveByPk($toAccountNumber, $con);

  $con->beginTransaction();

  try {
    // restamos $amount de $fromAccount
    $fromAccount->setValue($fromAccount->getValue() - $amount);
    $fromAccount->save($con);
    // sumamos $amount a $toAccount
    $toAccount->setValue($toAccount->getValue() + $amount);
    $toAccount->save($con);

    $con->commit();
  } catch (Exception $e) {
    $con->rollback();
    throw $e;
  }
}

Las declaraciones de transacción se realizan mediante los métodos BeginTransaction(), commit() y rollback(), que son métodos del objeto de conexión PDO. Los métodos de transacciones se utilizan típicamente dentro de un bloque try/catch. La excepción es relanzada después de hacer retroceder la transacción, lo que asegura que el usuario sabe que algo malo pasa.

En este ejemplo, si sucede algo erróneo, es lanzada una excepción y toda la operación se revierte. Esto significa que la transferencia es cancelada, asegurando que el dinero no ha desaparecido. Si ambas modificaciones en cuenta trabajan como se esperaba, toda la transacción se ha completado, lo que significa que los cambios de datos adjuntos en la transacción se conservan en la base de datos.

Comportamientos

Los comportamientos son una buena manera de formar paquetes de extensiones para el modelo de reutilización. Ellos son poderosos, versátiles, rápidos y le ayudarán a organizar el código de una mejor manera. Existen varios tipos de comportamientos:

  • Los comportamientos pueden modificar su tabla, e incluso añadir otra, mediante la aplicación del método modifyTable. En este método, utiliza $this-> getTable() para recuperar el modelo de la tabla en tiempo de compilación y manipularla.
<?php
// default parameters value
protected $parameters = array(
  'column_name' => 'foo',
);

public function modifyTable()
{
  $table = $this->getTable();
  $columnName = $this->getParameter('column_name');
  // add the column if not present
  if(!$this->getTable()->containsColumn($columnName)) {
    $column = $this->getTable()->addColumn(array(
      'name'    => $columnName,
      'type'    => 'INTEGER',
    ));
  }
}
  • Comportamientos que pueden agregar código al modelo de objetos generados por la aplicación de uno de los siguientes métodos:
objectAttributes()     // añade atributos al objeto
objectMethods()        // añade métodos al objeto
preInsert()            // añade código que se ejecuta antes de la inserción de un nuevo objeto
postInsert()           // añade código que se ejecuta después de la inserción de un nuevo objeto
preUpdate()            // añade código que se ejecuta antes de actualizar un objeto
postUpdate()           // añade código que se ejecuta después de actualizar un objeto
preSave()              // añade código que se ejecuta antes de salvar un objeto
postSave()             // añade código que se ejecuta después de salvar un objeto
preDelete()            // añade código que se ejecuta antes del borrado de un objeto
postDelete()           // añade código que se ejecuta después del borrado de un objeto
objectCall()           // añade código que se ejecuta inside the object's __call()
objectFilter(&$script) // hace lo que quieras con el código generado pasado por referencia
  • Comportamientos que pueden añadir código a los objetos generados de consultas implementando alguno de estos métodos
queryAttributes()     // añade atributos a la clase Query
queryMethods()        // añade métodos a la clase Query
preSelectQuery()      // añade código que se ejecuta antes de seleccionar un objeto
preUpdateQuery()      // añade código que se ejecuta antes de actualizar un objeto
postUpdateQuery()     // añade código que se ejecuta después de actualizar un objeto
preDeleteQuery()      // añade código que se ejecuta antes del borrado de un objeto
postDeleteQuery()     // añade código que se ejecuta después del borrado de un objeto
queryFilter(&$script) // hace lo que quieras con el código generado pasado por referencia
  • Comportamientos que pueden agregar código a los objetos por pares generados por la aplicación de uno de los siguientes métodos:
staticAttributes()   // añade atributos estáticos a la clase
staticMethods()      // añade métodos estáticos
preSelect()          // añade código antes de cualquier SELECT
peerFilter(&$script) // hace lo que quieras con el código generado pasado por referencia

Log y motor de depuración

Propel proporciona herramientas para controlar y depurar el modelo. Si usted necesita comprobar el código SQL de consultas lentas, o buscar mensajes de error previamente lanzados, Propel facilita la tarea de encontrar y corregir problemas. Propel utiliza la facilidad de logging configurada en runtime-conf.xml para almacenar errores, avisos e información de depuración. El manejador de log se configura en la sección <log> del fichero runtime-conf.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<config>
  <log>
    <type>file</type>
    <name>./propel.log</name>
    <ident>propel</ident>
    <level>7</level> <!-- PEAR_LOG_DEBUG -->
    <conf></conf>
  </log>
  <propel>
    ...
  </propel>
</config>

Los niveles de prioridad que utiliza el log

LogNivelDescripción
PEAR_LOG_EMERG0Sistema no disponible
PEAR_LOG_ALERT1Acción requerida inmediatamente
PEAR_LOG_CRIT2Condiciones críticas
PEAR_LOG_ERR3Condiciones de error
PEAR_LOG_WARNING4Condiciones de advertencias
PEAR_LOG_NOTICE5Mensajes normales pero importantes
PEAR_LOG_INFO6Informativo
PEAR_LOG_DEBUG7Mensajes de debug

Ejemplos

A continuación un ejemplo de uso de sentencias de consulta en la nueva API de Propel

/* 
 * Obteniendo un artículo por su clave primaria
 */ 
$article = ArticleQuery::create()->findPk(123); 

/*
 * Obteniendo un comentario relacionado con el artículo
 */ 

$comments = $article->getComments(); // no change 

/*
* Obteniendo un artículo por su título
*/ 
$article = ArticleQuery::create()->findOneByTitle('FooBar'); 
 
/*
* Obteniendo artículos que contienen una palabra concreta en el título
*/ 

$article = ArticleQuery::create() 
 ->filterByTitle('%FooBar%') 
 ->find(); 
/*
* Obteniendo artículos donde la fecha de publicación está entre la semana pasada y hoy
*/ 
$article = ArticleQuery::create()
  ->filterByPublishedAt(array( 
    'min' => time() - (7 * 24 * 60 * 60),  
    'max' => time(), 
  )) 
  ->find(); 
 
/*
* Obteniendo artículos basados en una condición personalizada
*/ 

$article = ArticleQuery::create() 
  ->where('UPPER(Article.Title) like ?', '%FooBar%') // vinculación hecha por PDO, no existe riego de inyección
  find(); 
   
/*
* Obteniendo artículos que contienen una palabra concreta en su título o en su resumen
*/ 

$article = ArticleQuery::create() 
  ->where('Article.Title like ?', '%FooBar%') 
  ->orWhere('Article.Summary like ?', '%FooBar%') 
  find(); 
 
/*
* Obteniendo artículos con varias cláusulas AND/OR donde el nomre o el resumen es
* %FooBar% y la fecha de publicación está entre $begin y $end
 */ 

$articles = ArticleQuery::create() 
      ->condition('cond1', 'Title like ?', '%FooBar%') 
      ->condition('cond2', 'Summary' like ?', '%FooBar%') 
      ->combine(array('cond1', 'cond2'), 'or', 'cond3') 
      ->condition('cond4', 'PublishedAt > ?', $begin) 
      ->condition('cond5', 'PublishedAt < ?', $end) 
      ->combine(array('cond4', 'cond5'), 'and', 'cond6') 
      ->combine(array('cond3', 'cond6'), 'and') 
      ->find(); 

/*
* Obteniendo los últimos 5 artículos
*/ 

$articles = ArticleQuery::create() 
  ->orderByPublishedAt('desc') 
  ->limit(5) 
  ->find(); 
/*
* Obteniendo el último comentario relacionado a un artículo
*/ 

$comment = CommentQuery::create() 
  ->filterByArticle($article) 
  ->orderByPublishedAt('desc') 
  ->findOne(); 
   
 /*
* Obteniendo los artículos de alguien
*/ 
 

$articles = ArticleQuery::create() 
  ->useAuthorQuery() 
  ->filterByName('John Doe') 
  ->endUse() 
  ->find() 
 
 
/*
* Obteniendo todos los artículos y su categoría mediante un Join en la misma consulta
*/ 

$articles = ArticleQuery::create() 
  ->joinWith('Category') 
  ->find(); 
/*
* Obteniendo un artículo y su categoría por la clave primaria del artículo
*/ 

$articles = ArticleQuery::create() 
  ->joinWith('Category') 
  ->findPk(123);

Enlaces externos

Contenidos relacionados

Pautas
Área: Desarrollo » Construcción de Aplicaciones por Capas » Capa de Persistencia » PHP
Código Título Tipo Carácter
PAUT-0317 Uso de Propel Pauta Directriz Recomendada
Recursos
Área: Desarrollo » Construcción de Aplicaciones por Capas » Capa de Persistencia » PHP
Código Título Tipo Carácter
RECU-0258 PDO Ficha Técnica Recomendado