Skip to content

Conversation

@rvanlaak
Copy link

@rvanlaak rvanlaak commented Dec 5, 2025

The capability of the adapter is directly available on the Doctrine object that gets hydrated. The media's storage is private, so without such additional method, a custom normalizer will need to overwrite the values when handled by the serializer.

Let me know what you think on how to approach rendering temporary URLs for media stored on buckets (over serving them through php as proxy directly).

Alternative media normalizer

The following normalizer would otherwise be needed to overwrite url on the output:

<?php

declare(strict_types=1);

namespace App\UserInterface\Serializer;

use App\Application\Files\FileStorage;
use App\Shared\Media;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
 * Normalizer for Media entities that generates temporary URLs for S3 storage.
 */
#[AutoconfigureTag('serializer.normalizer', ['priority' => 100])]
final class MediaNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
    use NormalizerAwareTrait;

    private const ALREADY_CALLED = 'MEDIA_NORMALIZER_ALREADY_CALLED';

    /**
     * Default expiration time for temporary URLs (1 hour).
     */
    private const DEFAULT_EXPIRY_SECONDS = 3600;

    public function __construct(
        private readonly FileStorage $fileStorage,
    ) {
    }

    /**
     * @param Media                 $object
     * @param array<string, mixed>  $context
     *
     * @return array<string, mixed>
     */
    public function normalize(mixed $object, ?string $format = null, array $context = []): array
    {
        $context[self::ALREADY_CALLED] = true;

        /** @var array<string, mixed> $data */
        $data = $this->normalizer->normalize($object, $format, $context);

        // Replace the URL with a temporary URL from S3
        $mediaPath = $object->media->getPath();
        $data['url'] = $this->fileStorage->getTemporaryUrl($mediaPath, self::DEFAULT_EXPIRY_SECONDS);

        return $data;
    }

    /**
     * @param array<string, mixed> $context
     */
    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
    {
        if (isset($context[self::ALREADY_CALLED])) {
            return false;
        }

        return $data instanceof Media;
    }

    /**
     * @return array<string, bool>
     */
    public function getSupportedTypes(?string $format): array
    {
        return [
            Media::class => false, // false because we need to call the normalizer recursively
        ];
    }
}

as without this, a custom normalizer will need to overwrite the values when handled by the serializer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant