Una de las principales tareas a la hora de realizar un desarrollo o aplicación web , es la permanencia de los datos, Symfony incluye la librería Doctrine, que proporciona herramientas para simplificar el acceso y manejo de la información de la base de datos. Por esta razón me propuse a escribir este artículo, donde quiero que aprendas la filosofía de trabajo de Doctrine y lo fácil que puede ser trabajar con bases de datos.¿Interesado(a)? ¡Sigue leyendo!
Ahora bien, antes de comenzar, ten en cuenta que para seguir este tutorial ya debes contar con un Bundle o paquete dentro de tu aplicación, si no sabes cómo, revisa el artículo Crear Bundles en Symfony 2.
Es importante destacar que Doctrine no guarda ninguna relación con Symfony y su uso es totalmente opcional, por lo que adentrado más en Symfony si conoces algún otro ORM (Object Relational Mapper o Mapeo Objeto Relacional) puedes sentir libertad de usarlo.
En esta artículo te enseñare a configurar el acceso a la base de datos, a crear un objeto llamado Producto, para persistir su información en la base de datos y obtenerlo de nuevo mediante una consulta.

Comencemos

1.- Configurando la base de datos

Es necesario configurar la base de datos. Por convención, esta información se configura en el archivo app/config/parameters.yml:

# app/config/parameters.yml
parameters:
    database_driver:   pdo_mysql
    database_host:     localhost
    database_name:     Ponceelrelajado_db
    database_user:     root
    database_password: 12345
# ...

En este momento Doctrine ya conoce nuestra base de datos, solo falta crearla con el siguiente comando:

php app/console doctrine:database:create

Uno de los errores más habituales que cometen incluso los programadores más experimentados consiste en no configurar correctamente la codificación de caracteres de la base de datos.
En ocasiones, el problema es que lo configuran bien la primera vez, pero no cada vez que se crea de nuevo la base de datos. Esto sucede mucho cuando se desarrolla la aplicación, ya que es habitual emplear los siguientes comandos para borrar la base de datos y regenerarla con nueva información:

$ php app/console doctrine:database:drop --force
$ php app/console doctrine:database:create

Doctrine no permite configurar estos valores por defecto en su archivo de configuración, ya que trata de ser lo más agnóstico posible en lo que se refiere a la configuración del entorno de ejecución.
Así que la solución más sencilla consiste en establecer estos valores por defecto en la propia configuración del servidor de base de datos. Si utilizas MySQL, añade las dos siguientes líneas en su archivo de configuración, que normalmente es my.cnf:

[mysqld]
collation-server = utf8_general_ci
character-set-server = utf8
2.- Creando la entidad

Imagina que estás desarrollando una aplicación en la que vas a mostrar productos. Olvidándote de Doctrine y de las bases de datos, seguramente estás pensando en utilizar un objeto Producto para representar a los productos. Crea esta clase dentro del directorio Entity del bundle PonceelrelajadoDemoBundle:

// src/Ponceelrelajado/DemoBundle/Entity/Producto.php
namespace Ponceelrelajado\DemoBundle\Entity;
class Producto
{
    protected $name;
    protected $price;
    protected $description;
}

Esta clase normalmente se llama “entidad” o “entity”, lo que significa que es una clase muy sencilla que sólo se utiliza para almacenar datos. Aunque se trata de una clase muy básica, cumple su objetivo de representar a los productos de tu aplicación. No obstante, esta clase no se puede guardar en una base de datos — es sólo una clase PHP simple.
Una vez aprendidos los conceptos fundamentales de Doctrine, podrás generar las clases de tipo entidad más fácilmente con el siguiente comando. Una vez ejecutado, Doctrine te hará varias preguntas para generar la entidad de forma interactiva:

$ php app/console doctrine:generate:entity
3.- Añadir información de mapeo

Imagina que estás desarrollando una aplicación en la que vas a mostrar productos. Olvidándote de Doctrine y de las bases de datos, seguramente estás pensando en utilizar un objeto Producto para representar a los productos. Crea esta clase dentro del directorio Entity del bundle PonceelrelajadoDemoBundle:
Trabajar con Doctrine es mucho más interesante que hacerlo directamente con la base de datos. En vez de trabajar con filas y tablas, Doctrine te permite guardar y obtener objetos enteros a partir de la información de la base de datos. El truco para que esto funcione consiste en mapear una clase PHP a una tabla de la base de datos y después, mapear las propiedades de la clase PHP a las columnas de esa tabla, de manera que sólo tienes que añadir algunos metadatos a la clase PHP para configurar cómo se mapean la clase Producto y sus propiedades.

// src/Ponceelrelajado/DemoBundle/Entity/Producto.php
namespace Ponceelrelajado\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
 * @ORM\Entity
 * @ORM\Table(name="producto")
 */
