How to add a dynamic constraint on a form field - symfony

I have a non required field in my 'Session' entity, field is named 'location'. I want to make it required only if another field is equal to true, this other property is named 'isVirtual'
I thaught i'd create an eventSubscriber with the event BeforeEntityUpdatedEvent but I don't know how to add a constraint and I am not sure when this event is triggered.
I tried to do a custom validation constraint but I don't know how to get value of 'isVirtual' value in my ConstraintValidator class.
I need someone to help me look in the right direction. I put there my Entity
<?php
namespace App\Entity;
use App\Repository\SessionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: SessionRepository::class)]
class Session
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column]
private ?\DateTimeImmutable $beginsAt = null;
#[ORM\Column]
private ?\DateTimeImmutable $endsAt = null;
#[ORM\ManyToOne(inversedBy: 'sessions')]
#[ORM\JoinColumn(nullable: false)]
private ?Formation $formation = null;
#[ORM\ManyToOne(inversedBy: 'sessions')]
#[ORM\JoinColumn(nullable: true)]
private ?Location $location = null;
#[ORM\Column]
private ?bool $isVirtual = null;
#[ORM\OneToMany(mappedBy: 'session', targetEntity: UserSession::class)]
private Collection $users;
#[ORM\Column(nullable: true)]
private ?int $numberOfSeats = null;
public function __construct()
{
$this->users = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getBeginsAt(): ?\DateTimeImmutable
{
return $this->beginsAt;
}
public function setBeginsAt(\DateTimeImmutable $beginsAt): self
{
$this->beginsAt = $beginsAt;
return $this;
}
public function getEndsAt(): ?\DateTimeImmutable
{
return $this->endsAt;
}
public function setEndsAt(\DateTimeImmutable $endsAt): self
{
$this->endsAt = $endsAt;
return $this;
}
public function getFormation(): ?Formation
{
return $this->formation;
}
public function setFormation(?Formation $formation): self
{
$this->formation = $formation;
return $this;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function setLocation(?Location $location): self
{
$this->location = $location;
return $this;
}
public function isIsVirtual(): ?bool
{
return $this->isVirtual;
}
public function setIsVirtual(bool $isVirtual): self
{
$this->isVirtual = $isVirtual;
return $this;
}
/**
* #return Collection<int, UserSession>
*/
public function getUsers(): Collection
{
return $this->users;
}
public function addUser(UserSession $user): self
{
if (!$this->users->contains($user)) {
$this->users->add($user);
$user->setSession($this);
}
return $this;
}
public function removeUser(UserSession $user): self
{
if ($this->users->removeElement($user)) {
// set the owning side to null (unless already changed)
if ($user->getSession() === $this) {
$user->setSession(null);
}
}
return $this;
}
public function getNumberOfSeats(): ?int
{
return $this->numberOfSeats;
}
public function setNumberOfSeats(int $numberOfSeats): self
{
$this->numberOfSeats = $numberOfSeats;
return $this;
}
public function retrieveSeat($number): self
{
$this->setNumberOfSeats($this->getNumberOfSeats() - $number);
return $this;
}
}
and my EasyAdmin Controller
<?php
namespace App\Controller\Admin;
use App\Entity\Session;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class SessionCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Session::class;
}
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id')->hideOnForm(),
AssociationField::new('formation'),
DateField::new('beginsAt'),
DateField::new('endsAt'),
BooleanField::new('isVirtual'),
AssociationField::new('location')
->setRequired(false)
->setFormTypeOption('attr', ['placeholder' => 'Enter location']),
IntegerField::new('numberOfSeats')
->setRequired(false)
];
}
}
Thanks a lot in advance.

Here is the callback implementation. Thanks to craigh
<?php
namespace App\Entity;
........
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class Session
{
........
#[Assert\Callback]
public function validate(ExecutionContextInterface $context, $payload)
{
if (!$this->isIsVirtual()) {
if ($this->getLocation() === null) {
$context->buildViolation("Session needs a location when it's not virtual")
->atPath('location')
->addViolation();
}
if ($this->getNumberOfSeats() === null) {
$context->buildViolation("Session needs a number of seats when it's not virtual")
->atPath('numberOfSeats')
->addViolation();
}
}
}
}

