Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

API Platform - onPreSerialize with MediaObject URI Resolver in normalization group

I have a MediaObject exposed as a subresource within a normalization context group "modules":

/**
 * @ApiResource(
 *     attributes={"access_control"="is_granted('ROLE_ADMIN')"},
 *     collectionOperations={
 *          "get",
 *          "post",
 *          "allData"={
 *              "method"="GET",
 *              "path"="/appdata",
 *              "normalization_context"={"groups"={"modules"}},
 *              "access_control"="is_granted('IS_AUTHENTICATED_ANONYMOUSLY')"
 *          }
 *     }
 * )
 * @ORM\Entity(repositoryClass="App\Repository\LearningModuleRepository")
 * @UniqueEntity("identifier")
 *
 */
class LearningModule
/**
 * @ORM\Entity(
 *     repositoryClass="App\Repository\MediaObjectRepository"
 * )
 * @ApiResource(
 *     iri="http://schema.org/MediaObject",
 *     normalizationContext={
 *         "groups"={"media_object_read"},
 *     },
 *     attributes={"access_control"="is_granted('ROLE_ADMIN')"},
 *     collectionOperations={
 *         "post"={
 *             "controller"=CreateMediaObject::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


    /**
     * @var string|null
     *
     * @ApiProperty(iri="http://schema.org/contentUrl")
     * @Groups({"media_object_read", "modules"})
     */
    public $contentUrl;

    /**
     * @var string|null
     * @Groups({"modules"})
     * @ORM\Column(nullable=true)
     */
    public $filePath;

When exposed through the normalization group I only get filePath but not contentUrl. Im assuming the propblem has to do with the the ContentUrlSubscriber as outlined in the official docs:

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::VIEW => ['onPreSerialize', EventPriorities::PRE_SERIALIZE],
        ];
    }

    public function onPreSerialize(GetResponseForControllerResultEvent $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->contentUrl = $this->storage->resolveUri($mediaObject, 'file');
        }
    }

Does anybody have a clue how to handle the preSerialization in this case?

Thank You

like image 674
9lex Avatar asked Nov 21 '25 02:11

9lex


1 Answers

I have found a solution to this problem:

  • The callback on PRE_SERIALIZE is aborted, since a LearningModule Object (not a MediaObject) has triggered the serialization ($attributes['resource_class'] === LearningModule).

  • To overcome this limitation, a decorated normalizer should be used as follows:

<?php

namespace App\Serializer;

use App\Entity\MediaObject;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Vich\UploaderBundle\Storage\StorageInterface;

final class MediaObjectContentUrlNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface
{
    private $decorated;
    private $storage;

    public function __construct(NormalizerInterface $decorated, StorageInterface $storage)
    {
        if (!$decorated instanceof DenormalizerInterface) {
            throw new \InvalidArgumentException(sprintf('The decorated normalizer must implement the %s.', DenormalizerInterface::class));
        }

        $this->decorated = $decorated;
        $this->storage = $storage;
    }

    public function supportsNormalization($data, $format = null)
    {
        return $this->decorated->supportsNormalization($data, $format);
    }

    public function normalize($object, $format = null, array $context = [])
    {
        $data = $this->decorated->normalize($object, $format, $context);
        if ($object instanceof MediaObject) {
            $data['contentUrl'] = $this->storage->resolveUri( $object, 'file');
        }

        return $data;
    }

    public function supportsDenormalization($data, $type, $format = null)
    {
        return $this->decorated->supportsDenormalization($data, $type, $format);
    }

    public function denormalize($data, $class, $format = null, array $context = [])
    {
        return $this->decorated->denormalize($data, $class, $format, $context);
    }

    public function setSerializer(SerializerInterface $serializer)
    {
        if($this->decorated instanceof SerializerAwareInterface) {
            $this->decorated->setSerializer($serializer);
        }
    }
}

Implementation details can be found here: https://api-platform.com/docs/core/serialization/#decorating-a-serializer-and-adding-extra-data

like image 148
9lex Avatar answered Nov 24 '25 04:11

9lex



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!