Skip to content

Commit

Permalink
Added support for 'ReplacingFile' to 'upload' files not uploaded via …
Browse files Browse the repository at this point in the history
…HTTP (#1300)

Fix #1296
  • Loading branch information
UlrichThomasGabor authored Aug 9, 2022
1 parent fb44772 commit 3b64675
Show file tree
Hide file tree
Showing 17 changed files with 217 additions and 15 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"league/flysystem-bundle": "^2.0",
"league/flysystem-memory": "^2.0",
"matthiasnoback/symfony-dependency-injection-test": "^4.1",
"mikey179/vfsstream": "^1.6",
"mikey179/vfsstream": "^1.6.8",
"phpunit/phpunit": "^9.5",
"symfony/asset": "^4.4 || ^5.0 || ^6.0",
"symfony/browser-kit": "^4.4 || ^5.0 || ^6.0",
Expand Down
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ composer require symfony/form
* [Using the bundled file form type](form/vich_file_type.md)
* [Using the bundled image form type](form/vich_image_type.md)

### Other usages

* [Inject files from other sources](other_usages/replacing_file.md)

### Download-related

* [Serving files with a controller](downloads/serving_files_with_a_controller.md)
Expand Down
47 changes: 47 additions & 0 deletions docs/other_usages/replacing_file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Inject files from other sources

The bundle provides a way to inject files into entities coming from other sources than [HTTP uploads via forms](../form/vich_file_type.md).

When you have configured your entity like laid out in the [usage page](../usage.md), you can use code like the one below to inject files which are coming from other sources like

- already a file on the server,
- downloaded with curl/wget or
- migrate old uploads.


## Example

```php
// ...
use Acme\DemoBundle\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Vich\UploaderBundle\FileAbstraction\ReplacingFile;

class MigrationCommand extends Command
{
public function __construct(
protected EntityManagerInterface $em,
) {
parent::__construct();
}
// ...

protected function execute(InputInterface $input, OutputInterface $output): int
{
$product = new Product();
$product->imageFile = new ReplacingFile('myFile.png');
// ...

$this->em->persist($product);
$this->em->flush();

return Command::SUCCESS;
}
}
```

## That was it!

Check out the docs for information on how to use the bundle! [Return to the
index.](../index.md)
2 changes: 2 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,6 @@ When you submit and save, the uploaded file will automatically be moved to the
location you configured and the `imageName` field will be set to the filename of
the uploaded file.

Alternatively, you can use `ReplacingFile` to [inject files coming from other sources](other_usages/replacing_file.md).

[Return to the index to explore the other possibilities of the bundle.](index.md)
20 changes: 20 additions & 0 deletions src/FileAbstraction/ReplacingFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Vich\UploaderBundle\FileAbstraction;

use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
* This class can be used to signal that the given file should be "uploaded" into the Vich-abstraction
* in cases where it is not possible to construct an `UploadedFile`.
*/
class ReplacingFile extends File
{
public function getClientOriginalName(): string
{
return $this->getFilename();
}
}
3 changes: 2 additions & 1 deletion src/Handler/UploadHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Vich\UploaderBundle\Event\Event;
use Vich\UploaderBundle\Event\Events;
use Vich\UploaderBundle\FileAbstraction\ReplacingFile;
use Vich\UploaderBundle\Injector\FileInjectorInterface;
use Vich\UploaderBundle\Mapping\PropertyMapping;
use Vich\UploaderBundle\Mapping\PropertyMappingFactory;
Expand Down Expand Up @@ -122,6 +123,6 @@ protected function hasUploadedFile(object $obj, PropertyMapping $mapping): bool
{
$file = $mapping->getFile($obj);

return null !== $file && $file instanceof UploadedFile;
return $file instanceof UploadedFile || $file instanceof ReplacingFile;
}
}
4 changes: 2 additions & 2 deletions src/Mapping/PropertyMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public function __construct(string $filePropertyPath, string $fileNamePropertyPa
*
* @param object $obj The object
*
* @return \Symfony\Component\HttpFoundation\File\UploadedFile|null The file
* @return \Symfony\Component\HttpFoundation\File\UploadedFile|\Vich\UploaderBundle\FileAbstraction\ReplacingFile|null The file
*
* @throws \InvalidArgumentException
*/
Expand Down Expand Up @@ -368,7 +368,7 @@ protected function fixPropertyPath($object, string $propertyPath): string

protected function getAccessor(): PropertyAccessor
{
//TODO: reuse original property accessor from forms
// TODO: reuse original property accessor from forms
if (null !== $this->accessor) {
return $this->accessor;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Naming/OrignameNamer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Vich\UploaderBundle\Naming;

use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\FileAbstraction\ReplacingFile;
use Vich\UploaderBundle\Mapping\PropertyMapping;
use Vich\UploaderBundle\Util\Transliterator;

Expand Down Expand Up @@ -40,7 +41,7 @@ public function configure(array $options): void

public function name($object, PropertyMapping $mapping): string
{
/* @var $file UploadedFile */
/* @var $file UploadedFile|ReplacingFile */
$file = $mapping->getFile($object);
$name = $file->getClientOriginalName();

Expand Down
7 changes: 6 additions & 1 deletion src/Naming/Polyfill/FileExtensionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

namespace Vich\UploaderBundle\Naming\Polyfill;

use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\FileAbstraction\ReplacingFile;

trait FileExtensionTrait
{
/**
* Guess the extension of the given file.
*/
private function getExtension(UploadedFile $file): ?string
private function getExtension(File $file): ?string
{
if (!$file instanceof UploadedFile && !$file instanceof ReplacingFile) {
throw new \InvalidArgumentException('Unexpected type for $file: '.get_class($file));
}
$originalName = $file->getClientOriginalName();

if ('' !== ($extension = \pathinfo($originalName, \PATHINFO_EXTENSION))) {
Expand Down
7 changes: 4 additions & 3 deletions src/Storage/AbstractStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Vich\UploaderBundle\Storage;

use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Exception\MappingNotFoundException;
use Vich\UploaderBundle\FileAbstraction\ReplacingFile;
use Vich\UploaderBundle\Mapping\PropertyMapping;
use Vich\UploaderBundle\Mapping\PropertyMappingFactory;

Expand All @@ -27,13 +29,12 @@ public function __construct(PropertyMappingFactory $factory)
/**
* @return mixed
*/
abstract protected function doUpload(PropertyMapping $mapping, UploadedFile $file, ?string $dir, string $name);
abstract protected function doUpload(PropertyMapping $mapping, File $file, ?string $dir, string $name);

public function upload($obj, PropertyMapping $mapping): void
{
$file = $mapping->getFile($obj);

if (null === $file || !($file instanceof UploadedFile)) {
if (!$file instanceof UploadedFile && !$file instanceof ReplacingFile) {
throw new \LogicException('No uploadable file found');
}

Expand Down
13 changes: 11 additions & 2 deletions src/Storage/FileSystemStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@
*/
class FileSystemStorage extends AbstractStorage
{
protected function doUpload(PropertyMapping $mapping, UploadedFile $file, ?string $dir, string $name): ?File
protected function doUpload(PropertyMapping $mapping, File $file, ?string $dir, string $name): ?File
{
$uploadDir = $mapping->getUploadDestination().\DIRECTORY_SEPARATOR.$dir;

return $file->move($uploadDir, $name);
if ($file instanceof UploadedFile) {
return $file->move($uploadDir, $name);
} else {
$targetPathname = $uploadDir.\DIRECTORY_SEPARATOR.$name;
if (!\copy($file->getPathname(), $targetPathname)) {
throw new \Exception('Could not copy file');
}

return new File($targetPathname);
}
}

protected function doRemove(PropertyMapping $mapping, ?string $dir, string $name): ?bool
Expand Down
4 changes: 2 additions & 2 deletions src/Storage/FlysystemStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use League\Flysystem\MountManager;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\PropertyMapping;
use Vich\UploaderBundle\Mapping\PropertyMappingFactory;

Expand Down Expand Up @@ -37,7 +37,7 @@ public function __construct(PropertyMappingFactory $factory, $registry)
$this->registry = $registry;
}

protected function doUpload(PropertyMapping $mapping, UploadedFile $file, ?string $dir, string $name): void
protected function doUpload(PropertyMapping $mapping, File $file, ?string $dir, string $name): void
{
$fs = $this->getFilesystem($mapping);
$path = !empty($dir) ? $dir.'/'.$name : $name;
Expand Down
4 changes: 2 additions & 2 deletions src/Storage/GaufretteStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Gaufrette\Exception\FileNotFound;
use Gaufrette\FilesystemInterface;
use Gaufrette\FilesystemMapInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\PropertyMapping;
use Vich\UploaderBundle\Mapping\PropertyMappingFactory;

Expand Down Expand Up @@ -43,7 +43,7 @@ public function __construct(PropertyMappingFactory $factory, FilesystemMapInterf
$this->protocol = $protocol;
}

protected function doUpload(PropertyMapping $mapping, UploadedFile $file, ?string $dir, string $name): void
protected function doUpload(PropertyMapping $mapping, File $file, ?string $dir, string $name): void
{
$filesystem = $this->getFilesystem($mapping);
$path = !empty($dir) ? $dir.'/'.$name : $name;
Expand Down
40 changes: 40 additions & 0 deletions tests/Storage/FileSystemStorageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,46 @@ public function testUploadedFileIsCorrectlyMoved(string $uploadDir, string $dir,
$this->storage->upload($this->object, $this->mapping);
}

/**
* @group upload
*/
public function testReplacingFileIsCorrectlyUploaded(): void
{
$file = $this->getReplacingFileMock();

$file
->method('getClientOriginalName')
->willReturn('filename.txt');
$file
->method('getPathname')
->willReturn($this->getValidUploadDir().'/test.txt');

$this->mapping
->expects(self::once())
->method('getFile')
->with($this->object)
->willReturn($file);

$this->mapping
->expects(self::once())
->method('getUploadDestination')
->willReturn($this->root->url().\DIRECTORY_SEPARATOR.'storage');

$this->mapping
->expects(self::once())
->method('getUploadName')
->with($this->object)
->willReturn('test.txt');

$this->mapping
->expects(self::once())
->method('getUploadDir')
->with($this->object)
->willReturn('vich_uploader_bundle');

$this->storage->upload($this->object, $this->mapping);
}

public function filenameWithDirectoriesDataProvider(): array
{
return [
Expand Down
3 changes: 3 additions & 0 deletions tests/Storage/StorageTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ protected function setUp(): void
'uploads' => [
'test.txt' => 'some content',
],
'storage' => [
'vich_uploader_bundle' => [],
],
]);
}

Expand Down
11 changes: 11 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPUnit\Framework\TestCase as BaseTestCase;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\String\Slugger\AsciiSlugger;
use Vich\UploaderBundle\FileAbstraction\ReplacingFile;
use Vich\UploaderBundle\Mapping\PropertyMapping;
use Vich\UploaderBundle\Mapping\PropertyMappingFactory;
use Vich\UploaderBundle\Util\Transliterator;
Expand All @@ -21,6 +22,16 @@ protected function getUploadedFileMock(): UploadedFile
->getMock();
}

/**
* @return ReplacingFile&\PHPUnit\Framework\MockObject\MockObject
*/
protected function getReplacingFileMock(): ReplacingFile
{
return $this->getMockBuilder(ReplacingFile::class)
->setConstructorArgs(['lala', false])
->getMock();
}

/**
* @return PropertyMapping&\PHPUnit\Framework\MockObject\MockObject
*/
Expand Down
Loading

0 comments on commit 3b64675

Please sign in to comment.