API Platform: Apply a filter to multiple resources - symfony

I'm trying to apply SearchFilter to multiple resources.
Let's imagine I've got two resources: Post and Article (just for the illustration):
#[ApiRsource]
class Post
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $postDescription = null;
...
}
#[ApiRsource]
class Article
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $articleDescription = null;
...
}
I want to add a unique endpoint /search?q=key that will return Post and Article resources with a postDescription/articleDescription containing key.
Is there a way to do this without resorting to inheritance?

Related

A circular reference has been detected when serializing the object of class "App\Entity\Orders" (configured limit: 1)

I am encountering problems regarding the circular references of the entities.
I am creating an API where one route will have the purpose of providing in detail, all entities in one single call. So in the OrdersRepository, i have a function that provide to give all the infos.
I have tried, as read in other questions made on stack overflow (and as suggest in the doc), to use the annotations #[Ignore] and #[Groups(['group_name'])], but I was not able to make it work. Probably I did not understand the concept of this solution well.
Even the "MaxDepth" solution didn't work.
Basically, I have a repository function that provides the result of 4 entities: Order, Address, OrderProducts and Product. And they are structured in this way:
Order
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne]
private ?Carrier $carrier = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?Address $address_delivery = null;
#[ORM\ManyToOne]
private ?Address $address_invoice = null;
#[ORM\Column]
private ?int $invoice = null;
#[ORM\OneToMany(mappedBy: 'order_related', targetEntity: OrderProducts::class)]
private Collection $orderProducts;
#[ORM\OneToMany(mappedBy: 'order_related', targetEntity: OrderDetail::class)]
private Collection $orderDetails;
OrderDetail
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 6, nullable: true)]
private ?string $weight = null;
#[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 6, nullable: true)]
private ?string $height = null;
#[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 6, nullable: true)]
private ?string $depth = null;
#[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 6, nullable: true)]
private ?string $length = null;
#[ORM\ManyToOne(inversedBy: 'orderDetails')]
private ?Orders $order_related = null;
OrderProducts
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'orderProducts')]
#[ORM\JoinColumn(nullable: false)]
private ?Orders $order_related = null;
#[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 6)]
private ?string $price= null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?Product $product = null;
Product
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\Column(length: 255)]
private ?string $description_short = null;
#[ORM\Column(length: 255)]
private ?string $description = null;
#[ORM\Column(length: 255)]
private ?string $reference = null;
OrderController
#[Route('v1/api/all', name: 'dashboard', methods: 'GET')]
public function all_orders_info(CustomSerializer $serializer): Response
{
$orders = $this->ordersRepository->findAll();
$responseContent = $serializer->serialize($orders, 'json');
return new JsonResponse(data: $responseContent, status: Response::HTTP_OK, json: true);
}
Service CustomSerializer.php
class CustomSerializer implements SerializerInterface
{
private SerializerInterface $serializer;
public function __construct()
{
$this->serializer = new Serializer(
normalizers : [new ObjectNormalizer()],
encoders : [new JsonEncoder()],
);
}
public function serialize(mixed $data, string $format, array $context = []): string
{
return $this->serializer->serialize($data, $format, $context);
}
public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed
{
return $this->serializer->deserialize($data, $type, $format, $context);
}
}
The problem is that I get the classic error "A circular reference has been detected when serializing the object of class "App\Entity\Orders" (configured limit: 1).". I inserted the Ignore and Groups annotations within the "Orders" entity, above the two properties "orderProducts" and "orderDetails" of Orders entity, but nothing.
Where i'm wrong?
None of these attributes would would work if you initialize the serializer component manually like that (your CustomSerializer), a little more is needed. Luckily there is an alternative;
It is recommended when within the Symfony framework to just inject the SerializerInterface service.
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Serializer\SerializerInterface;
class DefaultController extends AbstractController
{
public function index(SerializerInterface $serializer)
{
// keep reading for usage examples
}
}
As described here https://symfony.com/doc/current/serializer.html.
This comes with a few encoders and a lot of normalizers already configured to work with other aspects of the framework.
Sadly when you search through google the standalone component documentation (https://symfony.com/doc/current/components/serializer.html) comes up first. Which does explain some good core principles in how its internals work.
But within the framework most explained configuration things are already done and you can use it right away.
You could then start playing with your Ignore/Groups and MaxDepth things to get it working.
Another minor note you can also change some settings for the injected version in https://symfony.com/doc/current/reference/configuration/framework.html#serializer
these settings used to be in framework.yaml as a comment not sure if they are still there or if it got its own file now when you install the package though flex.
For those who are using the standalone component and still want attributes you can read up on how to set up that at https://symfony.com/doc/current/components/serializer.html#attributes-groups

How to eliminate this Column not found error?

Originally, the entity Gut had a field reaction that contained a string. The options for reaction were hard-wired in a template. By adding an entity Reaction and changing the Gut form's reaction to an EntityType I'm now plagued with the error message
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.reaction' in 'field list'
even though I've rewritten the Gut & Reaction entities. I've probably lost sight of the forest for the trees. What's wrong with the following?
MySQL table gut: reaction column replaced by reaction_id; reaction_id correctly created; foreign key created manually.
Error occurs with this controller method:
#[Route('/', name: 'app_gut_index', methods: ['GET'])]
public function index(GutRepository $gutRepository): Response
{
$guts = $gutRepository->findBy([], ['happened' => 'DESC']); // error thrown here
return $this->render('gut/index.html.twig', [
'guts' => $guts,
]);
}
Gut entity:
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[ORM\ManyToOne(targetEntity: Reaction::class)]
#[ORM\JoinColumn(name: 'reaction_id', referencedColumnName: 'id')]
protected $reaction;
#[ORM\Column(length: 255, nullable: true)]
private ?string $description = null;
#[ORM\Column(name: "datetime")]
private ?\DateTime $happened = null;
public function getId(): ?int
{
return $this->id;
}
public function getReaction(): ?Reaction
{
return $this->reaction;
}
public function setReaction(?Reaction $reaction): self
{
$this->reaction = $reaction;
return $this;
}
...
}
Reaction entity:
use App\Entity\Gut;
use App\Repository\ReactionRepository;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\Entity(repositoryClass: ReactionRepository::class)]
class Reaction
{
public function __construct()
{
$this->guts = new ArrayCollection();
}
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 45)]
private ?string $reaction = null;
public function getId(): ?int
{
return $this->id;
}
public function getReaction(): ?string
{
return $this->reaction;
}
public function setReaction(string $reaction): self
{
$this->reaction = $reaction;
return $this;
}
#[ORM\OneToMany(targetEntity: Gut::class, mappedBy: 'reaction')]
private $guts;
/**
* #return Collection|Product[]
*/
public function getGuts(): Collection
{
return $this->guts;
}
public function addGut($gut): self
{
$this->guts[] = $gut;
return $this;
}
public function __toString()
{
return $this->getReaction();
}
}
Your $reaction property should not have both ORM\Column and ORM\JoinColumn annotations at the same time.
Because of this Doctrine thinks it's a regular column so it's looking for a database field based on the variable name: $reaction -> gut.reaction.
Remove #[ORM\Column(length: 255)] then make sure that you have gut.reaction_id in your database and now it should work.
As a little side note I don't think you need name: 'reaction_id', referencedColumnName: 'id' in ORM\JoinColumn because that's how Doctrine will name them automatically anyway
Just couldn't let go. I eventually found a path to get the Gut and Reaction entities to play nicely together. What I did:
cloned the project
manually deleted reaction property from Gut entity; created & executed a migration
in MySQL, added back in a reaction column
used make:entity Gut to add a reaction property as ManyToOne on Reaction; made a migration
used MySQL to populate the reaction_id column from the database of the cloned project.
(Probably missed a step in here somewhere, but) gut->getReaction(),etc,
now behave as expected - in a ManyToOne relationship.

