How to create a child attribute table in doctrine 2? - php

How can I create a child attribute table through doctrine 2 (basically like a join table but without an entity on the other side). Do I need to make a separate entity for it (I know if I do a ManyToMany with a join table it'll just create the join table in the db but does not create an Entity).
i.e.- I have work orders that can have a bunch of assigned work dates. I want to be able to fill a table with those dates so
______________________
| Orders |
|____________________|
id
1
2
3
______________________
| Order_Dates |
|____________________|
order_id | Date
1 | 2019-01-01
1 | 2019-05-01
2 | 2019-01-01
2 | 2019-02-01
2 | 2019-03-01
3 | 2019-01-02
3 | 2019-01-05
So basically so far I have
class Orders
{
public function __construct()
{
$this->dates = new ArrayCollection();
}
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* One Order has Many Dates
*/
private $dates;
public function getDates() : ArrayCollection
{
return $this->dates;
}
public function addDate(\DateTime $date): self
{
$this->dates->add($date);
return $this;
}
public function setDates(ArrayCollection $dates): self
{
$this->dates = $dates;
return $this;
}
}
I assume I need a #OneToMany or something but against I'm expecting the child table to be FILLED FROM the Order Entity, not a mapping. Everything I look in the doctrine association documentation at here seems to expect some sort of mapping (perhaps I'm reading it wrong?)
Thanks in advance!
Edit:
This is my controllers now:
Orders
/**
* #ORM\OneToMany(targetEntity="OrderDates", mappedBy="order", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
private $dates;
...
public function getDates() : PersistentCollection
{
return $this->dates;
}
public function addDate(\DateTime $date): self
{
$orderDate = new OrderDates();
$orderDate->setDate($date);
$orderDate->setOrder($this);
$this->dates[] = $orderDate;
return $this;
}
public function setDates(array $dates): self
{
foreach($dates as $date)
{
$this->addDate(new \DateTime($date));
}
return $this;
}
OrderDates
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\OrderDatesRepository")
*/
class OrderDates
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="Orders", inversedBy="dates")
*/
private $order;
public function getId(): ?int
{
return $this->id;
}
public function getDate(): ?\DateTimeInterface
{
return $this->date;
}
public function setDate(\DateTimeInterface $date): self
{
$this->date = $date;
return $this;
}
public function getOrder(): Orders
{
return $this->order;
}
public function setOrder(Orders $order): self
{
$this->order = $order;
return $this;
}
}

You have to create an entity for Order_Dates and use OneToMany annotation if you want an extra database table to be created for order dates. Doctrine does not support associations between entities handled by Doctrine and entities handled by another driver. Doctrine doesn't even support associations between entities handled by different entity managers.
To sum it up: You have to create a OrderDates entity and setup the association the traditional way.
EDIT: You should set up your association like this:
class Orders {
// id and other fields here...
/**
* #ORM\OneToMany(targetEntity="OrderDates", mappedBy="order", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
private $dates;
public function addDate(\DateTime $date): self
{
$orderDate = new OrderDates();
$orderDate->setDate($date);
$orderDate->setOrder($this);
$this->dates[] = $orderDate;
return $this;
}
}
class OrderDates {
// id here...
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="dates")
*/
private $order;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="datetime")
*/
private $date;
// setters and getters here
}

Related

Doctrine ORM: composite and foreign keys as primary key

