Codeigniter y Doctrine

Codeigniter y Doctrine

Después de algún tiempo desarrollando herramientas con el framework Codeigniter a pelo en cuanto a la manipulación de los repositorios, es decir, creando las clases que controlarían los modelos de datos manualmente, gestionando sql de la forma más primigenia posible,… Y un larguísimo etc que nos da como conclusión una tarea bastante pesada y arduo trabajo (y eso que el aprendizaje de utilización de dicho framework es bastante rápido), que nos ralentizaba más que agilizaba, así que lo que ganábamos por un lado (la curva de aprendizaje es muy liviana en comparación con otros frameworks como por ejemplo Symfony, muy extendido sí, pero casi tienes que sacarte un máster para iniciar un proyecto grande con él, muy bonitos los tutoriales, pero…), lo perdíamos por otro.

En fin, que ya hemos aprendido el gran error de intentar reinventar la rueda. Cuando todavía era estudiante (aunque siempre lo seguiré siendo) ya un profesor me echó en alguna ocasión la típica charla de que estaba muy bien el ser creativo e intentar mejorar herramientas ya existentes, pero no iniciarlas desde cero con el particular de crear una herramienta completamente nueva, cuando otra que hacía prácticamente lo mismo ya estaba en el mercado. No reinventes la rueda, me decía. Me a costado unos años darme cuenta de la importancia de ese dicho, uno es cabezón y simpre quiere lograr sus metas, pero cuando se da cuenta que puede hacerlo igual basando sus proyectos en herramientas existentes en el mercado, se da cuenta que la idea y la integración de los diferentes sistemas sumado a la finalidad de la aplicación desarrollada es lo que dan como resultado final, tu herramienta.

Después de este extenso preludio, que espero también os sirva de algo, vamos al ajo. En primer lugar y siguiendo la premisa de no reinventar la rueda, os voy a proporcionar un paquete que contiene Codeigniter 2.1.3 y Doctrine 2.3.2. Lo podéis descargar desde aquí codeigniter2_doctrine2.rar

Instalación

Lo primero que debemos hacer, una vez descargado el paquete, es descomprimirlo en la raíz de nuestro servidor. En mi caso estoy utilizando EasyPHP, pues la integración con Windows 7 de 64 bits es completa. Además mediante un pequeño tip se puede utilizar el puerto 80 sin problemas y así generar un alias real que nos facilitará mucho la vida cuando migremos la aplicación de local a nuestro servidor en la red. (Esto se supone que ya se sabe hacer, si no, lo solicitáis y creo un nuevo tutorial o quizás con el tiempo lo hago, para explicar todo lo relacionado con EasyPHP y sus ventajas).

Una vez instalado (simplemente con descomprimir el paquete rar en la raíz de nuestro sitio es suficiente) nos vamos a dotar a nuestro nuevo framework unas cuantas configuraciones necesarias. Tenéis que localizar el archivo application/config/config.php Ahí daremos todas las configuraciones necesarias para que trabaje bien con nuestro sitio, como por ejemplo la ruta de nuestro sitio en la variable :

$config['base_url'] = '';

El formato de fecha ya está configurado para el idioma español, y bueno podéis ir tocando cosillas por ahí, el fin de este post no es más que ponerse manos a la obra, más que dar las claves de instalación y configuración de Codeigniter, quizás en un futuro…

También debéis tener en cuenta el archivo application/config/database.php para dotarlo de los datos de conexión a nuestra base de datos, esto es fundamental.

Una vez tengamos esos dos ficheros de configuración editados y acomodados a nuestro espacio de trabajo, ya podemos ir viendo como trabaja Doctrine con Codeigniter. Lo estupendo de esto es trabajar mediante la consola y que él nos genere automáticamente todos los ficheros necesarios para abstraer perfectamente las clases de los diferentes objetos que componen nuestro modelo de datos. Para ello, y ya que en este momento me encuentro trabajando bajo Windows, abrimos nuestro CMD y nos dirigimos hasta nuestro proyecto en Codeigniter (esto ya deberías saberlo hacer, utiliza el comando cd para ir hasta c:\ruta_de_tu_proyecto) y así poder ejecutar el cliente de Doctrine vía línea de comandos.

Aquí es donde me encontré con algún que otro inconveniente, ya que el paquete original no funcionaba con la lógica que yo quería aplicarle, así que me tomé la molestia de realizar unas cuantas modificaciones, que a mi entender y según la experiencia obtenida, son claves para que funcione correctamente.

Lo primero que tuve que modificar fue el archivo application/libraries/Doctrine.php ya que cuando ejecutaba el comando para generar las clases de las diferentes entidades de mi modelo de datos, el cliente de doctrine no encontraba dichos ficheros en formato YAML, así que incluí las siguiente líneas:

// Sirve para establecer el directorio donde se encuentran
 // los ficheros YAML
