I have an entity Professionnal with Jobs in API Plateform project 3.2.
When I call host/api/professionnals
I got a result like this :
{
"data": [
{
"id": "/api/professionnals/1",
"type": "Professionnal",
"attributes": {
"email": "foo@bar.com",
"firstname": "JOHN",
"lastname": "DOE"
},
"relationships": {
"jobs": {
"data": [
{
"type": "Job",
"id": "/api/jobs/1"
},
{
"type": "Job",
"id": "/api/jobs/2"
}
]
}
}
},
...
]
}
I would like the job label property but it seems to be more difficult with relation ManyToMany in API Plateform to get it.
Professionnal.php Entity
<?php
namespace App\Entity;
use ...
#[ApiResource(normalizationContext: ['groups' => ['professionnal:read']])]
#[ORM\Entity(repositoryClass: ProfessionnalRepository::class)]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
class Professionnal implements UserInterface, PasswordAuthenticatedUserInterface {
...
#[Assert\NotBlank]
#[Assert\Email]
#[ORM\Column(length: 180, unique: true)]
#[Groups(['professionnal:read'])]
private ?string $email = null;
...
#[ORM\Column(length: 150)]
#[Groups(['professionnal:read'])]
private ?string $firstname = null;
#[ORM\Column(length: 255)]
#[Groups(['professionnal:read'])]
private ?string $lastname = null;
...
/**
* @var Collection<int, Job>
*/
#[ORM\ManyToMany(targetEntity: Job::class, inversedBy: 'professionnals', fetch: "EAGER")]
#[ORM\JoinTable(name: 'professionnal_job')]
#[ORM\JoinColumn(name: 'professionnal_id', referencedColumnName: 'id', nullable: false)]
#[ORM\InverseJoinColumn(name: 'job_id', referencedColumnName: 'id', nullable: false)]
#[Groups(['professionnal:read'])]
private Collection $jobs;
public function __construct() {
$this->jobs = new ArrayCollection();
}
...
/**
* @return Job[]
*/
#[Groups(['professionnal:read'])]
public function getJobs(): array {
return $this->jobs->getValues();
}
}
Job.php Entity
<?php
namespace App\Entity;
...
#[ORM\Entity(repositoryClass: JobRepository::class)]
#[ApiResource()]
class Job {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Groups(['professionnal:read', 'job:read'])]
private ?string $label = null;
/**
* @var Collection<int, Professionnal>
*/
#[ORM\ManyToMany(targetEntity: Professionnal::class, mappedBy: 'jobs')]
private Collection $professionnals;
public function __construct() {
$this->professionnals = new ArrayCollection();
}
public function __toString(): string {
return (is_null($this->getLabel())) ? '' : $this->getLabel();
}
public function getId(): ?int {
return $this->id;
}
public function getLabel(): ?string {
return $this->label;
}
public function setLabel(string $label): static {
$this->label = $label;
return $this;
}
/**
* @return Collection<int, Professionnal>
*/
public function getProfessionnals(): Collection {
return $this->professionnals;
}
...
}
I tried to add this config code without success :/
api_platform:
eager_loading:
max_joins: 100
Someone has got an idea ? Should I find a workaround or there is a proper solution to do this ?
I found a solution based on custom DTO and Provider. Basicaly, API Plateform does not offer an easy way yet to get object properties from an ManyToMany relation.
So...
Make a DTO and Provider
Replace #[ApiResource]
with #[GetCollection(output: ProfessionalDTO::class, provider: ProfessionalsProvider::class)]
with jobs
property as Collection
create a new method.
public function getJobs(): array { return $this->jobs->getValues(); }
Declare the provider in services.yaml
Professional.php
namespace App\Entity;
use ...
use ApiPlatform\Metadata\GetCollection;
#[GetCollection(output: ProfessionalDTO::class, provider: ProfessionalsProvider::class)]
#[ORM\Entity(repositoryClass: ProfessionalRepository::class)]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
class Professional implements UserInterface, PasswordAuthenticatedUserInterface {
...
#[Assert\NotBlank]
#[Assert\Email]
#[ORM\Column(length: 180, unique: true)]
#[Groups(['professional:read'])]
private ?string $email = null;
...
#[ORM\Column(length: 150)]
#[Groups(['professional:read'])]
private ?string $firstname = null;
#[ORM\Column(length: 255)]
#[Groups(['professional:read'])]
private ?string $lastname = null;
...
/**
* @var Collection<int, Job>
*/
#[ORM\ManyToMany(targetEntity: Job::class, inversedBy: 'professionals', fetch: "EAGER")]
#[ORM\JoinTable(name: 'professional_job')]
#[ORM\JoinColumn(name: 'professional_id', referencedColumnName: 'id', nullable: false)]
#[ORM\InverseJoinColumn(name: 'job_id', referencedColumnName: 'id', nullable: false)]
#[Groups(['professional:read'])]
private Collection $jobs;
public function __construct() {
$this->jobs = new ArrayCollection();
}
...
/**
* @return Job[]
*/
public function getJobs(): array {
return $this->jobs->getValues();
}
}
src/Dto/ProfessionalDTO.php
namespace App\Dto;
use App\Entity\Professional;
class ProfessionalDTO {
public string $id;
public string $email;
public string $firstname;
public string $lastname;
public array $jobs;
public function __construct(Professional $professional) {
$this->id = $professional->getId();
$this->email = $professional->getEmail();
$this->firstname = $professional->getFirstname();
$this->lastname = $professional->getLastname();
$this->jobs = [];
foreach ($professional->getJobs() as $job) {
$this->jobs[] = [
'id' => $job->getId(),
'label' => $job->getLabel()
];
}
}
}
src/State/ProfessionalsProvider.php
<?php
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Dto\ProfessionalDTO;
use App\Entity\Professional;
use App\Repository\ProfessionalRepository;
class ProfessionalsProvider implements ProviderInterface {
private ProfessionalRepository $professionalRepository;
public function __construct(ProfessionalRepository $professionalRepository)
{
$this->professionalRepository = $professionalRepository;
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null {
$professionals = $this->professionalRepository->findAll();
return array_map(
fn(Professional $professional) => new ProfessionalDTO($professional),
$professionals
);
}
}
services.yaml.
App\State\ProfessionalsProvider:
arguments:
$professionalRepository: '@App\Repository\ProfessionalRepository'