I am creating an API with API platform. One of the features is to be able to upload and download files from a React client developped independently from my API
1 - First try
I followed the docs to setup VichUploaderBundle which led me to the exact same configuration as the docs (https://api-platform.com/docs/core/file-upload/)
From this, I can get my images by sending a GET request to the contentURL attribute set by my subscriber, which has the following format : "localhost/media/{fileName}" . However, I get a "CORS Missing allow origin" from my app when doing this.
2 - Second try
I fixed this by :
- removing the subscriber and the contentUrl attribute
- writing an itemOperation on the get method to serve my files directly through the "media_objects/{id}" route :
<?php
// api/src/Controller/GetMediaObjectAction.php
namespace App\Controller;
use App\Entity\MediaObject;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use App\Repository\MediaObjectRepository;
final class GetMediaObjectAction
{
private $mediaObjectRepository;
public function __construct(MediaObjectRepository $mediaObjectRepository)
{
$this->mediaObjectRepository = $mediaObjectRepository;
}
public function __invoke(Request $request): BinaryFileResponse
{
$id = $request->attributes->get('id');
$filePath = $this->mediaObjectRepository->findOneById($id)->getFilePath();
$file = "media/" . $filePath;
return new BinaryFileResponse($file);
}
}
EDIT : Here is my implementation of the MediaObject entity as requested
<?php
// api/src/Entity/MediaObject.php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Controller\CreateMediaObjectAction;
use Doctrine\ORM\Mapping as ORM;
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(
* iri="http://schema.org/MediaObject",
* normalizationContext={
* "groups"={"media_object_read"}
* },
* collectionOperations={
* "post"={
* "controller"=CreateMediaObjectAction::class,
* "deserialize"=false,
* "validation_groups"={"Default", "media_object_create"},
* "openapi_context"={
* "requestBody"={
* "content"={
* "multipart/form-data"={
* "schema"={
* "type"="object",
* "properties"={
* "file"={
* "type"="string",
* "format"="binary"
* }
* }
* }
* }
* }
* }
* }
* },
* "get"
* },
* itemOperations={
* "get"
* }
* )
* @Vich\Uploadable
*/
class MediaObject
{
/**
* @var int|null
*
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
* @ORM\Id
*/
protected $id;
/**
* @var string|null
*
* @ApiProperty(iri="http://schema.org/contentUrl")
* @Groups({"media_object_read"})
*/
public $contentUrl;
/**
* @var File|null
*
* @Assert\NotNull(groups={"media_object_create"})
* @Vich\UploadableField(mapping="media_object",fileNameProperty="filePath")
*/
public $file;
/**
* @var string|null
*
* @ORM\Column(nullable=true)
*/
public $filePath;
public function getId(): ?int
{
return $this->id;
}
}
END OF EDIT
Now I don't have this CORS problem anymore since API-platform is directly serving the file when responding to my "media_objects/{id}" route.
However, this brought some questions :
- Why did the CORS error pop in the first place ? I would guess it is because when performing a get request directly on the "public" folder, API-platform is not enforcing its CORS policy and not providing the required headers to the client
- Is it a correct practice to serve the files this way ? The fact that the documentation introduces a subscriber to create a contentUrl makes me wonder...
- Now that the server handles retrieving the file in the Action, does it make sense to have the files in the public folder ? Wouldn't it allow anyone to retrieve my files, and make enforcing security rules on them more difficult ?
Thank you in advance !