How can I resolve contentUrl field in MediaObject entity using the event system or any suitable approach
I have followed the Api-platform file upload documentation. The endpoint works fine with rest operations however the problem emerges when I switch to graphql. The contentUrl field in MediaObject is never resolved in graphql as in conventional rest. A scrutiny on symfony profiler reveals that Kernel::VIEW event is never dispatched and so I reckon ResolveMediaObjectContentUrlSubscriber is not notified to resolve contentUrl.
Graphql query
{
mediaObjects {
edges {
node {
id
contentUrl
}
}
}
}
Graphql output
{
"data": {
"mediaObjects": {
"edges": [
{
"node": {
"id": "/api/media_objects/1",
"contentUrl": null
}
}
]
}
}
}
Using rest with Jsonld content-type the field contentUrl is resolved properly.
{
"@context": "/api/contexts/MediaObject",
"@id": "/api/media_objects",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/api/media_objects/1",
"@type": "http://schema.org/MediaObject",
"contentUrl": "/media/my_image.png"
}
],
"hydra:totalItems": 1
}
I want to get contentUrl field resolved in graphql as well. How best can I achieve it?
MediaObject entity class with reference to section Configuring the Entity Receiving the Uploaded File of the aip-platform documentation.
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Controller\CreateMediaObjectAction;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
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;
/**
* Class MediaObject
*
* @ORM\Entity(repositoryClass="App\Repository\MediaObjectRepository")
* @ORM\Table(name="media_object")
* @ApiResource(
* iri="http://schema.org/MediaObject",
* normalizationContext={
* "groups"={"media_object_read"},
* },
* collectionOperations={
* "post"={
* "controller"=CreateMediaObjectAction::class,
* "deserialize"=false,
*
* "validation_groups"={"Default", "media_object_create"},
* "swagger_context"={
* "consumes"={
* "multipart/form-data",
* },
* "parameters"={
* {
* "in"="formData",
* "name"="file",
* "type"="file",
* "description"="The file to upload",
* },
* },
* },
* },
* "get",
* },
* itemOperations={
* "get",
* "delete",
* },
* )
* @Vich\Uploadable
*/
class MediaObject
{
use TimestampableEntity;
/**
* @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;
}
}
The documentation further recommends using the event system to resolve contentUrl field with reference to section Resolving the file URL of the documentation, I have the following event subscriber.
namespace App\EventSubscriber;
use ApiPlatform\Core\EventListener\EventPriorities;
use ApiPlatform\Core\Util\RequestAttributesExtractor;
use App\Entity\MediaObject;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Vich\UploaderBundle\Storage\StorageInterface;
final class ResolveMediaObjectContentUrlSubscriber implements EventSubscriberInterface
{
private $storage;
public function __construct(StorageInterface $storage)
{
$this->storage = $storage;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::VIEW => ['onPreSerialize', EventPriorities::PRE_SERIALIZE],
];
}
public function onPreSerialize(ViewEvent $event): void
{
$controllerResult = $event->getControllerResult();
$request = $event->getRequest();
if ($controllerResult instanceof Response || !$request->attributes->getBoolean('_api_respond', true)) {
return;
}
if (!($attributes = RequestAttributesExtractor::extractAttributes($request)) || !\is_a($attributes['resource_class'], MediaObject::class, true)) {
return;
}
$mediaObjects = $controllerResult;
if (!is_iterable($mediaObjects)) {
$mediaObjects = [$mediaObjects];
}
foreach ($mediaObjects as $mediaObject) {
if (!$mediaObject instanceof MediaObject) {
continue;
}
$mediaObject->setContentUrl($this->storage->resolveUri($mediaObject, 'file'));
}
}
}
I only changed the GetResponseForController event type-hint as it reported deprecated since symfony 4.3 and used the suggested ViewEvent and switching it back does not seem to solve the problem though. When I check the profiler after hitting the media_objects endpoint with a get operation, the given subscriber is only notified with REST API and never with grapqhl.
I suspect the issue is with kernel.view event not firing in grapql, I'm not sure if that's the expected behaviour or not with using graphql (I'm new to graphql).
Any improvement or further clarification will be highly appreciated.