class Product
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;
    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $name;
    /**
     * @ORM\Column(type="decimal", scale=2)
     */
    protected $price;
    /**
     * @ORM\Column(type="text")
     */
    protected $description;
}
4.- Generando getters y setters

Doctrine ya sabe cómo persistir los objetos de tipo Producto en la base de datos, pero esa clase no es muy útil por el momento. Cómo Producto es una clase PHP normal y corriente, es necesario crear métodos getters y setters (getName(), setName(), etc.) para poder acceder a sus propiedades (porque son de tipo protected). Como esto es bastante habitual, existe un comando para que Doctrine añada estos métodos automáticamente:

$ php app/console doctrine:generate:entities Ponceelrelajado/DemoBundle/Entity/Product

Este comando asegura que la clase Product contiene todos los getters y setters necesarios. Puedes ejecutar de forma segura este comando una y otra vez, ya que los getters y setters sólo se generan si no existen (no se borran los métodos existentes).
Es importante destacar que los getters y setters generados por Doctrine son muy básicos, por lo dependiendo de la complejidad de tu aplicación, tendrás que ajustar el código a tus necesidades.
Utilizando el comando doctrine:generate:entities puedes:

  1. generar getters y setters;
  2. generar las clases de tipo Repository configuradas con la anotación @ORM\Entity(repositoryClass=”…”);
  3. generar el constructor adecuado para relaciones 1:n (uno a muchos) y n:m (muchos a muchos).

Para evitar problemas, el comando doctrine:generate:entities guarda una copia de seguridad del archivo Producto.php original en el archivo Producto.php~ (la diferencia está en el carácter ~ al final del nombre). En ocasiones esta copia de seguridad puede provocar en la aplicación errores de tipo “Cannot redeclare class”. Si se producen esos errores, puedes borrar este archivo sin problemas. También puedes añadir la opción –no-backup al comando anterior para no generar estas copias de seguridad.
También puedes generar a la vez todas las entidades de un bundle (es decir, cualquier clase PHP con información de mapeo de Doctrine) o de un espacio de nombres:

$ php app/console doctrine:generate:entities PonceelrelajadoDemoBundle
$ php app/console doctrine:generate:entities Ponceelrelajado
5.- Crear tabla en la base de datos

Aunque tienes una clase Product utilizable con información de mapeo para que Doctrine sepa persistirla, todavía no tienes su correspondiente tabla producto en la base de datos. Afortunadamente, Doctrine puede crear automáticamente todas las tablas necesarias en la base de datos (una para cada entidad conocida de tu aplicación). Para ello, ejecuta el siguiente comando:

$ php app/console doctrine:schema:update --force

En realidad, este comando es muy poderoso. Internamente compara la estructura que debería tener tu base de datos (según la información de mapeo de tus entidades) con la estructura que realmente tiene y genera las sentencias SQL necesarias para actualizar la estructura de la base de datos.
En otras palabras, si añades una nueva propiedad a la clase Producto y ejecutas este comando otra vez, se genera una sentencia de tipo ALTER TABLE para añadir la nueva columna a la tabla producto existente.
Una vez hayas ejecutado este comando, la base de datos cuenta ahora con una tabla llamada producto completamente funcional, y sus columnas coinciden con los metadatos que has especificado en la clase Producto.php.

6.- Persistiendo objetos en la base de datos

Ahora que tienes mapeada una entidad Producto y su tabla producto correspondiente, ya puedes persistir la información en la base de datos. De hecho, persistir información dentro de un controlador es bastante sencillo. Añade el siguiente método al controlador DefaultController del bundle:

// src/Ponceelrelajado/DemoBundle/Controller/DefaultController.php
// ...
use Ponceelrelajado\DemoBundle\Entity\Producto;
use Symfony\Component\HttpFoundation\Response; 
public function createAction()
{
    /*En esta sección, creas una instancia y trabajas con el objeto $product como harías con cualquier otro objeto PHP normal.*/
    $product = new Producto();
    $product->setName('A Foo Bar');
    $product->setPrice('19.99');
    $product->setDescription('Lorem ipsum dolor');
    /*Esta línea obtiene el "entity manager" o gestor de entidades de Doctrine, que se utiliza para persistir y recuperar objetos hacia y desde la base de datos.*/
    $em = $this->getDoctrine()->getManager();
    /*El método persist() le dice a Doctrine que debe persistir el objeto $product, pero todavía no se genera (y por tanto, tampoco se ejecuta) la sentencia SQL correspondiente.*/
    $em->persist($product);
    /*Cuando se llama al método flush(), Doctrine examina todos los objetos que está gestionando para ver si es necesario persistirlos en la base de datos. En este ejemplo, el objeto $product aún no se ha persistido, por lo que el gestor de la entidad ejecuta una consulta de tipo INSERT y crea una fila en la tabla producto.*/
    $em->flush();
  return new Response('Created product id '.$product->getId());
}

