Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/Filesystem/Doctrine/EventListener/NodeLifecycleListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
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;
use Zenstruck\Filesystem\Node\File\SerializableFile;
use Zenstruck\Filesystem\Node\File\TemporaryFile;
use Zenstruck\Filesystem\Node\Mapping;
use Zenstruck\Filesystem\Node\PathGenerator;

Expand Down Expand Up @@ -143,7 +143,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);
}

Expand Down Expand Up @@ -176,7 +179,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
Expand Down Expand Up @@ -231,7 +237,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));
Expand All @@ -246,11 +252,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;
Expand Down
2 changes: 1 addition & 1 deletion src/Filesystem/Glide/GlideTransformUrlGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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"
};

Expand Down
32 changes: 32 additions & 0 deletions src/Filesystem/Node/File/Image/TemporaryImage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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\Node\File\Image;

use Zenstruck\Filesystem\Node\File\Image;
use Zenstruck\Filesystem\Node\File\TemporaryFile;

/**
* @author Jakub Caban <[email protected]>
*/
final class TemporaryImage extends TemporaryFile implements Image
{
use DecoratedImage;

public function __construct(private Image $image)
{
}

protected function inner(): Image
{
return $this->image;
}
}
11 changes: 10 additions & 1 deletion src/Filesystem/Node/File/PendingFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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)) {
Expand Down
68 changes: 68 additions & 0 deletions src/Filesystem/Node/File/TemporaryFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?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\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 <[email protected]>
*/
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): self
{
if ($node instanceof self) {
return $node;
}

if ($node instanceof Image) {
return new TemporaryImage($node);
}

return new self($node->ensureFile());
}
}
7 changes: 7 additions & 0 deletions src/Filesystem/Symfony/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
->booleanNode('temporary')
->defaultFalse()
->info(<<<EOF
If true, this filesystem will be configured to store
uploaded files in a serializable way.
EOF)
->end()
->booleanNode('reset_before_tests')
->defaultFalse()
->info(<<<EOF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use Zenstruck\Filesystem\Symfony\Routing\RouteTemporaryUrlGenerator;
use Zenstruck\Filesystem\Symfony\Routing\RouteTransformUrlGenerator;
use Zenstruck\Filesystem\Symfony\Serializer\NodeNormalizer;
use Zenstruck\Filesystem\TemporaryFilesystem;
use Zenstruck\Filesystem\Test\Node\Foundry\LazyMock;
use Zenstruck\Filesystem\TraceableFilesystem;
use Zenstruck\Filesystem\Twig\TwigPathGenerator;
Expand Down Expand Up @@ -375,6 +376,13 @@ private function registerFilesystem(string $name, array $config, ContainerBuilde
;
}

if ($config['temporary']) {
$container->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)
Expand Down
108 changes: 108 additions & 0 deletions src/Filesystem/TemporaryFilesystem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?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;

use League\Flysystem\FilesystemException;
use Zenstruck\Filesystem;
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 <[email protected]>
*/
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());
}
}
27 changes: 27 additions & 0 deletions tests/Filesystem/TemporaryFilesystemTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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\Tests\Filesystem;

use Zenstruck\Filesystem;
use Zenstruck\Filesystem\TemporaryFilesystem;
use Zenstruck\Tests\FilesystemTest;

/**
* @author Jakub Caban <[email protected]>
*/
class TemporaryFilesystemTest extends FilesystemTest
{
protected function createFilesystem(): Filesystem
{
return new TemporaryFilesystem(in_memory_filesystem());
}
}