circular reference when trying to find entities - symfony

It's my first Symfony app.
I try to do an intervention manager, so I have created three entities: Intervention, Comment and User.
Users are managed by FOSUserBundle.
Classes:
Intervention
<?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\InterventionRepository")
*/
class Intervention
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="smallint")
*/
private $num_rue;
/**
* #ORM\Column(type="string", length=255)
*/
private $nom_rue;
/**
* #ORM\Column(type="string", length=5)
*/
private $cp;
/**
* #ORM\Column(type="decimal", precision=12, scale=9, nullable=true)
*/
private $lat;
/**
* #ORM\Column(type="decimal", precision=12, scale=9, nullable=true)
*/
private $longi;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* #ORM\Column(type="boolean", options={"default":true})
*/
private $statut;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="intervention", orphanRemoval=true)
*/
private $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNumRue(): ?int
{
return $this->num_rue;
}
public function setNumRue(int $num_rue): self
{
$this->num_rue = $num_rue;
return $this;
}
public function getNomRue(): ?string
{
return $this->nom_rue;
}
public function setNomRue(string $nom_rue): self
{
$this->nom_rue = $nom_rue;
return $this;
}
public function getCp(): ?string
{
return $this->cp;
}
public function setCp(string $cp): self
{
$this->cp = $cp;
return $this;
}
public function getLat()
{
return $this->lat;
}
public function setLat($lat): self
{
$this->lat = $lat;
return $this;
}
public function getLongi()
{
return $this->longi;
}
public function setLongi($longi): self
{
$this->longi = $longi;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getStatut(): ?bool
{
return $this->statut;
}
public function setStatut(bool $statut): self
{
$this->statut = $statut;
return $this;
}
/**
* #return Collection|Comment[]
*/
public function getComments(): Collection
{
return $this->comments;
}
public function addComment(Comment $comment): self
{
if (!$this->comments->contains($comment)) {
$this->comments[] = $comment;
$comment->setIntervention($this);
}
return $this;
}
public function removeComment(Comment $comment): self
{
if ($this->comments->contains($comment)) {
$this->comments->removeElement($comment);
// set the owning side to null (unless already changed)
if ($comment->getIntervention() === $this) {
$comment->setIntervention(null);
}
}
return $this;
}
}
Comment
<?php
namespace App\Entity;
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\Column(type="text")
*/
private $text;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Intervention", inversedBy="comments")
* #ORM\JoinColumn(name="intervention_id", referencedColumnName="id")
*/
private $intervention;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="comments")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
private $author;
public function getId(): ?int
{
return $this->id;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
public function getIntervention(): ?Intervention
{
return $this->intervention;
}
public function setIntervention(?Intervention $intervention): self
{
$this->intervention = $intervention;
return $this;
}
public function getAuthor(): ?User
{
return $this->author;
}
public function setAuthor(?User $author): self
{
$this->author = $author;
return $this;
}
}
User
<?php
// src/Entity/User.php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="author")
*/
private $comments;
public function __construct()
{
parent::__construct();
$this->comments = new ArrayCollection();
}
/**
* #return Collection|Comment[]
*/
public function getComments(): Collection
{
return $this->comments;
}
public function addComment(Comment $comment): self
{
if (!$this->comments->contains($comment)) {
$this->comments[] = $comment;
$comment->setAuthor($this);
}
return $this;
}
public function removeComment(Comment $comment): self
{
if ($this->comments->contains($comment)) {
$this->comments->removeElement($comment);
// set the owning side to null (unless already changed)
if ($comment->getAuthor() === $this) {
$comment->setAuthor(null);
}
}
return $this;
}
}
The problem is when I try to access the Intervention with Doctrine, I have a circular reference.
I thought that comments under intervention return user collection which also returns comments...
$repository = $this->getDoctrine()->getRepository(Intervention::class);
$intervention = $repository->findBy(['statut' => 1]);
return $this->json($intervention);
Do you have any suggestion to make my code work please?
Thank you.
Solution:
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
public function getLocations(){
$encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceHandler(function ($object, string $format = null, array $context = array()) {
return $object->getId();
});
$serializer = new Serializer(array($normalizer), array($encoder));
$repository = $this->getDoctrine()->getRepository(Intervention::class);
$intervention = $repository->findBy(['statut' => 1]);
$response = new Response($serializer->serialize($intervention, 'json'));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
Thanks for your help.
this is just the 1/100 of the dump:
array(3) { [0]=> object(App\Entity\Intervention)#483 (9) { ["id":"App\Entity\Intervention":private]=> int(1) ["num_rue":"App\Entity\Intervention":private]=> int(4) ["nom_rue":"App\Entity\Intervention":private]=> string(14) "rue d’alsace" ["cp":"App\Entity\Intervention":private]=> string(5) "49000" ["lat":"App\Entity\Intervention":private]=> string(12) "47.470484600" ["longi":"App\Entity\Intervention":private]=> string(12) "-0.551233000" ["description":"App\Entity\Intervention":private]=> string(69) "
étanchéité toiture
carrelage SB
" ["statut":"App\Entity\Intervention":private]=> bool(true) ["comments":"App\Entity\Intervention":private]=> object(Doctrine\ORM\PersistentCollection)#485 (9) { ["snapshot":"Doctrine\ORM\PersistentCollection":private]=> array(0) { } ["owner":"Doctrine\ORM\PersistentCollection":private]=> *RECURSION* ["association":"Doctrine\ORM\PersistentCollection":private]=> array(15) { ["fieldName"]=> string(8) "comments" ["mappedBy"]=> string(12) "intervention" ["targetEntity"]=> string(18) "App\Entity\Comment" ["cascade"]=> array(0) { } ["orphanRemoval"]=> bool(true) ["fetch"]=> int(2) ["type"]=> int(4) ["inversedBy"]=> NULL ["isOwningSide"]=> bool(false) ["sourceEntity"]=> string(23) "App\Entity\Intervention" ["isCascadeRemove"]=> bool(true) ["isCascadePersist"]=> bool(false) ["isCascadeRefresh"]=> bool(false) ["isCascadeMerge"]=> bool(false) ["isCascadeDetach"]=> bool(false) } ["em":"Doctrine\ORM\PersistentCollection":private]=> object(Doctrine\ORM\EntityManager)#234 (11) { ["config":"Doctrine\ORM\EntityManager":private]=> object(Doctrine\ORM\Configuration)#188 (1) { ["_attributes":protected]=> array(14) { ["entityNamespaces"]=> array(1) { ["App"]=> string(10) "App\Entity" } ["metadataCacheImpl"]=> object(Symfony\Component\Cache\DoctrineProvider)#195 (3) { ["pool":"Symfony\Component\Cache\DoctrineProvider":private]=> object(Symfony\Component\Cache\Adapter\PhpFilesAdapter)#197 (16) { ["createCacheItem":"Symfony\Component\Cache\Adapter\AbstractAdapter":private]=> object(Closure)#199 (2) { ["static"]=> array(1) { ["defaultLifetime"]=> int(0) } ["parameter"]=> array(3) { ["$key"]=> string(10) "" ["$value"]=> string(10) "" ["$isHit"]=> string(10) "" } } ["mergeByLifetime":"Symfony\Component\Cache\Adapter\AbstractAdapter":private]=> object(Closure)#201 (2) { ["static"]=> array(1) { ["getId"]=> object(Closure)#198 (2) { ["this"]=> *RECURSION* ["parameter"]=> array(1) { ["$key"]=> string(10) "" } } } ["parameter"]=> array(3) { ["$deferred"]=> string(10) "" ["$namespace"]=> string(10) "" ["&$expiredIds"]=> string(10) "" } } ["namespace":"Symfony\Component\Cache\Adapter\AbstractAdapter":private]=> string(0) "" ["namespaceVersion":"Symfony\Component\Cache\Adapter\AbstractAdapter":private]=> string(0) "" ["versioningIsEnabled":"Symfony\Component\Cache\Adapter\AbstractAdapter":private]=> bool(false) ["deferred":"Symfony\Component\Cache\Adapter\AbstractAdapter":private]=> array(0) { } ["ids":"Symfony\Component\Cache\Adapter\AbstractAdapter":private]=> array(4) { ["DoctrineNamespaceCacheKey%5B%5D"]=> string(31) "DoctrineNamespaceCacheKey%5B%5D" ["%5BApp%5CEntity%5CUser%24CLASSMETADATA%5D%5B1%5D"]=> string(48) "%5BApp%5CEntity%5CUser%24CLASSMETADATA%5D%5B1%5D" ["%5BApp%5CEntity%5CComment%24CLASSMETADATA%5D%5B1%5D"]=> string(51) "%5BApp%5CEntity%5CComment%24CLASSMETADATA%5D%5B1%5D" ["%5BApp%5CEntity%5CIntervention%24CLASSMETADATA%5D%5B1%5D"]=> string(56) "%5BApp%5CEntity%5CIntervention%24CLASSMETADATA%5D%5B1%5D" } ["maxIdLength":protected]=> NULL ["logger":protected]=> object(Symfony\Bridge\Monolog\Logger)#196 (5) { ["name":protected]=> string(5) "cache" ["handlers":protected]=> array(2) { [0]=> object(Monolog\Handler\FingersCrossedHandler)#94 (11) { ["handler":protected]=> object(Monolog\Handler\StreamHandler)#92 (10) { ["stream":protected]=> NULL ["url":protected]=> string(56) "xxxxxxxxxxxxxxxxxxxxxxxx/var/log/prod.log" ["errorMessage":"Monolog\Handler\StreamHandler":private]=> NULL ["filePermission":protected]=> NULL ["useLocking":protected]=> bool(false) ["dirCreated":"Monolog\Handler\StreamHandler":private]=> NULL ["level":protected]=> int(100) ["bubble":protected]=> bool(true) ["formatter":protected]=> NULL ["processors":protected]=> array(1) { [0]=> object(Monolog\Processor\PsrLogMessageProcessor)#93 (0) { } } } ["activationStrategy":protected]=> object(Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy)#95 (3) { ["blacklist":"Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy":private]=> string(7) "{(^/)}i" ["requestStack":"Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy":private]=> object(Symfony\Component\HttpFoundation\RequestStack)#96 (1) { ["requests":"Symfony\Component\HttpFoundation\RequestStack":private]=> array(1) { [0]=> object(Symfony\Component\HttpFoundation\Request)#4 (23) { ["attributes"]=> object(Symfony\Component\HttpFoundation\ParameterBag)#7 (1) { ["parameters":protected]=> array(5) { ["_route"]=> string(13) "location_list" ["_controller"]=> string(44) "App\Controller\OsimgController::getLocations" ["_route_params"]=> array(0) { } ["_firewall_context"]=> string(34) "security.firewall.map.context.main" ["_security"]=> array(1) { [0]=> object(Sensio\Bundle\FrameworkExtraBundle\Configuration\Security)#407 (3) { ["expression":"Sensio\Bundle\FrameworkExtraBundle\Configuration\Security":private]=> string(21) "has_role('ROLE_USER')" ["statusCode":protected]=> NULL ["message":protected]=> string(14) "Access denied." } } } } ["request"]=> object(Symfony\Component\HttpFoundation\ParameterBag)#5 (1) { ["parameters":protected]=> array(0) { } } ["query"]=> object(Symfony\Component\HttpFoundation\ParameterBag)#6 (1) { ["parameters":protected]=> array(0) { } } ["server"]=> object(Symfony\Component\HttpFoundation\ServerBag)#10 (1) { ["parameters":protected]=> array(66) { ["PATH"]=> string(28) "/usr/local/bin:/usr/bin:/bin" ["TEMP"]=> string(4) "/tmp" ["TMP"]=> string(4) "/tmp" ["TMPDIR"]=> string(4) "/tmp" ["PWD"]=> string(1) "/" ["HTTP_ACCEPT"]=>

If you want to return as a json, serializer will serialize it,
You have 3 options,
You can Install jms serializer, which is more easy to serialize, less painless.
- https://symfony.com/doc/current/components/serializer.html#handling-circular-references
or
- https://symfony.com/doc/current/reference/configuration/framework.html#reference-serializer-circular-reference-handler
List item

Related

How to persist a polymorphic collection with API Platform

I'm developping a quotation tool with NextJS and an API developed with Symfony and API Platform.
A Quotation has a collection of QuotationRow which is extended by three others entities : QuotationRowProduct, QuotationRowText, QuotationRowSubtotal.
Below, the entire entities:
Quotation.php
<?php
namespace App\Entity\Quotation;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use App\Entity\Quotation\QuotationRow;
use App\Repository\QuotationRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use App\Entity\Client;
use App\Entity\ClientAgency;
#[ORM\Entity(repositoryClass: QuotationRepository::class)]
#[ApiResource(
normalizationContext: [
"groups" => ["Quotation:get"]
],
denormalizationContext: [
"groups" => ["Quotation:post"]
]
)]
#[ApiFilter(DateFilter::class, properties: ['createdAt' => DateFilter::EXCLUDE_NULL])]
#[HasLifecycleCallbacks]
class Quotation
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column()]
#[Groups(["Quotation:get"])]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Groups(["Quotation:get", "Quotation:post"])]
private ?string $status = null;
#[ORM\Column]
#[Groups(["Quotation:get", "Quotation:post"])]
private ?int $validityPeriod = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(["Quotation:get", "Quotation:post"])]
private ?string $comment = null;
#[ORM\OneToMany(mappedBy: 'quotation', targetEntity: QuotationRow::class, orphanRemoval: true, cascade: ["all"])]
#[Groups(["Quotation:get", "Quotation:post"])]
#[ORM\OrderBy(["position" => "ASC"])]
private $quotationRows;
#[ORM\ManyToOne]
#[Groups(["Quotation:get", "Quotation:post"])]
#[Assert\NotBlank(message: "Le client est requis")]
private ?Client $client = null;
#[ORM\ManyToOne]
#[Groups(["Quotation:get", "Quotation:post"])]
private ?ClientAgency $clientAgency = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
#[Groups(["Quotation:get"])]
private ?\DateTimeInterface $createdAt = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
#[Groups(["Quotation:get"])]
private ?\DateTimeInterface $updatedAt = null;
public function __construct()
{
$this->quotationRows = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
public function getValidityPeriod(): ?int
{
return $this->validityPeriod;
}
public function setValidityPeriod(int $validityPeriod): self
{
$this->validityPeriod = $validityPeriod;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
}
public function setComment(?string $comment): self
{
$this->comment = $comment;
return $this;
}
public function getClient(): ?Client
{
return $this->client;
}
public function setClient(?Client $client): self
{
$this->client = $client;
return $this;
}
/**
* #return Collection<int, QuotationRow>
*/
public function getQuotationRows(): Collection
{
return $this->quotationRows;
}
public function addQuotationRow(QuotationRow $quotationRow): self
{
if (!$this->quotationRows->contains($quotationRow)) {
$this->quotationRows[] = $quotationRow;
$quotationRow->setQuotation($this);
}
return $this;
}
public function removeQuotationRow(QuotationRow $quotationRow): self
{
if ($this->quotationRows->removeElement($quotationRow)) {
// set the owning side to null (unless already changed)
if ($quotationRow->getQuotation() === $this) {
$quotationRow->setQuotation(null);
}
}
return $this;
}
public function getClientAgency(): ?ClientAgency
{
return $this->clientAgency;
}
public function setClientAgency(?ClientAgency $clientAgency): self
{
$this->clientAgency = $clientAgency;
return $this;
}
#[ORM\PrePersist]
public function prePersist()
{
$currDateTime = new DateTime();
$this->createdAt = $currDateTime;
$this->updatedAt = $currDateTime;
$this->status = "waiting";
}
#[ORM\PreUpdate]
public function preUpdate()
{
$this->updatedAt = new DateTime();
}
}
QuotationRow.php
<?php
namespace App\Entity\Quotation;
use Doctrine\ORM\Mapping as ORM;
use App\Entity\Quotation\Quotation;
use App\Entity\Quotation\QuotationRowText;
use App\Repository\QuotationRowRepository;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Entity\Quotation\QuotationRowProduct;
use App\Entity\Quotation\QuotationRowSubtotal;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: QuotationRowRepository::class)]
#[ApiResource]
#[ORM\InheritanceType("SINGLE_TABLE")]
#[ORM\DiscriminatorColumn(name: "type", type: "string")]
#[ORM\DiscriminatorMap([
"product" => QuotationRowProduct::class,
"subtotal" => QuotationRowSubtotal::class,
"text" => QuotationRowText::class,
])]
class QuotationRow
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(["Quotation:get"])]
protected int $id;
#[ORM\ManyToOne(targetEntity: Quotation::class, inversedBy: 'quotationRows')]
#[ORM\JoinColumn(nullable: false)]
protected ?Quotation $quotation;
#[Groups(["Quotation:get", "Quotation:post"])]
#[ORM\Column]
protected ?int $position;
public function getId(): ?int
{
return $this->id;
}
public function getQuotation(): ?Quotation
{
return $this->quotation;
}
public function setQuotation(?Quotation $quotation): self
{
$this->quotation = $quotation;
return $this;
}
public function getPosition(): int
{
return $this->position;
}
public function setPosition(int $position)
{
$this->position = $position;
}
public function setType($type = "unknown"): self
{
$this->type = "unknown";
return $this;
}
}
QuotationRowProduct.php
<?php
namespace App\Entity\Quotation;
use App\Entity\VAT;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\QuotationRowRepository;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: QuotationRowRepository::class)]
#[ApiResource]
class QuotationRowProduct extends QuotationRow
{
const TYPE = "product";
#[ORM\Column]
#[Groups(["Quotation:get", "Quotation:post"])]
private float $preTaxPrice;
#[ORM\Column]
#[Groups(["Quotation:get", "Quotation:post"])]
private int $quantity;
#[ORM\Column(type: Types::TEXT)]
#[Groups(["Quotation:get", "Quotation:post"])]
private string $description;
#[ORM\Column]
#[Groups(["Quotation:get", "Quotation:post"])]
private float $preTaxDiscount;
#[ORM\Column]
#[Groups(["Quotation:get", "Quotation:post"])]
private float $discountRate;
#[ORM\ManyToOne(targetEntity: VAT::class)]
#[ORM\JoinColumn(nullable: true)]
#[Groups(["Quotation:get", "Quotation:post"])]
private ?VAT $vat;
#[Groups(["Quotation:get", "Quotation:post"])]
public function getType(): string
{
return self::TYPE;
}
public function getPreTaxPrice(): ?float
{
return $this->preTaxPrice;
}
public function setPreTaxPrice(float $preTaxPrice): self
{
$this->preTaxPrice = $preTaxPrice;
return $this;
}
public function getQuantity(): ?int
{
return $this->quantity;
}
public function setQuantity(int $quantity): self
{
$this->quantity = $quantity;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getPreTaxDiscount(): ?float
{
return $this->preTaxDiscount;
}
public function setPreTaxDiscount(float $preTaxDiscount): self
{
$this->preTaxDiscount = $preTaxDiscount;
return $this;
}
public function getDiscountRate(): ?float
{
return $this->discountRate;
}
public function setDiscountRate(float $discountRate): self
{
$this->discountRate = $discountRate;
return $this;
}
public function getVat(): ?VAT
{
return $this->vat;
}
public function setVat(?VAT $vat): self
{
$this->vat = $vat;
return $this;
}
public function setType($type = "product"): self
{
$this->type = "product";
return $this;
}
// Utility
public function getSubtotalWithoutVAT()
{
$price = $this->preTaxPrice * $this->quantity;
return $price - ($price * $this->discountRate / 100);
}
public function getSubtotalWithVAT()
{
$price = $this->getSubtotalWithoutVAT();
return $price + ($price * $this->vat->getRate() / 100);
}
}
QuotationRowSubtotal.php
<?php
namespace App\Entity\Quotation;
use Doctrine\ORM\Mapping as ORM;
use App\Entity\Quotation\QuotationRow;
use App\Repository\QuotationRowRepository;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: QuotationRowRepository::class)]
#[ApiResource]
class QuotationRowSubtotal extends QuotationRow
{
const TYPE = "subtotal";
#[Groups(["Quotation:get", "Quotation:post"])]
public function getType(): string
{
return self::TYPE;
}
private function getAllRows()
{
return $this->quotation->getQuotationRows();
}
/**
* Return all subtotals in the Quotation#rows list
* #return QuotationRowSubtotal[]
*/
private function getSubtotalRows()
{
$filtered = [];
foreach ($this->getAllRows() as $row) {
if ($row instanceof QuotationRowSubtotal) {
$filtered[] = $row;
}
}
return $filtered;
}
/**
* Get the previous Subtotal priority
* #return integer
*/
private function getPrevSubtotalPos()
{
// Reverse result for find more quicker the last subtotal
$subtotalRows = array_reverse($this->getSubtotalRows());
foreach ($subtotalRows as $row) {
if ($this->position > $row->getPosition()) {
return $row->getPosition();
}
}
// Return -1 if any result for the next function condition
return -1;
}
/**
* Return the Subtotal from the last Subtotal without VAT
* #return float
*/
#[Groups(["Quotation:get"])]
public function getSubtotal()
{
$lastPosition = $this->getPrevSubtotalPos();
$total = 0;
foreach ($this->getAllRows() as $row) {
if ($row->getPosition() < $this->position && $row->getPosition() > $lastPosition) {
if ($row instanceof QuotationRowProduct) {
$total += $row->getSubtotalWithoutVAT();
}
}
}
return $total;
}
/**
* Return the Subtotal from the last Subtotal with VAT
* #return float
*/
#[Groups(["Quotation:get"])]
public function getSubtotalWithVat()
{
$lastPosition = $this->getPrevSubtotalPos();
$total = 0;
foreach ($this->getAllRows() as $row) {
if ($row->getPosition() < $this->position && $row->getPosition() > $lastPosition) {
if ($row instanceof QuotationRowProduct) {
$total += $row->getSubtotalWithVAT();
}
}
}
return $total;
}
}
QuotationRowText.php
<?php
namespace App\Entity\Quotation;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use App\Entity\Quotation\QuotationRow;
use App\Repository\QuotationRowRepository;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: QuotationRowRepository::class)]
#[ApiResource]
class QuotationRowText extends QuotationRow
{
const TYPE = "text";
#[ORM\Column(type: Types::TEXT)]
#[Groups(["Quotation:get", "Quotation:post"])]
private string $text;
#[Groups(["Quotation:get", "Quotation:post"])]
public function getType(): string
{
return self::TYPE;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
}
As you can see above in Quotation.php, the collection of QuotationRow is persisted by the Quotation entity.
The probleme is when I do a POST request to the API on /api/quotations endpoint, the API throw an SQL error : "column 'type' cannot be null", but 'type' is the discriminator column.
So I tried somethings before write this topic:
Send 'type' in the body request to the API: it's ignored by the API
Map $type as a protected property of QuotationRow : Makes an error because column 'type' already exsists in table 'quotation_row'.
I would like persist all QuotationRow from the Quotation API endpoint . Is there a way to persist a polymorphic collection in OneToMany context from the owner side ? Maybe by wrting a custom API Controller ?
Note that: there is no problem to update QuotationRow when they already exist in database, only to persist them.

Modify POST data before storing in database with Symfony and API-Platform

I'm fairly new to the Symfony universe, so please bear with me if this question has already been answered.
I have provided endpoints with the api-platform to create a RegistrationRequest. A RegistrationRequest has a user that is connected via a ManyToOne relation, so it is stored in another table. In the API, the user or user_id is read-only, this is why the user can not be set in the post data. If a RegistrationRequest is made via the API, the creation fails because the user_id is obviously null.
This is why I would like to set the user manually after a registration request is made via the API but before the RegistrationRequest is stored in the database.
The user is known via the global REMOTE_USER from where I can derive the corresponding user object.
src/Entity/RegistrationRequest.php:
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=App\Repository\RegistrationRequestRepository::class)
* #ApiResource(
* normalizationContext={"groups" = {"read"}},
* denormalizationContext={"groups" = {"write"}},
* paginationEnabled=false,
* )
*/
class RegistrationRequest
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"read"})
*/
private $id;
/**
* #ORM\Column(type="string", length=64, nullable=true)
* #Groups({"read", "write"})
*/
private $opt_email;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Groups({"read", "write"})
*/
private $title;
/**
* #Gedmo\Mapping\Annotation\Timestampable(on="create")
* #ORM\Column(type="datetime")
* #Groups({"read"})
*/
private $created_at;
/**
* #Gedmo\Mapping\Annotation\Timestampable(on="update")
* #ORM\Column(type="datetime")
* #Groups({"read"})
*/
private $updated_at;
/**
* #ORM\Column(type="text", nullable=true)
* #Groups({"read", "write"})
*/
private $notes;
/**
* #ORM\Column(type="string", length=16)
* #Groups({"read", "write"})
*/
private $language_code;
/**
* #ORM\Column(type="text")
* #Groups({"read", "write"})
*/
private $data;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="registrationRequests")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
public function getId(): ?int
{
return $this->id;
}
public function getOptEmail(): ?string
{
return $this->opt_email;
}
public function setOptEmail(?string $opt_email): self
{
$this->opt_email = $opt_email;
return $this;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeInterface $created_at): self
{
$this->created_at = $created_at;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updated_at;
}
public function setUpdatedAt(\DateTimeInterface $updated_at): self
{
$this->updated_at = $updated_at;
return $this;
}
public function getNotes(): ?string
{
return $this->notes;
}
public function setNotes(?string $notes): self
{
$this->notes = $notes;
return $this;
}
public function getLanguageCode(): ?string
{
return $this->language_code;
}
public function setLanguageCode(string $language_code): self
{
$this->language_code = $language_code;
return $this;
}
public function getData(): ?string
{
return $this->data;
}
public function setData(string $data): self
{
$this->data = $data;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
}
src/Controller/RegistrationRequestController.php:
<?php
namespace App\Controller;
use App\Service\IGSNService;
use App\Entity\RegistrationRequest;
use App\Form\RegistrationRequestType;
use App\Repository\RegistrationRequestRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* #Route("/registration_request")
*/
class RegistrationRequestController extends AbstractController
{
/**
* #Route("/")
*/
public function indexNoLocale(): Response
{
return $this->redirectToRoute('app_registration_request_index', ['_locale' => 'de']);
}
/**
* #Route("/{_locale<%app.supported_locales%>}/", name="app_registration_request_index", methods={"GET"})
*/
public function index(RegistrationRequestRepository $registrationRequestRepository): Response
{
return $this->render('registration_request/index.html.twig', [
'registration_requests' => $registrationRequestRepository->findAll(),
]);
}
/**
* #Route("/{_locale<%app.supported_locales%>}/new", name="app_registration_request_new", methods={"GET", "POST"})
*/
public function new(Request $request, RegistrationRequestRepository $registrationRequestRepository, IGSNService $igsnService, TranslatorInterface $translator): Response
{
$registrationRequest = new RegistrationRequest();
$form = $this->createForm(RegistrationRequestType::class, $registrationRequest);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$json_data = json_decode($registrationRequest->getData());
$err = $igsnService->validate_data($json_data);
if ($json_data !== null && empty($err)) {
$registrationRequestRepository->add($registrationRequest, true);
$this->addFlash(
'success',
$translator->trans('Your changes were saved!')
);
return $this->redirectToRoute('app_registration_request_index', [], Response::HTTP_SEE_OTHER);
} else {
$this->addFlash(
'schema_error',
$err
);
}
}
return $this->renderForm('registration_request/new.html.twig', [
'registration_request' => $registrationRequest,
'form' => $form,
]);
}
/**
* #Route("/{_locale<%app.supported_locales%>}/{id}", name="app_registration_request_show", methods={"GET"})
*/
public function show(RegistrationRequest $registrationRequest): Response
{
return $this->render('registration_request/show.html.twig', [
'registration_request' => $registrationRequest,
]);
}
/**
* #Route("/{_locale<%app.supported_locales%>}/{id}/edit", name="app_registration_request_edit", methods={"GET", "POST"})
*/
public function edit(Request $request, RegistrationRequest $registrationRequest, RegistrationRequestRepository $registrationRequestRepository, IGSNService $igsnService, TranslatorInterface $translator): Response
{
$form = $this->createForm(RegistrationRequestType::class, $registrationRequest);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$json_data = json_decode($registrationRequest->getData());
$err = $igsnService->validate_data($json_data);
if ($json_data !== null && empty($err)) {
$registrationRequestRepository->add($registrationRequest, true);
$this->addFlash(
'success',
$translator->trans('Your changes were saved!')
);
return $this->redirectToRoute('app_registration_request_index', [], Response::HTTP_SEE_OTHER);
} else {
$this->addFlash(
'schema_error',
$err
);
}
}
return $this->renderForm('registration_request/edit.html.twig', [
'registration_request' => $registrationRequest,
'form' => $form,
]);
}
/**
* #Route("/{_locale<%app.supported_locales%>}/{id}", name="app_registration_request_delete", methods={"POST"})
*/
public function delete(Request $request, RegistrationRequest $registrationRequest, RegistrationRequestRepository $registrationRequestRepository, TranslatorInterface $translator): Response
{
if ($this->isCsrfTokenValid('delete' . $registrationRequest->getId(), $request->request->get('_token'))) {
$registrationRequestRepository->remove($registrationRequest, true);
$this->addFlash(
'success',
$translator->trans('Request successfully deleted!')
);
}
return $this->redirectToRoute('app_registration_request_index', [], Response::HTTP_SEE_OTHER);
}
}
config/packages/api_platform.yaml
api_platform:
mapping:
paths: ['%kernel.project_dir%/src/Entity']
patch_formats:
json: ['application/merge-patch+json']
swagger:
versions: [3]
# Fixes empty api endpoint list with error:
# No operations defined in spec!
# See https://github.com/api-platform/core/issues/4485
metadata_backward_compatibility_layer: false
I have now got it solved with a DataPersister, as suggested by Julien B. in the comments.
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Entity\RegistrationRequest;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
final class RequestDataPersister implements DataPersisterInterface
{
private $security;
public function __construct(Security $security, EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->security = $security;
}
public function supports($data): bool
{
return $data instanceof RegistrationRequest;
}
public function persist($data)
{
$data->setUser($this->security->getUser());
$this->entityManager->persist($data);
$this->entityManager->flush();
return $data;
}
public function remove($data): void
{
// no action needed
}
}

