Symfony incluye la librería Doctrine para manejar la persistencia de información en una base de datos. Doctrine no tiene ninguna relación directa con Symfony, su uso es opcional.

Mediante el ORM ( Object Relational Model ) permite manejar la información de la base de datos a modo de objetos en PHP. Para realizar consultas SQL se utiliza la librería DBAL de Doctrine en lugar del ORM .

Tiene soporte para las siguientes plataformas:

  • MySQL . Utilizando la extensión PDO Mysql .
  • PgSQL . Utilizando la extensión PDO PostgresSQL .
  • Oracle . Utilizando la extensión OCI .
  • Sqlite Utilizando la extensión PDO SQLite .

Si prefieres utilizar MongoDB puedes hacer uso de la librería ODM de Doctrine , que es el equivalente al ORM pero para bases de datos no relacionales.

Creación de entidades

Doctrine incorpora un intérprete para línea de comandos muy útil. Uno de ellos sirve para la creación de entidades. Vamos a crear la primera entidad “Post” para nuestra aplicación.

$ php app/console doctrine:generate:entity
- The Entity shortcut name: AppBundle:Post
- Configuration format: annotation (por defecto)
- Add some fields:

    - New field name: title
    - Field type: string
    - Field length: 255

    - New field name: description
    - Field type: text

    - New field name: slug
    - Field type: string
    - Field length: 255

Do you want to generate an empty repository class [no]?: no

Do you confirm generation [yes]? : yes


Dentro de /src/AppBundle vemos que se ha creado un nuevo directorio Entity , donde se guardan todas las entidades de un bundle .

La clase Post.php ha sido generada con todos los campos indicados, las anotaciones correspondientes y los métodos get y set de cada uno de ellos.

Las entidades hacen uso del ORM de Doctrine mediante la anotación @ORM . Para saber más sobre el mapeo de entidades y sus propiedades echa un vistazo a la documentación oficial de Doctrine :

El listado de entidades para nuestra aplicación será el siguiente:

  • Post
  • Category
  • Tag
  • Comment

Más adelante definiremos las relaciones entre ellas. Antes, vamos a ver las utilidades que ofrece el CLI de Doctrine .

Una vez que hayamos definido una o más entidades, ya podemos crear el esquema de la base de datos y validarlo:

$ php app/console doctrine:schema:create
$ php app/console doctrine:schema:validate


Si se produce algún error, revisa la configuración para la conexión a base de datos en el archivo /app/config/parameters.yml

Una vez creado el esquema de la base de datos, podemos actualizarlo de la siguiente forma si hacemos algún cambio en nuestras entidades.

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


Para eliminar la base de datos por completo:

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


Para regenerar el constructor y los métodos set y get de una entidad tras modificar sus campos:

$ php app/console doctrine:generate:entities AppBundle:Post

Esquema de base de datos para la aplciación de ejemplo my_blog

  • Post
    • title (string)
    • description (text)
    • slug (string)
    • category (ManyToOne)
    • tags (ManyToMany)
    • comments (OneToMany)
  • Category
    • name (string)
    • slug (string)
    • posts (OneToMany)
  • Tag
    • name (string)
    • slug (string)
    • posts (ManyToMany)
  • Comment
    • content (text)
    • post (ManyToOne)

Doctrine permite dos tipos de relaciones entre entidades: unidireccional y bidireccional. A nivel de SQL no hay diferencia. Sí la hay a nivel de ORM , ya que éste maneja las entidades a modo de objetos PHP.

En una relación bidireccional las dos entidades pueden acceder al modelo de su relación e interactuar entre sí. En las relaciones unidireccionales sólo la entidad propietario(también llamada entidad fuerte o entidad padre) puede acceder al modelo de la entidad con la que está relacionada.

En la documentación de Doctrine, Mapeo de entidades se exponen todas las opciones posibles.

Volvemos ahora a la clase Post para ver de qué forma se establecen las relaciones en nuestra aplicación.


<?php

// Post - Category

class Post
{
  /**
   * @ORM\ManyToOne(targetEntity="Category", inversedBy="posts")
   * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
   **/
  private $category;

}

class Category
{
  /**
   * @ORM\OneToMany(targetEntity="Post", mappedBy="category")
   **/
  private $posts;

  /**
   * Constructor
   */
  public function __construct()
  {
      $this->posts = new \Doctrine\Common\Collections\ArrayCollection();
  }  

}


Esta es una relación 1:N bidireccional. En este caso Category es la entidad propietario, ya que la entidad Post es la que almacena la clave foránea de la relación a través del campo category_id .

