Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
SignedImageUrl
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
2 / 2
3
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 for
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace App\Shared\Infrastructure\Storage;
6
7use DateInterval;
8use Symfony\Component\HttpFoundation\UriSigner;
9use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
10
11/**
12 * Mints the short-lived URL a browser uses to fetch a stored image. The JWT lives
13 * in memory only, so an <img src> cannot send Authorization: the capability has to
14 * travel in the URL itself, signed with APP_SECRET and expiring in an hour.
15 *
16 * The URL is signed absolute (that is what UriSigner::checkRequest rebuilds from
17 * the incoming request) but returned as a path + query, so the caller prefixes it
18 * with whatever API base it uses — `/api` in prod, `http://localhost:8080` in dev.
19 */
20final readonly class SignedImageUrl
21{
22    private const TTL = 'PT1H';
23
24    public function __construct(
25        private UriSigner $signer,
26        private UrlGeneratorInterface $urls,
27    ) {
28    }
29
30    public function for(?string $name): ?string
31    {
32        if ($name === null) {
33            return null;
34        }
35
36        $absolute = $this->signer->sign(
37            $this->urls->generate('files_show', ['name' => $name], UrlGeneratorInterface::ABSOLUTE_URL),
38            new DateInterval(self::TTL),
39        );
40
41        return (string) preg_replace('#^\w+://[^/]+#', '', $absolute);
42    }
43}