Sulex95
Sulex95

Reputation: 1

Api Platform - Can not define multiple GetCollection

I'm trying to define an entity with multiple endpoints using Api Platform. When I try to define multiple Get or GetCollection endpointd, it returns a 404 error. Looking for the reason, the only conclusion I can find was a bad uriTemplate definition, but does not make sense to me.

I try the next and it work, but I don't understand why.

If I define the endpoint like /sport_centers_top_rated it works If I define the endpoint like /sport_centers/top_rated it does not work.

I can don't understand why.

Here is my class definition:

<?php

namespace App\Entity\App;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\Controller\App\SportCenter\SportCenterCreateController;
use App\Controller\App\SportCenter\SportCenterDeleteController;
use App\Controller\App\SportCenter\SportCenterReadController;
use App\Controller\App\SportCenter\SportCenterUpdateController;
use App\Entity\Master\City;
use App\Repository\App\SportCenterRepository;
use App\Traits\Entity\DatetimeTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;

#[ORM\Table(schema: 'app')]
#[ORM\Entity(repositoryClass: SportCenterRepository::class)]
#[ORM\HasLifecycleCallbacks]
#[ApiResource(
    operations: [
        new Get(
            name: 'sport_center:read'
        ),
        new Get(
            uriTemplate: '/sport_centers/{id}/sports',
            controller: SportCenterReadController::class,
            name: 'sport_center:read:sport_types'
        ),
        new Get(
            uriTemplate: '/sport_centers/{id}/reserves_availability',
            controller: SportCenterReadController::class,
            name: 'sport_center:read:reserves_availability'
        ),
        new GetCollection(
            uriTemplate: '/sport_centers',
            controller: SportCenterReadController::class,
            name: 'sport_center:read:list'
        ),
        new GetCollection(
            uriTemplate: '/sport_centers/top_reserves',
            controller: SportCenterReadController::class,
            normalizationContext: [
                'groups' => [
                    'sport_center:read:top_rated'
                ]
            ],
            name: 'sport_center:read:list:top_reserves'
        ),
        new Post(
            uriTemplate: '/sport_centers',
            controller: SportCenterCreateController::class,
            name: 'sport_center:write:create'
        ),
        new Put(
            uriTemplate: '/sport_centers/{id}',
            controller: SportCenterUpdateController::class,
            denormalizationContext: [
                'groups' => [
                    'sport_center:write:update',
                ]
            ],
            name: 'sport_center:write:update'
        ),
        new Delete(
            uriTemplate: '/sport_centers/{id}',
            controller: SportCenterDeleteController::class,
            name: 'sport_center:write:delete'
        ),
    ],

    normalizationContext: [
        'groups' => [
            'sport_center:read',
        ]
    ],

    denormalizationContext: [
        'groups' => [
            'sport_center:write',
        ]
    ]
)]
class SportCenter
{

    use DatetimeTrait;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(
        groups: [
            'sport_center:read',
            'court:write',
            'court:read',
            'reserve:read',
            'reserve:read:search_reserves',
            'city:read'
        ]
    )]
    private int $id;

    #[ORM\Column(length: 255)]
    #[Groups(
        groups: [
            'sport_center:write',
            'sport_center:read',
            'court:read',
            'sport_center:write:update',
            'reserve:read',
            'reserve:read:search_reserves',
            'city:read'
        ]
    )]
    private string $name;

    #[ORM\Column(length: 255)]
    #[Groups(
        groups: [
            'sport_center:write',
            'sport_center:read',
            'court:read',
            'reserve:read',
            'sport_center:write:update',
            'city:read'
        ]
    )]
    private string $street;

    #[ORM\Column(length: 255)]
    #[Groups(
        groups: [
            'sport_center:write',
            'sport_center:read',
            'court:read',
            'sport_center:write:update',
        ]
    )]
    private string $postalCode;

    #[ORM\OneToMany(mappedBy: 'sportCenter', targetEntity: Court::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
    #[Groups(
        groups: [
            'sport_center:read',
            'sport_center:read:details:admin'
        ]
    )]
    private Collection $courts;

    #[ORM\ManyToOne(inversedBy: 'sportCenters')]
    #[ORM\JoinColumn(nullable: false)]
    private User $admin;

    #[ORM\ManyToOne(inversedBy: 'sportCenters')]
    #[Groups(
        groups: [
            'sport_center:write',
            'sport_center:read',
            'court:read',
            'sport_center:write:update',
            'reserve:read',
            'reserve:read:search_reserves'
        ]
    )]
    private City $city;

    public function __construct()
    {
        $this->courts = 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;
    }

    public function getStreet(): ?string
    {
        return $this->street;
    }

    public function setStreet(string $street): self
    {
        $this->street = $street;

        return $this;
    }

    public function getPostalCode(): ?string
    {
        return $this->postalCode;
    }

    public function setPostalCode(string $postalCode): self
    {
        $this->postalCode = $postalCode;

        return $this;
    }

    /**
     * @return Collection<int, Court>
     */
    public function getCourts(): Collection
    {
        return $this->courts;
    }

    public function addCourt(Court $court): self
    {
        if (!$this->courts->contains($court)) {
            $this->courts->add($court);
            $court->setSportCenter($this);
        }

        return $this;
    }

    public function removeCourt(Court $court): self
    {
        if ($this->courts->removeElement($court)) {
            // set the owning side to null (unless already changed)
            if ($court->getSportCenter() === $this) {
                $court->setSportCenter(null);
            }
        }

        return $this;
    }

    public function getAdmin(): ?User
    {
        return $this->admin;
    }

    public function setAdmin(?User $admin): self
    {
        $this->admin = $admin;

        return $this;
    }

    public function getCity(): ?City
    {
        return $this->city;
    }

    public function setCity(?City $city): static
    {
        $this->city = $city;

        return $this;
    }

    public function getSportTypes(): array
    {
        $sports = [];
        foreach ($this->courts as $court) {
            $sports[] = $court->getSport()->value;
        }

        return array_unique($sports);
    }

    #[Groups([
        'sport_center:read'
    ])]
    public function getNCourts()
    {
        return sizeof($this->getCourts());
    }
}

Upvotes: 0

Views: 307

Answers (1)

You should add a requirement the first GET operation :

 new Get(
        uriTemplate: '/sport_centers/{id}',
        name: 'sport_center:read',
        requirements: {"id"="\d+"}
    ),

So a call to "sport_centers/top_rated " won't go through this route and the right operation will apply.

As a side note you can debug your routing with this command :

php bin/console router:match /sport_centers/top_rated --method=GET

You will see that you go through the first operation. As there is no sport center with id = top_rated, you get a not found as expected.

Upvotes: 0

Related Questions