I am trying to get composite and foreign keys as primary keys working in Doctrine ORM. I know what I'm trying to do is possible because it is described here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/tutorials/composite-primary-keys.html#composite-and-foreign-keys-as-primary-key. This is exactly my use-case: I have some products, an order and an order item.
However, doctrine orm is unable to map this relation unto the database. The current problem is that only one of the annotated \Id primary keys is reflected on the mysql database. So $producto is translated unto the database correctly as producto_id and is both a primary key and a foreign key. However, the $orden property which is annotated in the same way doesn't appear whatsoever on my database.
This seems odd because when I was first testing this feature I tried only with one of the two properties and it worked fine, however, when both properties are annotated only one seems to be parsed by the metadata parser. Furthermore, I tried to revert my project to a usable state by forgetting about the foreign keys and just have a composite primary key (like I had it before), but now the parser doesn't seem to even recognize the primary key. For example, for:
class ProductoOrden
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $idOrden;
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $idProducto;
I get:
bash-3.2$ php bin/console make:migration
In MappingException.php line 52:
No identifier/primary key specified for Entity "App\Entity\ProductoOrden". Every Entity must
have an identifier/primary key.
So I'm unable to set it up properly or to revert it to the previous state (which is the strangest of all).
I'm about to restart my whole project from scratch, because I cannot make sense of how the metadata parsing works. I am worried I have screwed up the process because I have manually erased the files at 'src\Migrations' because of similar issues before and php bin/console doctrine:migrations:version --delete --all didn't seem to work or I haven't understood the proper use of it.
In conclusion: ¿Could anyone assert if what I am trying to do with ProducoOrden is possible (maybe I'm not understanding the documentation example)? Is there any way to completely wipe out previous cache about the annotations/ schema metadata?
I've looked unto the orm:schema-tool but I don't really get how to configure it properly or why I have to configure it at all of I already have the bin/console tool on my project.
I will show all three involved classes for completeness sake, but the main problem is within ProductoOrden (Order-items).
<?php
//Products
namespace App\Entity;
use App\Repository\ProductosRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=ProductosRepository::class)
* #UniqueEntity("idProducto", message=" {producto {{ value }}}: llave primaria violada ")
*/
class Productos
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $nombreProducto;
/**
* #ORM\Column(type="string", length=255)
*/
private $descripcionProducto;
/**
* #ORM\Column(type="string", length=255)
*/
private $urlImagen;
/**
* #ORM\Column(type="integer")
*/
private $puntosProducto;
public function getIdProducto(): ?int
{
return $this->idProducto;
}
public function getCodProducto(): ?int
{
return $this->idProducto;
}
public function setIdProducto(int $codProducto): self
{
$this->idProducto = $codProducto;
return $this;
}
public function getNombreProducto(): ?string
{
return $this->nombreProducto;
}
public function setNombreProducto(string $nombreProducto): self
{
$this->nombreProducto = $nombreProducto;
return $this;
}
public function getDescripcionProducto(): ?string
{
return $this->descripcionProducto;
}
public function setDescripcionProducto(string $descripcionProducto): self
{
$this->descripcionProducto = $descripcionProducto;
return $this;
}
public function getUrlImagen(): ?string
{
return $this->urlImagen;
}
public function setUrlImagen(string $urlImagen): self
{
$this->urlImagen = $urlImagen;
return $this;
}
public function getPuntosProducto(): ?int
{
return $this->puntosProducto;
}
public function setPuntosProducto(int $puntosProducto): self
{
$this->puntosProducto = $puntosProducto;
return $this;
}
public function __toString(){
$str = '{producto:'.$this->getIdProducto().', nombre: '.$this->getNombreProducto().'}';
return $str;
}
}
<?php
\\Orders
namespace App\Entity;
use App\Repository\OrdenesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=OrdenesRepository::class)
* #UniqueEntity("idOrden", message="{orden {{ value }}}: llave primaria violada")
*/
class Ordenes
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $totalOrden;
/**
* #ORM\Column(type="string", length=255)
*/
private $estado;
/**
* #ORM\OneToMany(targetEntity=ProductoOrden::class, mappedBy="orden", orphanRemoval=true)
*/
private $productosOrden;
public function __construct()
{
$this->productosOrden = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getIdOrden(): ?int
{
return $this->idOrden;
}
public function setIdOrden(int $idOrden): self
{
$this->idOrden = $idOrden;
return $this;
}
public function getTotalOrden(): ?int
{
return $this->totalOrden;
}
public function setTotalOrden(int $totalOrden): self
{
$this->totalOrden = $totalOrden;
return $this;
}
public function getEstado(): ?string
{
return $this->estado;
}
public function setEstado(string $estado): self
{
$this->estado = $estado;
return $this;
}
public function __toString(){
$str = '{orden:'.$this->getIdOrden().'}';
return $str;
}
/**
* #return Collection|ProductoOrden[]
*/
public function getProductosOrden(): Collection
{
return $this->productosOrden;
}
public function addProductosOrden(ProductoOrden $productosOrden): self
{
if (!$this->productosOrden->contains($productosOrden)) {
$this->productosOrden[] = $productosOrden;
$productosOrden->setOrden($this);
}
return $this;
}
public function removeProductosOrden(ProductoOrden $productosOrden): self
{
if ($this->productosOrden->contains($productosOrden)) {
$this->productosOrden->removeElement($productosOrden);
// set the owning side to null (unless already changed)
if ($productosOrden->getOrden() === $this) {
$productosOrden->setOrden(null);
}
}
return $this;
}
}
<?php
\\Order-items
namespace App\Entity;
use App\Repository\ProductoOrdenRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=ProductoOrdenRepository::class)
* #UniqueEntity(fields={"idOrden","idProducto"}, message="{prod. orden {{ value }}}: llave primaria violada")
*/
class ProductoOrden
{
/*
* #ORM\Id
* #ORM\ManyToOne(targetEntity=Ordenes::class, inversedBy="productosOrden")
* #ORM\JoinColumn(nullable=false)
*/
private $orden;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=Productos::class)
* #ORM\JoinColumn(nullable=false)
*/
private $producto;
/**
* #ORM\Column(type="integer")
*/
private $puntos;
/**
* #ORM\Column(type="integer")
*/
private $cantidad;
public function getId(): ?int
{
return $this->idOrden;
}
public function setIdOrden(int $idOrden): self
{
$this ->idOrden = $idOrden;
return $this;
}
public function getIdProducto(): ?int
{
return $this->idProducto;
}
public function setIdProducto(int $idProducto): self
{
$this->idProducto = $idProducto;
return $this;
}
public function getPuntos(): ?int
{
return $this->puntos;
}
public function setPuntos(int $puntos): self
{
$this->puntos = $puntos;
return $this;
}
public function getCantidad(): ?int
{
return $this->cantidad;
}
public function setCantidad(int $cantidad): self
{
$this->cantidad = $cantidad;
return $this;
}
public function __toString(){
$str = '{productoOrden:'.$this->getId().', '.$this->getIdProducto().'}';
return $str;
}
public function getOrden(): ?Ordenes
{
return $this->orden;
}
public function setOrden(?Ordenes $orden): self
{
$this->orden = $orden;
return $this;
}
}
For the shown classes, the migration it generates is
final class Version20200814210929 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE producto_orden ADD puntos INT NOT NULL');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE producto_orden DROP puntos');
}
}
As you can see, small changes like changing the type of a property work; but it doesn't seem to take on the id() and the association annotations.
Many thanks

