Manipulando datos con Doctrine
En el capítulo anterior ya hemos configurado nuestra base de datos y hemos mostrado como, a partir de nuestra aplicación, crear la base de datos y las tablas. En este capítulo nos concentraremos en acceder a los datos de las tablas para consultarlos, insertarlos, actualizarlos y borrarlos. Al tener nuestro ORM bien configurado y nuestros Entities mapeando las tablas veremos como fácilmente tenemos acceso a los datos de las mismas.
De ahora en más cuando nos refiramos a una tabla dentro de Symfony hablaremos de un entity o entidad ya que este último es la forma en que Symfony ve a las tablas es decir objetos. Recordemos que nuestras entidades se encuentran dentro de nuestro Bundle en de carpeta src\MDW\DemoBundle\Entity\ y tenemos mapeadas las tablas “articles” y “comments”.
El acceso a nuestras entidades se hará por medio de un objeto de Doctrine llamado EntityManager que vamos a decir que sería como el administrador de las entidades y quién sabrá como interactuar con ellas. Para obtener este objeto simplemente, dentro de nuestro action lo invocamos de la siguiente manera:
$em = $this->getDoctrine()->getEntityManager();
Con esto ya tenemos la referencia a este objeto dentro de una variable “$em”. Ahora bien, para trabajar con los datos necesitaremos de un repositorio. Este repositorio sería el objeto que nos permite solicitar y actualizar datos y para obtenerlo simplemente usamos el nombre lógico de nuestra entidad de esta manera:
$em->getRepository('MDWDemoBundle:Articles');
Obteniendo Datos
Para obtener datos de las tablas tenemos varios métodos realmente mágicos:
- findAll(): Obtiene todos los registros de la tabla. Retorna un array.
- find(): Obtiene un registro a partir de la clave primaria de la tabla.
- findBy(): Obtiene los registros encontrados pudiendo pasar como argumentos los valores que irían dentro del WHERE. Retorna un array.
- findOneBy(): obtiene un registro pudiendo pasar como argumentos los valores que irían dentro del WHERE.
Veamos unos ejemplos de la utilización de estos métodos:
$em = $this->getDoctrine()->getEntityManager(); //-- Obtenemos todos los artículos de la tabla $articulos = $em->getRepository('MDWDemoBundle:Articles')->findAll(); //-- Obtenemos el artículo con el id igual a 5 $articulo = $em->getRepository('MDWDemoBundle:Articles')->find(5); //-- Obtenemos el artículo cuyo slug sea "articulo-1" $articulos = $em->getRepository('MDWDemoBundle:Articles')->findOneBy(array('slug' => 'articulo-1')); //-- Obtenemos todos los artículos de autor John Doe que sean de la categoría "Symfony" $articulos = $em->getRepository('MDWDemoBundle:Articles')->findBy( array( 'author' => 'John Doe', 'category' => 'Symfony' ) );
Caso de ejemplo
Para realizar nuestros ejemplos, crearemos unas páginas para nuestras pruebas. Primeramente como ya vimos en el capítulo 3 tenemos que crear nuestras rutas en el archivo src\MDW\DemoBundle\Resources\config\routing.yml, por lo tanto agreguemos el siguiente código:
articulo_listar: pattern: /articulos/listar defaults: { _controller: MDWDemoBundle:Articulos:listar } articulo_crear: pattern: /articulos/crear defaults: { _controller: MDWDemoBundle:Articulos:crear } articulo_editar: pattern: /articulos/editar/{id} defaults: { _controller: MDWDemoBundle:Articulos:editar } articulo_visualizar: pattern: /articulos/visualizar/{id} defaults: { _controller: MDWDemoBundle:Articulos:visualizar } articulo_borrar: pattern: /articulos/borrar/{id} defaults: { _controller: MDWDemoBundle:Articulos:borrar }
Como segundo paso crearemos un nuevo controlador dentro de nuestro Bundle con el nombre ArticulosController.php. El archivo lo crearemos dentro de src\MDW\DemoBundle\Controller y contendrá inicialmente el siguiente código con 5 actions para nuestras pruebas, uno por cada ruta creada anteriormente:
<?php namespace MDW\DemoBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; use MDW\DemoBundle\Entity\Articles; class ArticulosController extends Controller { public function listarAction() { } public function crearAction() { } public function editarAction($id) { } public function borrarAction($id) { } }
Ahora crearemos dos plantillas para usarlas como visualización de respuesta de nuestros actions. Los archivos los crearemos en src\MDW\DemoBundle\Resources\views\Articulos\ con los nombres listar.html.twig y articulo.html.twig.
- listar.html.twig: Conforme un array de artículos crea una tabla para mostrar datos:
<h1>Listado de Articulos</h1> <table border="1"> <tr> <th>ID</th> <th>Titulo</th> <th>Fecha de Creacion</th> </tr> {% for articulo in articulos %} <tr> <td>{{ articulo.id }}</td> <td>{{ articulo.title }}</td> <td>{{ articulo.created | date('d-m-Y') }}</td> </tr> {% endfor %} </table>
- articulo.html.twig: Conforme a un artículo muestra sus datos:
<h1>Articulo con ID {{ articulo.id }}</h1> <ul> <li>Titulo: {{ articulo.title }}</li> <li>Fecha de creacion: {{ articulo.created | date('d-m-Y') }}</li> </ul>
Ahora ya tenemos nuestro código inicial y así que comencemos a trabajar con los datos.
Manipulando datos
1. Inserción de datos
Primeramente trabajaremos en el método crearAction() de nuestro ArticulosController en donde usaremos la entidad Articles para insertar registros. Para esto es bueno notar que en las primeras lineas de nuestro controlador estamos importando el namespace de la entidad con la palabra reservada “use”.
Crearemos un objeto nuevo de la manera tradicional:
$articulo = new Articles(); $articulo->setTitle('Articulo de ejemplo 1'); $articulo->setAuthor('John Doe'); $articulo->setContent('Contenido'); $articulo->setTags('ejemplo'); $articulo->setCreated(new \DateTime()); $articulo->setUpdated(new \DateTime()); $articulo->setSlug('articulo-de-ejemplo-1'); $articulo->setCategory('ejemplo');
Usando los setters insertamos los datos y pongamos atención en que no usamos el setId() ya que le dijimos a nuestra entidad que se encargue de generarlo por medio de la anotación @ORM\GeneratedValue(strategy=”AUTO”). Tambień notemos que para asignar las fechas de creación y modificación cargamos un objeto DateTime nuevo agregándole una barra invertida adelante para especificar que es una clase del CORE de php ya que si no la ponemos va a esperar que importemos un namespace tal cual como lo hicimos con la clase Articles.
Una vez que tenemos nuestro objeto creado por medio del EntityManager le diremos que sea insertado con el siguiente código:
$em = $this->getDoctrine()->getEntityManager(); $em->persist($articulo); $em->flush();
Con el método persist() le decimos que el objeto pasado por argumento sea guardado para ser insertado y la inserción en sí se realizará cuando ejecutemos el método flush(). Con esta orden Doctrine generará la sentencia INSERT necesaria. Finalmente vamos a invocar a nuestra plantilla a quién le pasaremos el artículo para que muestre sus datos:
return $this->render('MDWDemoBundle:Articulos:articulo.html.twig', array('articulo' => $articulo));
2. Actualizando datos
La actualización de datos es exactamente igual a la inserción con la diferencia que estamos modificando un objeto Articles ya existente, para este ejemplo crearemos el siguiente código en el método editarAction($id):
$em = $this->getDoctrine()->getEntityManager(); $articulo = $em->getRepository('MDWDemoBundle:Articles')->find($id); $articulo->setTitle('Articulo de ejemplo 1 - modificado'); $articulo->setUpdated(new \DateTime()); $em->persist($articulo); $em->flush(); return $this->render('MDWDemoBundle:Articulos:articulo.html.twig', array('articulo' => $articulo));
El método obtiene el id que llega por GET y por medio del EntityManager obtiene el registro y nos devuelve el objeto al cual, usando los setters asignamos los cambios y de la misma forma que la inserción ejecutamos el persist() y el flush(). Esto creará un update en lugar de un insert ya que doctrine puede identificar perfectamente que el artículo ya existe en la base de datos porque ya fue persistido con anterioridad.
3. Mostrando un listado de artículos
Para obtener todos los artículos de una entidad ya vimos el método findAll() por lo que tendremos el siguiente código dentro del método listarAction() para luego enviar el array de Articles a la vista que se encargará de mostrarlos en una tabla.
$em = $this->getDoctrine()->getEntityManager(); $articulos = $em->getRepository('MDWDemoBundle:Articles')->findAll(); return $this->render('MDWDemoBundle:Articulos:listar.html.twig', array('articulos' => $articulos));
4. Eliminar un artículo
Para eliminar un artículo simplemente lo obtenemos e invocamos el método remove() que marcará el artículo para ser borrado hasta que ejecutemos el método flush(). Este código lo pondremos en el método borrarAction()
$em = $this->getDoctrine()->getEntityManager(); $articulo = $em->getRepository('MDWDemoBundle:Articles')->find($id); $em->remove($articulo); $em->flush(); return $this->redirect( $this->generateUrl('articulo_listar') );
Una vez que lo borramos, redirigimos al usuario a la ruta “articulo_listar”.
Formas alternativas de obtener datos
1. Extensión de los métodos findBy() y findOneBy()
Los métodos findBy() y findOneBy() tienen otros métodos similares conocidos como findBy*() y findOneBy*() donde el asterísco representa cualquier propiedad de nuestra entidad:
//-- Obtenemos todos los artículos de la categoría 'Symfony' $articulos = $em->getRepository('MDWDemoBundle:Articles')->findByCategory('Symfony'); //-- Obtenemos el artículo con el slug 'artículo-1' $articulo = $em->getRepository('MDWDemoBundle:Articles')->findOneBySlug('articulo-1');
2. Utilizando las claves foráneas
Otra de las formas de obtener datos es por medio de las claves foráneas las cuales fueros configuradas en nuestra entidad por medio de los annotatios @ManyToOne, @OneToMany. Una vez que obtemos por ejemplo un artículo podríamos obtener todos sus comentarios de la siguiente manera.
$em = $this->getDoctrine()->getEntityManager(); $articulo = $em->getRepository('MDWDemoBundle:Articles')->findOneBySlug('articulo-de-ejemplo-1'); $comentarios = $articulo->getComments(); foreach($comentarios as $c) { echo $c->getContent(); }
Al utilizar la clave foránea configurada en nuestra entidad invocando al getter getComments(), doctrine se encargará se generar la sentencia SELECT necesaria para obtener todos los comentarios.
3. Generando DQL
Por si las formas de obtener datos que ya vimos nos quedan cortas, cosa que por lo general es así, Doctrine nos permite trabajar con algo muy parecido al SQL estándar al que estamos acostumbrados a trabajar solo que como estamos trabajando con el ORM se llama DQL es decir Doctrine Query Language.
El DQL es realmente muy parecido al SQL con la diferencia que en lugar de hacer queries contra registros de las tablas, los hacemos sobre objetos de tipo Entity, por ejemplo un select bien sencillo:
SELECT * FROM articles
en DQL sería:
select a from MDWDemoBundle:Articles a
donde la “a” es nada más que un simple alias que podemos llamar como queramos. El cambio principal se nota en que en lugar se hacer referencia a la tabla articles estamos haciendo referencia a la entidad MDWDemoBundle:Articles. Con esta sintaxis estamos dejando que doctrine se encargue de la traducción al SQL necesario para el motor de base de datos utilizado y configurado inicialmente.
También es posible pedir solo algunos campos y no un SELECT * poniendo los nombres de las propiedades del objeto usando el alias:
select a.id, a.title, c.author from MDWDemoBundle:Articles a
Para decirle a Doctrine que ejecute este DQL lo hacemos a través del EntityManager de la siguiente manera:
$em = $this->getDoctrine()->getEntityManager(); $dql = "select a from MDWDemoBundle:Articles a"; $query = $em->createQuery($dql); $articulos = $query->getResult();
Con el código anterior utilizamos el DQL para generar un objeto de Doctrine llamado “Doctrine_Query” representado por $query y luego a este objeto le pedimos que nos devuelva los resultados invocando al getResult() lo que nos devolverá un array de objetos Articles y para acceder a sus datos simplemente utilizamos los getters del objeto. Por ejemplo si quisieramos recorrer el array de articulos y obtener el id lo haríamos así ya que siguen siendo objetos metidos dentro de un array:
foreach($articulos as $articulo) { $id = $articulo->getId(); $title = $articulo->getTitle(); }
En caso de necesitar pasar filtros para el WHERE, podemos hacerlo usando el método setParameter() del objeto Doctrine_Query de la siguiente manera:
$em = $this->getDoctrine()->getEntityManager(); $dql = "select a from MDWDemoBundle:Articles a where a.author=:author and a.title like :title"; $query = $em->createQuery($dql); $query->setParameter('author', 'John Doe'); $query->setParameter('title', '%ejemplo 1%'); $articulos = $query->getResult();
Con la utilización del setParameter() ya no nos preocupamos de poner por ejemplo comillas a los filtros que no son numéricos ya que Doctrine ya sabe de que tipo de dato es cada columna por medio de la definición que hicimos de la entidad.
También tenemos por supuesto soporte para unir entidades por medio de la cláusula JOIN por lo que este SQL estándar lo podríamos convertir a DQL de la siguiente manera:
$em = $this->getDoctrine()->getEntityManager(); $dql = "select a.id, a.title, c.author from MDWDemoBundle:Comments c join c.article a where a.author=:author and a.title like :title"; $query = $em->createQuery($dql); $query->setParameter('author', 'John Doe'); $query->setParameter('title', '%ejemplo 1%'); $articulos = $query->getResult();
Hay una diferencia a la hora de obtener los datos. Ya que estamos obteniendo una mezcla de datos de articulos y comentarios, el método getResult() nos devuelve todo ya directamente en un array como siempre estuvimos acostumbrados a trabajar con PDO por lo tanto la estructura del array devuelto sería la siguiente:
Array ( [0] => Array ( [id] => 4 [title] => Articulo de ejemplo 1 [author] => Autor 1 ) [1] => Array ( [id] => 4 [title] => Articulo de ejemplo 1 [author] => Autor 2 ) )
4. Utilizando el Repositorio
En el capítulo anterior cuando nos ocupamos de crear nuestras entidades Articles y Comments con el generador doctrine:entity:create, se nos hizo una pregunta sobre si queríamos que el generador nos cree un repositorio vacío para la entidad a crear y hemos dicho que sí. Esto hizo que se cree un segundo archivo a parte de la entidad llamado NombreEntidadRepository.php. Para nuestro ejemplo hemos creado el ArticlesRepository.php y el CommentsRepository.php.
Estos archivos se utilizan para organizar sentencias DQL de una entidad en cuestión. Por ejemplo, en lugar de tener todos los códigos de DQL escritos más arriba esparcidos por nuestros Controladores, podríamos ( y deberíamos para mantener el código más ordenado y mantenible) tener todos las consultas relacionadas con los articulos dentro de nuestro repositorio ArticlesRepository.php. Esto es muy útil ya que desde el primer capítulo hablamos que Symfony intenta mantener todas las cosas en su lugar y es realmente útil.
En este mismo momento nuestro repositorio de artículos se encuentra de la siguiente manera:
<?php namespace MDW\DemoBundle\Entity; use Doctrine\ORM\EntityRepository; /** * ArticlesRepository * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ class ArticlesRepository extends EntityRepository { }
Dentro de esta clase crearemos métodos que serán cada una de nuestras consultas . Fijémonos que la clase hereda de EntityRepository lo cual ya nos da ciertas ventajas por ejemplo que para obtener el EntityManager simplemente tenemos que invocarlo como $this->getEntityManager(). Así que podríamos tener por ejemplo un método para obtener artículos de un autor con un cierto contenido en el título creando el siguiente método:
public function findArticlesByAuthorAndTitle($author, $title) { $em = $this->getEntityManager(); $dql = "select a.id, a.title, c.author from MDWDemoBundle:Comments c join c.article a where a.author=:author and a.title like :title"; $query = $em->createQuery($dql); $query->setParameter('author', $author); $query->setParameter('title', '%' . $title . '%'); $articulos = $query->getResult(); return $articulos; }
Una vez que tengamos nuestros métodos en el repositorio lo accedemos de la misma forma que los métodos find() o findAll() ya vistos dentro de nuestros actions en los controladores:
$em = $this->getDoctrine()->getEntityManager(); $articulos = $em->getRepository('MDWDemoBundle:Articles')->findArticlesByAuthorAndTitle('John Doe', 'ejemplo 1');
En cada repositorio podemos tener la cantidad de métodos que necesitemos y hasta podríamos hacer que métodos genéricos que reutilicen otros métodos de la misma clase lo cual, si pensamos en una aplicación que puede llegar a utilizar muchas sentencias SQL lograríamos tener un código mucho más ordenado y por supuesto también tenemos identificado donde pueden ocurrir los errores ya que cada cosa está en su lugar.
Resumen Final
Si pensáramos nuevamente en la arquitectura Model-View-Controller (MVC) estaríamos viendo que la presentación de los datos (View) se encuentran en las plantillas, la programación del modelado de datos y trabajo con los mismos (Model) se encuentran en las Entidades y Repositorios.
Por último nos queda la lógica de la aplicación (Controller) que se mantendrá dentro de los actions programados dentro de cada Controlador. Esto al mirarlo desde un enfoque de arquitectura de software demuestra un proyecto muy ordenado y por sobre todo con facilidades de mantener y escalar a futuro, con un equipo de desarrollo donde cada quién maneja su parte. Algo que siempre suelo decir es que si una página llega a tener más de 20 líneas de código es porque nos estamos olvidando de modularizar y es probable que una de las partes del MVC no se encuentre en el lugar correcto.
En el siguiente capítulo, el último orientado al modelado, hablaremos sobre la validación de los datos que Doctrine nos proporciona y sobre como Symfony nos ayuda a crear objetos de tipo formularios para que los usuarios finales interactuen con la aplicación.
[…] datos con Doctrine VN:F [1.9.16_1159]please wait…Rating: 0.0/5 (0 votes cast)Un nuevo capítulo lanzado de la guía de Symfony2 publicado en Maestros del Web. En esta entrega les hablo sobre como […]
Excelente capítulo y muy bien explicado, sigo el curso desde que inicio el primer capítulo. Gracias a ustedes entiendo Symfony 2.
que tal amigos. les informo que la visualizacion del codigo no se ve completa en dispositivos mobiles algo esta fallando… en su diseño web
Estoy leyendo el tuto desde que salió, ha sido excelente.
Excelente tutorial, solo tengo dos observaciones, al probarlo me genera un error al crear el articulo ya que no existe ningún metodo setCategory(); asi que comenté la linea 30 del ArticulosController.php //$articulo->setCategory(‘ejemplo’);
El otro punto es que al borrar me dice que el metodo delete() is undefine, lo cambié por remove() y funcionó muy bien.
Por todo lo demas me ha parecido un excelente trabajo en especial para quienes como yo estamos empezando a conocer este framework.
Gracias por el comentario Luis.
Con relación al delete() tienes toda la razón, el método correcto es remove(). Ya se ha corregido.
Con respecto al otro tema, debería de existir el setCategory() ya que si ves el capítulo anterior fue el último campo que creamos para la tabla Article como un string de 255 caracteres:
New field name (press to stop adding fields): category to stop adding fields):
Field type [string]:
Field length [255]:
New field name (press
Do you want to generate an empty repository class [no]? yes
Hola Juan excelente articulo, estoy trabajando esta parte de tu articulo usando doctrine, tengo un problema al crear un objeto apartir de un controlador donde tengo mis metodos usando doctrine pero el problema es de una function del controller.php de la funcion has() cuando en su momento se utiliza el error es el siguiente:
“Call to a member function has() on a non-object”
El ejercicio de esto es utilizar metodos de una clase donde utilizo doctrine, esa dicha clase es de un controlador X que cuando creo el objeto y empiezo a instanciar me surge ese error queria saber cual es mi problema, estoy haciando la mal declaracion o no debe de hacerlo de esa manera?
Saludos…
Hola Juan
Tienes razon el error fue mio al no incluir el campo category, gracias por la rápida respuesta.
A ver José Guillermo. Creo no estar entendiendo la pregunta.
En un principio me estás hablando que usas doctrine donde tienes un objeto (entiendo que te refieres a un entity) pero luego me dices que no puedes usar una función (método) del controlador y es ahí donde me confundo porque no entiendo si el método que te da el error que dice que no existe ese método en el objeto que utilizas es del controlador o de un Entity.
Me puedes explicar un poco por favor para tratar de ayudarte?
Excelente capitulo!!! Cada vez comprendo más el framework Symfony2. Gracias por tu gran aporte!
Hola, te hago una consulta, siyo quero ordenar los datos obtenidos por findall, por otro campo que no sea id, o en ves de que sea acendente sea desendente osea que en ves de que se muestre el mas chico primero muestre alrevez. como lo aclaro en la consulta?
Muchas gracias.
En mi opinión Nahuel siempre prefiero un DQL ya que ahí tengo el control completo de la sentencia. Puedes ir creándolos en tu Repository y podrías así tener tus propios findAll. Hasta podrías crear un Repository tuyo que extienda de la clase Repository y hacer algo genérico
Hola, para actualizar una entrada, tengo una duda, no soy capaz de entenderlo a traves del manual de doctrine y aquí explicais lo mismo en castellano, así que os pregunto directamente:
$articulo = $em->getRepository(‘MDWDemoBundle:Articles’)->find($id);
decís que recoge la $id del método GET, pero si yo lo que tengo es una tabla con id, idpropia, name, cont.
Y lo que quiero es pasarle los valores de un array en caso de que no exista ya una fila, objeto en este caso, con la misma idpropia; en caso de existir ya, actualizarlo, en caso de no existir crearlo. Debería hacerlo con DQL? o me valdría tal vez un $exists=$em->getRepository(‘MDWDemoBundle:Articles’)->FindByIdpropia y luego un if !($exists) X else Yl?
Que buen tutorial, increible desde el primer capitulo.
Muchas gracias por compartir sus conocimientos y más para los que estamos dispuestos a utilizar un framework para trabajar con PHP.
buenas tardes, tengo el siguiente problema cuando intento obtener los comentarios:
Notice: Undefined index: Articulos in C:\xampp\htdocs\PhpProject1\vendor\doctrine\lib\Doctrine\ORM\Persisters\BasicEntityPersister.php line 1280
no se que pueda estar ocurriendo, lo unico que hice fue cambiar el nombre de la tabla en la base de datos, no se llama articles sino articulos, adjunto los fragmentos de codigo del controller y de los entity por si es necesario
ArticulosController
public function obtenerComentariosAction($idArticulo)
{
$entityManager = $this->getDoctrine()->getEntityManager();
$articulo = $entityManager->getRepository(‘MDWDemoAnderBundle:Articulos’)->find($idArticulo);
$comentarios = $articulo->getComments();
foreach($comentarios as $c)
{
echo $c->getContent();
}
return $this->render(‘MDWDemoAnderBundle:Comentarios:Listar.html.twig’, array(‘comentarios’ => $comentarios));
}
CommentsEntity
/**
* @ORM\ManyToOne(targetEntity=”Articulos”, inversedBy=”Comments”)
* @ORM\JoinColumn(name=”article_id”, referencedColumnName=”id”)
* @return integer
*/
private $articulo;
ArticulosEntity
/**
* @ORM\OneToMany(targetEntity=”Comments”, mappedBy=”Articulos”)
*/
private $Comments;
public function _contructor()
{
$this->Comments = new \Doctrine\Common\Collections\ArrayCollection();
}
Yo habilité el php_pgsql.dll en el PHP.ini dentro WAMP para crear las tablas con postgresSQL. Todas me las creo perfectamente y en el archivo parameters.init puse en el database_driver= pdo_pgsql.
Ahora cuando voy a insertar o a consultar los datos me da un error:
PDOException: could not find driver (uncaught exception) at C:\wamp\www\Symfony\vendor\doctrine-dbal\lib\Doctrine\DBAL\Driver\PDOConnection.php line 36
Que podria ser???
Muy bueno el curso