Related

How to exactly use the findBy to display articles by category

I'm new to symfony, I work on an e-commerce website. I solved some bugs already trying different solutions but with this one I feel stuck. I'm trying to display my articles by categories but it's not working because it displays an error:
Warning: Trying to access array offset on value of type null
I made a consoles controller in symfony to call the view associated to display only articles with that category, expected to do the same with the games after but it doesn't seem to find the category associated with the articles ?
This is my consolescontroller:
namespace App\Controller;
use App\Repository\ArticlesRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ConsolesController extends AbstractController
{
#[Route('/consoles', name: 'app_consoles')]
public function index(ArticlesRepository $repositoryArticles): Response
{
$consoles = $repositoryArticles->findBy(['category' => 1]);
return $this->render('consoles/index.html.twig', [
'controller_name' => 'ConsolesController',
'consoles' => $consoles,
]);
}
}
My Entity Articles
<?php
namespace App\Entity;
use App\Repository\ArticlesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ArticlesRepository::class)]
class Articles
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 80)]
private ?string $nomArticle = null;
#[ORM\Column(length: 255)]
private ?string $imageArticle = null;
#[ORM\Column]
private ?float $prixArticle = null;
#[ORM\Column(length: 200)]
private ?string $descriptionArticle = null;
#[ORM\ManyToMany(targetEntity: Categories::class, inversedBy: 'articles')]
#[ORM\JoinColumn(nullable: false)]
private Collection $category;
public function __construct()
{
$this->category = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNomArticle(): ?string
{
return $this->nomArticle;
}
public function setNomArticle(string $nomArticle): self
{
$this->nomArticle = $nomArticle;
return $this;
}
public function getImageArticle(): ?string
{
return $this->imageArticle;
}
public function setImageArticle(string $imageArticle): self
{
$this->imageArticle = $imageArticle;
return $this;
}
public function getPrixArticle(): ?float
{
return $this->prixArticle;
}
public function setPrixArticle(float $prixArticle): self
{
$this->prixArticle = $prixArticle;
return $this;
}
public function getDescriptionArticle(): ?string
{
return $this->descriptionArticle;
}
public function setDescriptionArticle(string $descriptionArticle): self
{
$this->descriptionArticle = $descriptionArticle;
return $this;
}
/**
* #return Collection<int, Categories>
*/
public function getCategory(): Collection
{
return $this->category;
}
public function addCategory(Categories $category): self
{
if (!$this->category->contains($category)) {
$this->category->add($category);
}
return $this;
}
public function removeCategory(Categories $category): self
{
$this->category->removeElement($category);
return $this;
}
public function __toString()
{
return $this->nomArticle;
}
}
And my Entity Categories
<?php
namespace App\Entity;
use App\Repository\CategoriesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CategoriesRepository::class)]
class Categories
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 50)]
private ?string $nomCategorie = null;
#[ORM\ManyToMany(targetEntity: Articles::class, mappedBy: 'category')]
private Collection $articles;
public function __construct()
{
$this->articles = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNomCategorie(): ?string
{
return $this->nomCategorie;
}
public function setNomCategorie(string $nomCategorie): self
{
$this->nomCategorie = $nomCategorie;
return $this;
}
/**
* #return Collection<int, Articles>
*/
public function getArticles(): Collection
{
return $this->articles;
}
public function addArticle(Articles $article): self
{
if (!$this->articles->contains($article)) {
$this->articles->add($article);
$article->addCategory($this);
}
return $this;
}
public function removeArticle(Articles $article): self
{
if ($this->articles->removeElement($article)) {
$article->removeCategory($this);
}
return $this;
}
public function __toString()
{
return $this->nomCategorie;
}
}
They're both linked by a "many to many" relationship and they're both supposed to have access to the other but there still something not working.
Thank you in advance for your help.

How to make left join in dql Symfony