Request a linked field in symfony

I have a page that displays information about a movie. I recover in GET the id of the film. What I would like to do is retrieve the comments for each film (there is a filmId column in my table linked to the primary id of the film table)
/**
* #Route("/user/film/{id}", name="film")
*/
public function film(FilmRepository $repo, CommentRepository $comRepo, EntityManagerInterface $em, Request $req, $id)
{
$film = $repo->find($id);
$comments = $comRepo->findBy(array('id' => $id));
return $this->render('film/film.html.twig', [
'controller_name' => 'FilmController',
'film' => $film,
'comments' => $comments
]);
}
when I make a $comments = $comRepo->findBy(array('id' => $id)); I get some comments, but based on their id and NOT the film id (the comment with id 1 will be displayed on the film with id 1, but for example a comment with id 4 and the filmId a 1 will not appear on film 1, but on the film with id 4)
I tried to access the filmId field by simply making a $comments = $comRepo->findBy(array ('filmId' => $ id)); but i get the error :
An exception occurred while executing 'SELECT t0.id AS id_1, t0.content AS content_2, t0.created_at AS created_at_3, t0.author_id AS author_id_4 FROM comment t0 WHERE comment_film.film_id = ?' with params ["1"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'comment_film.film_id' in 'where clause'
I tried a personalized request with, in my Comment repository:
public function findAllWithFilmId($filmId)
{
$em = $this->getEntityManager();
$query = $em->createQuery(
'SELECT c
FROM App\Entity\Comment c
WHERE c.filmId = :filmId'
)->setParameter('filmId', $filmId);
return $query->getResult();
}
But it doesn't seem to work..
Where do I go to make a request like this ?
How to modify the request, which seems erroneous, from symfony without disorganizing everything? or is there a better method to correct the problem?
This is my Comment Entity
<?php
namespace App\Entity;
use App\Entity\Film;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CommentRepository")
*/
class Comment
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="comments")
* #ORM\JoinColumn(nullable=false)
*/
private $author;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Film", inversedBy="comments")
* #ORM\JoinColumn(nullable=false)
*/
private $filmId;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
public function getId(): ?int
{
return $this->id;
}
public function getAuthor(): ?User
{
return $this->author;
}
public function setAuthor(?User $author): self
{
$this->author = $author;
return $this;
}
public function getFilmId(): ?Film
{
return $this->filmId;
}
public function setFilmId(?Film $filmId): self
{
$this->filmId = $filmId;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}
I think it is possible that the error comes from annotations, because starting on symfony during the make: entity, I defined types relations which I corrected later in phpmyadmin, but not the code. For example we can see that filmId is in ManyToMany, but I think it should be in OneToOne (FilmId can only have one id and an id can only correspond to one filmId), but I'm afraid that if I change certain things it breaks everything.
If you have set up your ORM relations correctly, it should be as simple as:
$film = $repo->find($id);
$comments = $film->getComments();
You might be missing a mapping in Film.php.
Here's an XML example, should be easy enough to convert to annotations:
In film:
<one-to-many field="comments" target-entity="App\...\Comments" mapped-by="film"/>
In comments:
<many-to-one field="film" target-entity="App\...\Film" inversed-by="comments"/>
First of all, I advise you to read more about the relations between entities.
Because, the current annotations says that you can have a lot of comments on many films. It's not right. One comment may belong to one film. One movie can have many comments.
Also, I want to note that, as far as I know, #JoinColumn should be in a child entity, that is, where the link to FK is contained.
Therefore, your entities should look like this:
Comment:
<?php
namespace App\Entity;
use App\Entity\Film;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CommentRepository")
*/
class Comment
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="comments")
*/
private $author;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Film", inversedBy="comments")
* Here we set property for our table and property of foreign table to map our comment to the right film
* nullable, because comment couldn't be without film
* #ORM\JoinColumn(name="film_id", referencedColumnName="id", nullable=false)
*/
private $film;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
public function getId(): ?int
{
return $this->id;
}
public function getAuthor(): ?User
{
return $this->author;
}
public function setAuthor(?User $author): self
{
$this->author = $author;
return $this;
}
public function getFilmId(): ?Film
{
return $this->filmId;
}
public function setFilmId(?Film $filmId): self
{
$this->filmId = $filmId;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getCreatedAt(): ?DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}
Film:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\FilmRepository")
*/
class Film
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="film")
*/
private $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
return $this;
}
public function getComments(): Collection
{
return $this->comments;
}
public function setComments(Collection $comments): Film
{
$this->comments = $comments;
return $this;
}
}
So, now, you can retrieve your comments via:
/**
* #Route("/user/film/{id}", name="film")
*/
public function film($id)
{
/** #var null|EntityManager $entityManager */
$entityManager = $this->get('doctrine.orm.entity_manager');
if (null == ($film = $entityManager->getRepository(Film::class)->find($id))){
throw new NotFoundHttpException('Film not found');
}
$comments = $film->getComments();
return $this->render('film/film.html.twig', [
'film' => $film,
'comments' => $comments
]);
}