$driver = new YamlDriver(array(APPPATH . 'models/Mappings'));
$config->setMetadataDriverImpl($driver);

Con esto solventé dicho problema, vosotros ya lo tenéis algo más fácil, ya que tenéis todo el mini-proyecto generado y listo para empezar. Voy a explicar muy brevemente la estructura que utiliza esta configuración de Doctrine 2 con Codeigniter 2.

  • application/models/Mappings: En este directorio guardaremos todas las entidades de nuestro modelo de datos en formato YAML (que es el utilizado por este paquete, esto se puede cambiar por XML). Más adelante veremos los dos modelos que os he colocado de ejemplo y explicamos algunas claves para entender el formato y la sintaxis de dichos ficheros.
  • application/models/Generated: Aquí podréis destinar los ficheros ya convertidos en clases php representativas de nuestras entidades mapeadas a través de nuestro fichero YAML anteriormente citado para luego enviarlo al directorio Entity que veremos más adelante con la función de no machacar las clases ya creadas anteriormente y los posibles métodos añadidos a la misma por nosotros.
  • application/models/Entity: Este será el destino final de las clases generadas con nuestro ORM Doctrine. Se generan de forma automática, esto es simplemente mágico!
  • application/models/Proxies: Doctrine requiere de estas extensiones de clase para trabajar y abstraernos de nuestro modelo de datos. No debéis modificar dichos ficheros, pues se generan para trabajar con nuestras entidades de forma automática.

Vamos ahora a crear nuestra primera entidad. En este ejemplo queremos crear usuarios y grupos de usuarios, simplemente eso, así que lo primero por lógica sería crear los grupos de usuarios ya que esta entidad no depende de la de usuarios, mientras que un usuario sí dependerá de un grupo en concreto. Esto lo entenderéis más adelante.

---
Entity\UserGroup:
  type: entity
  table: groups
  uniqueConstraints:
    name_index:
      columns:
        - name
  fields:
    id:
      type: integer
      id: true
      generator:
        strategy: AUTO
    name:
      type: string
      length: 32
      nullable: false

Ese archivo se llamaapplication/models/ Entity/Entity.UserGroup.dcm.yml En primer lugar la palabra Entity hace relación al espacio de nombres al que pertenece la entidad, si os fijáis el fichero empieza indicando que Entity será el raíz de nuestro bundle. Tener en cuenta que el nombre del fichero (prefijo = Entity en este caso), la declaración de la entidad (la línea en la que se declara Entity\UserGroup) y el directorio final application/models/Entity han de llamarse igual, si no no funcionarán los diferentes comandos de generación de nuestro cliente Doctrine. El sufijo dcm.yml viene ya dado por Doctrine aunque también es modificable. Viene a ser algo como Data Controller Manager seguida de la extensión yml que es la utilizada por el formato YAML.

---
Entity\User:
  type: entity
  table: user
  uniqueConstraints:
    nickname_index:
      columns:
        - nickname
    email_index:
      columns:
        - email
  fields:
    id:
      type: integer
      id: true
      generator:
        strategy: AUTO
    nickname:
      type: string
      length: 16
      nullable: false
      index: unique
    email:
      type: string
      length: 255
      required: true
      index: unique
    firstName:
      type: string
      length: 35
      required: true
      column: first_name
    lastName:
      type: string
      length: 35
      required: true
      column: last_name     
    password:
      type: string
      length: 32
      nullable: false
    created_at:
      type: datetime
      nullable: false
  manyToOne:
    group:
      targetEntity: UserGroup
      joinColumns:
        group_id:
          referencedColumnName: id

Aquí vemos exactamente lo mismo, este fichero es application/models/Entity/Entity.User.dcm.yml Vemos que se repite la misma condición de los nombres. Con eso mucho ojo u os dirá Doctrine que no se encuentra el fichero indicado o no tiene un formato correcto.

Bien una vez explicado esto, vamos a pasar a los comandos de nuestro cliente doctrine mediante la consola. Debemos dirigirnos como es lógico a la raíz de nuestra aplicación web desde nuestra consola. Una vez allí vamos a generar Y EN ESE ORDEN las entidades y los proxies de las mismas con los siguiente comandos:

>php.exe ./application/doctrine.php orm:generate-entities models

Con esta instrucción le estamos diciendo a Doctrine que genere nuestras entidades y las meta en el directorio application/models. ¿Por qué no he especificado el directorio destino models/Entity? Pues porque ya tengo configurado mi cliente de Doctrine para que lo haga así, el que habéis descargado también lo hará. Una vez tengamos los modelos, podemos observar los dos ficheros que nos ha generado Doctrine, simplemente magia y en cuestión de segundos. La de trabajo que uno se ahorra, ¿verdad?

<?php

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * UserGroup
 */