I would like to understand dql logic and i would like to create this kind of request.
Bellow this is the SQL request i would like to do :
SELECT titlemission,descriptionmission,onsetdate,deadline,budgetmission,prioritymission,company FROM mission
left join mission_tag mt on mission.id = mt.mission_id
left join tag on mt.tag_id = tag.id
left join user on user.id = mission.iduser_id
where nomtag = "test20";
I read a lot of documentation but no one explain exatly how to use this QueryBuilder.
Symfony documentation is too abstract for me.
I only know that you have to forget the way you edit the sql request and think in object and entity relation. But i don't know how to do that.
This is my entity Tag
<?php
namespace App\Entity;
use App\Repository\TagRepository;
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: TagRepository::class)]
class Tag
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 100)]
#[Assert\NotBlank]
#[Assert\Length(min:2)]
#[Assert\Valid]
private ?string $nomtag = null;
#[ORM\ManyToMany(targetEntity: Mission::class, mappedBy: 'tags', cascade: ['persist'])]
#[ORM\JoinTable(name: 'mission_tag')]
#[ORM\JoinColumn(name: 'tag_id',referencedColumnName: 'id')]
#[ORM\InverseJoinColumn(name:'mission_id', referencedColumnName: 'id')] // Mission_id est couplé à l'id de mission
private Collection $missions;
public function __construct()
{
$this->missions = new ArrayCollection();
}
// #[ORM\ManyToMany(targetEntity: Mission::class, inversedBy:"$idtagmissionassign")]
// private $genusScientists;
public function getId(): ?int
{
return $this->id;
}
public function getNomtag(): ?string
{
return $this->nomtag;
}
public function setNomtag(string $nomtag): self
{
$this->nomtag = $nomtag;
return $this;
}
/**
* #return Collection<int, Mission>
*/
public function getMissions(): Collection
{
return $this->missionstag;
}
public function addMission(Mission $missions): self
{
if (!$this->missions->contains($missions)) {
$this->missions->add($missions);
$missions->addTag($this);
}
return $this;
}
public function removeMission(Mission $missions): self
{
if ($this->missions->removeElement($missions)) {
$missions->removeTag($this);
}
return $this;
}
public function __toString(): string
{
return $this->nomtag;
}
}
This is my entity user :
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'user')]
#[UniqueEntity(fields: ['email'], message: 'Un compte existe déjà avec cette adresse email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
/**
* #var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 100)]
private ?string $name = null;
#[ORM\Column(length: 100)]
private ?string $firstname = null;
#[ORM\Column(length: 150)]
private ?string $company = null;
#[ORM\Column(type: 'boolean')]
private $isVerified = false;
#[ORM\Column(length: 255)]
private ?string $companyadress = null;
#[ORM\OneToMany(mappedBy: 'iduser', targetEntity: Mission::class)]
private Collection $missions;
#[ORM\OneToMany(mappedBy: 'iduser', targetEntity: Service::class)]
private Collection $services;
#[ORM\ManyToMany(targetEntity: Service::class, mappedBy: 'idserviceassign')]
private Collection $servicesassign;
#[ORM\ManyToMany(targetEntity: Mission::class, mappedBy: 'idmissionassign')]
#[ORM\JoinTable(name: 'mission_user')]
#[ORM\JoinColumn(name: 'user_id',referencedColumnName: 'id')]
#[ORM\InverseJoinColumn(name:'mission_id', referencedColumnName: 'id')]
private Collection $missionsassign;
public function __construct()
{
$this->missions = new ArrayCollection();
$this->services = new ArrayCollection();
$this->servicesassign = new ArrayCollection();
$this->missionsassign = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'USER_DISCOVER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getCompany(): ?string
{
return $this->company;
}
public function setCompany(string $company): self
{
$this->company = $company;
return $this;
}
public function isVerified(): bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): self
{
$this->isVerified = $isVerified;
return $this;
}
public function getCompanyadress(): ?string
{
return $this->companyadress;
}
public function setCompanyadress(string $companyadress): self
{
$this->companyadress = $companyadress;
return $this;
}
public function __toString(){
return $this->name;
}
/**
* #return Collection<int, Mission>
*/
public function getMissions(): Collection
{
return $this->missions;
}
public function addMission(Mission $missions): self
{
if (!$this->missions->contains($missions)) {
$this->missions->add($missions);
$missions->setIduser($this);
}
return $this;
}
public function removeMission(Mission $missions): self
{
if ($this->missions->removeElement($missions)) {
// set the owning side to null (unless already changed)
if ($missions->getIduser() === $this) {
$missions->setIduser(null);
}
}
return $this;
}
/**
* #return Collection<int, Service>
*/
public function getServices(): Collection
{
return $this->services;
}
public function addService(Service $service): self
{
if (!$this->services->contains($service)) {
$this->services->add($service);
$service->setIduser($this);
}
return $this;
}
public function removeService(Service $service): self
{
if ($this->services->removeElement($service)) {
// set the owning side to null (unless already changed)
if ($service->getIduser() === $this) {
$service->setIduser(null);
}
}
return $this;
}
/**
* #return Collection<int, Service>
*/
public function getServicesassign(): Collection
{
return $this->servicesassign;
}
public function addServicesassign(Service $servicesassign): self
{
if (!$this->servicesassign->contains($servicesassign)) {
$this->servicesassign->add($servicesassign);
$servicesassign->addIdserviceassign($this);
}
return $this;
}
public function removeServicesassign(Service $servicesassign): self
{
if ($this->servicesassign->removeElement($servicesassign)) {
$servicesassign->removeIdserviceassign($this);
}
return $this;
}
/**
* #return Collection<int, Mission>
*/
public function getMissionsassign(): Collection
{
return $this->missionsassign;
}
public function addMissionsassign(Mission $missionsassign): self
{
if (!$this->missionsassign->contains($missionsassign)) {
$this->missionsassign->add($missionsassign);
$missionsassign->addIdmissionassign($this);
}
return $this;
}
public function removeMissionsassign(Mission $missionsassign): self
{
if ($this->missionsassign->removeElement($missionsassign)) {
$missionsassign->removeIdmissionassign($this);
}
return $this;
}
}
And this is my entity Mission
<?php
namespace App\Entity;
use App\Repository\MissionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MissionRepository::class)]
#[ORM\Table(name: 'mission')]
class Mission
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $titlemission = null;
#[ORM\Column(length: 255)]
private ?string $descriptionmission = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $onsetdate = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $deadline = null;
#[ORM\Column]
private ?int $budgetmission = null;
/*#[ORM\Column(length: 255)]
private ?string $codeapemission = null;*/
#[ORM\Column(length: 255)]
private ?string $prioritymission = null;
#[ORM\ManyToMany(targetEntity: Tag::class, inversedBy: 'missions', cascade: ['persist'])]
#[ORM\JoinTable(name: 'mission_tag')]
#[ORM\JoinColumn(name: 'mission_id',referencedColumnName: 'id')]
#[ORM\InverseJoinColumn(name:'tag_id', referencedColumnName: 'id')] //tag_id qui est couplé avec id de tag
private Collection $tags;
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'missions', cascade: ['persist'], fetch:"EAGER")]
#[ORM\JoinColumn(nullable: false)]
private User $iduser;
#[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'missionsassign')]
#[ORM\JoinTable(name: 'mission_user')]
#[ORM\JoinColumn(name: 'mission_id',referencedColumnName: 'id')]
#[ORM\InverseJoinColumn(name:'user_id', referencedColumnName: 'id')]
private Collection $idmissionassign;
#[ORM\Column(length: 100)]
private ?string $remote = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $datecreation = null;
public function __construct()
{
$this->tags = new ArrayCollection();
$this->idmissionassign = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitlemission(): ?string
{
return $this->titlemission;
}
public function setTitlemission(string $titlemission): self
{
$this->titlemission = $titlemission;
return $this;
}
public function getDescriptionmission(): ?string
{
return $this->descriptionmission;
}
public function setDescriptionmission(string $descriptionmission): self
{
$this->descriptionmission = $descriptionmission;
return $this;
}
public function getOnsetdate(): ?\DateTimeInterface
{
return $this->onsetdate;
}
public function setOnsetdate(\DateTimeInterface $onsetdate): self
{
$this->onsetdate = $onsetdate;
return $this;
}
public function getDeadline(): ?\DateTimeInterface
{
return $this->deadline;
}
public function setDeadline(\DateTimeInterface $deadline): self
{
$this->deadline = $deadline;
return $this;
}
public function getBudgetmission(): ?int
{
return $this->budgetmission;
}
public function setBudgetmission(int $budgetmission): self
{
$this->budgetmission = $budgetmission;
return $this;
}
/*public function getCodeapemission(): ?string
{
return $this->codeapemission;
}
public function setCodeapemission(string $codeapemission): self
{
$this->codeapemission = $codeapemission;
return $this;
}*/
public function getPrioritymission(): ?string
{
return $this->prioritymission;
}
public function setPrioritymission(string $prioritymission): self
{
$this->prioritymission = $prioritymission;
return $this;
}
/**
* #return Collection<int, tag>
*/
public function getIdtagmissionassign(): Collection
{
return $this->tags;
}
public function addIdtagmissionassign(tag $tags): self
{
if (!$this->tags->contains($tags)) {
$this->tags->add($tags);
}
return $this;
}
public function removeIdtagmissionassign(tag $tags): self
{
$this->tags->removeElement($tags);
return $this;
}
public function getIduser(): ?user
{
return $this->iduser;
}
public function setIduser(?user $iduser): self
{
$this->iduser = $iduser;
return $this;
}
/**
* #return Collection<int, user>
*/
public function getIdmissionassign(): Collection
{
return $this->idmissionassign;
}
public function addIdmissionassign(user $idmissionassign): self
{
if (!$this->idmissionassign->contains($idmissionassign)) {
$this->idmissionassign->add($idmissionassign);
}
return $this;
}
public function removeIdmissionassign(user $idmissionassign): self
{
$this->idmissionassign->removeElement($idmissionassign);
return $this;
}
public function getRemote(): ?string
{
return $this->remote;
}
public function setRemote(string $remote): self
{
$this->remote = $remote;
return $this;
}
public function __toString(){
return $this->titlemission;
}
public function getDatecreation(): ?\DateTimeInterface
{
return $this->datecreation;
}
public function setDatecreation(\DateTimeInterface $datecreation): self
{
$this->datecreation = $datecreation;
return $this;
}
}
This is my DQL request but i use Querybuilder without understand the concept. Could you help me ?
public function selectMissionWhereTag($tag){
return $this->createQueryBuilder('mi')
->select('mi.titlemission','mi.descriptionmission','mi.onsetdate','mi.deadline','mi.budgetmission','mi.prioritymission')
->leftJoin(User::class,'user', 'WITH', 'mi.iduser_id = user.id')
->leftJoin(Tag::class,'tag', 'WITH' , 'mi.tags = tag.id')
->where('tag.nomtag = :nomtag')
->setParameter('nomtag',$tag)
->getQuery()
->getResult();
}
Many thanks :)
To better understand how the QueryBuilder works I would recommend reading through how Doctrine ORM Mapping works first: Basic Mapping and Association Mapping
QueryBuilder's Joins use this mapping in a way that looks pretty similar to how you would do things with getters in your PHP code.
For example in a controller to get the tags of a mission you would do something like $tags = $mission->getTags(); well in QueryBuilder you simply do ->leftJoin('mi.tags', 'tag') and that's it. You don't have to worry about WITH unless you need extra conditions.
As you might have guessed there is a lot more to it but this should be enough to get you going.

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.

