Implementar ORM en Zend Framework Db Table

Por zsamer en Agosto 29, 2008

Como todos ya sabemos, Zend Framework no tiene una implementacion de ORM propiamentetal, si cubre algunos aspectos pero no todos.

Dada esta carencia en Zend Framework y mi inquietud por este tipo de técnica o patrón, he querido dedicar tiempo y desarrollar uno implementado con Zend_Db_Table Relationships, sin alterar ni tocar ninguna de sus funcionalidades ya existentes. En otras palabras es un proyecto que tiene como finalidad dar mayor funcionalidad de ORM a Zend_Db_Table Relationships.

Me ha tocado trabajar en proyectos JEE que he utilizando Hibernate como ORM, a mi gusto uno de los mejores ORM en Java y también he trabajado con Propel en PHP5, y he tomado funcionalidades de ambos para incorporarlas en este proyecto.

Puedes descargar las librerías mediante SVN en el siguiente link, o bien descargarlo directamente aquí.

Una ves que hayan hecho checkout o descargado y tengan una copia del repositorio deben copiar la carpeta Zsamer dentro de la carpeta library de nuestro proyecto (donde mismo está Zend Framework).

Es importante mencionar que es necesario utilizar el Adaptador de base de datos Zsamer_Db_Adapter_Pdo_Mysql, ya que da algunas ventajas para el uso de transacciones empleadas en el ORM. En caso de utilizar Mysql recomiendo usar el motor InnoDB ya que soporta transacciones en Mysql, entre algunas de sus ventajas.

Por defecto vine el adaptador para Mysql listo, pero si van a ocupar otro adaptador por ejemplo Oracle es necesario crear la clase correspondiente que herede del adaptador a utilizar, ver enlace de instalación & configuración

Para poder utilizar el Adaptador mencionado es necesario incluir la siguiente linea en el config.ini de conexión:

CODE:
  1. database.adapter = pdo_mysql
  2. database.params.adapterNamespace = Zsamer_Db_Adapter;

El repositorio cuenta con un completo set de ejemplos de aplicación dentro de la carpeta application, el cual incluye el archivo config.ini, orm.sql y un modulo llamado "cms", con controladores(controllers) de ejemplos(ContentController y ProjectController) y sus respectivas clases de modelos (models).

En el repositorio también se incluye las tablas de Mysql de los ejemplos en un archivo sql (orm.sql), se encuentran en la carpeta application junto con el archivo ini de config.

Las tablas de ejemplos vienen en InnoDB para soportar transacciones y Constraint (Integridad Referencial).

Ejemplo de Uso

Vamos al grano con un ejemplo de CMS:

Clases de Modelos:

PHP:
  1. <?php
  2. class Content extends Zsamer_Db_Table_Orm
  3. {
  4.         protected $_primary = 'id';
  5.  
  6.         protected $_name = 'cms_content';
  7.  
  8.         protected $_referenceMap    = array(
  9.         'Category' => array(
  10.             'columns'           => array('category_id'),
  11.             'refTableClass'     => 'Category',
  12.             'refColumns'        => array('id')
  13.         ));
  14. }
  15.  
  16. class Category extends Zsamer_Db_Table_Orm
  17. {
  18.         protected $_primary = 'id';
  19.        
  20.         protected $_name = 'cms_content_category';
  21.        
  22.         protected $_dependentTables = array('Content');
  23. }

Si se fijan las clases de modelos heredan de la clase Zsamer_Db_Table_Orm y esta a su vez heredará de Zend_Db_Table_Abstract.

Ejemplo en la clase Controladora ContentController:

