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
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.
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
}
}
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']
],
];
}
}
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,
]);
}
}
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.