Why is EasyAdmin 4 function configureMenuItems() in DashboardController not working?

I am currently using EasyAdmin 4 with Symfony to create a backend admin page to manage users.
I did all the step that was in the documentation but yet it still giving me an error:
"Unable to find the controller related to the "App\Controller\Admin\User" Entity; did you forget to extend "EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController"?"
Here is the code in DashboardController:
<?php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/dashboard.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('Symfony Melody')
->renderContentMaximized();
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::linkToCrud('User', 'fas fa-user', User::class);
}
}
Here is the code in UserCrudController:
<?php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud;
}
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
}
Here is the code in the User class:
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
/**
* #var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 255)]
private ?string $nom = null;
#[ORM\Column(length: 255)]
private ?string $prenom = null;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
}
I tried reading the documentation again but i can't find any answers.
Can someone help please? it would be great, thank you.
You are Missing the use of App\Entity\User
In your DashboardController try to add this line :
use App\Entity\User;
On the top of your class file
Happy coding 🙂

Api-platform, filtering collection result based on JWT identified user on a ManyToMany relational entity

I'm using Symfony + api-platform in their latest versions as of today.
I've got a User Entity and a Team entity which are related through a ManyToMany ORM relation.
A User can have several Teams a Team can have several Users, the Team "owns" the relation.
Once my user is logged in through a JWT Token, I would like the endpoint GET /teams to only send back the Teams in which the identified User is part of.
Here is my User entity :
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\GetCollection;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ApiResource(operations: [
new Get(),
new GetCollection()
])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
/**
* #var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
/** #var book[] Available reviews for this book. */
#[ORM\OneToMany(targetEntity: Book::class, mappedBy: 'user', cascade: ['persist', 'remove'])]
public iterable $books;
#[ORM\ManyToMany(targetEntity: Team::class, inversedBy: 'users')]
private Collection $teams;
public function __construct()
{
$this->teams = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* Méthode getUsername qui permet de retourner le champ qui est utilisé pour l'authentification.
*
* #return string
*/
public function getUsername(): string {
return $this->getUserIdentifier();
}
/**
* #return Collection<int, Team>
*/
public function getTeams(): Collection
{
return $this->teams;
}
public function addTeam(Team $team): self
{
if (!$this->teams->contains($team)) {
$this->teams->add($team);
}
return $this;
}
public function removeTeam(Team $team): self
{
$this->teams->removeElement($team);
return $this;
}
}
Here is my Team Entity :
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Repository\TeamRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use App\Entity\User;
#[ORM\Entity(repositoryClass: TeamRepository::class)]
#[ApiResource(security: "is_granted('ROLE_USER')")]
class Team
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 100)]
private ?string $name = null;
#[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'teams')]
private Collection $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
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;
}
/**
* #return Collection<int, User>
*/
public function getUsers(): Collection
{
return $this->users;
}
public function addUser(User $user): self
{
if (!$this->users->contains($user)) {
$this->users->add($user);
$user->addTeam($this);
}
return $this;
}
public function removeUser(User $user): self
{
if ($this->users->removeElement($user)) {
$user->removeTeam($this);
}
return $this;
}
}
Here is my CurrentUserExtension Class that filters result based on the current user :
<?php
// api/src/Doctrine/CurrentUserExtension.php
namespace App\Doctrine;
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use App\Entity\Book;
use App\Entity\Team;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
final class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
{
$this->addWhere($queryBuilder, $resourceClass);
}
public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void
{
$this->addWhere($queryBuilder, $resourceClass);
}
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
if ((Book::class !== $resourceClass && Team::class !== $resourceClass) || $this->security->isGranted('ROLE_ADMIN') || null === $user = $this->security->getUser()) {
return;
}
$rootAlias = $queryBuilder->getRootAliases()[0];
if (Team::class == $resourceClass){
$queryBuilder->andWhere(sprintf('%s.users = :current_user', $rootAlias));
//trigger_error($queryBuilder);
}
$queryBuilder->setParameter('current_user', $user->getId());
}
}
Obviously it doesn't work because of the nature of the relation existing between the two tables. I also tried to use the query builder to use leftjoin and join the user_team table. But since the user_team table is not an Entity it failed.
Here is an SQL equivalent of what I would like to get as a result :
select * from team t
left join user_team ut on ut.team_id = t.id
where user_id = :current_user
You should be able to just check if your user is IN or MEMBER OF one of your team $users.
Your query was close, try updating it with:
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
if ((Book::class !== $resourceClass && Team::class !== $resourceClass) || $this->security->isGranted('ROLE_ADMIN') || null === $user = $this->security->getUser()) {
return;
}
$rootAlias = $queryBuilder->getRootAliases()[0];
if (Team::class == $resourceClass){
$queryBuilder->andWhere(sprintf(':current_user MEMBER OF %s.users', $rootAlias));
$queryBuilder->setParameter('current_user', $user);
}
}
You could either use MEMBER OF or IN but in a manyToMany it's usually easier to use MEMBER OF.

Resources