PHP:
  1. <?php
  2. require 'cms/models/Content.php';
  3.  
  4. require 'cms/models/Category.php';
  5.  
  6. class Cms_ContentController extends Zend_Controller_Action
  7. {
  8.  
  9.     public function indexAction()
  10.     {
  11.         $this->_forward('test1');
  12.     }
  13.  
  14.     public function testAction()
  15.     {
  16.         $contentModel = new Content();
  17.        
  18.         $categoryModel = new Category();
  19.        
  20.         $category = $categoryModel->createRow();
  21.         $category->setName('New Category 6');
  22.  
  23.         $content = $contentModel->createRow();
  24.         $content->setTitle('Title 10');
  25.         $content->setContent('Content 10');
  26.         $content->setIsActive(1);
  27.        
  28.         $content2 = $contentModel->createRow();
  29.         $content2->setTitle('Title 11');
  30.         $content2->setContent('Content 11');
  31.         $content2->setIsActive(0);
  32.  
  33.         $category->addContent($content);
  34.         $category->addContent($content2);
  35.  
  36.         $category->save();
  37.     }
  38.    
  39.     public function test7Action()
  40.     {
  41.         $contentModel = new Content();
  42.        
  43.         $categoryModel = new Category();
  44.        
  45.         $category = $categoryModel->findById(3);
  46.  
  47.         $content = $contentModel->createRow();
  48.         $content->setTitle('Title 8');
  49.         $content->setContent('Content 8');
  50.         $content->setIsActive(1);
  51.        
  52.         $content2 = $contentModel->createRow();
  53.         $content2->setTitle('Title 9');
  54.         $content2->setContent('Content 9');
  55.         $content2->setIsActive(0);
  56.  
  57.         $category->addContent($content);
  58.         $category->addContent($content2);
  59.  
  60.         $category->save();
  61.     }
  62.    
  63.     public function test6Action()
  64.     {
  65.         $contentModel = new Content();
  66.        
  67.         $content = $contentModel->findById(5);
  68.         $coll = $content->getCategory()->getName();
  69.        
  70.         print '<pre>';
  71.         var_dump($coll);
  72.         die;
  73.     }
  74.    
  75.     public function test5Action()
  76.     {      
  77.         $categoryModel = new Category();
  78.        
  79.         $category = $categoryModel->findById(4);
  80.         $coll = $category->getContents();
  81.        
  82.         print '<pre>';
  83.         var_dump($coll);
  84.         die;
  85.     }
  86.  
  87.     public function test4Action()
  88.     {
  89.         $contentModel = new Content();
  90.        
  91.         $categoryModel = new Category();
  92.        
  93.         $category = $categoryModel->findById(4);
  94.  
  95.         $content = $contentModel->createRow();
  96.         $content->setTitle('Title 7');
  97.         $content->setContent('Content 7');
  98.         $content->setIsActive(1);
  99.  
  100.         $category->setContent($content);
  101.  
  102.         $category->save();
  103.     }
  104.  
  105.     public function test3Action()
  106.     {
  107.         $contentModel = new Content();
  108.        
  109.         $categoryModel = new Category();
  110.        
  111.         $category = $categoryModel->createRow();
  112.         $category->setName('New Category 5');
  113.  
  114.         $content = $contentModel->createRow();
  115.         $content->setTitle('Title 6');
  116.         $content->setContent('Content 6');
  117.         $content->setIsActive(1);
  118.  
  119.         $category->setContent($content);
  120.  
  121.         $category->save();
  122.     }
  123.  
  124.     public function test2Action()
  125.     {
  126.         $contentModel = new Content();
  127.        
  128.         $categoryModel = new Category();
  129.        
  130.         $category = $categoryModel->createRow();
  131.         $category->setName('Name Category 4');
  132.  
  133.         $content = $contentModel->createRow();
  134.         $content->setTitle('Title 4');
  135.         $content->setContent('Content 4');
  136.         $content->setIsActive(1);
  137.  
  138.         $content->setCategory($category);
  139.         $content->save();
  140.     }
  141.  
  142.     public function test1Action()
  143.     {
  144.         $contentModel = new Content();
  145.        
  146.         $categoryModel = new Category();
  147.        
  148.         $category = $categoryModel->findById(2);
  149.  
  150.         $content = $contentModel->createRow();
  151.         $content->setTitle('Title 5');
  152.         $content->setContent('Content 5');
  153.         $content->setIsActive(1);
  154.  
  155.         $content->setCategory($category);
  156.         $content->save();
  157.     }
  158. }

Para la gente que tenga experiencia en desarrollos con Propel o Doctrine en PHP5 se darán cuenta que tiene las mismas funcionalidades de relaciones de objetos tanto para set, get, add, save etc, lo mismo para quienes han desarrollado en otros lenguajes como en JEE con Hibernate, RoR con ActiveRecord, etc.