Can you please simplify (or help me refactor) my logic

I am aggregating data in json to convert it into symfony object.
A category (otology) has a type (swimming pool, restaurant). This category has a description that is translated into several languages.
I would like to be able to simply do getXXX('fr'), to have this description in French, getXXX('en') for English.
With a repository I think I can do it, but maybe I complicate it:
My class Otolongy:
class Ontology
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $idOntology = null;
#[ORM\OneToMany(mappedBy: 'ontology', targetEntity: Label::class, cascade: ['persist', 'remove'], fetch:'EAGER')]
private Collection $descriptions;
my class label (for fields description):
#[ORM\Entity(repositoryClass: LabelRepository::class)]
class Label
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::TEXT)]
private ?string $label = null;
#[ORM\ManyToOne(inversedBy: 'descriptions')]
private ?Ontology $ontology = null;
#[ORM\ManyToOne(inversedBy: 'labels', fetch:'EAGER')]
private ?Lang $lang = null;
my class lang:
#[ORM\Entity(repositoryClass: LangRepository::class)]
class Lang
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 10)]
private ?string $labelLang = null;
#[ORM\OneToMany(mappedBy: 'lang', targetEntity: Label::class)]
private Collection $labels;
You can see that for my tests I had to put fetch:'EAGER' I'm not convinced that this is the right way to do it... well after all put 1 eager or have to make a 'complex' request in my controller it should be equivalent...
I have already done this system on java and I had made a map key, so I used this command ontology.description.get(lang) in the template, in order to use the local as an argument and java gave me the description in the good language.
if i can avoid loading the ontology in eager i think the display of twig will go faster? Namely that I must also check that the translation in the visitor's local exists, otherwise I must display by default (English or French)
So my goal is to use the visitor's locale to know which language (language of the ontology in the database) that twig should display. Is there maybe a simpler way to do it? Maybe I'm totally wrong?!

Symfony ArrayCollection dans form symfony

I'm trying somehow to attach a partner's modules to my PartnerModule entity, but I can't because it has to create as many relationships as there are associated modules.
A partner has several modules and a module belongs to several partners.
Hence this intermediate table: PartnerModule. I understand that this is definitely a problem in my entities but I can't manage to fix it. Here are my entities and my form. I specify that when I put "multiple" => false it works well.
enter image description here
My error :
Expected argument of type "?App\Entity\Module", "Doctrine\Common\Collections\ArrayCollection" given at property path "modules".
Entity Module :
#[ORM\Entity(repositoryClass: ModuleRepository::class)]
class Module
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 250)]
private ?string $nom = null;
#[ORM\Column]
private ?bool $statut = null;
#[ORM\OneToMany(mappedBy: 'Modules', targetEntity: PartenaireModule::class)]
private Collection $partenaireModules;
public function __construct()
{
$this->partenaireModules = new ArrayCollection();
}
Entity Partenaire Module
#[ORM\Entity(repositoryClass: PartenaireModuleRepository::class)]
class PartenaireModule
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column]
private ?bool $is_active = true;
#[ORM\ManyToOne(inversedBy: 'partenaireModules')]
private ?Partenaire $Partenaires = null;
#[ORM\ManyToOne(inversedBy: 'partenaireModules')]
private ?Module $Modules = null;
This is how my data should be mapped:
-> module_id, partenaire_id, is_active, id
Any help or clue to help me will be welcome :)
Thanks

Symfony simple form filter question only allow unique entries

I'm stuck for a while now in trying to get the following scenario to work.
I have a registration system for a sporting team.
Each team can register for any available contest. The problem is that each team is only allowed once to register on a contest. I simply want to filter the teams that are already registered for the current selected contest. I'm using the Symfony framework together with Doctrine ORM and the symfony form system.
I tried to create a custom function inside the teamRepository to only get the teams that are not registered for a given contest. But i can't seem to find a way to gt this to work.
I'm using 3 entities for this: User, Team, Registration, Contest
The registration entity holds all te submitted registrations with a relation to the Contest and the User (team). I hope that someone can help me here. For reference, this is my registration entity:
class Registrations
{
use TimestampableEntity;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\ManyToOne(targetEntity: Team::class, inversedBy: 'registrations')]
private $Team;
#[ORM\ManyToMany(targetEntity: Dancers::class, inversedBy: 'registrations', fetch: 'EAGER')]
private $Dancers;
#[ORM\Column(type: 'text', nullable: true)]
private $Comments;
#[ORM\ManyToOne(targetEntity: Contest::class, fetch: 'EAGER', inversedBy: 'registrations')]
#[ORM\JoinColumn(nullable: false)]
private $Contest;
#[ORM\Column(type: 'string', length: 255)]
#[Gedmo\Blameable(on: 'create')]
private $RegisteredBy;
#[ORM\OneToMany(mappedBy: 'Registration', targetEntity: Orders::class)]
private $orders;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $MusicFile;
public function __construct()
{
$this->Dancers = new ArrayCollection();
$this->orders = new ArrayCollection();
}
public function __toString()
{
return $this->id;
}
public function getId(): ?int
{
return $this->id;
}
public function getTeam(): ?Team
{
return $this->Team;
}
public function setTeam(?Team $Team): self
{
$this->Team = $Team;
return $this;
}
/**
* #return Collection|Dancers[]
*/
public function getDancers(): Collection
{
return $this->Dancers;
}
public function addDancer(Dancers $dancer): self
{
if (!$this->Dancers->contains($dancer)) {
$this->Dancers[] = $dancer;
}
return $this;
}
public function removeDancer(Dancers $dancer): self
{
$this->Dancers->removeElement($dancer);
return $this;
}
public function getComments(): ?string
{
return $this->Comments;
}
public function setComments(?string $Comments): self
{
$this->Comments = $Comments;
return $this;
}
public function getRegisteredBy(): ?string
{
return $this->RegisteredBy;
}
public function setRegisteredBy(?string $RegisteredBy): self
{
$this->RegisteredBy = $RegisteredBy;
return $this;
}
public function getContest(): ?Contest
{
return $this->Contest;
}
public function setContest(?Contest $Contest): self
{
$this->Contest = $Contest;
return $this;
}
/**
* #return Collection<int, Orders>
*/
public function getOrders(): Collection
{
return $this->orders;
}
public function addOrder(Orders $order): self
{
if (!$this->orders->contains($order)) {
$this->orders[] = $order;
$order->setRegistration($this);
}
return $this;
}
public function removeOrder(Orders $order): self
{
if ($this->orders->removeElement($order)) {
// set the owning side to null (unless already changed)
if ($order->getRegistration() === $this) {
$order->setRegistration(null);
}
}
return $this;
}
public function getMusicFile(): ?string
{
return $this->MusicFile;
}
public function setMusicFile(string $MusicFile): self
{
$this->MusicFile = $MusicFile;
return $this;
}
}
This is the repo function as mentioned:
public function getTeamsNotRegistered($organisation, $contest)
{
return $this->createQueryBuilder('t')
->leftJoin('t.Registrations', 'r')
->andWhere('t.Organisation = :org')
->andWhere('r.Contest = :contest')
->setParameter('org', $organisation)
->setParameter('contest', $contest)
->getQuery()
->getResult();
}

Resources