'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'application/pdf' => 'pdf', ]; public function __construct( private readonly EntityManagerInterface $entityManager, private readonly Security $security, private readonly string $uploadDir, ) {} #[Route('/api/absence_requests/{id}/justificatif', name: 'absence_justification_upload', methods: ['POST'], priority: 1)] #[IsGranted('ROLE_USER')] public function __invoke(int $id, Request $request): JsonResponse { $absence = $this->entityManager->getRepository(AbsenceRequest::class)->find($id); if (null === $absence) { throw new NotFoundHttpException('Absence request not found.'); } if (!$this->security->isGranted('ROLE_ADMIN') && $absence->getUser() !== $this->security->getUser()) { throw new AccessDeniedHttpException('You can only attach a file to your own request.'); } $file = $request->files->get('file'); if (null === $file || !$file->isValid()) { throw new BadRequestHttpException('No valid file uploaded.'); } if ($file->getSize() > self::MAX_FILE_SIZE) { throw new BadRequestHttpException('File size exceeds 10 MB limit.'); } $mimeType = $file->getMimeType() ?: 'application/octet-stream'; if (!isset(self::MIME_TO_EXTENSION[$mimeType])) { throw new BadRequestHttpException(sprintf('File type "%s" is not allowed (PDF or image only).', $mimeType)); } $fileName = Uuid::v4()->toRfc4122().'.'.self::MIME_TO_EXTENSION[$mimeType]; if (!is_dir($this->uploadDir)) { mkdir($this->uploadDir, 0o775, true); } // Remove a previously uploaded file if any $previous = $absence->getJustificationFileName(); if (null !== $previous && file_exists($this->uploadDir.'/'.$previous)) { unlink($this->uploadDir.'/'.$previous); } $file->move($this->uploadDir, $fileName); $absence->setJustificationFileName($fileName); $this->entityManager->flush(); return $this->json($absence, context: ['groups' => ['absence_request:read']]); } }