How to join multiple entities on a foreign ID in Symfony 4 using a query builder?

I'm trying to learn Symfony. Today I was following The associations tutorial. I decided to make a small application that a House, Kitchens, Bedrooms, and cabinets. I (tried to ;-) ) make a small Class diagram using draw.io to give you a better idea.
So basically a House can have multiple Bedrooms and multiple Kitchens. Each kitchen can have multiple cabinets. The House has an id and a name. The Bedroom and Kitchen as well. The cabinet has id, shopUrl and is also linked via a foreign key (account_id) to its parent Kitchen.
I also link the Kitchen and the Bedroom to the House using a foreign key (house_id). So I followed the tutorial and created the House entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
* #ORM\Entity(repositoryClass="App\Repository\HouseRepository")
*/
class House implements \JsonSerializable
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Kitchen", mappedBy="house")
*/
private $kitchen;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Bedroom", mappedBy="house")
*/
private $bedroom;
/**
* House constructor
*/
public function __construct()
{
$this->kitchen = new ArrayCollection();
$this->bedroom = new ArrayCollection();
}
/**
* #return Collection|Kitchen[]
*/
public function getKitchen(): Collection
{
return $this->kitchen;
}
/**
* #return Collection|Bedroom[]
*/
public function getBedroom(): Collection
{
return $this->bedroom;
}
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function jsonSerialize()
{
return get_object_vars($this);
}
}
The House repository is empty (a.k.a: only containts the automatically generated code from Symfony):
<?php
namespace App\Repository;
use App\Entity\House;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* #method House|null find($id, $lockMode = null, $lockVersion = null)
* #method House|null findOneBy(array $criteria, array $orderBy = null)
* #method House[] findAll()
* #method House[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class HouseRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, House::class);
}
}
The Bedroom entity is this:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\BedroomRepository")
*/
class Bedroom implements \JsonSerializable
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\House", inversedBy="bedroom")
*/
private $house;
public function getHouse(): House
{
return $this->house;
}
public function setHouse(House $house): self
{
$this->house = $house;
return $this;
}
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function jsonSerialize()
{
return get_object_vars($this);
}
}
and the Bedroom repository is also empty.
The Kitchen entity has the following code:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\KitchenRepository")
*/
class Kitchen implements \JsonSerializable
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Cabinet", mappedBy="kitchen")
*/
private $cabinet;
/**
* Kitchen constructor
*/
public function __construct()
{
$this->cabinet= new ArrayCollection();
}
/**
* #return Collection|Cabinet[]
*/
public function getCabinet(): Collection
{
return $this->cabinet;
}
/**
* #ORM\ManyToOne(targetEntity="App\Entity\House", inversedBy="kitchen")
*/
private $house;
public function getHouse(): House
{
return $this->house;
}
public function setHouse(House $house): self
{
$this->house = $house;
return $this;
}
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="UUID")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
public function getId(): int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function jsonSerialize()
{
return get_object_vars($this);
}
}
and the Kitchen repository is also empty.
Finally, the cabinet consists of the following:
<?php
namespace App\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CabinetRepository")
*/
class Cabinet
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $shopUrl;
private $account_id;
/**
* #ORM\ManyToOne(targetEntity="Kitchen", inversedBy="cabinet")
*/
private $kitchen;
/**
* Cabinet constructor.
* #param string $shopUrl
* #param Kitchen $kitchen
* #param int $id
*/
public function __construct(string $shopUrl, Kitchen $kitchen = null, int $id = null)
{
$this->shopUrl = $shopUrl;
$this->kitchen = $kitchen;
$this->id = $id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public function getId(): int
{
return $this->id;
}
public function getShopUrl(): string
{
return $this->shopUrl;
}
public function getKitchen(): Kitchen
{
return $this->kitchen;
}
public function setKitchen(Kitchen $kitchen): self
{
$this->kitchen = $kitchen;
$this->account_id = $kitchen->getId();
return $this;
}
public function setAccount_id(int $account_id): self
{
$this->account_id = $account_id;
return $this;
}
public function getAccount_id(): int
{
return $this->account_id;
}
}
In contrast to the other entities, the cabinet has some logic (this is where I actually need help). Since Bedroom and Kitchen are associated with a House, I would like to give a Bedroom, then look up all the kitchens associated with the same house as the Bedroom and then return all cabinets that these kitchens have. I know it may seem illogical but I discovered this too late to come up with another concept. My current code doesn't work because I'm not sure whether this is possible and because it's a bit too complex for me to grasp at this moment. But I have this as the content of the cabinet repo:
<?php
namespace App\Repository;
use App\Entity\Bedroom;
use App\Entity\House;
use App\Entity\Cabinet;
use App\Entity\Kitchen;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* #method Cabinet|null find($id, $lockMode = null, $lockVersion = null)
* #method Cabinet|null findOneBy(array $criteria, array $orderBy = null)
* #method Cabinet[] findAll()
* #method Cabinet[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class CabinetRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Cabinet::class);
}
public function findByBedroom(Bedroom $bedroom) //use joins??
{
return $this->createQueryBuilder('cabinet')
->join('cabinet.bedroom', 'bedroom')
->join('cabinet.kitchen', 'kitchen')
->addSelect('cabinet')
->andWhere('cabinet.bedroom = :idBedroom')
->setParameter('idBedroom', $bedroom->getId())
->orderBy('time', 'ASC')
->getQuery()
->getResult();
}
}
I'm using PHPStorm and no error are showing anywhere but of course, the querybuilder doesn't return anything. How can I fix it? I couldn't find any questions that try to achieve what I'm trying to do.
I added data manually to the database, so there's data in there. And using Sequel Pro I can see the relations. The data (as far as I'm concerned) is fine. The queryBuilder is where the mistakes are.
A demonstrative example with some data:
This is the Bedroom data:
id name house_id
325 bigBedroomOne 1666
815 smallBedroomOne 555
902 bigBedroomTwo 1666
This is the House data:
id name
1666 bigHouse
555 smallHouse
This is the Kitchen data:
id name house_id
1 bigKitchen 1666
2 smallKitchen 555
55 mediumKitchen 555
And finally, this is the cabinets data:
id shopUrl account_id
1 ur.l 55
88 co.m 2
33 ne.t 1
So in this example I would like to plug in the Bedroom id 815 which is associated with the house_id 555. Then from there all the Kitchen associated with that house_id, should be selected, so 2 and 55. Finally, the cabinets with id 1 and 88 should be returned.
Edit: When running bin/console doc:sch:val I get this back:
`Mapping
[OK] The mapping files are correct.
Database
[OK] The database schema is in sync with the mapping files.`
In your debug bar in symfony you should probably be seeing some errors in doctrine. These won't show up in PHPStorm. You need to rename $kitchen and $bedroom to their plural forms $kitchens and $bedrooms (and change your getters/setters to match) since this is how you define things in the owning side of your doctrine relationships.
A simpler approach than your repository method would be to do what you want in your controller to let doctrine do your heavy lifting:
$cabinets = [];
$house = $bedroom->getHouse();
$kitchens = $house->getKitchens();
foreach ($kitchens as $kitchen) {
$kitchenCabinets = $kitchen->getCabinets();
$cabinets = array_merge($cabinets, $kitchenCabinets);
}
There are several minor problems in your code, more on that later.
Here is \App\Repository\CabinetRepository::findByBedroom:
public function findByBedroom(Bedroom $bedroom) //use joins??
{
return $this->createQueryBuilder('cabinet')
->join('cabinet.kitchen', 'kitchen')
->join('kitchen.house', 'house')
->join('house.bedroom', 'bedroom')
->addSelect('cabinet')
->andWhere('bedroom = :bedroom')
->setParameter('bedroom', $bedroom)
->getQuery()
->getResult();
}
For bedroom entity with ID 815 the code above returns the following (formatted as symfony/var-dumper would do that):
array:2 [▼
0 => Cabinet {#387 ▼
-id: 88
-shopUrl: "co.m"
-account_id: null
-kitchen: Kitchen {#354 ▼
+__isInitialized__: false
-cabinet: null
-house: null
-id: 2
-name: null
…2
}
}
1 => Cabinet {#364 ▼
-id: 1
-shopUrl: "ur.l "
-account_id: null
-kitchen: Kitchen {#370 ▼
+__isInitialized__: false
-cabinet: null
-house: null
-id: 55
-name: null
…2
}
}
]
Note: house references are null because of lazy loading.
So, small problems in your code:
Your query in CabinerRepository was doing wrong joins. For correct joins see code above.
That query referring to unknown field time. I have removed that reference.
And also was using bedroom ID instead of bedroom entity.
Your Kitchen.php is incomplete, it refers Collection and ArrayCollection classes, but there are no corresponding use directives. Just add this after namespace before class:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
Update: here is how to get repository reference:
public function myControllerAction(/* other parameters */, CabinetRepository $cabRepo)
{
$cabinet = $cabRepo->find($id);
// OR, if you don't want to add parameter for some reason:
$cabRepo = $this->getDoctrine()->getRepository(Cabinet::class);
$cabinet = $cabRepo->find($id);
}
First I think because you joining with both entities at the same time in this
...
->join('cabinet.bedroom', 'bedroom')
->join('cabinet.kitchen', 'kitchen')
...
and because that will be with INNER JOIN, it will require that cabined is required both bedroom and kitchen cabinet.
For that there is few solutions to work through:
Proper one would be redesign you entities. I think it might not be hard to use Doctrine inheritance
you might change joins to left, so relation is not mandatory (will work, but in general its not good solution because of wrong design)