class UserGroup
{
    /**
     * @var integer
     */
    private $id;

    /**
     * @var string
     */
    private $name;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return UserGroup
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }
}

Este sería el archivo UserGroup.php generado a raíz de ejecutar la instrucción anterior y basándose Doctrine en nuestro fichero Entity.UserGroup.dcm.yml ¿Qué os parece?

<?php

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * User
 */
class User
{
    /**
     * @var integer
     */
    private $id;

    /**
     * @var string
     */
    private $nickname;

    /**
     * @var string
     */
    private $email;

    /**
     * @var string
     */
    private $firstName;

    /**
     * @var string
     */
    private $lastName;

    /**
     * @var string
     */
    private $password;

    /**
     * @var \DateTime
     */
    private $created_at;

    /**
     * @var \Entity\UserGroup
     */
    private $group;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set nickname
     *
     * @param string $nickname
     * @return User
     */
    public function setNickname($nickname)
    {
        $this->nickname = $nickname;

        return $this;
    }

    /**
     * Get nickname
     *
     * @return string 
     */
    public function getNickname()
    {
        return $this->nickname;
    }

    /**
     * Set email
     *
     * @param string $email
     * @return User
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Get email
     *
     * @return string 
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set firstName
     *
     * @param string $firstName
     * @return User
     */
    public function setFirstName($firstName)
    {
        $this->firstName = $firstName;

        return $this;
    }

    /**
     * Get firstName
     *
     * @return string 
     */
    public function getFirstName()
    {
        return $this->firstName;
    }

    /**
     * Set lastName
     *
     * @param string $lastName
     * @return User
     */
    public function setLastName($lastName)
    {
        $this->lastName = $lastName;

        return $this;
    }

    /**
     * Get lastName
     *
     * @return string 
     */
    public function getLastName()
    {
        return $this->lastName;
    }

    /**
     * Set password
     *
     * @param string $password
     * @return User
     */
    public function setPassword($password)
    {
        $this->password = $password;

        return $this;
    }

    /**
     * Get password
     *
     * @return string 
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * Set created_at
     *
     * @param \DateTime $createdAt
     * @return User
     */
    public function setCreatedAt($createdAt)
    {
        $this->created_at = $createdAt;

        return $this;
    }

    /**
     * Get created_at
     *
     * @return \DateTime 
     */
    public function getCreatedAt()
    {
        return $this->created_at;
    }

    /**
     * Set group
     *
     * @param \Entity\UserGroup $group
     * @return User
     */
    public function setGroup(\Entity\UserGroup $group = null)
    {
        $this->group = $group;

        return $this;
    }

    /**
     * Get group
     *
     * @return \Entity\UserGroup 
     */
    public function getGroup()
    {
        return $this->group;
    }
}

Y este sería el generado por Entity.User.dcm.yml en el que por supuesto podemos añadir nuestros métodos para la manipulación de nuestro objeto usuario.

Ahora vamos a generar los proxies, que se generan con la siguiente instrucción:

>php.exe ./application/doctrine.php orm:generate-proxies

Esto generará las extensiones que Doctrine utilizará de forma interna y que no deberíamos modificar ubicándolas en el directorio application/models/Proxies

Una vez tenemos estos pasos completados, ahora tan solo nos faltaría que nuestro ORM nos generara las tablas en la base de datos ¿no? Pues bien, para ello y en primer lugar, deberemos limpiar nuestra base de datos de cualquier posible tabla que pueda existir para no tener problemas utilizando el siguiente comando:

>php.exe ./application/doctrine.php orm:schema-tool:drop --force

Ahora la magia pura, vamos a pedirle a Doctrine que nos cree las tablas declaradas:

>php.exe ./application/doctrine.php orm:schema-tool:create

Si nos vamos a nuestra base de datos, deberíamos ver las dos tablas creadas. ¡Impresionante! Bien pues ahora solo nos queda ver cómo se mete un registro y como podemos posteriormente realizar alguna consulta. Para introducir un registro emplearíamos el siguiente código en nuestro controlador:

$this->load->library('doctrine');

		$group = new Entity\UserGroup;
		$group->setName('FreeUsers');

		$user = new Entity\User;
		$user->setNickname('zekinash');
                $user->setFirstName('Alex');
                $user->setLastName('Romero');
		$user->setPassword('Passw0rd');
		$user->setEmail('alex@suenyos.com');
                $user->setCreatedAt(new DateTime());
		$user->setGroup($group);

Con estas instrucciones cargamos la librería de Doctrine en nuestro controlador, y creamos las dos entidades, por un lado la de grupos y por el otro la de usuarios. Como veis el orden importa ya que al usuario hay que especificarle un grupo y éste debe existir primero. La forma estandarizada para acceder a la librería de doctrine a través de Doctrine es la siguiente y veréis cómo se usa más adelante:

