Skip to content

Commit

Permalink
Merge pull request #108 from kbond/zs-uri
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond authored Mar 28, 2024
2 parents a7913ba + e8ababe commit 613885d
Show file tree
Hide file tree
Showing 20 changed files with 162 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/.github export-ignore
/tests export-ignore
/bin export-ignore
phpunit.dist.xml export-ignore
10 changes: 10 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env php
<?php

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Zenstruck\Tests\Fixtures\TestKernel;

require_once __DIR__ . '/../tests/bootstrap.php';

$application = new Application(new TestKernel('test', true));
$application->run();
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"league/flysystem": "^3.11",
"zenstruck/image": "^1.0",
"zenstruck/stream": "^1.2",
"zenstruck/temp-file": "^1.2.1"
"zenstruck/temp-file": "^1.2.1",
"zenstruck/uri": "^2.2"
},
"require-dev": {
"doctrine/doctrine-bundle": "^2.8",
Expand All @@ -43,11 +44,11 @@
"symfony/twig-bundle": "^6.4|^7.0",
"symfony/validator": "^6.4|^7.0",
"symfony/var-dumper": "^6.4|^7.0",
"symfony/yaml": "^6.4|^7.0",
"twig/twig": "^3.6",
"zenstruck/assert": "^1.2",
"zenstruck/console-test": "^1.4",
"zenstruck/foundry": "^1.30.3",
"zenstruck/uri": "^2.2"
"zenstruck/foundry": "^1.30.3"
},
"config": {
"preferred-install": "dist",
Expand Down
1 change: 1 addition & 0 deletions src/Filesystem/Flysystem/Operator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Zenstruck\Filesystem\Exception\UnsupportedFeature;
use Zenstruck\Filesystem\Flysystem\UrlGeneration\TransformUrlGenerator;

/**
* @author Kevin Bond <[email protected]>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/

namespace Zenstruck\Filesystem\Flysystem;
namespace Zenstruck\Filesystem\Flysystem\UrlGeneration;

use League\Flysystem\Config;

Expand Down
59 changes: 59 additions & 0 deletions src/Filesystem/Flysystem/UrlGeneration/VersionUrlGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

/*
* This file is part of the zenstruck/filesystem package.
*
* (c) Kevin Bond <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Zenstruck\Filesystem\Flysystem\UrlGeneration;

use League\Flysystem\Config;
use League\Flysystem\UrlGeneration\PublicUrlGenerator;
use Zenstruck\Filesystem\Node\File;
use Zenstruck\Filesystem\Node\Mapping;
use Zenstruck\Uri\ParsedUri;

/**
* @author Kevin Bond <[email protected]>
*/
final class VersionUrlGenerator implements PublicUrlGenerator
{
/**
* @param Mapping::LAST_MODIFIED|Mapping::SIZE|Mapping::CHECKSUM $metadata
*/
public function __construct(
private PublicUrlGenerator $inner,
private string $metadata = Mapping::LAST_MODIFIED,
private string $queryParameter = 'v',
) {
}

public function publicUrl(string $path, Config $config): string
{
if (false === ($version = $config->get('version') ?? $this->metadata)) {
return $this->inner->publicUrl($path, $config);
}

$file = $config->get('_file');

if (!$file instanceof File) {
throw new \LogicException(\sprintf('"%s::publicUrl()" requires the "_file" option to be set to a "%s" instance.', self::class, File::class));
}

$value = match ($version) {
Mapping::LAST_MODIFIED => $file->lastModified()->getTimestamp(),
Mapping::SIZE => $file->size(),
Mapping::CHECKSUM => $file->checksum(),
default => throw new \InvalidArgumentException(\sprintf('Unknown version "%s".', $version)),
};

return ParsedUri::new($this->inner->publicUrl($path, $config))
->withQueryParam($this->queryParameter, $value)
->toString()
;
}
}
2 changes: 1 addition & 1 deletion src/Filesystem/Glide/GlideTransformUrlGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

use League\Flysystem\Config;
use League\Glide\Urls\UrlBuilder;
use Zenstruck\Filesystem\Flysystem\TransformUrlGenerator;
use Zenstruck\Filesystem\Flysystem\UrlGeneration\TransformUrlGenerator;

/**
* @author Jakub Caban <[email protected]>
Expand Down
4 changes: 3 additions & 1 deletion src/Filesystem/Node/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ public function checksum(?string $algo = null): string;
/**
* @see FilesystemReader::publicUrl()
*
* @param array<string,mixed> $config
* @param array{
* version?: false|Mapping::LAST_MODIFIED|Mapping::SIZE|Mapping::CHECKSUM,
* } $config
*
* @throws UnableToGeneratePublicUrl
* @throws FilesystemException
Expand Down
5 changes: 4 additions & 1 deletion src/Filesystem/Node/File/FlysystemFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ public function checksum(?string $algo = null): string

public function publicUrl(array $config = []): string
{
return $this->cache['public-url'][\serialize($config)] ??= $this->operator->publicUrl($this->path(), $config);
return $this->cache['public-url'][\serialize($config)] ??= $this->operator->publicUrl(
$this->path(),
\array_merge($config, ['_file' => $this])
);
}

public function temporaryUrl(\DateTimeInterface|string $expires, array $config = []): string
Expand Down
42 changes: 33 additions & 9 deletions src/Filesystem/Symfony/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\VarExporter\LazyObjectInterface;
use Zenstruck\Filesystem;
use Zenstruck\Filesystem\Node\Mapping;
use Zenstruck\Filesystem\Operation;

/**
Expand Down Expand Up @@ -82,19 +83,23 @@ public function getConfigTreeBuilder(): TreeBuilder
->arrayNode('public_url')
->info('Public URL generator for this filesystem')
->beforeNormalization()
->ifString()
->then(function(string $v) {
return match (true) {
\str_starts_with($v, 'route:') => ['route' => ['name' => \mb_substr($v, 6)]],
\str_starts_with($v, '@') => ['service' => \mb_substr($v, 1)],
default => ['prefix' => $v],
};
})
->ifString()
->then(function(string $v) {
return match (true) {
\str_starts_with($v, 'route:') => ['route' => ['name' => \mb_substr($v, 6)]],
\str_starts_with($v, '@') => ['service' => \mb_substr($v, 1)],
default => ['prefix' => $v],
};
})
->end()
->validate()
->ifTrue(fn($v) => \count(\array_filter($v)) > 1)
->ifTrue(fn($v) => \count(\array_filter($v)) > 2)
->thenInvalid('Can only set one of "prefix", "service", "route"')
->end()
->validate()
->ifTrue(fn($v) => 1 === \count(\array_filter($v)) && $v['version']['enabled'])
->thenInvalid('Must set a url generation strategy to use versioning')
->end()
->children()
->variableNode('prefix')
->info('URL prefix or multiple prefixes to use for this filesystem (can be an array)')
Expand Down Expand Up @@ -132,6 +137,25 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
->arrayNode('version')
->info('Enables cache busting for public urls')
->beforeNormalization()
->ifString()
->then(fn(string $v) => ['metadata' => $v])
->end()
->canBeEnabled()
->children()
->enumNode('metadata')
->info('The metadata to use for versioning')
->values([Mapping::LAST_MODIFIED, Mapping::SIZE, Mapping::CHECKSUM])
->defaultValue(Mapping::LAST_MODIFIED)
->end()
->scalarNode('parameter')
->info('The query parameter to use for versioning')
->defaultValue('v')
->end()
->end()
->end()
->end()
->end()
->arrayNode('temporary_url')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

use League\Flysystem\Filesystem as Flysystem;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\UrlGeneration\PrefixPublicUrlGenerator;
use League\Flysystem\UrlGeneration\PublicUrlGenerator;
use League\Flysystem\UrlGeneration\ShardedPrefixPublicUrlGenerator;
use League\Flysystem\UrlGeneration\TemporaryUrlGenerator;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
Expand All @@ -35,7 +37,8 @@
use Zenstruck\Filesystem\Event\EventDispatcherFilesystem;
use Zenstruck\Filesystem\FilesystemRegistry;
use Zenstruck\Filesystem\Flysystem\AdapterFactory;
use Zenstruck\Filesystem\Flysystem\TransformUrlGenerator;
use Zenstruck\Filesystem\Flysystem\UrlGeneration\TransformUrlGenerator;
use Zenstruck\Filesystem\Flysystem\UrlGeneration\VersionUrlGenerator;
use Zenstruck\Filesystem\FlysystemFilesystem;
use Zenstruck\Filesystem\LoggableFilesystem;
use Zenstruck\Filesystem\MultiFilesystem;
Expand Down Expand Up @@ -294,7 +297,15 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde
// public url config
switch (true) {
case isset($config['public_url']['prefix']):
$config['config']['public_url'] = $config['public_url']['prefix'];
$container
->register(
$id = '.zenstruck_filesystem.filesystem_public_url.'.$name,
\is_array($config['public_url']['prefix']) ? ShardedPrefixPublicUrlGenerator::class : PrefixPublicUrlGenerator::class,
)
->setArguments([$config['public_url']['prefix']])
;

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

break;

Expand Down Expand Up @@ -331,6 +342,19 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde
break;
}

if (isset($config['public_url']) && $config['public_url']['version']['enabled'] && isset($features[PublicUrlGenerator::class])) {
$container->register($id = '.zenstruck_filesystem.filesystem_version_public_url.'.$name, VersionUrlGenerator::class)
->setDecoratedService((string) $features[PublicUrlGenerator::class])
->setArguments([
new Reference('.inner'),
$config['public_url']['version']['metadata'],
$config['public_url']['version']['parameter'],
])
;

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

// temporary url config
switch (true) {
case isset($config['temporary_url']['service']):
Expand All @@ -340,7 +364,7 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde

case isset($config['temporary_url']['route']):
if (!$canSignUrls) {
throw new LogicException('zenstruck/url is required to sign urls. Install with "composer require zenstruck/uri" and be sure the bundle is enabled.');
throw new LogicException(\sprintf('%s needs to be enabled to sign urls.', ZenstruckUriBundle::class));
}

$container->register($id = '.zenstruck_filesystem.filesystem_temporary_url.'.$name, RouteTemporaryUrlGenerator::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
namespace Zenstruck\Filesystem\Symfony\Routing;

use League\Flysystem\Config;
use Zenstruck\Filesystem\Flysystem\TransformUrlGenerator;
use Zenstruck\Filesystem\Flysystem\UrlGeneration\TransformUrlGenerator;

/**
* @author Kevin Bond <[email protected]>
Expand Down
3 changes: 2 additions & 1 deletion src/Filesystem/Symfony/Routing/RouteUrlGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Psr\Container\ContainerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Zenstruck\Uri\Bridge\Symfony\Routing\SignedUrlGenerator;
use Zenstruck\Uri\Bridge\Symfony\ZenstruckUriBundle;

/**
* @author Kevin Bond <[email protected]>
Expand Down Expand Up @@ -54,7 +55,7 @@ final protected function generate(string $path, array $routeParameters, ?bool $s
}

if (!$this->container->has(SignedUrlGenerator::class)) {
throw new \LogicException('zenstruck/url is required to sign urls. Install with "composer require zenstruck/uri" and be sure the bundle is enabled.');
throw new \LogicException(\sprintf('%s needs to be enabled to sign urls.', ZenstruckUriBundle::class));
}

$builder = $this->container->get(SignedUrlGenerator::class)->build($this->route, $routeParameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ public function metadata_is_pulled_from_attributes(): void
$this->assertSame('public', $image->visibility());
$this->assertSame(10862, $image->size());
$this->assertSame('ac6884fc84724d792649552e7211843a', $image->checksum());
$this->assertSame('/prefix/some/image.png', $image->publicUrl());
$this->assertSame('/prefix/some/image.png?v=10862', $image->publicUrl());
$this->assertSame('http://localhost/transform/some/image.png?filter=grayscale', $image->transformUrl('grayscale'));
$this->assertSame(563, $image->dimensions()->width());
$this->assertSame(678, $image->dimensions()->height());
Expand Down
2 changes: 1 addition & 1 deletion tests/Filesystem/Node/File/LazyFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public function can_create_with_attributes(): void
$this->assertSame('image/png', $file->mimeType());
$this->assertSame(7, $file->size());
$this->assertSame('9a0364b9e99bb480dd25e1f0284c8555', $file->checksum());
$this->assertSame('/prefix/some/file.png', $file->publicUrl());
$this->assertSame('/prefix/some/file.png?v=7', $file->publicUrl());
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/Filesystem/Node/File/SerializableFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected function serializedProvider(): iterable
'mime_type' => 'image/jpeg',
'checksum' => '42890a25562a1803949caa09d235f242',
'size' => 25884,
'public_url' => '/prefix/some/image.jpg',
'public_url' => '/prefix/some/image.jpg?v=25884',
'extension' => 'jpg',
],
];
Expand Down
3 changes: 2 additions & 1 deletion tests/Filesystem/Node/FileTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ public function file_urls(): void
{
$file = $this->createFile(fixture('symfony.png'), 'some/file.png');

$this->assertSame('/prefix/some/file.png', $file->publicUrl());
$this->assertSame('/prefix/some/file.png?v=10862', $file->publicUrl());
$this->assertSame('/prefix/some/file.png', $file->publicUrl(['version' => false]));
$this->assertSame('/temp/some/file.png?expires=1640995200', $file->temporaryUrl(new \DateTime('2022-01-01')));
$this->assertStringContainsString('/temp/some/file.png?expires=', $file->temporaryUrl('+30 minutes'));
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Filesystem/Symfony/ZenstruckFilesystemBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ public function can_generate_urls(): void
{
$publicFile = $this->filesystem()->write('public://foo/file.png', 'content')->ensureImage();

$this->assertSame('/prefix/foo/file.png', $publicFile->publicUrl());
$this->assertSame('/prefix/foo/file.png?v=7', $publicFile->publicUrl());
$this->assertSame('/prefix/foo/file.png', $publicFile->publicUrl(['version' => false]));
$this->assertStringContainsString('/temp/foo/file.png', $publicFile->temporaryUrl('tomorrow'));
$this->assertStringContainsString('_hash=', $publicFile->temporaryUrl('tomorrow'));
$this->assertStringContainsString('_expires=', $publicFile->temporaryUrl('tomorrow'));
Expand Down
5 changes: 4 additions & 1 deletion tests/Fixtures/TestKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load
'filesystems' => [
'public' => [
'dsn' => '%kernel.project_dir%/var/public',
'public_url' => '/prefix',
'public_url' => [
'prefix' => '/prefix',
'version' => 'size',
],
'temporary_url' => 'route:public_temp',
'image_url' => 'route:public_transform',
'reset_before_tests' => true,
Expand Down
6 changes: 4 additions & 2 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
use League\Flysystem\UrlGeneration\PublicUrlGenerator;
use League\Flysystem\UrlGeneration\TemporaryUrlGenerator;
use Zenstruck\Filesystem;
use Zenstruck\Filesystem\Flysystem\TransformUrlGenerator;
use Zenstruck\Filesystem\Flysystem\UrlGeneration\TransformUrlGenerator;
use Zenstruck\Filesystem\Flysystem\UrlGeneration\VersionUrlGenerator;
use Zenstruck\Filesystem\FlysystemFilesystem;
use Zenstruck\Filesystem\Node\Mapping;

require_once __DIR__.'/../vendor/autoload.php';

Expand Down Expand Up @@ -54,7 +56,7 @@ function in_memory_filesystem(string $name = 'default'): Filesystem
new InMemoryFilesystemAdapter(),
name: $name,
features: [
PublicUrlGenerator::class => new PrefixPublicUrlGenerator('/prefix'),
PublicUrlGenerator::class => new VersionUrlGenerator(new PrefixPublicUrlGenerator('/prefix'), Mapping::SIZE),
TemporaryUrlGenerator::class => new class() implements TemporaryUrlGenerator {
public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string
{
Expand Down

0 comments on commit 613885d

Please sign in to comment.