DQL Jsor/doctrine-postgis

I'm triying to get a calculated value from database where this value depends on an external value. I got the next function:
public function findAllBy($search, $order, $limit, $offset) {
$qb = $this->entityManager
->createQueryBuilder()
->select('a, (ST_DISTANCE(ST_MakePoint(0,0), ST_MakePoint(0,0)) / 1000) as distance')
->from('App\Entity\App\Advertisement', 'a')
->setMaxResult($limit)
->setFirstResult($offset);
for ($order as $key => $value)
$qb->andWhere($key !== "distance" ? "a.{$key}" : "distance", $value);
return $qb->getQuery()->getResult();
}
This function works, but the problem comes now. Like I said, I need to modify two parameters, one of theme come from outside of this function, that's not a problem, the problem comes with the other one. I the advertisement table I got a column named "coordinate" of type Point (postgis), when I try to substitute one of ST_MakePoint like this:
(ST_DISTANCE(ST_MakePoint(a.coordinate[0] a.coordinate[1]), ST_MakePoint(0,0)) / 1000) as distance
I got the next error:
"[Syntax Error] line 0, col 49: Error: Expected Doctrine\\ORM\\Query\\Lexer::T_COMMA, got '['"
I also tried:
(ST_DISTANCE(a.coordinate::geometry, ST_MakePoint(0,0)) / 1000) as distance
"[Syntax Error] line 0, col 35: Error: Expected Doctrine\\ORM\\Query\\Lexer::T_COMMA, got ':'"
Other:
ERROR: function st_distance(point, geometry) does not exist
(ST_DISTANCE(a.coordinate, ST_MakePoint(0,0)) / 1000) as distance // Error coordinate not a geometry
Other:
[Syntax Error] line 0, col 54: Error: Expected Doctrine\\ORM\\Query\\Lexer::T_CLOSE_PARENTHESIS, got ':'"
(ST_DISTANCE(a.coordinate, ST_MakePoint(0,0)::point) / 1000) as distance
None of them works. I dont how can I use the coordinate column on DQL.
If that helps, I got another function that works, but it is SQL not DQL, and I dont know how to parse the result as entity. Here it is:
SELECT x.id
FROM (
SELECT
g.id,
(ST_DISTANCE(ST_MakePoint(g.coordinate[0], g.coordinate[1])::geography, ST_MakePoint($point)::geography) / 1000) as distance
FROM app.graveyard g
) AS x
WHERE x.distance <= 100
ORDER BY x.distance ASC
Entity:
<?php
namespace App\Entity\App;
use App\DBAL\Types\Geolocation\Point;
use App\Repository\App\AdvertisementRepository;
use App\Tools\Tools;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\MaxDepth;
/**
* #ORM\Entity(repositoryClass=AdvertisementRepository::class)
* #ORM\Table(schema="app")
*/
class Advertisement
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=App\Entity\App\Company::class, inversedBy="advertisements")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
/**
* #ORM\Column(type="AdvertisementTypeEnum")
*/
private $type;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="string", length=500)
*/
private $description;
/**
* #ORM\Column(type="float")
*/
private $price;
/**
* #ORM\Column(type="integer")
*/
private $nicheTime;
/**
* #ORM\Column(type="boolean")
*/
private $enabled;
/**
* #ORM\OneToMany(targetEntity=App\Entity\App\AdvertisementComplement::class, mappedBy="advertisement")
*/
private $advertisementComplements;
/**
* #ORM\OneToMany(targetEntity=App\Entity\App\AdvertisementTransfer::class, mappedBy="advertisement")
*/
private $advertisementTransfers;
/**
* #ORM\OneToMany(targetEntity=App\Entity\App\AdvertisementGraveyard::class, mappedBy="advertisement")
*/
private $advertisementGraveyards;
/**
* #ORM\OneToMany(targetEntity=App\Entity\App\AdvertisementFuneralParlor::class, mappedBy="advertisement")
*/
private $advertisementFuneralParlors;
/**
* #ORM\OneToMany(targetEntity=AdvertisementCrematorium::class, mappedBy="advertisement")
*/
private $advertisementCrematoria;
/**
* #ORM\Column(type="integer")
*/
private $query;
/**
* #ORM\Column(type="datetime")
*/
private $createAt;
/**
* #ORM\Column(type="datetime")
*/
private $updatedAt;
/**
* #ORM\OneToMany(targetEntity=AdvertisementStatistic::class, mappedBy="advertisement")
*/
private $advertisementStatistics;
/**
* #ORM\Column(type="date", nullable=true)
*/
private $publishDate;
/**
* #ORM\OneToMany(targetEntity=AdvertisementRate::class, mappedBy="advertisement")
*/
private $advertisementRates;
/**
* #ORM\Column(type="integer", options={"default":10000})
*/
private $version;
/**
* #ORM\OneToMany(targetEntity=Contract::class, mappedBy="advertisement", cascade={"persist"}, fetch="LAZY")
*/
private $contracts;
/**
* #ORM\Column(type="boolean")
*/
private $wake;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $wakeTime;
/**
* #ORM\Column(type="float", nullable=true)
*/
private $wakePrice;
/**
* #ORM\Column(type="float", nullable=true)
*/
private $kmExtraPrice;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
private $wakeIncluded;
/**
* #ORM\Column(type="datetime_immutable", nullable=true)
*/
private $deletedAt;
/**
* #ORM\ManyToOne(targetEntity=City::class)
*/
private $city;
/**
* #ORM\Column(type="point", nullable=true)
*/
private $coordinate;
public function __construct()
{
$this->advertisementComplements = new ArrayCollection();
$this->advertisementTransfers = new ArrayCollection();
$this->advertisementGraveyards = new ArrayCollection();
$this->advertisementFuneralParlors = new ArrayCollection();
$this->advertisementCrematoria = new ArrayCollection();
$this->createAt = new \DateTime("now");
$this->updatedAt = new \DateTime("now");
$this->advertisementStatistics = new ArrayCollection();
$this->advertisementRates = new ArrayCollection();
$this->contracts = new ArrayCollection();
$this->coordinate = $this->city->getCoordinate();
}
public function getId(): ?int
{
return $this->id;
}
public function getCompany(): ?Company
{
return $this->company;
}
public function setCompany(?Company $company): self
{
$this->company = $company;
return $this;
}
public function getType()
{
return $this->type;
}
public function setType($type): self
{
$this->type = $type;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getPrice(): ?float
{
return $this->price;
}
public function setPrice(float $price): self
{
$this->price = $price;
return $this;
}
public function getNicheTime(): ?int
{
return $this->nicheTime;
}
public function setNicheTime(?int $nicheTime): self
{
$this->nicheTime = $nicheTime;
return $this;
}
public function getEnabled(): ?bool
{
return $this->enabled;
}
public function setEnabled(bool $enabled): self
{
$this->enabled = $enabled;
return $this;
}
/**
* #return Collection|AdvertisementComplement[]|null
*/
public function getAdvertisementComplements(): ?Collection
{
return $this->advertisementComplements;
}
public function addAdvertisementComplement(AdvertisementComplement $advertisementComplement): self
{
if (!$this->advertisementComplements->contains($advertisementComplement)) {
$this->advertisementComplements[] = $advertisementComplement;
$advertisementComplement->setAdvertisement($this);
}
return $this;
}
public function removeAdvertisementComplement(AdvertisementComplement $advertisementComplement): self
{
if ($this->advertisementComplements->removeElement($advertisementComplement)) {
// set the owning side to null (unless already changed)
if ($advertisementComplement->getAdvertisement() === $this) {
$advertisementComplement->setAdvertisement(null);
}
}
return $this;
}
/**
* #return Collection|AdvertisementTransfer[]|null
*/
public function getAdvertisementTransfers(): ?Collection
{
return $this->advertisementTransfers;
}
public function addAdvertisementTransfer(AdvertisementTransfer $advertisementTransfer): self
{
if (!$this->advertisementTransfers->contains($advertisementTransfer)) {
$this->advertisementTransfers[] = $advertisementTransfer;
$advertisementTransfer->setAdvertisement($this);
}
return $this;
}
public function removeAdvertisementTransfer(AdvertisementTransfer $advertisementTransfer): self
{
if ($this->advertisementTransfers->removeElement($advertisementTransfer)) {
// set the owning side to null (unless already changed)
if ($advertisementTransfer->getAdvertisement() === $this) {
$advertisementTransfer->setAdvertisement(null);
}
}
return $this;
}
/**
* #return Collection|AdvertisementGraveyard[]
*/
public function getAdvertisementGraveyards(): Collection
{
return $this->advertisementGraveyards;
}
public function addAdvertisementGraveyard(AdvertisementGraveyard $advertisementGraveyard): self
{
if (!$this->advertisementGraveyards->contains($advertisementGraveyard)) {
$this->advertisementGraveyards[] = $advertisementGraveyard;
$advertisementGraveyard->setAdvertisement($this);
}
return $this;
}
public function removeAdvertisementGraveyard(AdvertisementGraveyard $advertisementGraveyard): self
{
if ($this->advertisementGraveyards->removeElement($advertisementGraveyard)) {
// set the owning side to null (unless already changed)
if ($advertisementGraveyard->getAdvertisement() === $this) {
$advertisementGraveyard->setAdvertisement(null);
}
}
return $this;
}
/**
* #return Collection|AdvertisementFuneralParlor[]
*/
public function getAdvertisementFuneralParlors(): Collection
{
return $this->advertisementFuneralParlors;
}
public function addAdvertisementFuneralParlor(AdvertisementFuneralParlor $advertisementFuneralParlor): self
{
if (!$this->advertisementFuneralParlors->contains($advertisementFuneralParlor)) {
$this->advertisementFuneralParlors[] = $advertisementFuneralParlor;
$advertisementFuneralParlor->setAdvertisement($this);
}
return $this;
}
public function removeAdvertisementFuneralParlor(AdvertisementFuneralParlor $advertisementFuneralParlor): self
{
if ($this->advertisementFuneralParlors->removeElement($advertisementFuneralParlor)) {
// set the owning side to null (unless already changed)
if ($advertisementFuneralParlor->getAdvertisement() === $this) {
$advertisementFuneralParlor->setAdvertisement(null);
}
}
return $this;
}
public function getQuery(): ?int
{
return $this->query;
}
public function setQuery(int $query): self
{
$this->query = $query;
return $this;
}
/**
* #return Collection|AdvertisementCrematorium[]
*/
public function getAdvertisementCrematoria(): Collection
{
return $this->advertisementCrematoria;
}
public function addAdvertisementCrematorium(AdvertisementCrematorium $advertisementCrematorium): self
{
if (!$this->advertisementCrematoria->contains($advertisementCrematorium)) {
$this->advertisementCrematoria[] = $advertisementCrematorium;
$advertisementCrematorium->setAdvertisement($this);
}
return $this;
}
public function removeAdvertisementCrematorium(AdvertisementCrematorium $advertisementCrematorium): self
{
if ($this->advertisementCrematoria->removeElement($advertisementCrematorium)) {
// set the owning side to null (unless already changed)
if ($advertisementCrematorium->getAdvertisement() === $this) {
$advertisementCrematorium->setAdvertisement(null);
}
}
return $this;
}
public function getCreateAt(): ?\DateTimeInterface
{
return $this->createAt;
}
public function getCreateAtNormalized(): string
{
return $this->createAt->format("Y-m-d H:i:s");
}
public function setCreateAt(): self
{
$this->createAt = new \DateTime("now");
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function getUpdateAtNormalized(): string
{
return $this->updatedAt->format("Y-m-d H:i:s");
}
public function setUpdatedAt(): self
{
$this->updatedAt = new \DateTime("now");
return $this;
}
/**
* #return Collection|AdvertisementStatistic[]
*/
public function getAdvertisementStatistics(): Collection
{
return $this->advertisementStatistics;
}
public function addAdvertisementStatistic(AdvertisementStatistic $advertisementStatistic): self
{
if (!$this->advertisementStatistics->contains($advertisementStatistic)) {
$this->advertisementStatistics[] = $advertisementStatistic;
$advertisementStatistic->setAdvertisement($this);
}
return $this;
}
public function removeAdvertisementStatistic(AdvertisementStatistic $advertisementStatistic): self
{
if ($this->advertisementStatistics->removeElement($advertisementStatistic)) {
// set the owning side to null (unless already changed)
if ($advertisementStatistic->getAdvertisement() === $this) {
$advertisementStatistic->setAdvertisement(null);
}
}
return $this;
}
public function getPublishDate(): ?\DateTimeInterface
{
return $this->publishDate;
}
public function setPublishDate(): self
{
$this->publishDate = new \DateTime("now");
return $this;
}
public function clearPublishDate(): self
{
$this->publishDate = null;
return $this;
}
/**
* #return Collection|AdvertisementRate[]
*/
public function getAdvertisementRates(): Collection
{
return $this->advertisementRates;
}
public function addAdvertisementRate(AdvertisementRate $advertisementRate): self
{
if (!$this->advertisementRates->contains($advertisementRate)) {
$this->advertisementRates[] = $advertisementRate;
$advertisementRate->setAdvertisement($this);
}
return $this;
}
public function removeAdvertisementRate(AdvertisementRate $advertisementRate): self
{
if ($this->advertisementRates->removeElement($advertisementRate)) {
// set the owning side to null (unless already changed)
if ($advertisementRate->getAdvertisement() === $this) {
$advertisementRate->setAdvertisement(null);
}
}
return $this;
}
public function getVersion(): int
{
return $this->version;
}
public function getStringVersion(): string
{
$v = $this->getVersion();
$result = "";
while ($v > 0) {
$result = ((strlen($result) + 2) % 3) == 0 ? '.' . $v %10 . $result : $v %10 . $result;
$v = (int)($v / 10);
}
return $result;
}
public function setVersion(int $version): self
{
$this->version = $version;
return $this;
}
/**
* #return Collection|Contract[]
*/
public function getContracts(): Collection
{
return $this->contracts;
}
public function addContract(Contract $contract): self
{
if (!$this->contracts->contains($contract)) {
$this->contracts[] = $contract;
$contract->setAdvertisement($this);
}
return $this;
}
public function removeContract(Contract $contract): self
{
if ($this->contracts->removeElement($contract)) {
// set the owning side to null (unless already changed)
if ($contract->getAdvertisement() === $this) {
$contract->setAdvertisement(null);
}
}
return $this;
}
public function getWake(): bool
{
return $this->wake;
}
public function setWake(bool $wake): self
{
$this->wake = $wake;
return $this;
}
public function getWakeTime(): ?int
{
return $this->wakeTime;
}
public function setWakeTime(?int $wakeTime): self
{
$this->wakeTime = $wakeTime;
return $this;
}
public function getWakePrice(): ?float
{
return $this->wakePrice;
}
public function setWakePrice(?float $wakePrice): self
{
$this->wakePrice = $wakePrice;
return $this;
}
public function getKmExtraPrice(): ?float
{
return $this->kmExtraPrice;
}
public function setKmExtraPrice(?float $kmExtraPrice): self
{
$this->kmExtraPrice = $kmExtraPrice;
return $this;
}
public function getWakeIncluded(): ?bool
{
return $this->wakeIncluded;
}
private function getJsonRates(): array
{
$size = sizeof($this->advertisementRates);
if ($size == 0) return [];
$average = 0.0;
$jsonRates = [];
foreach ($this->advertisementRates as $advertisementRate) {
array_push($jsonRates, $advertisementRate->toJsonArray());
$average += $advertisementRate->getScore();
}
$average /= $size;
return ["average" => $average, "rates" => $jsonRates, "size" => $size];
}
public function getDeletedAt(): ?\DateTimeImmutable
{
return $this->deletedAt;
}
public function setDeletedAt(?\DateTimeImmutable $deletedAt): self
{
$this->deletedAt = $deletedAt;
return $this;
}
public function setWakeIncluded(?bool $wakeIncluded): self
{
$this->wakeIncluded = $wakeIncluded;
return $this;
}
public function getCity(): ?City
{
return $this->city;
}
public function setCity(?City $city): self
{
$this->city = $city;
return $this;
}
public function getDistance(Point $destiny)
{
dump($this->tools);
return 0;
// return $this->tools->getDistanceBetweenPoint($this->coordinate, $destiny);
}
public function toJsonArray(): array
{
$publishDate = $this->getPublishDate();
$rates = $this->getJsonRates();
return [
"id" => $this->id,
"companyId" => $this->company->getId(),
"name" => $this->name,
"description" => $this->description,
"type" => $this->type,
"basePrice" => $this->price,
"query" => $this->query,
"updateAt" => $this->getUpdateAtNormalized(),
"publishDate" => !is_null($publishDate) ? $publishDate->format("d-m-Y") : null,
"enable" => $this->enabled,
"version" => $this->getStringVersion(),
"wake" => $this->wake,
"wakeIncluded" => $this->wakeIncluded,
"wakePrice" => $this->wakePrice,
"wakeTime" => $this->wakeTime,
"kmExtraPrice" => $this->kmExtraPrice,
"rates" => empty($rates) ?
null :
[
"average" => $rates['average'],
"rates" => $rates['rates'],
"ratesCount" => $rates['size']
],
];
}
}

My filename is not persisted in my database with Vichuploader

I tried to upload images in my database with Symfony 5 and Vichuploader bundle but my filename is not persisted in my database and I don't know why.
This the error that I have : An exception occurred while executing a query: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'filename' cannot be null
This is my entity Person.php :
namespace App\Entity;
use App\Repository\PersonRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass=PersonRepository::class)
*/
class Person
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="datetime")
*/
private $birthDate;
/**
* #ORM\ManyToMany(targetEntity=Movie::class, mappedBy="writers")
*/
public $writedMovies;
/**
* #ORM\ManyToMany(targetEntity=Movie::class, mappedBy="directors")
*/
private $directedMovies;
/**
* #ORM\ManyToMany(targetEntity=Movie::class, mappedBy="producers")
*/
private $producedMovies;
/**
* #ORM\OneToMany(targetEntity=MovieActor::class, mappedBy="person", orphanRemoval=true, cascade={"persist"})
*/
private $movieActors;
/**
* #ORM\Column(type="boolean")
*/
private $sex;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $pseudo;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $deathDate;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $firstname;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $middleName;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $biography;
/**
* #ORM\OneToMany(targetEntity=MovieMusicComposer::class, mappedBy="person")
*/
private $movieMusicComposers;
/**
* #ORM\OneToMany(targetEntity=AwardPerson::class, mappedBy="person")
*/
private $awardPeople;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $award1;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $award2;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $award3;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $award4;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $award5;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $award6;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $nationality;
/**
* #ORM\OneToMany(targetEntity=PicturePerson::class, mappedBy="person", orphanRemoval=true, cascade={"persist"})
*/
private $picturePeople;
/**
* #Assert\All({
* #Assert\Image(mimeTypes="image/jpeg")
* })
*/
private $pictureFiles;
/**
* #ORM\Column(type="datetime")
*/
private $created_at;
/**
* #ORM\Column(type="datetime")
*/
private $updated_at;
public function __construct()
{
$this->writedMovies = new ArrayCollection();
$this->movieActors = new ArrayCollection();
$this->movieMusicComposers = new ArrayCollection();
$this->awardPeoples = new ArrayCollection();
$this->directedMovies = new ArrayCollection();
$this->producedMovies = new ArrayCollection();
$this->awardPeople = new ArrayCollection();
$this->picturePeople = new ArrayCollection();
$this->created_at = new \DateTime();
$this->updated_at = new \DateTime();
}
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 getBirthDate(): ?\DateTimeInterface
{
return $this->birthDate;
}
public function setBirthDate(\DateTimeInterface $birthDate): self
{
$this->birthDate = $birthDate;
return $this;
}
/**
* #return Collection|Movie[]
*/
public function getWritedMovies(): Collection
{
return $this->writedMovies;
}
public function addWritedMovie(Movie $writedMovie): self
{
if (!$this->writedMovies->contains($writedMovie)) {
$this->writedMovies[] = $writedMovie;
}
return $this;
}
public function removeWritedMovie(Movie $writedMovie): self
{
$this->writedMovies->removeElement($writedMovie);
return $this;
}
/**
* #return Collection|MovieActor[]
*/
public function getMovieActors(): Collection
{
return $this->movieActors;
}
public function addMovieActor(MovieActor $movieActor): self
{
if (!$this->movieActors->contains($movieActor)) {
$this->movieActors[] = $movieActor;
$movieActor->setPerson($this);
}
return $this;
}
public function removeMovieActor(MovieActor $movieActor): self
{
if ($this->movieActors->removeElement($movieActor)) {
// set the owning side to null (unless already changed)
if ($movieActor->getPerson() === $this) {
$movieActor->setPerson(null);
}
}
return $this;
}
public function getSex(): ?bool
{
return $this->sex;
}
public function setSex(bool $sex): self
{
$this->sex = $sex;
return $this;
}
public function getPseudo(): ?string
{
return $this->pseudo;
}
public function setPseudo(?string $pseudo): self
{
$this->pseudo = $pseudo;
return $this;
}
public function getDeathDate(): ?\DateTimeInterface
{
return $this->deathDate;
}
public function setDeathDate(?\DateTimeInterface $deathDate): self
{
$this->deathDate = $deathDate;
return $this;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(?string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getMiddleName(): ?string
{
return $this->middleName;
}
public function setMiddleName(string $middleName): self
{
$this->middleName = $middleName;
return $this;
}
public function getBiography(): ?string
{
return $this->biography;
}
public function setBiography(?string $biography): self
{
$this->biography = $biography;
return $this;
}
/**
* #return Collection|MovieMusicComposer[]
*/
public function getMovieMusicComposers(): Collection
{
return $this->movieMusicComposers;
}
public function addMovieMusicComposer(MovieMusicComposer $movieMusicComposer): self
{
if (!$this->movieMusicComposers->contains($movieMusicComposer)) {
$this->movieMusicComposers[] = $movieMusicComposer;
$movieMusicComposer->setPerson($this);
}
return $this;
}
public function removeMovieMusicComposer(MovieMusicComposer $movieMusicComposer): self
{
if ($this->movieMusicComposers->removeElement($movieMusicComposer)) {
// set the owning side to null (unless already changed)
if ($movieMusicComposer->getPerson() === $this) {
$movieMusicComposer->setPerson(null);
}
}
return $this;
}
/**
* #return Collection|Movie[]
*/
public function getDirectedMovies(): Collection
{
return $this->directedMovies;
}
public function addDirectedMovie(Movie $directedMovie): self
{
if (!$this->directedMovies->contains($directedMovie)) {
$this->directedMovies[] = $directedMovie;
$directedMovie->addDirector($this);
}
return $this;
}
public function removeDirectedMovie(Movie $directedMovie): self
{
if ($this->directedMovies->removeElement($directedMovie)) {
$directedMovie->removeDirector($this);
}
return $this;
}
/**
* #return Collection|Movie[]
*/
public function getProducedMovies(): Collection
{
return $this->producedMovies;
}
public function addProducedMovie(Movie $producedMovie): self
{
if (!$this->producedMovies->contains($producedMovie)) {
$this->producedMovies[] = $producedMovie;
$producedMovie->addProducer($this);
}
return $this;
}
public function removeProducedMovie(Movie $producedMovie): self
{
if ($this->producedMovies->removeElement($producedMovie)) {
$producedMovie->removeProducer($this);
}
return $this;
}
/**
* #return Collection|AwardPerson[]
*/
public function getAwardPeople(): Collection
{
return $this->awardPeople;
}
public function addAwardPerson(AwardPerson $awardPerson): self
{
if (!$this->awardPeople->contains($awardPerson)) {
$this->awardPeople[] = $awardPerson;
$awardPerson->setPerson($this);
}
return $this;
}
public function removeAwardPerson(AwardPerson $awardPerson): self
{
if ($this->awardPeople->removeElement($awardPerson)) {
// set the owning side to null (unless already changed)
if ($awardPerson->getPerson() === $this) {
$awardPerson->setPerson(null);
}
}
return $this;
}
public function getAward1(): ?string
{
return $this->award1;
}
public function setAward1(?string $award1): self
{
$this->award1 = $award1;
return $this;
}
public function getAward2(): ?string
{
return $this->award2;
}
public function setAward2(?string $award2): self
{
$this->award2 = $award2;
return $this;
}
public function getAward3(): ?string
{
return $this->award3;
}
public function setAward3(?string $award3): self
{
$this->award3 = $award3;
return $this;
}
public function getAward4(): ?string
{
return $this->award4;
}
public function setAward4(?string $award4): self
{
$this->award4 = $award4;
return $this;
}
public function getAward5(): ?string
{
return $this->award5;
}
public function setAward5(?string $award5): self
{
$this->award5 = $award5;
return $this;
}
public function getAward6(): ?string
{
return $this->award6;
}
public function setAward6(?string $award6): self
{
$this->award6 = $award6;
return $this;
}
public function getNationality(): ?string
{
return $this->nationality;
}
public function setNationality(?string $nationality): self
{
$this->nationality = $nationality;
return $this;
}
/**
* #return Collection|PicturePerson[]
*/
public function getPicturePeople(): Collection
{
return $this->picturePeople;
}
public function addPicturePerson(PicturePerson $picturePerson): self
{
if (!$this->picturePeople->contains($picturePerson)) {
$this->picturePeople[] = $picturePerson;
$picturePerson->setPerson($this);
}
return $this;
}
/**
* #return mixed
*/
public function getPictureFiles()
{
return $this->pictureFiles;
}
/**
* #param mixed $pictureFiles
* #return Person
*/
public function setPictureFiles($pictureFiles): self
{
foreach($pictureFiles as $pictureFile) {
$picture = new PicturePerson();
$picture->setImageFile($pictureFile);
$this->addPicturePerson($picture);
}
$this->pictureFiles = $pictureFiles;
return $this;
}
public function removePicturePerson(PicturePerson $picturePerson): self
{
if ($this->picturePeople->removeElement($picturePerson)) {
// set the owning side to null (unless already changed)
if ($picturePerson->getPerson() === $this) {
$picturePerson->setPerson(null);
}
}
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeInterface $created_at): self
{
$this->created_at = $created_at;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updated_at;
}
public function setUpdatedAt(\DateTimeInterface $updated_at): self
{
$this->updated_at = $updated_at;
return $this;
}
}
This is my Entity PicturePerson.php :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass=PicturePersonRepository::class)
*/
class PicturePerson
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var File|null
* #Assert\Image(
* mimeTypes="image/jpeg"
* )
* #Vich\UploadableField(mapping="person_image", fileNameProperty="filename")
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*/
private $filename;
/**
* #ORM\ManyToOne(targetEntity=Person::class, inversedBy="picturePeople")
* #ORM\JoinColumn(nullable=false)
*/
private $person;
public function getId(): ?int
{
return $this->id;
}
public function getFilename(): ?string
{
return $this->filename;
}
public function setFilename(?string $filename): self
{
$this->filename = $filename;
return $this;
}
public function getPerson(): ?Person
{
return $this->person;
}
public function setPerson(?Person $person): self
{
$this->person = $person;
return $this;
}
/**
* #return null|File
*/
public function getImageFile(): ?File
{
return $this->imageFile;
}
/**
* #param null|File $imageFile
* #return self
*/
public function setImageFile(?File $imageFile): self
{
$this->imageFile = $imageFile;
return $this;
}
}
This is my config file vich_uploader.yaml :
db_driver: orm
mappings:
movie_image:
uri_prefix: /images/movies
upload_destination: '%kernel.project_dir%/public/images/movies'
namer: Vich\UploaderBundle\Naming\UniqidNamer
movie_video:
uri_prefix: /videos/movies
upload_destination: '%kernel.project_dir%/public/videos/movies'
namer: Vich\UploaderBundle\Naming\UniqidNamer
movie_audio:
uri_prefix: /audios/movies
upload_destination: '%kernel.project_dir%/public/audios/movies'
namer: Vich\UploaderBundle\Naming\UniqidNamer
person_image:
uri_prefix: /images/person_image
upload_destination: '%kernel.project_dir%/public/images/person_image'
namer: Vich\UploaderBundle\Naming\UniqidNamer
This is my form PersonType.php:
namespace App\Form;
use App\Entity\Person;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pseudo', TextType::class, [
"label" => "Pseudonyme",
"required" => false
])
->add('name', TextType::class, [
"label" => "Nom",
"required" => true
])
->add('firstname', TextType::class, [
"label" => "Prénom",
"required" => true
])
->add('middleName', TextType::class, [
"label" => "2ème Prénom",
"required" => false
])
->add('birthDate', BirthdayType::class, [
"widget" => "single_text",
"label" => "Date de naissance",
])
->add('deathDate', BirthdayType::class, [
"widget" => "single_text",
"label" => "Date de mort",
"required" => false
])
->add('sex', ChoiceType::class, [
"label" => "Genre",
'choices' => [
'Homme' => true,
'Femme' => false,
],
])
->add('nationality', TextType::class, [
'required' => false,
"label" => "Nationalité"
])
->add('award1', TextType::class, [
'required' => false,
"label" => "Récompense 1"
])
->add('award2', TextType::class, [
'required' => false,
"label" => "Récompense 2"
])
->add('award3', TextType::class, [
'required' => false,
"label" => "Récompense 3"
])
->add('award4', TextType::class, [
'required' => false,
"label" => "Récompense 4"
])
->add('award5', TextType::class, [
'required' => false,
"label" => "Récompense 5"
])
->add('award6', TextType::class, [
'required' => false,
"label" => "Récompense 6"
])
->add('pictureFiles', FileType::class, [
'required' => false,
'multiple' => true,
'label' => 'Chargement d\'images (jpeg uniquement) '
])
->add('biography', TextareaType::class, [
'required' => false,
"label" => "Biographie",
])
// ->add('awardPeople', TextType::class, [
// "label" => "Prix remporté(s)"
// ])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Person::class,
]);
}
}

