Skip to content

Commit 7ca0810

Browse files
authored
Merge pull request #116 from kbond/remove-zenstruck-uri
Remove `zenstruck\uri` as a hard dep
2 parents 6ea6a0a + adf906b commit 7ca0810

File tree

10 files changed

+123
-79
lines changed

10 files changed

+123
-79
lines changed

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
"league/flysystem": "^3.11",
1717
"zenstruck/image": "^1.0",
1818
"zenstruck/stream": "^1.2",
19-
"zenstruck/temp-file": "^1.2.1",
20-
"zenstruck/uri": "^2.2"
19+
"zenstruck/temp-file": "^1.2.1"
2120
},
2221
"require-dev": {
2322
"doctrine/doctrine-bundle": "^2.8",
@@ -48,7 +47,8 @@
4847
"twig/twig": "^3.6",
4948
"zenstruck/assert": "^1.2",
5049
"zenstruck/console-test": "^1.4",
51-
"zenstruck/foundry": "^2.0"
50+
"zenstruck/foundry": "^2.0",
51+
"zenstruck/uri": "^2.3"
5252
},
5353
"config": {
5454
"preferred-install": "dist",

src/Filesystem/Flysystem/UrlGeneration/VersionUrlGenerator.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public function __construct(
3030
private string $metadata = Mapping::LAST_MODIFIED,
3131
private string $queryParameter = 'v',
3232
) {
33+
if (!\class_exists(ParsedUri::class)) {
34+
throw new \LogicException('zenstruck\filesystem requires zenstruck/uri to use versioned public URLs. Install with "composer require zenstruck/uri".');
35+
}
3336
}
3437

3538
public function publicUrl(string $path, Config $config): string

src/Filesystem/Node/File.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,7 @@ public function checksum(?string $algo = null): string;
8888
/**
8989
* @see FilesystemReader::publicUrl()
9090
*
91-
* @param array{
92-
* version?: false|Mapping::LAST_MODIFIED|Mapping::SIZE|Mapping::CHECKSUM,
93-
* } $config
91+
* @param array<string,mixed> $config
9492
*
9593
* @throws UnableToGeneratePublicUrl
9694
* @throws FilesystemException

src/Filesystem/Symfony/DependencyInjection/ZenstruckFilesystemExtension.php

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
use Symfony\Component\DependencyInjection\ContainerInterface;
2525
use Symfony\Component\DependencyInjection\Exception\LogicException;
2626
use Symfony\Component\DependencyInjection\Reference;
27+
use Symfony\Component\DependencyInjection\ServiceLocator;
28+
use Symfony\Component\HttpFoundation\UriSigner;
2729
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
30+
use Symfony\Component\HttpKernel\Kernel;
2831
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2932
use Symfony\Component\String\Slugger\SluggerInterface;
3033
use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -57,8 +60,7 @@
5760
use Zenstruck\Filesystem\Symfony\Serializer\NodeNormalizer;
5861
use Zenstruck\Filesystem\TraceableFilesystem;
5962
use Zenstruck\Filesystem\Twig\TwigPathGenerator;
60-
use Zenstruck\Uri\Bridge\Symfony\Routing\SignedUrlGenerator;
61-
use Zenstruck\Uri\Bridge\Symfony\ZenstruckUriBundle;
63+
use Zenstruck\Uri\ParsedUri;
6264

6365
/**
6466
* @author Kevin Bond <kevinbond@gmail.com>
@@ -278,14 +280,14 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde
278280
}
279281

280282
$features = [];
281-
$canSignUrls = \in_array(ZenstruckUriBundle::class, (array) $container->getParameter('kernel.bundles'), true);
282-
$routers = [UrlGeneratorInterface::class => new Reference('router')];
283283

284-
if ($canSignUrls) {
285-
$routers[SignedUrlGenerator::class] = new Reference(SignedUrlGenerator::class);
286-
}
287-
288-
$routers = new ServiceLocatorArgument($routers);
284+
$container->register('.zenstruck_filesystem.route_url_generator.locator', ServiceLocator::class)
285+
->setArgument(0, [
286+
UrlGeneratorInterface::class => new Reference('router'),
287+
UriSigner::class => new Reference('uri_signer'),
288+
])
289+
->addTag('container.service_locator')
290+
;
289291

290292
// public url config
291293
switch (true) {
@@ -310,7 +312,7 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde
310312
case isset($config['public_url']['route']):
311313
$container->register($id = '.zenstruck_filesystem.filesystem_public_url.'.$name, RoutePublicUrlGenerator::class)
312314
->setArguments([
313-
$routers,
315+
new Reference('.zenstruck_filesystem.route_url_generator.locator'),
314316
$config['public_url']['route']['name'],
315317
$config['public_url']['route']['parameters'],
316318
$config['public_url']['route']['sign'],
@@ -320,22 +322,24 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde
320322

321323
$features[PublicUrlGenerator::class] = new Reference($id);
322324

323-
if ($canSignUrls) {
324-
$container->register($id = '.zenstruck_filesystem.filesystem_temporary_url.'.$name, RouteTemporaryUrlGenerator::class)
325-
->setArguments([
326-
new Reference(SignedUrlGenerator::class),
327-
$config['public_url']['route']['name'],
328-
$config['public_url']['route']['parameters'],
329-
])
330-
;
325+
$container->register($id = '.zenstruck_filesystem.filesystem_temporary_url.'.$name, RouteTemporaryUrlGenerator::class)
326+
->setArguments([
327+
new Reference('.zenstruck_filesystem.route_url_generator.locator'),
328+
$config['public_url']['route']['name'],
329+
$config['public_url']['route']['parameters'],
330+
])
331+
;
331332

332-
$features[TemporaryUrlGenerator::class] = new Reference($id);
333-
}
333+
$features[TemporaryUrlGenerator::class] = new Reference($id);
334334

335335
break;
336336
}
337337

338338
if (isset($config['public_url']) && $config['public_url']['version']['enabled'] && isset($features[PublicUrlGenerator::class])) {
339+
if (!\class_exists(ParsedUri::class)) {
340+
throw new LogicException('zenstruck\filesystem requires zenstruck/uri to use versioned public URLs. Install with "composer require zenstruck/uri".');
341+
}
342+
339343
$container->register($id = '.zenstruck_filesystem.filesystem_version_public_url.'.$name, VersionUrlGenerator::class)
340344
->setDecoratedService((string) $features[PublicUrlGenerator::class])
341345
->setArguments([
@@ -356,13 +360,13 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde
356360
break;
357361

358362
case isset($config['temporary_url']['route']):
359-
if (!$canSignUrls) {
360-
throw new LogicException(\sprintf('%s needs to be enabled to sign urls.', ZenstruckUriBundle::class));
363+
if (Kernel::VERSION_ID < 70100) { // @phpstan-ignore smaller.alwaysFalse
364+
throw new LogicException('"temporary_url" requires Symfony 7.1 or higher.');
361365
}
362366

363367
$container->register($id = '.zenstruck_filesystem.filesystem_temporary_url.'.$name, RouteTemporaryUrlGenerator::class)
364368
->setArguments([
365-
new Reference(SignedUrlGenerator::class),
369+
new Reference('.zenstruck_filesystem.route_url_generator.locator'),
366370
$config['temporary_url']['route']['name'],
367371
$config['temporary_url']['route']['parameters'],
368372
])
@@ -383,7 +387,7 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde
383387
case isset($config['image_url']['route']):
384388
$container->register($id = '.zenstruck_filesystem.filesystem_image_url.'.$name, RouteTransformUrlGenerator::class)
385389
->setArguments([
386-
$routers,
390+
new Reference('.zenstruck_filesystem.route_url_generator.locator'),
387391
$config['image_url']['route']['name'],
388392
$config['image_url']['route']['parameters'],
389393
$config['image_url']['route']['sign'],

src/Filesystem/Symfony/Routing/RoutePublicUrlGenerator.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ final class RoutePublicUrlGenerator extends RouteUrlGenerator implements PublicU
2222
public function publicUrl(string $path, Config $config): string
2323
{
2424
return $this->generate(
25-
$path,
26-
$config->get('parameters', []),
27-
$config->get('sign'),
28-
$config->get('expires'),
25+
path: $path,
26+
routeParameters: $config->get('parameters', []),
27+
expires: $config->get('expires'),
2928
);
3029
}
3130
}

src/Filesystem/Symfony/Routing/RouteTemporaryUrlGenerator.php

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,27 @@
1313

1414
use League\Flysystem\Config;
1515
use League\Flysystem\UrlGeneration\TemporaryUrlGenerator;
16-
use Zenstruck\Uri\Bridge\Symfony\Routing\SignedUrlGenerator;
16+
use Psr\Container\ContainerInterface;
1717

1818
/**
1919
* @author Kevin Bond <kevinbond@gmail.com>
2020
*/
21-
final class RouteTemporaryUrlGenerator implements TemporaryUrlGenerator
21+
final class RouteTemporaryUrlGenerator extends RouteUrlGenerator implements TemporaryUrlGenerator
2222
{
23-
/**
24-
* @param array<string,mixed> $routeParameters
25-
*/
2623
public function __construct(
27-
private SignedUrlGenerator $router,
28-
private string $route,
29-
private array $routeParameters = [],
24+
ContainerInterface $container,
25+
string $route,
26+
array $routeParameters = [],
3027
) {
28+
parent::__construct($container, $route, $routeParameters, true);
3129
}
3230

3331
public function temporaryUrl(string $path, \DateTimeInterface $expiresAt, Config $config): string
3432
{
35-
return $this->router->temporary(
36-
$expiresAt,
37-
$this->route,
38-
\array_merge($this->routeParameters, $config->get('parameters', []), ['path' => $path]),
33+
return $this->generate(
34+
path: $path,
35+
routeParameters: $config->get('parameters', []),
36+
expires: $expiresAt,
3937
);
4038
}
4139
}

src/Filesystem/Symfony/Routing/RouteTransformUrlGenerator.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@ public function transformUrl(string $path, array|string $filter, Config $config)
2626
}
2727

2828
return $this->generate(
29-
$path,
30-
\array_merge($config->get('parameters', []), $filter),
31-
$config->get('sign'),
32-
$config->get('expires'),
29+
path: $path,
30+
routeParameters: \array_merge($config->get('parameters', []), $filter),
31+
expires: $config->get('expires'),
3332
);
3433
}
3534
}

src/Filesystem/Symfony/Routing/RouteUrlGenerator.php

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
namespace Zenstruck\Filesystem\Symfony\Routing;
1313

1414
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\HttpFoundation\UriSigner;
16+
use Symfony\Component\HttpKernel\Kernel;
1517
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
16-
use Zenstruck\Uri\Bridge\Symfony\Routing\SignedUrlGenerator;
17-
use Zenstruck\Uri\Bridge\Symfony\ZenstruckUriBundle;
1818

1919
/**
2020
* @author Kevin Bond <kevinbond@gmail.com>
@@ -23,43 +23,51 @@
2323
*/
2424
abstract class RouteUrlGenerator
2525
{
26+
private bool $signByDefault;
27+
2628
/**
2729
* @param array<string,mixed> $routeParameters
2830
*/
2931
public function __construct(
3032
private ContainerInterface $container,
3133
private string $route,
3234
private array $routeParameters = [],
33-
private bool $signByDefault = false,
35+
bool $signByDefault = false,
3436
private ?string $defaultExpires = null,
3537
) {
38+
$this->signByDefault = $this->defaultExpires ? true : $signByDefault;
3639
}
3740

3841
/**
3942
* @param array<string,mixed> $routeParameters
4043
*/
41-
final protected function generate(string $path, array $routeParameters, ?bool $sign, string|\DateTimeInterface|null $expires): string
44+
final protected function generate(string $path, array $routeParameters, string|\DateTimeInterface|null $expires): string
4245
{
43-
$routeParameters = \array_merge($this->routeParameters, $routeParameters, ['path' => $path]);
44-
$sign ??= $this->signByDefault;
4546
$expires ??= $this->defaultExpires;
47+
$url = $this->container->get(UrlGeneratorInterface::class)
48+
->generate(
49+
$this->route,
50+
\array_merge($this->routeParameters, $routeParameters, ['path' => $path]),
51+
UrlGeneratorInterface::ABSOLUTE_URL,
52+
)
53+
;
4654

47-
if (null !== $expires) {
48-
$sign = true;
55+
if ($expires && !$this->signByDefault) {
56+
throw new \LogicException('Cannot set expiry when signing is disabled.');
4957
}
5058

51-
if (!$sign) {
52-
return $this->container->get(UrlGeneratorInterface::class)
53-
->generate($this->route, $routeParameters, UrlGeneratorInterface::ABSOLUTE_URL)
54-
;
59+
if (!$this->signByDefault) {
60+
return $url;
5561
}
5662

57-
if (!$this->container->has(SignedUrlGenerator::class)) {
58-
throw new \LogicException(\sprintf('%s needs to be enabled to sign urls.', ZenstruckUriBundle::class));
63+
if (\is_string($expires)) {
64+
$expires = new \DateTimeImmutable($expires);
5965
}
6066

61-
$builder = $this->container->get(SignedUrlGenerator::class)->build($this->route, $routeParameters);
67+
if ($expires && Kernel::VERSION_ID < 70100) { // @phpstan-ignore smaller.alwaysFalse, booleanAnd.alwaysFalse
68+
throw new \LogicException('Expiring URLs requires Symfony 7.1 or higher.');
69+
}
6270

63-
return $expires ? $builder->expires($expires) : $builder;
71+
return $this->container->get(UriSigner::class)->sign($url, $expires);
6472
}
6573
}

tests/Filesystem/Symfony/ZenstruckFilesystemBundleTest.php

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Zenstruck\Tests\Filesystem\Symfony;
1313

1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
15+
use Symfony\Component\HttpKernel\Kernel;
1516
use Zenstruck\Filesystem\Node\Path\Expression;
1617
use Zenstruck\Filesystem\Test\InteractsWithFilesystem;
1718
use Zenstruck\Filesystem\Test\ResetFilesystem;
@@ -95,25 +96,61 @@ public function can_generate_urls(): void
9596

9697
$this->assertSame('/prefix/foo/file.png?v=7', $publicFile->publicUrl());
9798
$this->assertSame('/prefix/foo/file.png', $publicFile->publicUrl(['version' => false]));
98-
$this->assertStringContainsString('/temp/foo/file.png', $publicFile->temporaryUrl('tomorrow'));
99-
$this->assertStringContainsString('_hash=', $publicFile->temporaryUrl('tomorrow'));
100-
$this->assertStringContainsString('_expires=', $publicFile->temporaryUrl('tomorrow'));
10199
$this->assertSame('http://localhost/transform/foo/file.png?filter=grayscale', $publicFile->transformUrl('grayscale'));
102100
$this->assertSame('http://localhost/transform/foo/file.png?w=100&h=200', $publicFile->transformUrl(['w' => 100, 'h' => 200]));
103101

104102
$privateFile = $this->filesystem()->write('private://bar/file.png', 'content')->ensureImage();
105103

106104
$this->assertStringContainsString('http://localhost/private/bar/file.png', $privateFile->publicUrl());
105+
$this->assertSame('/glide/bar/file.png?w=100&h=200', $privateFile->transformUrl(['w' => 100, 'h' => 200]));
106+
}
107+
108+
/**
109+
* @test
110+
*/
111+
public function can_generate_signed_urls(): void
112+
{
113+
$privateFile = $this->filesystem()->write('private://bar/file.png', 'content')->ensureImage();
114+
107115
$this->assertStringContainsString('_hash=', $privateFile->publicUrl());
108-
$this->assertStringNotContainsString('_expires=', $privateFile->publicUrl());
109-
$this->assertStringContainsString('http://localhost/private/bar/file.png', $privateFile->publicUrl(['expires' => 'tomorrow']));
110-
$this->assertStringContainsString('_hash=', $privateFile->publicUrl(['expires' => 'tomorrow']));
111-
$this->assertStringContainsString('_expires=', $privateFile->publicUrl(['expires' => 'tomorrow']));
112-
$this->assertSame('http://localhost/private/bar/file.png', $privateFile->publicUrl(['sign' => false]));
116+
$this->assertStringNotContainsString('_expiration=', $privateFile->publicUrl());
117+
}
118+
119+
/**
120+
* @test
121+
*/
122+
public function can_generate_temporary_urls(): void
123+
{
124+
if (Kernel::VERSION_ID < 70100) {
125+
$this->markTestSkipped('Temporary URLs are not supported in Symfony < 7.1.');
126+
}
127+
128+
$publicFile = $this->filesystem()->write('public://foo/file.png', 'content')->ensureImage();
129+
130+
$this->assertStringContainsString('/temp/foo/file.png', $publicFile->temporaryUrl('tomorrow'));
131+
$this->assertStringContainsString('_hash=', $publicFile->temporaryUrl('tomorrow'));
132+
$this->assertStringContainsString('_expiration=', $publicFile->temporaryUrl('tomorrow'));
133+
134+
$privateFile = $this->filesystem()->write('private://bar/file.png', 'content')->ensureImage();
135+
113136
$this->assertStringContainsString('/private/bar/file.png', $privateFile->temporaryUrl('tomorrow'));
114137
$this->assertStringContainsString('_hash=', $privateFile->temporaryUrl('tomorrow'));
115-
$this->assertStringContainsString('_expires=', $privateFile->temporaryUrl('tomorrow'));
116-
$this->assertSame('/glide/bar/file.png?w=100&h=200', $privateFile->transformUrl(['w' => 100, 'h' => 200]));
138+
$this->assertStringContainsString('_expiration=', $privateFile->temporaryUrl('tomorrow'));
139+
}
140+
141+
/**
142+
* @test
143+
*/
144+
public function can_generate_expiring_public_urls(): void
145+
{
146+
if (Kernel::VERSION_ID < 70100) {
147+
$this->markTestSkipped('Expiring URLs are not supported in Symfony < 7.1.');
148+
}
149+
150+
$privateFile = $this->filesystem()->write('private://bar/file.png', 'content')->ensureImage();
151+
152+
$this->assertStringContainsString('_hash=', $privateFile->publicUrl(['expires' => 'tomorrow']));
153+
$this->assertStringContainsString('_expiration=', $privateFile->publicUrl(['expires' => 'tomorrow']));
117154
}
118155

119156
/**

0 commit comments

Comments
 (0)