De nuevo continuando con el post anterior sobre cómo trabajar modificaciones en la base de datos, vengo hoy con otro HOWTO para modificar los datos de nuestra aplicación web que tenemos en la BD. Es decir, no para modificar la estructura de la BD como en el post anterior, sino para crear, listar, modificar o borrar los datos con Doctrine. Para los nuevos, Doctrine es la herramienta que viene con Symfony Flex, Symfony 4 para trabajar la persistencia de los datos.
Así que, continuando con el ejemplo del post anterior..
Creando un controlador para probar en el navegador
Continuando con el proyecto creado en el post anterior, llamado symfony-tutorial-working-with-databases, vamos a crear un controlador con las 4 acciones:
php bin/console make:controller
Si le llamamos creandoListadoModificandoBorrando, nos dará el siguiente código:
<?php namespace App\Controller; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class CreandoListadoModificandoBorrandoController extends Controller { /** * @Route("/creando/listado/modificando/borrando", name="creando_listado_modificando_borrando") */ public function creando() { return $this->render('creando_listado_modificando_borrando/index.html.twig', [ 'controller_name' => 'CreandoListadoModificandoBorrandoController', ]); } }
Ahora vamos a modificarlo para hacer las acciones
El código que puede quedar, una vez editado el controlador de src/Controller/CreandoListadoModificandoBorrandoController.php, es algo como esto:
<?php namespace App\Controller; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use App\Entity\Direccion; class CreandoListadoModificandoBorrandoController extends Controller { /** * @Route("/creando", name="creando") */ public function creando() { $entityManager = $this->getDoctrine()->getManager(); $debug = ''; for ($i = 1; $i < 10; ++$i) { $address = new Direccion(); $address->setNombre('Nombre '.$i); $address->setMovil('Móvil '.$i); $address->setDireccion('Dirección '.$i); $address->setEmail('Email '.$i); $address->setFijo('Fijo '.$i); $entityManager->persist($address); $debug .= 'Guardando.. '.$address->getNombre().'<br>'; } $entityManager->flush(); return $this->render('creando_listado_modificando_borrando/index.html.twig', [ 'controller_name' => 'CreandoListadoModificandoBorrandoController', 'debug' => $debug, ]); } /** * @Route("/listando", name="listando") */ public function listando() { $entityManager = $this->getDoctrine()->getManager(); $debug = ''; $addresses = $entityManager->getRepository('App:Direccion')->findAll(); foreach ($addresses as $address) { $debug .= $address->getNombre().'<br>' .$address->getMovil().'<br>' .$address->getDireccion().'<br>' .$address->getEmail().'<br>' .$address->getFijo().'<br><br>'; } return $this->render('creando_listado_modificando_borrando/index.html.twig', [ 'controller_name' => 'CreandoListadoModificandoBorrandoController', 'debug' => $debug, ]); } /** * @Route("/editando", name="modificando") */ public function editando() { $entityManager = $this->getDoctrine()->getManager(); $debug = ''; $addresses = $entityManager->getRepository('App:Direccion')->findAll(); foreach ($addresses as $address) { $address->setNombre('Editando '.$address->getNombre()); $entityManager->persist($address); $debug .= 'Guardando editado.. '.$address->getNombre().'<br>'; } $entityManager->flush(); return $this->render('creando_listado_modificando_borrando/index.html.twig', [ 'controller_name' => 'CreandoListadoModificandoBorrandoController', 'debug' => $debug, ]); } /** * @Route("/borrando", name="borrando") */ public function borrando() { $entityManager = $this->getDoctrine()->getManager(); $debug = ''; $addresses = $entityManager->getRepository('App:Direccion')->findAll(); foreach ($addresses as $address) { $entityManager->remove($address); $debug .= 'Borrando.. '.$address->getNombre().'<br>'; } $entityManager->flush(); return $this->render('creando_listado_modificando_borrando/index.html.twig', [ 'controller_name' => 'CreandoListadoModificandoBorrandoController', 'debug' => $debug, ]); } }
Y la plantilla Twig que nos ha autogenerado Symfony en templates/creando_listado_modificando_borrando/index.html.twig también editada:
{% extends 'base.html.twig' %} {% block title %}Hello {{ controller_name }}!{% endblock %} {% block body %} <style> .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } </style> <div class="example-wrapper"> <h1>Hello {{ controller_name }}! ?</h1> <p>This is debug info:</p> <code>{{ debug | raw }}</code> </div> {% endblock %}
Terminando, probando los resultados
Sólo nos queda lanzar el servidor de desarrollo para probar en el navegador los resultados. Así que lanzamos el servidor:
php bin/console server:start
Y tenemos las siguientes direcciones que lanzar y juguetear para probar el código:
http://localhost:8000/creando
http://localhost:8000/listando
http://localhost:8000/editando
http://localhost:8000/borrando
Espero que sirva.
¡Un saludo!
Unos tutoriales de symfony excelentes, me estan ayudando a enterder como funciona, pero tengo una duda.
El $entityManager->flush() para que vale????
Saludos
Hola Jaime, muchas gracias por tu comentario!
$entityManager->flush() sirve para hacer efectivas las consultas en la base de datos.
Es decir, todas las consultas a la BD no se aplican hasta que hacer el flush del manejador de entidades de Doctrine. Si estás trabajando, por ejemplo, en una funcionalidad que hace muchas cosas durante mucho tiempo en la BD, conviene ir haciendo flush de vez en cuando. Cuando son scripts cortos, con hacer un flush al final de todo es suficiente.
Un saludo!
Entonces en el caso de listar, como no se van a realizar acciones sobre la BDD (añadir,modificar o eliminar) no es necesario el uso de flush() ¿Estoy en lo cierto?
Cierto, así es. En esos casos no necesitas hacer el flush. Puedes hacer consultas a la BD haciendo cosas como:
$contacts = $entityManager->getRepository(\App\Entity\Contact)->findAll();
$contacts = $entityManager->getRepository(\App\Entity\Contact)->findByName(‘Nombre’);
$contacts = $entityManager->getRepository(\App\Entity\Contact)->findBy(array(‘name’ => ‘Nombre’));
$contacts = $entityManager->createQuery(‘SELECT c FROM App:Contact’)->getResult();
etc..
Si quieres investigar más sobre este tema tienes que ver Doctrine, que es la herramienta que integra Symfony para todo esto. Tiene además un lenguaje propio de consultas de base de datos muy muy potente, y parecido a SQL, se llama DQL (Doctrine Query Language).
Saludos!
Buenas tardes de nuevo
Estoy intentando realizar un update, pero este me lanza el siguiente error
[Semantical Error] line 0, col 31 near ‘cursoscurso =’: Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
La tabla user está unida con una muchosmuchos con la tabla cursos
return $this->getEntityManager()
->createQuery(
«UPDATE App\Entity\User u SET u.cursoscurso = $idcurso WHERE u.id = $iduser»
)
->execute();
El problema seguramente lo tenga en u.cursoscurso y no lo entiendo, he esta investigando y al parecer no se puede combinar update con JOIN en DQL,
¿Cual podría ser el problema?
ENTIDAD USER
class User implements UserInterface
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type=»integer»)
*/
private $id;
/**
* @ORM\Column(type=»string», length=180, unique=true)
*/
private $email;
/**
* @ORM\Column(type=»json»)
*/
private $roles = [];
/**
* @var string The hashed password
* @ORM\Column(type=»string»)
*/
private $password;
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\ManyToMany(targetEntity=»Cursos», inversedBy=»user»)
* @ORM\JoinTable(name=»cursos_has_user»,
* joinColumns={
* @ORM\JoinColumn(name=»user_id», referencedColumnName=»id»)
* },
* inverseJoinColumns={
* @ORM\JoinColumn(name=»cursos_idCurso», referencedColumnName=»idCurso»)
* }
* )
*/
private $cursoscurso;
/**
* @ORM\Column(type=»string», length=255)
*/
private $name;
/**
* Constructor
*/
public function __construct()
{
$this->cursoscurso = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = ‘ROLE_ALUMNO’;
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function getSalt()
{
// not needed when using the «bcrypt» algorithm in security.yaml
}
/**
* @see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* @return Collection|Cursos[]
*/
public function getCursoscurso(): Collection
{
return $this->cursoscurso;
}
public function addCursoscurso(Cursos $cursoscurso): self
{
if (!$this->cursoscurso->contains($cursoscurso)) {
$this->cursoscurso[] = $cursoscurso;
}
return $this;
}
public function removeCursoscurso(Cursos $cursoscurso): self
{
if ($this->cursoscurso->contains($cursoscurso)) {
$this->cursoscurso->removeElement($cursoscurso);
}
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function __toString(){
return $this->name;
}
}
Entidad CURSOS
class Cursos
{
/**
* @var int
*
* @ORM\Column(name=»idCurso», type=»integer», nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy=»IDENTITY»)
*/
private $idcurso;
/**
* @var string
*
* @ORM\Column(name=»nombre», type=»string», length=45, nullable=false)
* @Assert\NotBlank(message=»No puede estar vacio»)
* @Assert\Length(max=100)
*/
private $nombre;
/**
* @var float
*
* @ORM\Column(name=»precio», type=»float», precision=10, scale=0, nullable=false)
* @Assert\NotBlank
* @Assert\Type(type=»float»)
*/
private $precio;
/**
* @var string
*
* @ORM\Column(name=»pathImagen», type=»string», length=200, nullable=false)
* @Assert\NotBlank
* @Assert\Length(max=200)
*/
private $pathimagen;
/**
* @var string
*
* @ORM\Column(name=»descripcion», type=»string», length=200, nullable=false)
* @Assert\NotBlank
* @Assert\Length(max=200)
*/
private $descripcion;
/**
* @var \DateTime
*
* @ORM\Column(name=»fechaIncioCurso», type=»datetime», nullable=false)
*/
private $fechainciocurso;
/**
* @var \DateTime
*
* @ORM\Column(name=»fechaFinCurso», type=»datetime», nullable=false)
*/
private $fechafincurso;
/**
* @var \Categorias
* @Assert\NotBlank
* @ORM\ManyToOne(targetEntity=»Categorias»)
* @ORM\JoinColumns({
* @ORM\JoinColumn(name=»Categorias_idCategoria», referencedColumnName=»idCategoria»,onDelete=»CASCADE»)
* })
*/
private $categoriascategoria;
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\ManyToMany(targetEntity=»User», mappedBy=»cursoscurso»)
*/
private $user;
/**
* Constructor
*/
public function __construct()
{
$this->user = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getIdcurso(): ?int
{
return $this->idcurso;
}
public function getNombre(): ?string
{
return $this->nombre;
}
public function setNombre(string $nombre): self
{
$this->nombre = $nombre;
return $this;
}
public function getPrecio(): ?float
{
return $this->precio;
}
public function setPrecio(float $precio): self
{
$this->precio = $precio;
return $this;
}
public function getPathimagen()
{
return $this->pathimagen;
}
public function setPathimagen($pathimagen = null): self
{
$this->pathimagen = $pathimagen;
return $this;
}
public function getDescripcion(): ?string
{
return $this->descripcion;
}
public function setDescripcion(string $descripcion): self
{
$this->descripcion = $descripcion;
return $this;
}
public function getFechainciocurso(): ?\DateTimeInterface
{
return $this->fechainciocurso;
}
public function setFechainciocurso(\DateTimeInterface $fechainciocurso): self
{
$this->fechainciocurso = $fechainciocurso;
return $this;
}
public function getFechafincurso(): ?\DateTimeInterface
{
return $this->fechafincurso;
}
public function setFechafincurso(\DateTimeInterface $fechafincurso): self
{
$this->fechafincurso = $fechafincurso;
return $this;
}
public function getCategoriascategoria(): ?Categorias
{
return $this->categoriascategoria;
}
public function setCategoriascategoria(?Categorias $categoriascategoria): self
{
$this->categoriascategoria = $categoriascategoria;
return $this;
}
/**
* @return Collection|User[]
*/
public function getUser(): Collection
{
return $this->user;
}
public function addUser(User $user): self
{
if (!$this->user->contains($user)) {
$this->user[] = $user;
$user->addCursoscurso($this);
}
return $this;
}
public function removeUser(User $user): self
{
if ($this->user->contains($user)) {
$this->user->removeElement($user);
$user->removeCursoscurso($this);
}
return $this;
}
public function __toString(){
return $this->nombre;
}
}
Un saludo
¡Hola Jaime!
Creo que lo que quieres hacer en ese código fuente es añadir un curso a un usuario. En este caso, con Doctrine, lo más práctico es abstraerse de las consultas DQL. Por ejemplo, puedes hacer cosas como cargar usuario y curso así:
$curso = $entityManager->getRepository(\App\Entity\Curso)->load($idCurso);
$usuario = $entityManager->getRepository(\App\Entity\User)->load($idUser);
..luego añadir a un curso un usuario así:
$curso->addUser($user);
$entityManager->persist($curso);
$entityManager->flush();
..o puedes hacer al revés, añadir a un usuario un curso:
$user->addCursoscurso($curso);
$entityManager->persist($user);
$entityManager->flush();
Tienes que pensar que al tener Doctrine tienes una capa intermedia entre tus códigos fuentes y la base de datos. Te recomiendo que mires en la BD cómo hace Doctrine para hacer la relación ManyToMany, usa una tabla aparte. Si quisieras hacer la relación anterior deberías insertar una línea en esa tabla, no un update de la entidad User. Pero volviendo al código que te escribo aquí, te recomiendo que uses addCursoscurso o addUser para relacionar las entidades.
Espero que sirva, un saludo!