problem with a request on 2 tables with symfony

I trie to get the documents stored with a message in the message_document table with a doctrine request but the request loops and ends up filling my memory
I tried the same request with sql on Dbeaver and it runs with no problem
great thanks for your help
My message.php
enter code here<?php
namespace App\Entity;
use DateTime;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\MessageRepository;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass=MessageRepository::class)
*/
class Message
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #ORM\ManyToOne(targetEntity=Conversation::class, inversedBy="messages")
*/
private $conversation;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="messages")
*/
private $user;
/**
* #ORM\OneToMany(targetEntity=MessageDocument::class, mappedBy="messages")
*/
private $messageDocuments;
public function __construct( string $content, User $user, Conversation $converstation) {
$this->content = $content;
$this->user = $user;
$this->conversation = $converstation;
$this->createdAt = new \DateTime('now');
$this->messageDocuments = new ArrayCollection();
}
public function getId():?int {
return $this->id;
}
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;
}
public function getConversation():?Conversation
{
return $this->conversation;
}
public function setConversation(?Conversation $conversation):self {
$this->conversation = $conversation;
return $this;
}
public function getUser():?User {
return $this->user;
}
public function setUser(?User $user):self {
$this->user = $user;
return $this;
}
/**
* #return Collection|MessageDocument[]
*/
public function getMessageDocuments(): Collection
{
return $this->messageDocuments;
}
public function addMessageDocument(MessageDocument $messageDocument): self
{
if (!$this->messageDocuments->contains($messageDocument)) {
$this->messageDocuments[] = $messageDocument;
$messageDocument->setMessages($this);
}
return $this;
}
public function removeMessageDocument(MessageDocument $messageDocument): self
{
if ($this->messageDocuments->removeElement($messageDocument)) {
// set the owning side to null (unless already changed)
if ($messageDocument->getMessages() === $this) {
$messageDocument->setMessages(null);
}
}
return $this;
}
}
my MessageDocument.php
enter code here<?php
namespace App\Entity;
use App\Repository\MessageDocumentRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=MessageDocumentRepository::class)
*/
class MessageDocument
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $fileName;
/**
* #ORM\Column(type="datetime")
*/
private $updatedAt;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $type;
/**
* #ORM\ManyToOne(targetEntity=Message::class, inversedBy="messageDocuments")
*/
private $message;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="messageDocuments")
*/
private $sender;
public function getId(): ?int
{
return $this->id;
}
public function getFileName(): ?string
{
return $this->fileName;
}
public function setFileName(string $fileName): self
{
$this->fileName = $fileName;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(?string $type): self
{
$this->type = $type;
return $this;
}
public function getMessage(): ?Message
{
return $this->message;
}
public function setMessage(?Message $message): self
{
$this->message = $message;
return $this;
}
public function getSender(): ?User
{
return $this->sender;
}
public function setSender(?User $sender): self
{
$this->sender = $sender;
return $this;
}
}
the request on MessageDocument join Message
/**
* #return MessageDocument[] Returns an array of MessageDocument objects
*/
//$qb->expr()->eq('md.id = :val')
//,Join::WITH,$qb->expr()->eq('md.id = :val')
public function findDocByMessageId($messageId)
{
return $this->createQueryBuilder('md')
->select('md')
->join('md.message','m')
->where('m.id =:val')
->setParameter('val', $messageId)
->setMaxResults(20)
->getQuery()
->getResult();
}
the calling of the repo request
$allMessages = new ArrayCollection();
$docs=[];
foreach ($messages as $messageUnique) {
$messId = $messageUnique->getId();
$documentsMessages = $messageRepository->findDocByMessageId($messId);
if($documentsMessages !== null){
foreach($documentsMessages as $document){
$docs=$document;
}
//$messageUnique->addMessageDocument($document);
}
$conversation->setLastMessage($messageUnique);
$messageUnique = array(
'id' => $messageUnique->getId(),
'author' => $messageUnique->getUser()->getFullName(),
'authorId' => $messageUnique->getUser()->getId(),
'content' => $messageUnique->getContent(),
'createdAt' => $messageUnique->getCreatedAt()
);
$allMessages->add($messageUnique);
}
I finally solved the problem :)
I have used the #ignore decorator on two properties of the MessageDocument entity
I have changed my code and now error message is about circular reference
Message entity
<?php
namespace App\Entity;
use DateTime;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\MessageRepository;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass=MessageRepository::class)
*/
class Message
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #ORM\ManyToOne(targetEntity=Conversation::class, inversedBy="messages")
*/
private $conversation;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="messages")
*/
private $user;
/**
* #ORM\OneToMany(targetEntity=MessageDocument::class, mappedBy="message")
*/
private $messageDocument;
public function __construct( string $content, User $user, Conversation $converstation) {
$this->content = $content;
$this->user = $user;
$this->conversation = $converstation;
$this->createdAt = new \DateTime('now');
$this->messageDocuments = new ArrayCollection();
}
public function getId():?int {
return $this->id;
}
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;
}
public function getConversation():?Conversation
{
return $this->conversation;
}
public function setConversation(?Conversation $conversation):self {
$this->conversation = $conversation;
return $this;
}
public function getUser():?User {
return $this->user;
}
public function setUser(?User $user):self {
$this->user = $user;
return $this;
}
/**
* #return Collection|MessageDocument[]
*/
public function getMessageDocument(): Collection
{
return $this->messageDocument;
}
public function addMessageDocument(MessageDocument $messageDocument): self
{
if (!$this->messageDocument->contains($messageDocument)) {
$this->messageDocument[] = $messageDocument;
$messageDocument->setMessages($this);
}
return $this;
}
public function removeMessageDocument(MessageDocument $messageDocument): self
{
if ($this->messageDocuments->removeElement($messageDocument)) {
// set the owning side to null (unless already changed)
if ($messageDocument->getMessages() === $this) {
$messageDocument->setMessages(null);
}
}
return $this;
}
}
MessageDocument entity
<?php
namespace App\Entity;
use App\Repository\MessageDocumentRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=MessageDocumentRepository::class)
*/
class MessageDocument
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $fileName;
/**
* #ORM\Column(type="datetime")
*/
private $updatedAt;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $type;
/**
* #ORM\ManyToOne(targetEntity=Message::class, inversedBy="messageDocument")
*/
private $message;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="messageDocument")
*/
private $sender;
public function getId(): ?int
{
return $this->id;
}
public function getFileName(): ?string
{
return $this->fileName;
}
public function setFileName(string $fileName): self
{
$this->fileName = $fileName;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(?string $type): self
{
$this->type = $type;
return $this;
}
public function getMessage(): ?Message
{
return $this->message;
}
public function setMessage(?Message $message): self
{
$this->message = $message;
return $this;
}
public function getSender(): ?User
{
return $this->sender;
}
public function setSender(?User $sender): self
{
$this->sender = $sender;
return $this;
}
}
js use to call controller from twig
function getMessages(conversationId , userId) {
superConversationId = conversationId;
userIdEnCours = userId;
//* On vide ce qu'il y avait avant
removeAllChildNodes(document.querySelector('.msg_history'));
//* On remet toutes les conversations en blanc
let allDivs = document.getElementsByClassName('chat_list');
for (let div of allDivs) {
div.style.background = '#f8f8f8';
}
//* background-color light-grey quand conversation selectionné
$("#"+ conversationId).css('background', "#e1e1e1")
let xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
//si requête validé on traite la réponse
if (this.readyState == 4 && this.status == 200) {
let messages = JSON.parse(this.response);
let discussion = document.querySelector('.msg_history');
for (let message of messages) {
let dateMessage = new Date(message.createdAt)
var lastMessageId = message.id;
//* Affichage selon envoyé ou reçu
if (message.authorId == userIdEnCours) {
discussion.innerHTML += "<div class=\"outgoing_msg col-12 \"\><div class=\"sent_msg col-6 m-0\">"
+ "<p class='m-0'>" + message.content + "</p>"
+ "<span class=\"time_date\"> De " + message.author + " | " + dateMessage.toLocaleString() + "</span>"
+ "</div>"
;
} else {
discussion.innerHTML += "<div class=\"incoming_msg col-12 \">"
+ "<div class=\"received_msg col-12\"\><div class=\"received_withd_msg col-6\">"
+ "<p>" + message.content + "</p>"
+ "<span class=\"time_date_receiver\"> De " + message.author + " | " + dateMessage.toLocaleString() + "</span>"
+ "</div></div>"
;
}
}
//* scroll dernier message
let divMessagerie = document.querySelector(".msg_history");
divMessagerie.scrollTop = divMessagerie.scrollHeight;
//vl le 13/09
// ne voyant pas l'utilité ...
/* Interval = setInterval( () => {
$.ajax({
type: "POST",
url : "/checkMessage/" + lastMessageId,
success: function (response) {
if (response === true) {
clearInterval(Interval);
getMessages(
superConversationId,
userIdEnCours
);
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
}
});
}, 5000); */
}
};
//! Ouverture de la requete (MOCK VERS PROD)
//* PROD
// xmlhttp.open("GET", "http://www.generation-boomerang.com/messagerie/conv/" + conversationId);
//* DEV
xmlhttp.open("GET", "/messagerie/conv/" + conversationId);
xmlhttp.send();
}
The controller to get the messages from a conversation
/**
* #Route("/messagerie/conv/{id}" , name="messagerie_getMessagesOfConv")
* #Security("is_granted('ROLE_ABONNE') or is_granted('ROLE_ADMIN')", message="Merci de vous abonner au portail pour bénéficier de cette super fonctionnalité !")
*/
public function getMessagesOfConv(int $id, EntityManagerInterface $entityManager, ConversationRepository $conversationRepository, ParticipantRepository $participantRepository,MessageDocumentRepository $messageRepository) {
//* Récup du user + check à faire
$userEncours = $this->getUser();
$userId = $userEncours->getId();
//* Ckeck si la conversation appartient bien au user
$check = $participantRepository->checkBelongs($id, $userId);
if ($check != 1) {
return $this->json('cette conv ne te regarde pas !');
}
$conversation = $conversationRepository->find($id);
$messages = $conversation->getMessages();
// $documentsMessages = new ArrayCollection();//vl
$allMessages = new ArrayCollection();
// $docs=;
foreach ($messages as $messageUnique) {
$messId = $messageUnique->getId();
$documentsMessages = $messageRepository->findDocByMessageId($messId);
if($documentsMessages !== null){
foreach($documentsMessages as $document){
// $docs=$document;
$messageUnique->addMessageDocument($document);
}
}
$conversation->setLastMessage($messageUnique);
$messageUnique = array(
'id' => $messageUnique->getId(),
'author' => $messageUnique->getUser()->getFullName(),
'authorId' => $messageUnique->getUser()->getId(),
'content' => $messageUnique->getContent(),
'createdAt' => $messageUnique->getCreatedAt(),
'messageDocuments'=>$messageUnique->getMessageDocument()
);
$allMessages->add($messageUnique);
}
$entityManager->persist($conversation);
$entityManager->flush();
//echo '<pre>'; var_dump( $conversation);exit;echo '</pre>';//
return $this->json($allMessages);
}
conversation entity
<?php
namespace App\Entity;
use App\Repository\ConversationRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=ConversationRepository::class)
*/
class Conversation {
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity=Message::class, cascade={"persist", "remove"})
*/
private $lastMessage;
/**
* #ORM\OneToMany(targetEntity=Message::class, mappedBy="conversation")
*/
private $messages;
/**
* #ORM\OneToMany(targetEntity="Participant", mappedBy="conversation")
*/
private $participants;
/**
* #ORM\Column(type="string" , length=50)
*/
private $title;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
public function __construct() {
$this->participants = new ArrayCollection();
$this->messages = new ArrayCollection();
$this->createdAt = new \DateTime('now');
}
public function getId():?int {
return $this->id;
}
public function getLastMessage():?Message {
return $this->lastMessage;
}
public function setLastMessage(?Message $lastMessage):self {
$this->lastMessage = $lastMessage;
return $this;
}
/**
* #return Collection|Message[]
*/
public function getMessages():Collection {
return $this->messages;
}
public function addMessage(Message $message):self {
if (!$this->messages->contains($message)) {
$this->messages[] = $message;
$message->setConversation($this);
}
return $this;
}
public function removeMessage(Message $message):self {
if ($this->messages->contains($message)) {
$this->messages->removeElement($message);
// set the owning side to null (unless already changed)
if ($message->getConversation() === $this) {
$message->setConversation(null);
}
}
return $this;
}
/**
* #return Collection|Participant[]
*/
public function getParticipants():Collection {
return $this->participants;
}
public function addParticipant(Participant $participant):self {
if (!$this->participants->contains($participant)) {
$this->participants[] = $participant;
$participant->setConversation($this);
}
return $this;
}
public function removeParticipant(Participant $participant):self {
if ($this->participants->contains($participant)) {
$this->participants->removeElement($participant);
// set the owning side to null (unless already changed)
if ($participant->getConversation() === $this) {
$participant->setConversation(null);
}
}
return $this;
}
/**
* Get the value of title
*/
public function getTitle() {
return $this->title;
}
/**
* Set the value of title
* #return self
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getCreatedAt():?\DateTimeInterface {
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt):self {
$this->createdAt = $createdAt;
return $this;
}
}
and participant entity
<?php
namespace App\Entity;
use App\Repository\ParticipantRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=ParticipantRepository::class)
*/
class Participant
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="participants")
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="Conversation", inversedBy="participants")
*/
private $conversation;
private $messageReadAt;
/**
* Get the value of id
*/
public function getId()
{
return $this->id;
}
/**
* Get the value of user
*/
public function getUser()
{
return $this->user;
}
/**
* Set the value of user
*
* #return self
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* Get the value of conversation
*/
public function getConversation()
{
return $this->conversation;
}
/**
* Set the value of conversation
*
* #return self
*/
public function setConversation($conversation)
{
$this->conversation = $conversation;
return $this;
}
/**
* Get the value of messageReadAt
*/
public function getMessageReadAt()
{
return $this->messageReadAt;
}
/**
* Set the value of messageReadAt
*
* #return self
*/
public function setMessageReadAt($messageReadAt)
{
$this->messageReadAt = $messageReadAt;
return $this;
}
}
there is also a CreateMessageHandler and GetMessageHanler (but I don't understand how it works)
CreateMessageHandler
<?php
namespace App\MessageHandler;
use App\Entity\User;
use App\Entity\Message;
use App\Entity\Conversation;
use App\Message\CreateMessage;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class CreateMessageHandler implements MessageHandlerInterface
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager) {
$this->entityManager = $entityManager;
}
public function __invoke(CreateMessage $createMessage)
{
$conversation = $this->entityManager->getRepository(Conversation::class)->find($createMessage->getConversationId());
if(is_null($conversation))
{
$conversation = new Conversation();
$this->entityManager->persist($conversation);
$this->entityManager->flush();
} else {
$message = new Message(
$createMessage->getContent(),
$this->entityManager->getRepository(User::class)->find($createMessage->getUserId()),
$conversation,
);
// if ()
}
// Debug
// echo $createMessage->getContent();
// echo $message->getUser()->getId();
// echo $message->getConversation()->getId();
$this->entityManager->persist($message);
$this->entityManager->flush();
}
}
GetMessageHandler
<?php
namespace App\MessageHandler;
use App\Entity\Message;
use App\Message\GetMessages;
use App\Repository\MessageRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class GetMessagesHandler implements MessageHandlerInterface
{
private $entityManager;
private $messageRepository;
public function __construct(EntityManagerInterface $entityManager, MessageRepository $messageRepository) {
$this->entityManager = $entityManager;
}
public function __invoke(GetMessages $message)
{
//Récupérer les messages de la conversation
return $this->entityManager->getRepository(Message::class)->findBy(['conversation' => $message->getConversationId()]);
}
}
I think everything is there.
A little long sorry...
Hope somebody could find the reason why I have this circular reference
Thanks
I'm not sure at all about setting $messageUnique = array() with value of the same variable, you should probably use another name of variable. If you have an infinite loop problem it's coming from the PHP in your case.

Resources