Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Varnish invalidation on subresource

Open
#8,123 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
25/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Stale
Tech stack
php
Domain
api, backend

Research direction

Start by reproducing the Varnish cache behavior with the /suppliers/{id}/delivery_days collection and a POST to /delivery_days. Trace the cache invalidation service and collection route configuration described in the issue. Done means the subresource collection cache is invalidated without exposing an unwanted default collection route.

Written by the indexing model from the issue text.

Description

http State

API Platform version(s) affected: 3.3.6

Description
Varnish cache is not released on a subresource.
For example purposes, I use a Supplier and a DeliveryDay entity.

Supplier:

#[ORM\Entity(repositoryClass: SupplierRepository::class)]
#[ApiResource(
    operations: [
        new Post(),
        new Get(),
        new GetCollection(),
        new Put(),
        new Delete(),
    ],
    normalizationContext: ['groups' => ['supplier:read']],
    denormalizationContext: ['groups' => ['supplier:write']],
)]
class Supplier
{

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(groups: ['supplier:read'])]
    private ?int $id = null;

    #[ORM\Column(length: 128)]
    #[Groups(groups: ['supplier:read', 'supplier:write'])]
    #[Assert\NotBlank]
    #[Assert\Length(max: 128)]
    public string $name = '';

    #[ORM\OneToMany(targetEntity: DeliveryDay::class, mappedBy: 'supplier', orphanRemoval: true)]
    private Collection $deliveryDays;

    public function __construct()
    {
        $this->deliveryDays = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * @return Collection<int, DeliveryDay>
     */
    public function getDeliveryDays(): Collection
    {
        return $this->deliveryDays;
    }

    public function addDeliveryDay(DeliveryDay $deliveryDay): static
    {
        if (!$this->deliveryDays->contains($deliveryDay)) {
            $this->deliveryDays->add($deliveryDay);
            $deliveryDay->setSupplier($this);
        }

        return $this;
    }

    public function removeDeliveryDay(DeliveryDay $deliveryDay): static
    {
        if ($this->deliveryDays->removeElement($deliveryDay)) {
            // set the owning side to null (unless already changed)
            if ($deliveryDay->getSupplier() === $this) {
                $deliveryDay->setSupplier(null);
            }
        }

        return $this;
    }
}

DeliveryDay:

#[ORM\Entity]
#[ApiResource(
    operations: [
        new Post(),
        new Get(),
        new GetCollection(
            uriTemplate: '/suppliers/{id}/delivery_days',
            uriVariables: [
                'id' => new Link(
                    fromProperty: 'deliveryDays',
                    fromClass: Supplier::class
                ),
            ]
        ),
        new Put(),
        new Delete(),
    ],
    normalizationContext: ['groups' => ['delivery_day:read']],
    denormalizationContext: ['groups' => ['delivery_day:write']],
)]
class DeliveryDay
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(groups: ['delivery_day:read'])]
    private ?int $id = null;

    #[ORM\ManyToOne(inversedBy: 'deliveryDays')]
    #[ORM\JoinColumn(nullable: false)]
    #[Groups(groups: ['delivery_day:read', 'delivery_day:write'])]
    private ?Supplier $supplier = null;

    #[ORM\Column(length: 64)]
    #[Groups(groups: ['delivery_day:read', 'delivery_day:write'])]
    #[Assert\NotBlank]
    #[Assert\Length(max: 64)]
    public string $day = '';

    #[ORM\Column(length: 64)]
    #[Groups(groups: ['delivery_day:read', 'delivery_day:write'])]
    #[Assert\NotBlank]
    #[Assert\Length(max: 64)]
    public string $region = '';

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getSupplier(): ?Supplier
    {
        return $this->supplier;
    }

    public function setSupplier(?Supplier $supplier): static
    {
        $this->supplier = $supplier;

        return $this;
    }
}

How to reproduce
If you now use /suppliers/{id}/delivery_days, Varnish caches that response.
If you then make a POST request to /delivery_days, the cache from the collection route is not invalidated.

Possible Solution
If you add a default GetCollection:

new GetCollection(),
new GetCollection(
    uriTemplate: '/suppliers/{id}/delivery_days',
    uriVariables: [
        'id' => new Link(
            fromProperty: 'deliveryDays',
            fromClass: Supplier::class
        ),
    ]
),

It works, but then there is a route exposed that you don't really want.

So, I would suggest always adding a default cache tag to a collection.
In this case, that would be /delivery_days (even when the route doesn't exist)
and also add this tag to the invalidation service (even if the route doesn't exist).

end word
If I'm doing something that is not intended or missed some docs, please point me in the right direction.
I would like to help resolve this issue by contributing, but I have never done that before.
I would appreciate some help or guidance to get started.

Dominant language
PHP
Stars
2.6k
Forks
989
Avg merge
2d 1h
Merged PRs (30d)
104

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from api-platform/core

All issues in api-platform/core

Similar issues

More PHP issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.