No olvides crear una ruta que apunte a esta acción para poder probarla en el navegador, tal como se explica en el punto 2 del artículo Crear Bundles en Symfony, agrega estas lineas al final del archivo routing.yml de tu bundle:

routing.yml

# src/Ponceelrelajado/DemoBundle/Resources/config/routing.yml
nuevo:
    path:     /nuevo
    defaults: { _controller: PonceelrelajadoDemoBundle:Default:create }

De esta forma podrás crear tu primer objeto producto al acceder al siguiente enlace:
http://localhost/tu-directorio/web/app_dev.php/nuevo/

7.- Buscando objetos en la base de datos

Buscar información de la base de datos y recuperar en forma de objeto es todavía más fácil. Imagina que has configurado una ruta de la aplicación para mostrar la información de un producto a partir del valor de su id. El código del controlador correspondiente podría ser el siguiente:

public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('PonceelrelajadoDemoBundle:Producto')
        ->find($id);
    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }
    // ... (pasar el objeto $product a una plantilla)
}

Al realizar una consulta por un determinado objeto, Doctrine siempre utiliza lo que se conoce como “repositorio”. Estos repositorios son como clases PHP cuyo trabajo consiste en ayudarte a buscar las entidades de una determinada clase. Puedes acceder al repositorio de la entidad de una clase mediante el código:

$repository = $this->getDoctrine()
    ->getRepository('PonceelrelajadoDemoBundle:Product');

Una vez que obtienes el repositorio, tienes acceso a todo tipo de métodos útiles:

// consulta por la clave principal (generalmente 'id')
$product = $repository->find($id);
// métodos con nombres dinámicos para buscar un valor en función de alguna columna
$product = $repository->findOneById($id);
$product = $repository->findOneByName('foo');
// obtiene todos los productos
$products = $repository->findAll();
// busca productos basándose en el valor de una columna
$products = $repository->findByPrice(19.99);

También puedes utilizar los métodos findBy y findOneBy para obtener objetos en función de varias condiciones:

// busca un producto con ese nombre y ese precio
$product = $repository->findOneBy(array(
    'name'  => 'foo', 'price' => 19.99
));
// obtiene todos los productos con un nombre determinado
// y ordena los resultados por precio
$product = $repository->findBy(
    array('name'  => 'foo'),
    array('price' => 'ASC')
);

Al igual que el paso anterior. no olvides crear una ruta que apunte a esta acción para poder probarla en el navegador, agrega estas lineas al final del archivo routing.yml de tu bundle:
routing.yml

# src/Ponceelrelajado/DemoBundle/Resources/config/routing.yml

ver:

path:     /ver/{id}
    defaults: { _controller: PonceelrelajadoDemoBundle:Default:show }

De esta forma podrás ver tu primer objeto producto al acceder al siguiente enlace:
http://localhost/tu-directorio/web/app_dev.php/ver/1
Crea de esta misma forma las rutas para actualizar y eliminar, de esta forma podrás hacer las pruebas desde el navegador.

8.- Actualizar objeto en la base de datos

Una vez que hayas obtenido un objeto de Doctrine, actualizarlo es relativamente fácil. Supongamos que la aplicación dispone de una ruta que actualiza la información del producto cuyo id se indica:

public function updateAction($id)
{
    $em = $this->getDoctrine()->getManager();
    $product = $em->getRepository('PonceelrelajadoDemoBundle:Product')->find($id);
    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }
    $product->setName('New product name!');
    $em->flush();
    return $this->redirect($this->generateUrl('homepage'));
}

Actualizar un objeto requiere de tres pasos:

  1. Obtener el objeto utilizando Doctrine.
  2. Modificar el objeto.
  3. Invocar al método flush() del entity manager.
9.- Eliminar objeto en la base de datos

Eliminar objetos es un proceso similar, pero requiere invocar el método remove() del entity manager:

public function removeAction($id)
{
    $em = $this->getDoctrine()->getManager();
    $product = $em->getRepository('PonceelrelajadoDemoBundle:Product')->find($id);
    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }
    $em->remove($product);
    $em->flush();
    return $this->redirect($this->generateUrl('homepage'));
}

El método remove() avisa a Doctrine que quieres eliminar esa entidad, pero, esta no se ejecuta, hasta que se invoca el método flush()

Conclusión

En esta ocasión apreciamos cómo crear un esquema de base de datos funcional para una aplicación web en Symfony 2 a través de doctrine con una base de datos Mysql. y de cómo el uso de esta fabuloso ORM ayuda a manejar los datos de una forma bastante sencilla, en el artículo Tutorial: mini-backend de usuarios con Doctrine, puedes ver un ejemplo un poco más complejo de tablas relacionadas, para que puedas seguir dando rienda suelta a tu creatividad en el mundo Symfony.
En venideros tutoriales ahondare en otros tips y herramientas prácticas de desarrollo web, tanto a nivel de back-end como front-end. ¡No olvides compartir este artículo en tus redes sociales si te gustó!