Last active
July 2, 2021 09:09
-
-
Save meekowhy/39feb6121b74edaaa90a43e2c68a5b6d to your computer and use it in GitHub Desktop.
Api platform Base64 file upload
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\FileUpload; | |
interface Base64FileUpload extends FileUpload | |
{ | |
public function getBase64fileContent(): ?string; | |
public function setBase64fileContent(?string $base64fileContent): self; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Serializer; | |
use App\FileUpload\Base64FileUpload; | |
use App\FileUpload\Base64FileUploader; | |
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; | |
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; | |
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; | |
final class Base64FileUploadDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface | |
{ | |
use DenormalizerAwareTrait; | |
private const ALREADY_CALLED = 'FILE_UPLOAD_DENORMALIZER_ALREADY_CALLED'; | |
public function __construct(private Base64FileUploader $fileUploader) | |
{ | |
} | |
/** | |
* @phpstan-ignore-next-line | |
*/ | |
public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool | |
{ | |
if (isset($context[self::ALREADY_CALLED])) { | |
return false; | |
} | |
return is_subclass_of($type, Base64FileUpload::class); | |
} | |
/** | |
* @phpstan-ignore-next-line | |
*/ | |
public function denormalize($data, string $type, string $format = null, array $context = []) | |
{ | |
$context[self::ALREADY_CALLED] = true; | |
/** @var Base64FileUpload $object */ | |
$object = $this->denormalizer->denormalize($data, $type, $format, $context); | |
$this->fileUploader->prepareBase64FileUploadObject($object); | |
return $object; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\FileUpload; | |
use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException; | |
use Symfony\Component\HttpFoundation\File\UploadedFile; | |
use Symfony\Component\Mime\MimeTypesInterface; | |
class Base64FileUploader | |
{ | |
/** @var string[] */ | |
private static array $base64tmpPaths = []; | |
public function __construct( | |
private MimeTypesInterface $mimeTypes | |
) { | |
} | |
public function prepareBase64FileUploadObject(Base64FileUpload $base64FileUpload): void | |
{ | |
$tmpPath = sys_get_temp_dir().'/tmp_upload'.uniqid(); | |
if (null === $base64FileUpload->getBase64fileContent()) { | |
throw new \RuntimeException('No base64 file content'); | |
} | |
$decodedFileContent = base64_decode($base64FileUpload->getBase64fileContent()); | |
file_put_contents($tmpPath, $decodedFileContent); | |
$this->addBase64tmpPath($tmpPath); | |
$mimeType = $this->mimeTypes->guessMimeType($tmpPath); | |
if (!$mimeType) { | |
throw new \RuntimeException('No mimeType'); | |
} | |
$uploadedFile = new UploadedFile( | |
$tmpPath, $this->createFileName($mimeType), $mimeType, 0, true | |
); | |
$base64FileUpload->setFile($uploadedFile); | |
} | |
public function removeBase64tmpFiles(): void | |
{ | |
foreach (self::$base64tmpPaths as $base64TmpPath) { | |
if (is_file($base64TmpPath)) { | |
unlink($base64TmpPath); | |
} | |
} | |
} | |
private function addBase64tmpPath(string $base64tmpPath): void | |
{ | |
self::$base64tmpPaths[] = $base64tmpPath; | |
} | |
private function createFileName(string $mimeType): string | |
{ | |
$extension = $this->mimeTypes->getExtensions($mimeType)[0] ?? | |
throw new \RuntimeException('File has no extension'); | |
return uniqid().".$extension"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\FileUpload; | |
use Symfony\Component\HttpFoundation\File\File; | |
interface FileUpload | |
{ | |
public function getFile(): File; | |
public function setFile(File $file): self; | |
public function getFilePath(): string; | |
public function setFilePath(string $filePath): self; | |
public function getContentUrl(): ?string; | |
public function setContentUrl(string $contentUrl): self; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\EventSubscriber; | |
use App\FileUpload\Base64FileUploader; | |
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
use Symfony\Component\HttpKernel\Event\ResponseEvent; | |
use Symfony\Component\HttpKernel\KernelEvents; | |
class FileUploadEventSubscriber implements EventSubscriberInterface | |
{ | |
public function __construct(private Base64FileUploader $fileUploader) | |
{ | |
} | |
/** @return array<string, array> */ | |
public static function getSubscribedEvents(): array | |
{ | |
return [ | |
KernelEvents::RESPONSE => ['removeBase64tmpFiles'], | |
]; | |
} | |
public function removeBase64tmpFiles(ResponseEvent $event): void | |
{ | |
$this->fileUploader->removeBase64tmpFiles(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Serializer; | |
use App\FileUpload\FileUpload; | |
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; | |
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; | |
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; | |
use Vich\UploaderBundle\Storage\StorageInterface; | |
final class FileUploadNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface | |
{ | |
use NormalizerAwareTrait; | |
private const ALREADY_CALLED = 'FILE_UPLOAD_NORMALIZER_ALREADY_CALLED'; | |
public function __construct(private StorageInterface $storage) | |
{ | |
} | |
/** | |
* @phpstan-ignore-next-line | |
*/ | |
public function normalize($object, ?string $format = null, array $context = []): array | string | int | float | bool | \ArrayObject | null | |
{ | |
$context[self::ALREADY_CALLED] = true; | |
/* @var FileUpload $object */ | |
$object->setContentUrl($this->storage->resolveUri($object, 'file')); | |
return $this->normalizer->normalize($object, $format, $context); | |
} | |
/** | |
* @phpstan-ignore-next-line | |
*/ | |
public function supportsNormalization($data, ?string $format = null, array $context = []): bool | |
{ | |
if (isset($context[self::ALREADY_CALLED])) { | |
return false; | |
} | |
return $data instanceof FileUpload; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
//this is example Api resource implementing Base64FileUpload interface | |
declare(strict_types=1); | |
namespace App\Entity; | |
use ApiPlatform\Core\Annotation\ApiProperty; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use App\FileUpload\Base64FileUpload; | |
use Doctrine\ORM\Mapping as ORM; | |
use Doctrine\ORM\Mapping\OneToOne; | |
use Symfony\Component\HttpFoundation\File\File; | |
use Symfony\Component\Serializer\Annotation\Groups; | |
use Symfony\Component\Validator\Constraints as Assert; | |
use Vich\UploaderBundle\Mapping\Annotation as Vich; | |
/** | |
* @ORM\Entity | |
* @ApiResource( | |
* normalizationContext={"groups"={"place_logo:read"}}, | |
* denormalizationContext={"groups"={"place_logo:write"}}, | |
* collectionOperations={ | |
* "get", | |
* "post"={ | |
* "security"="is_granted('ROLE_ADMIN')" | |
* }, | |
* }, | |
* itemOperations={ | |
* "get", | |
* "delete"={ | |
* "security"="is_granted('ROLE_ADMIN')" | |
* }, | |
* }, | |
* ) | |
* @Vich\Uploadable | |
*/ | |
class PlaceLogo implements Base64FileUpload | |
{ | |
/** | |
* @ORM\Column(type="integer") | |
* @ORM\GeneratedValue | |
* @ORM\Id | |
* @Groups({"place_logo:read","place:read"}) | |
*/ | |
private int $id; | |
/** | |
* @ApiProperty(iri="http://schema.org/contentUrl") | |
* @Groups({"place_logo:read","place:read"}) | |
*/ | |
private string $contentUrl; | |
/** | |
* @Assert\File( | |
* groups={"Default","place:write"}, | |
* maxSize="1M", mimeTypes={"image/jpg","image/jpeg","image/png"}, | |
* ) | |
* @Vich\UploadableField(mapping="image_upload", fileNameProperty="filePath") | |
*/ | |
private File $file; | |
/** | |
* @ORM\Column(nullable=false) | |
*/ | |
private string $filePath; | |
/** | |
* @Assert\NotBlank() | |
* @Groups({"place_logo:write","place:write"}) | |
*/ | |
private ?string $base64fileContent = null; | |
/** | |
* @Assert\NotBlank() | |
* @Groups({"place_logo:write"}) | |
* @OneToOne(targetEntity=Place::class, mappedBy="logo") | |
*/ | |
private Place $place; | |
public function getId(): int | |
{ | |
return $this->id; | |
} | |
public function getContentUrl(): ?string | |
{ | |
return $this->contentUrl; | |
} | |
public function setContentUrl(string $contentUrl): PlaceLogo | |
{ | |
$this->contentUrl = $contentUrl; | |
return $this; | |
} | |
public function getFile(): File | |
{ | |
return $this->file; | |
} | |
public function setFile(File $file): PlaceLogo | |
{ | |
$this->file = $file; | |
return $this; | |
} | |
public function getFilePath(): string | |
{ | |
return $this->filePath; | |
} | |
public function setFilePath(string $filePath): PlaceLogo | |
{ | |
$this->filePath = $filePath; | |
return $this; | |
} | |
public function getPlace(): Place | |
{ | |
return $this->place; | |
} | |
public function setPlace(Place $place, bool $updateRelation = true): PlaceLogo | |
{ | |
$this->place = $place; | |
if ($updateRelation) { | |
$place->setLogo($this, false); | |
} | |
return $this; | |
} | |
public function getBase64fileContent(): ?string | |
{ | |
return $this->base64fileContent; | |
} | |
public function setBase64fileContent(?string $base64fileContent): PlaceLogo | |
{ | |
$this->base64fileContent = $base64fileContent; | |
return $this; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
services: | |
App\Serializer\Base64FileUploadDenormalizer: | |
tags: | |
- { name: serializer.normalizer } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment