New in Symfony 8.2 (in development). To keep two requests from doing the same thing at the same time, you had to inject a LockFactory, create the lock, acquire it, return an error if it was taken, and remember to release it. The #[Lock] attribute does all of this for a controller.
#[Lock]
use Symfony\Component\HttpKernel\Attribute\Lock; #[Route('/catalog/import', methods: ['POST'])] #[Lock('catalog')] public function import(): Response
- As long as a request holds the lock, the others get 409 Conflict. With
blocking: true, they wait for their turn. - The lock is released at the end of the controller, or once a
StreamedResponsehas been fully sent. {name}in the key is replaced by thenameroute parameter:#[Lock('sales-{conference}')]gives one lock per conference.read: truetakes a shared lock: reads run together, but not with the write that holds the same key.- The lock lasts 30 seconds at most (
ttl); Symfony does not extend it. It is taken as soon as the controller is known, even before#[IsGranted]is checked.
The Lock component must be installed; it then configures itself, with semaphores when the sysvsem extension is available, file locks otherwise. Read locks need a store that supports them (flock, Redis, PostgreSQL…): here, config/packages/lock.yaml enforces flock.
Your task
CatalogueController imports and exports the catalog of each shop. An import rewrites the catalog file: two simultaneous imports mix their products, and an export read during an import delivers half of it.
- Only one import at a time per shop: a concurrent import gets a 409.
- The exports of a shop run together, but not at the same time as an import: whichever of the two arrives second gets a 409.
- The product count, for its part, stays available at all times.
The nightly import and the accounting export, started outside the application, already take the catalog-{shop} lock (catalog-lyon for Lyon): use the same key.