El constructor también se genera de forma automática en el caso de que una relación sea del tipo 1:N ó N:M .

La propiedad targetEntity sirve para indicar con qúe entidad se establece la relación. Basta con indicar el nombre de la clase si ambas están en el mismo directorio. Si queremos referenciar una entidad de otro bundle hay que indicar el namespace completo.

* @ORM\ManyToOne(targetEntity="UploadFilesBundle\Entity\File", inversedBy="posts")


Una entidad propietario es siempre la que contiene el atributo mappedBy en la definición de su relación. La entidad débil contiene el atributo inversedBy .

JoinColumn establece que el campo en la tabla Post que hace referencia a Category tendrá el nombre de category_id y la columna referenciada de la entidad Category es su id .

Imagen 0 en Symfony2 tutorial: Modelo: base de datos con Doctrine

El resto de relaciones quedarían de la siguiente manera:


<?php

// Post - Tag

class Post
{
  /**
   * @ORM\ManyToMany(targetEntity="Tag", inversedBy="posts")
   * @ORM\JoinTable(name="posts_tags")
   **/
  private $tags;

  /**
   * Constructor
   */
  public function __construct()
  {
      $this->tags = new \Doctrine\Common\Collections\ArrayCollection();
  }  

}

class Tag
{
  /**
   * @ORM\ManyToMany(targetEntity="Post", mappedBy="tags")
   **/
  private $posts;

  /**
   * Constructor
   */
  public function __construct()
  {
      $this->posts = new \Doctrine\Common\Collections\ArrayCollection();
  }

}


Como resultado de la relación ManyToMany entre Post y Tag se crea una tabla intermedia que contiene el id de ambas tablas.

Imagen 1 en Symfony2 tutorial: Modelo: base de datos con Doctrine


<?php

// Post - Comment

class Post
{
  /**
   * @ORM\OneToMany(targetEntity="Comment", mappedBy="post")
   **/
  private $comments;

  /**
   * Constructor
   */
  public function __construct()
  {
        $this->comments = new \Doctrine\Common\Collections\ArrayCollection();
  }  

}

class Comment
{
  /**
   * @ORM\ManyToOne(targetEntity="Post", inversedBy="comments")
   * @ORM\JoinColumn(name="post_id", referencedColumnName="id")
   **/
  private $post;

}

Gestor de entidades


El entity manager es un gestor de entidades de Doctrine . Se utiliza para persistir, recuperar o eliminar información referente a objetos desde la base de datos. En un controlador, se obtiene de esta forma:


$em = $this->getDoctrine()->getManager();


Ejemplo para la creación de un registro en la entidad Comment :


<?php

// src/AppBundle/Controller/CommentsController.php

use AppBundle\Entity\Comment;

public function newAction(Request $request)
{
    // Entity Manager
    $em = $this->getDoctrine()->getManager();

    // Variables obtenidas de un formulario
    $postId = $request->get('post_id');
    $content = $request->get('content');

    // Creación del objeto
    $comment = new Comment();
    $product->setContent($content);

    // Relación con Post
    $post = $em->getRepository('AppBundle:Post')->find($postId);
    $comment->setPost($post);

    $em->persist($comment);
    $em->flush();

    return $this->render('public/post.html.twig', array('comment' => $comment));
}


El método persist() le indica a Doctrine que debe persistir el objeto. flush() ejecuta la sentencia SQL correspondiente e inserta el registro en la base de datos de la forma más eficiente posible. remove() elimina el objeto.

Para realizar una consulta por un determinado tipo de objeto se utiliza un repositorio . Es una clase PHP que facilita el trabajo a la hora de buscar por entidades. Se accede a él con el siguiente método:

$em->getRepository('AppBundle:Post');


Algunos ejemplos de métodos que podemos utilizar son:

<?php

// Busca por id. Devuelve un objeto.
$post = $em->getRepository('AppBundle:Post')->find($postId);

// Busca todos los objetos de una entidad. Devuelve un array de objetos (ArrayCollection en Doctrine)
$posts = $em->getRepository('AppBundle:Post')->findAll();

// Busca por el campo que se le indique de una entidad, en este caso 'title'. Devuelve un objeto.
$post = $em->getRepository('AppBundle:Post')->findOneByTitle('Symfony es genial');

// Igual que el anterior pero devolviendo una ArrayCollection
$post = $em->getRepository('AppBundle:Post')->findByTitle('Symfony es genial');

// Busca todos los objetos cuyo campo 'published' sea 'true', ordenados por fecha de creación
$post = $em->getRepository('AppBundle:Post')->findBy(
    array('published'  => true),
    array('created_at' => 'ASC')
);