Más ejemplos los encontraran en el sitio web del proyecto.

Cualquier comentarios, criticas, sugerencias, errores/bugs etc, serán muy bienvenidos.

Bueno hemos llegado al fin del artículo, espero que sea de gran utilidad.

:-)

Comentarios

7 Responses to “Implementar ORM en Zend Framework Db Table”

  1. DataGrid con Zend Framework | Zend Framework: Estado del Arte on Septiembre 11th, 2008 10:49 pm

    [...] librería esta dentro de un package/namespace llamado Core y no Zsamer como en mi anterior proyecto Implementar ORM en Zend Framework Db Table, mmm en realidad una muy buena pregunta, pero la respuesta tambien es buena. La respuesta es porque [...]

  2. meneame.net on Septiembre 22nd, 2008 8:33 pm

    ORM con Zend Framework…

    Como todos ya sabemos, Zend Framework no tiene una implementacion de ORM propiamentetal, si cubre algunos aspectos pero no todos. Dada esta carencia en Zend Framework y mi inquietud por este tipo de técnica o patrón, he querido dedicar tiempo y desar…

  3. Andrés on Marzo 2nd, 2009 2:02 pm

    Hola yo estoy trabajando en algo parecido, pero con con Zend ya que nosotros trabajamos con el CMS TYPO3. Yo uso como base de la creación de ORM los métodos de las clases de ADOdb. Primeramente desarrollamos clases fijas para cada tabla de nuestros sistemas(Class.Nombretabla.php), en base a un script que las generaba. Esto nos generó muchos problemas ya que al modificar algún campo de las tablas, ya sea agregar o eliminar campos, se debía modificar la clase generada lo que acarreaba modificar también los sistemas que utilizaban dicha tabla. Ahora estamos utilizando un sistema muy parecido al ActiveRecord de ADOdb. Agregamos un nuevo tipo de DML para optimizar la inserción de información en la BD con el manejo de UNION ALL (INSERTO INTO schema.table(c1,c2) SELECT 1,2 UNION ALL 3,4…).
    Mi pregunta es ¿como manejas el cambio de los campos de una tabla, ya sea eliminación agregación o modificación de un campo de una tabla, cunado un sistemas ya están funcionando con la ORM de la tabla antigua?.

    Saludos

  4. zsamer on Marzo 2nd, 2009 2:46 pm

    Estimado Zend_Db el API de abstracción de base de datos de Zend Framework maneja esto en forma implícita y transparente al desarrollador mediante los métodos mágicos de PHP5 (__get, __set).

    Implicitamente hace un DESCRIBE TABLE y genera los objetos entity (de identidad que representa a tabla), cada columna de la tabla es parte de los atributos de la clase.

  5. Andrés on Marzo 2nd, 2009 2:59 pm

    OK muchas gracias por tú respuesta. Yo trabajo de una forma parecida al momento que las clases de acceso a datos, de los distintos sistemas piden la instancia de una objeto, se genera un objeto según las columnas que tenga la tabla en el momento que se pide la instancia, por lo tanto al hacer __set a un método que no existe no se agregará dicho valor al DML requerido. Bueno nosotros desarrollamos en base a un conjunto de clases centralizadas encargadas de realizar todo el trabajado con las fuentes de datos (DML,DQL, debug de errores, transacciones, email de errores, etc ) todas nuestras clases de acceso a datos heredan de estas clases.

    Nuevamente gracias pro tú respuesta.

  6. Pablo on Septiembre 11th, 2009 7:23 am

    Gracias por el artículo, muy bueno.

    Lo que estoy buscando es persistencia de los objetos entre página y página. Por las características de HTTP ya he visto que simplemente con PHP no es posible. Por lo que he estado leyendo Propel aporta persistencia de los objetos.
    Me gustaría que confirmases que Propel permite persistencia de objetos al pasar de una página a otra, y en su caso si esto tiene ventaja en cuanto a eliminar el número de consultas a la base de datos.

    Gracias.

  7. Pablo on Septiembre 11th, 2009 1:06 pm

    Por último, si conoces Propel y Doctrine, que usarías mejor para Zend Framework

    Gracias

Deja tu comentario




XHTML: puedes usar estas etiquetas: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>