TWIG PersistentCollection could not be converted to string: output data on a table from OneToMany - ManyToOne entity

I'm trying to display data of each match in a table like this one:
img1 | name1 | score1 | score2 | name2 | img2 | date | statut_id
Here is how I would do it in sql:
SELECT j1.image AS img1, j1.name AS name1, s1.score AS score1, s2.score AS score2, j2.name AS name2, j2.image AS img2, m.date AS date, m.statut_id FROM jouster j1, jouster j2, matchs m, score s1, score s2 WHERE j1.id = s1.jouster_id AND m.id = s1.matchs_id AND j2.id = s2.jouster_id AND m.id = s2.matchs_id AND m.jouster1_id = j1.id AND m.jouster2_id = j2.id
I want to do it with the "symfony way" but i'm not used to it
I have an issue with scores. When I try to access to the score of each jousters I get this error:
An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class Doctrine\ORM\PersistentCollection could not be converted to string").
match.html.twig
{% for match in matchs %}
{{match.jouster1.image}}
{{match.jouster1.name}}
{{match.jouster1.scores}}
-
{{match.jouster2.scores}}
{{match.jouster2.name}}
{{match.jouster2.image}}
{{match.date | date('Y-m-d H:i:s') }}
<br>
{% endfor %}
MatchController.php
/**
* #Route("/match", name="match")
*/
public function match() {
$matchs = $this->getDoctrine()->getRepository(Matchs::class)->findAll();
return $this->render('page/match.html.twig', [
'matchs' => $matchs
]);
}
Matchs.php
class Matchs {
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Jouster", inversedBy="jouster2")
* #ORM\JoinColumn(nullable=false)
*/
private $jouster1;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Jouster", inversedBy="matchAsJouster2")
* #ORM\JoinColumn(nullable=false)
*/
private $jouster2;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Score", mappedBy="matchs", orphanRemoval=true)
*/
private $scores;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Statut", inversedBy="matchs")
* #ORM\JoinColumn(nullable=false)
*/
private $statut;
*************************
*** getters & setters ***
*************************
/**
* #return Collection|Score[]
*/
public function getScores(): Collection
{
return $this->scores;
}
public function addScore(Score $score): self
{
if (!$this->scores->contains($score)) {
$this->scores[] = $score;
$score->setMatchs($this);
}
return $this;
}
Score.php
class Score
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="smallint", nullable=true)
*/
private $score;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Jouster", inversedBy="scores")
* #ORM\JoinColumn(nullable=false)
*/
private $jouster;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Matchs", inversedBy="scores")
* #ORM\JoinColumn(nullable=false)
*/
private $matchs;
public function getId(): ?int
{
return $this->id;
}
public function getScore(): ?int
{
return $this->score;
}
public function setScore(int $score): self
{
$this->score = $score;
return $this;
}
public function getJouster(): ?Jouster
{
return $this->jouster;
}
public function setJouster(?Jouster $jouster): self
{
$this->jouster = $jouster;
return $this;
}
public function getMatchs(): ?Matchs
{
return $this->matchs;
}
public function setMatchs(?Matchs $matchs): self
{
$this->matchs = $matchs;
return $this;
}
}
Jouster.php
/**
* #return Collection|Score[]
*/
public function getScores(): Collection
{
return $this->scores;
}
public function addScore(Score $score): self
{
if (!$this->scores->contains($score)) {
$this->scores[] = $score;
$score->setJouster($this);
}
return $this;
}
public function removeScore(Score $score): self
{
if ($this->scores->contains($score)) {
$this->scores->removeElement($score);
// set the owning side to null (unless already changed)
if ($score->getJouster() === $this) {
$score->setJouster(null);
}
}
return $this;
}
EDIT:
I had to do a loop in order to display matchs scores.
{% for match in matchs %}
{{match.jouster1.image}}
{{match.jouster1.name}}
{% for score in match.scores%}
{{score.score}}
{% endfor %}
{{match.jouster2.name}}
{{match.jouster2.image}}
{{match.date | date('Y-m-d H:i:s') }}
as DarkBee stated in he's comment.
The error is clearly explained in the error message itself:
PersistentCollection could not be converted to string
Meaning, you had twig try to output something, which was actually not 1 thing, but a collection of things. (Scores in your case)