$em = $this->doctrine->em;

Si lo que queremos es guardar los datos de nuestros objetos Grupo y Usuario utilizaremos las siguientes sentencias:

$em->persist($group);
$em->persist($user);
$em->flush();

Con la primera preparamos el grupo con la segunda al usuario y con la tercera volcamos los datos en la base de datos y limpiamos la caché. Ahora si os movéis a la base de datos veréis como estarán dados de alta tanto el grupo como el usuario. Fantástico no?

Ahora si lo que queremos es recuperar datos, podemos hacerlo mediante una sentencia SQL clásica o mediante búsquedas sencillas que nos ofrece Doctrine. Vamos a ver como recuperar el usuario mediante un nombre de usuario dado:

// Lo primero es obtener el repositorio
// de la tabla usuario
$repUsuario = $em->getRepository('Entity\User');

// Ahora podemos hacer lo siguiente
$usuario = $repUsuario->findOneByNickname('zekinash');
$nombreUsuario = '';
if(!is_null($usuario)){
     $nombreUsuario = $usuario->getFirstName();
}
// Lo que hacemos es preguntar si hay un usuario
// con un nick igual a "zekinash"

Parece pan comido ¿verdad? Pues lo es. Ahora solo queda dejar volar la imaginación, completar información con la documentación de Codeigniter y Doctrine en la red y como he dicho a lo largo y ancho de este post, si me animo habrán más tutos y tips sobre estas dos plataformas en perfecta integración. Desde mi humilde punto de vista, nada tiene que envidiarle Symfony a Doctrine.

Os dejo un saludo y si conocéis alguna integración más reciente, otras soluciones, más tips o temas relacionados que consideréis interesantes, ya sabéis dónde tenéis el formulario de comentarios! Los esperamos con mucho ahínco!

6 thoughts on “Codeigniter y Doctrine

  • Gracias por compartir tus conocimientos, esta todo muy claro. Solo tengo una duda, hay alguna forma de crear los archivos yml de forma automatica, es decir yo tengo la base datos con las tablas creadas en sql, no habra alguna herramienta o traductor que ayude en esta tarea. Desde ya muchas gracias.

    • Desconozco ahora mismo si existe alguna herramienta que haga dicha tarea, pero si eres programador, ¿por qué no desarrollas una? jejeje No debería ser muy difícil crear un script que convierta a YML desde otro formato o incluso desde una estructura que idees para tal fin. ¿Te echamos un cable y pensamos en algo? Ah, y mientras escribía esta respuesta, he encontrado esto, ¿te sirve? http://yaml-online-parser.appspot.com/

  • Hola Alex,

    Gracias por tu post, estoy empezando con Doctrine y me ha parecido interesante. Le echaré un ojo a tu integración.

    Por otro lado, si que he visto algo parecido a los que pide ruben. Si usas la línea de comandos con Doctrine, tienes el método orm:convert-mapping que genera los ficheros de mapeo si le indicas el flag –from-database, aunque a mi me hace cosas raras ya que luego no me genera bien las clases…

    Un saludo,
    David.

  • Hola, Ruben y Alex,
    He estado investigando y haciendo pruebas sobre lo comenta Ruben ya que me encuentro en una situación donde quiero hacer ingenieria inversa, es decir, pasar de la base de datos a las entidades. (se que en Symfony se puede hacer porque me he ha pasado alguna vez).
    Para hacerlo hay que usar la siguiente orden Doctrine por consola:
    orm:convert-mapping –from-database yml models/Mappings
    (esto creará los archivos YML a partir de tu base de datos, luego ya puedes usar la orden para generar los archivos proxies y la orden para generar las entidades con sus getters y setters).
    Por cierto, Alex, para que se usan concretamente a los archivos Proxies? gracias y un saludo

  • muy bien con esto de usar un ORM como doctrine y evitar reinventar la rueda, sin embargo he notado que doctrine está como más adelantado que codeigniter en cuanto al uso de las nuevas bondades de php en sus nuevas versiones y codeigniter poco a poco se ha quedado en el pasado, aunque no desprestigio su uso y comodidad que brinda este framework, creo que doctrine como ORM está como mejor pensado para acoplarse a symfony, silex o quizás laravel. A pesar de esto, existe un ORM muy bueno y diseñado especialmente para codeigniter que sigue su filosofía, se llama DataMapper, te recomiendo que le eches un ojo y bueno quizás también compartas conocimientos al respecto en tu blog.

  • En mi opinión el problema es que datamapper es demasiado sencillo, por poner un ejemplo:
    Si quieres crear un slug para una entidad o tratar datos antes de un update o un insert, algo bastante común, no dispones de un método before_update o before_insert por lo que al final te ves obligado a crear tus propios métodos de insert y update en el modelo, o eso o ensuciar el controlador con código que debería contener el modelo.

    Un saludo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *