From 1dc7be7d2600740d82cc1f5b42886edd19dbb1c8 Mon Sep 17 00:00:00 2001 From: Jakub Caban Date: Fri, 24 Feb 2023 13:41:34 +0100 Subject: [PATCH 1/7] Add temporary filesystem concept --- .../Node/File/Image/TemporaryImage.php | 23 ++++ src/Filesystem/Node/File/TemporaryFile.php | 68 +++++++++++ src/Filesystem/TemporaryFilesystem.php | 109 ++++++++++++++++++ tests/Filesystem/TemporaryFilesystemTest.php | 27 +++++ 4 files changed, 227 insertions(+) create mode 100644 src/Filesystem/Node/File/Image/TemporaryImage.php create mode 100644 src/Filesystem/Node/File/TemporaryFile.php create mode 100644 src/Filesystem/TemporaryFilesystem.php create mode 100644 tests/Filesystem/TemporaryFilesystemTest.php diff --git a/src/Filesystem/Node/File/Image/TemporaryImage.php b/src/Filesystem/Node/File/Image/TemporaryImage.php new file mode 100644 index 00000000..376575df --- /dev/null +++ b/src/Filesystem/Node/File/Image/TemporaryImage.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Filesystem\Node\File\Image; + +use Zenstruck\Filesystem\Node\File\Image; +use Zenstruck\Filesystem\Node\File\TemporaryFile; + +/** + * @author Jakub Caban + */ +final class TemporaryImage extends TemporaryFile implements Image +{ + use DecoratedImage; +} diff --git a/src/Filesystem/Node/File/TemporaryFile.php b/src/Filesystem/Node/File/TemporaryFile.php new file mode 100644 index 00000000..6de7ed3d --- /dev/null +++ b/src/Filesystem/Node/File/TemporaryFile.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Filesystem\Node\File; + +use League\Flysystem\FilesystemException; +use Zenstruck\Filesystem\Node; +use Zenstruck\Filesystem\Node\DecoratedNode; +use Zenstruck\Filesystem\Node\File; +use Zenstruck\Filesystem\Node\File\Image\TemporaryImage; + +/** + * @author Jakub Caban + */ +class TemporaryFile implements File +{ + use DecoratedFile, DecoratedNode; + + public function __construct(private File $file) + { + } + + public function ensureFile(): File + { + return $this->ensureTemporary( + $this->inner()->ensureFile() + ); + } + + public function ensureImage(): Image + { + $image = $this->ensureTemporary( + $this->inner()->ensureImage() + ); + assert($image instanceof TemporaryImage); + + return $image; + } + + protected function inner(): File + { + return $this->file; + } + + /** + * @throws FilesystemException + */ + private function ensureTemporary(Node $node): TemporaryFile + { + if ($node instanceof self) { + return $node; + } + + if ($node instanceof Image) { + return new TemporaryImage($node); + } + + return new TemporaryFile($node->ensureFile()); + } +} diff --git a/src/Filesystem/TemporaryFilesystem.php b/src/Filesystem/TemporaryFilesystem.php new file mode 100644 index 00000000..2cc7c454 --- /dev/null +++ b/src/Filesystem/TemporaryFilesystem.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Filesystem; + +use League\Flysystem\FilesystemException; +use Zenstruck\Filesystem; +use Zenstruck\Filesystem\Node\Directory; +use Zenstruck\Filesystem\Node\File; +use Zenstruck\Filesystem\Node\File\Image; +use Zenstruck\Filesystem\Node\File\Image\TemporaryImage; +use Zenstruck\Filesystem\Node\File\TemporaryFile; + +/** + * @author Jakub Caban + */ +class TemporaryFilesystem implements Filesystem +{ + use DecoratedFilesystem; + + public function __construct(private Filesystem $inner) + { + } + + public function node(string $path): Node + { + $node = $this->inner()->node($path); + + if ($node instanceof File) { + return $this->ensureTemporary($node); + } + + return $node; + } + + public function file(string $path): File + { + return $this->ensureTemporary( + $this->inner()->file($path) + ); + } + + public function image(string $path): Image + { + $image = $this->ensureTemporary( + $this->inner()->image($path) + ); + assert($image instanceof TemporaryImage); + + return $image; + } + + public function copy(string $source, string $destination, array $config = []): File + { + return $this->ensureTemporary( + $this->inner()->copy($source, $destination, $config) + ); + } + + public function move(string $source, string $destination, array $config = []): File + { + return $this->ensureTemporary( + $this->inner()->move($source, $destination, $config) + ); + } + + public function chmod(string $path, string $visibility): Node + { + return $this->ensureTemporary( + $this->inner()->chmod($path, $visibility) + ); + } + + public function write(string $path, mixed $value, array $config = []): File + { + return $this->ensureTemporary( + $this->inner()->write($path, $value, $config) + ); + } + + protected function inner(): Filesystem + { + return $this->inner; + } + + /** + * @throws FilesystemException + */ + private function ensureTemporary(Node $node): TemporaryFile + { + if ($node instanceof TemporaryFile) { + return $node; + } + + if ($node instanceof Image) { + return new TemporaryImage($node); + } + + return new TemporaryFile($node->ensureFile()); + } +} diff --git a/tests/Filesystem/TemporaryFilesystemTest.php b/tests/Filesystem/TemporaryFilesystemTest.php new file mode 100644 index 00000000..dc35dcfd --- /dev/null +++ b/tests/Filesystem/TemporaryFilesystemTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Tests\Filesystem; + +use Zenstruck\Filesystem; +use Zenstruck\Filesystem\TemporaryFilesystem; +use Zenstruck\Tests\FilesystemTest; + +/** + * @author Jakub Caban + */ +class TemporaryFilesystemTest extends FilesystemTest +{ + protected function createFilesystem(): Filesystem + { + return new TemporaryFilesystem(in_memory_filesystem()); + } +} From a321599339cd6e60939a512ef040192ae907a265 Mon Sep 17 00:00:00 2001 From: Jakub Caban Date: Fri, 24 Feb 2023 13:50:54 +0100 Subject: [PATCH 2/7] Add Symfony config for temporary filesystem --- .../Symfony/DependencyInjection/Configuration.php | 7 +++++++ .../DependencyInjection/ZenstruckFilesystemExtension.php | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Filesystem/Symfony/DependencyInjection/Configuration.php b/src/Filesystem/Symfony/DependencyInjection/Configuration.php index d54c5314..d6e12fe2 100644 --- a/src/Filesystem/Symfony/DependencyInjection/Configuration.php +++ b/src/Filesystem/Symfony/DependencyInjection/Configuration.php @@ -266,6 +266,13 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() + ->booleanNode('temporary') + ->defaultFalse() + ->info(<<end() ->booleanNode('reset_before_tests') ->defaultFalse() ->info(<<register('.zenstruck_filesystem.filesystem.temporary_'.$name, TemporaryFilesystem::class) + ->setDecoratedService($filesystemId) + ->setArguments([new Reference('.inner')]) + ; + } + if ($config['log']['enabled']) { $container->register('.zenstruck_filesystem.filesystem.log_'.$name, LoggableFilesystem::class) ->setDecoratedService($filesystemId) From a621b919a542cf7b0bb8e5f200c84894cc0026b0 Mon Sep 17 00:00:00 2001 From: Jakub Caban Date: Fri, 24 Feb 2023 14:04:18 +0100 Subject: [PATCH 3/7] Wire TemporaryFilesystem into Doctrine events --- .../EventListener/NodeLifecycleListener.php | 21 +++++++++++++++---- src/Filesystem/TemporaryFilesystem.php | 1 - 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php b/src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php index ead78fb0..5323d658 100644 --- a/src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php +++ b/src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php @@ -34,8 +34,10 @@ use Zenstruck\Filesystem\Node\File\LazyFile; use Zenstruck\Filesystem\Node\File\PendingFile; use Zenstruck\Filesystem\Node\File\SerializableFile; +use Zenstruck\Filesystem\Node\File\TemporaryFile; use Zenstruck\Filesystem\Node\Mapping; use Zenstruck\Filesystem\Node\PathGenerator; +use Zenstruck\Filesystem\TemporaryFilesystem; /** * @author Kevin Bond @@ -143,7 +145,10 @@ public function prePersist(LifecycleEventArgs $event): void $original = $metadata->getFieldValue($object, $field); $new = null; - if ($original instanceof PendingFile) { + if ( + $original instanceof PendingFile + || $original instanceof TemporaryFile + ) { $new = $this->convertPendingFile($mapping, $original, $object, $field); } @@ -176,7 +181,10 @@ public function preUpdate(PreUpdateEventArgs|ORMPreUpdateEventArgs $event): void $old = $event->getOldValue($field); $new = $event->getNewValue($field); - if ($new instanceof PendingFile) { + if ( + $new instanceof PendingFile + || $new instanceof TemporaryFile + ) { $new = $this->convertPendingFile($mapping, $new, $object, $field); // just setting the new value does not update the property so refresh the object on flush @@ -231,7 +239,7 @@ private static function createSerialized(StoreWithMetadata $mapping, File $file) return new SerializableFile($file, $mapping->metadata); } - private function convertPendingFile(Mapping $mapping, PendingFile $file, object $object, string $field): LazyFile + private function convertPendingFile(Mapping $mapping, PendingFile|TemporaryFile $file, object $object, string $field): LazyFile { if (!$mapping->filesystem()) { throw new \LogicException(\sprintf('In order to save pending files, the %s::$%s mapping must have a filesystem configured.', $object::class, $field)); @@ -246,11 +254,16 @@ private function convertPendingFile(Mapping $mapping, PendingFile $file, object $this->postFlushOperations[] = fn() => $this->filesystem($mapping)->write($path, $file); } + if ($file instanceof TemporaryFile) { + $filesystem = $this->filesystem($mapping); + $this->postFlushOperations[] = static fn() => $filesystem->delete($filesystem->directory($path)->path()); + } + if ($mapping instanceof StoreAsDsn) { $path = Dsn::create($mapping->filesystem(), $path); } - $lazyFile = $file instanceof PendingImage ? new LazyImage($path) : new LazyFile($path); + $lazyFile = $file instanceof Image ? new LazyImage($path) : new LazyFile($path); $lazyFile->setFilesystem($this->filesystem($mapping)); return $lazyFile; diff --git a/src/Filesystem/TemporaryFilesystem.php b/src/Filesystem/TemporaryFilesystem.php index 2cc7c454..f0e009ee 100644 --- a/src/Filesystem/TemporaryFilesystem.php +++ b/src/Filesystem/TemporaryFilesystem.php @@ -13,7 +13,6 @@ use League\Flysystem\FilesystemException; use Zenstruck\Filesystem; -use Zenstruck\Filesystem\Node\Directory; use Zenstruck\Filesystem\Node\File; use Zenstruck\Filesystem\Node\File\Image; use Zenstruck\Filesystem\Node\File\Image\TemporaryImage; From c8f4dd7c1d6b034db423420c48e1c990f3bd7384 Mon Sep 17 00:00:00 2001 From: Jakub Caban Date: Fri, 24 Feb 2023 14:19:01 +0100 Subject: [PATCH 4/7] Add saveToTemporary filesystem --- src/Filesystem/Node/File/PendingFile.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Filesystem/Node/File/PendingFile.php b/src/Filesystem/Node/File/PendingFile.php index b2e9e435..cab795f8 100644 --- a/src/Filesystem/Node/File/PendingFile.php +++ b/src/Filesystem/Node/File/PendingFile.php @@ -47,7 +47,7 @@ public function __construct(string|\SplFileInfo $filename) } /** - * @param callable(self):string $path + * @param string|null|callable(self):string $path */ public function saveTo(Filesystem $filesystem, string|callable|null $path = null): static { @@ -60,6 +60,15 @@ public function saveTo(Filesystem $filesystem, string|callable|null $path = null return $this; } + public function saveToTemporary(Filesystem $filesystem): File + { + do { + $directory = (string) microtime(); + } while ($filesystem->has($directory)); + + return $filesystem->write($directory.'/.'.$this->path()->name(), $this); + } + public function path(): Path { if (isset($this->path)) { From ebdfd3e4629ebbb6c1a7c8f95dc66a12a9790438 Mon Sep 17 00:00:00 2001 From: Jakub Caban Date: Fri, 24 Feb 2023 14:33:51 +0100 Subject: [PATCH 5/7] Fix PhpStan --- src/Filesystem/Node/File/Image/TemporaryImage.php | 10 ++++++++++ .../Symfony/HttpKernel/PendingFileValueResolver.php | 0 2 files changed, 10 insertions(+) create mode 100644 src/Filesystem/Symfony/HttpKernel/PendingFileValueResolver.php diff --git a/src/Filesystem/Node/File/Image/TemporaryImage.php b/src/Filesystem/Node/File/Image/TemporaryImage.php index 376575df..ee6e8742 100644 --- a/src/Filesystem/Node/File/Image/TemporaryImage.php +++ b/src/Filesystem/Node/File/Image/TemporaryImage.php @@ -11,6 +11,7 @@ namespace Zenstruck\Filesystem\Node\File\Image; +use Zenstruck\Filesystem\Node\File; use Zenstruck\Filesystem\Node\File\Image; use Zenstruck\Filesystem\Node\File\TemporaryFile; @@ -20,4 +21,13 @@ final class TemporaryImage extends TemporaryFile implements Image { use DecoratedImage; + + public function __construct(private Image $image) + { + } + + protected function inner(): Image + { + return $this->image; + } } diff --git a/src/Filesystem/Symfony/HttpKernel/PendingFileValueResolver.php b/src/Filesystem/Symfony/HttpKernel/PendingFileValueResolver.php new file mode 100644 index 00000000..e69de29b From c0ef647322332d4e71255dc1ae05a9f529beaa56 Mon Sep 17 00:00:00 2001 From: Jakub Caban Date: Fri, 24 Feb 2023 14:39:07 +0100 Subject: [PATCH 6/7] CS fixer run --- .../Doctrine/EventListener/NodeLifecycleListener.php | 2 -- src/Filesystem/Glide/GlideTransformUrlGenerator.php | 2 +- src/Filesystem/Node/File/Image/TemporaryImage.php | 1 - src/Filesystem/Node/File/PendingFile.php | 2 +- src/Filesystem/Node/File/TemporaryFile.php | 6 +++--- src/Filesystem/Symfony/HttpKernel/RequestFilesExtractor.php | 0 src/Filesystem/TemporaryFilesystem.php | 2 +- 7 files changed, 6 insertions(+), 9 deletions(-) create mode 100644 src/Filesystem/Symfony/HttpKernel/RequestFilesExtractor.php diff --git a/src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php b/src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php index 5323d658..035f1ce5 100644 --- a/src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php +++ b/src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php @@ -29,7 +29,6 @@ use Zenstruck\Filesystem\Node\File; use Zenstruck\Filesystem\Node\File\Image; use Zenstruck\Filesystem\Node\File\Image\LazyImage; -use Zenstruck\Filesystem\Node\File\Image\PendingImage; use Zenstruck\Filesystem\Node\File\Image\SerializableImage; use Zenstruck\Filesystem\Node\File\LazyFile; use Zenstruck\Filesystem\Node\File\PendingFile; @@ -37,7 +36,6 @@ use Zenstruck\Filesystem\Node\File\TemporaryFile; use Zenstruck\Filesystem\Node\Mapping; use Zenstruck\Filesystem\Node\PathGenerator; -use Zenstruck\Filesystem\TemporaryFilesystem; /** * @author Kevin Bond diff --git a/src/Filesystem/Glide/GlideTransformUrlGenerator.php b/src/Filesystem/Glide/GlideTransformUrlGenerator.php index 0ea30e3e..b9dfc034 100644 --- a/src/Filesystem/Glide/GlideTransformUrlGenerator.php +++ b/src/Filesystem/Glide/GlideTransformUrlGenerator.php @@ -28,7 +28,7 @@ public function transformUrl(string $path, array|string $filter, Config $config) { $filter = match (true) { // @phpstan-ignore-line https://github.com/phpstan/phpstan/issues/8937 \is_string($filter) => ['p' => $filter], // is glide "preset" - \is_array($filter) && !array_is_list($filter) => $filter, // is standard glide parameters + \is_array($filter) && !\array_is_list($filter) => $filter, // is standard glide parameters \is_array($filter) => ['p' => \implode(',', $filter)], // is array of "presets" }; diff --git a/src/Filesystem/Node/File/Image/TemporaryImage.php b/src/Filesystem/Node/File/Image/TemporaryImage.php index ee6e8742..6e692f05 100644 --- a/src/Filesystem/Node/File/Image/TemporaryImage.php +++ b/src/Filesystem/Node/File/Image/TemporaryImage.php @@ -11,7 +11,6 @@ namespace Zenstruck\Filesystem\Node\File\Image; -use Zenstruck\Filesystem\Node\File; use Zenstruck\Filesystem\Node\File\Image; use Zenstruck\Filesystem\Node\File\TemporaryFile; diff --git a/src/Filesystem/Node/File/PendingFile.php b/src/Filesystem/Node/File/PendingFile.php index cab795f8..fe239cbd 100644 --- a/src/Filesystem/Node/File/PendingFile.php +++ b/src/Filesystem/Node/File/PendingFile.php @@ -63,7 +63,7 @@ public function saveTo(Filesystem $filesystem, string|callable|null $path = null public function saveToTemporary(Filesystem $filesystem): File { do { - $directory = (string) microtime(); + $directory = (string) \microtime(); } while ($filesystem->has($directory)); return $filesystem->write($directory.'/.'.$this->path()->name(), $this); diff --git a/src/Filesystem/Node/File/TemporaryFile.php b/src/Filesystem/Node/File/TemporaryFile.php index 6de7ed3d..c0cf7355 100644 --- a/src/Filesystem/Node/File/TemporaryFile.php +++ b/src/Filesystem/Node/File/TemporaryFile.php @@ -40,7 +40,7 @@ public function ensureImage(): Image $image = $this->ensureTemporary( $this->inner()->ensureImage() ); - assert($image instanceof TemporaryImage); + \assert($image instanceof TemporaryImage); return $image; } @@ -53,7 +53,7 @@ protected function inner(): File /** * @throws FilesystemException */ - private function ensureTemporary(Node $node): TemporaryFile + private function ensureTemporary(Node $node): self { if ($node instanceof self) { return $node; @@ -63,6 +63,6 @@ private function ensureTemporary(Node $node): TemporaryFile return new TemporaryImage($node); } - return new TemporaryFile($node->ensureFile()); + return new self($node->ensureFile()); } } diff --git a/src/Filesystem/Symfony/HttpKernel/RequestFilesExtractor.php b/src/Filesystem/Symfony/HttpKernel/RequestFilesExtractor.php new file mode 100644 index 00000000..e69de29b diff --git a/src/Filesystem/TemporaryFilesystem.php b/src/Filesystem/TemporaryFilesystem.php index f0e009ee..f31b5c6b 100644 --- a/src/Filesystem/TemporaryFilesystem.php +++ b/src/Filesystem/TemporaryFilesystem.php @@ -52,7 +52,7 @@ public function image(string $path): Image $image = $this->ensureTemporary( $this->inner()->image($path) ); - assert($image instanceof TemporaryImage); + \assert($image instanceof TemporaryImage); return $image; } From 74feef629ec0db8705fef73d486b818d6f8d1c72 Mon Sep 17 00:00:00 2001 From: Jakub Caban Date: Thu, 2 Mar 2023 13:23:46 +0100 Subject: [PATCH 7/7] Nuke empty files --- src/Filesystem/Symfony/HttpKernel/PendingFileValueResolver.php | 0 src/Filesystem/Symfony/HttpKernel/RequestFilesExtractor.php | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/Filesystem/Symfony/HttpKernel/PendingFileValueResolver.php delete mode 100644 src/Filesystem/Symfony/HttpKernel/RequestFilesExtractor.php diff --git a/src/Filesystem/Symfony/HttpKernel/PendingFileValueResolver.php b/src/Filesystem/Symfony/HttpKernel/PendingFileValueResolver.php deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Filesystem/Symfony/HttpKernel/RequestFilesExtractor.php b/src/Filesystem/Symfony/HttpKernel/RequestFilesExtractor.php deleted file mode 100644 index e69de29b..00000000