Doctrine ORM, ManyToMany - duplicates on lazy fetch

Symfony 2.8. When using default fetch mode duplicates are returned (why?), using fetch="EAGER" - everything is ok.
I have following objects:
/**
* #ORM\Entity()
*/
class User implements AdvancedUserInterface, \Serializable
{
(...)
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
* #ORM\JoinTable(name="user_role",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id")}
* )
*/
private $role;
public function addRole(\WerbeoBundle\Entity\Role $role)
{
$this->role[] = $role;
return $this;
}
public function removeRole(\WerbeoBundle\Entity\Role $role)
{
$this->role->removeElement($role);
}
public function getRole()
{
return $this->role;
}
Role:
/**
* #ORM\Entity()
*/
class Role
{
(...)
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="role")
*/
private $users;
(... and getters/setters ...)
Now I have following table user_role:
user_id | role_id
1 | ADMIN
1 | EDITOR
When I call $user->getRole() result is
ADMIN
EDITOR
EDITOR
ADMIN
EDITOR
This happens in twig/controller only when using default fetch mode (lazy). When fetch="EAGER" everything is ok.
Any ideas what am I doing wrong?
Thanks
You have to check if an entry already exists before adding
public function __construct()
{
$this-role = new \Doctrine\Common\Collections\ArrayCollection()
}
public function addRole(\WerbeoBundle\Entity\Role $role)
{
if(!$this->role->contains($role)){
$this->role->add($role)
}
return $this;
}

Categories