Skip to content

Commit

Permalink
Add FilesBag, add tests with file uploading
Browse files Browse the repository at this point in the history
  • Loading branch information
msmakouz committed May 26, 2022
1 parent 7c6e557 commit 68c7db3
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 47 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ use Spiral\Filters\Filter;
use Spiral\Filters\FilterDefinitionInterface;
use Spiral\Filters\HasFilterDefinition;
use Spiral\Validation\Laravel\FilterDefinition;
use Spiral\Validation\Laravel\Attribute\Input\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;

final class CreatePostFilter extends Filter implements HasFilterDefinition
{
Expand All @@ -68,12 +70,16 @@ final class CreatePostFilter extends Filter implements HasFilterDefinition
#[Post]
public int $sort;

#[File]
public UploadedFile $image;

public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition([
'title' => 'string|required|min:5',
'slug' => 'string|required|min:5',
'sort' => 'integer|required'
'sort' => 'integer|required',
'image' => 'required|image'
]);
}
}
Expand Down Expand Up @@ -104,12 +110,14 @@ final class CreatePostFilter extends Filter implements HasFilterDefinition
[
'title' => 'string|required|min:5',
'slug' => 'string|required|min:5',
'sort' => 'integer|required'
'sort' => 'integer|required',
'image' => 'required|image'
],
[
'title' => 'title',
'slug' => 'slug',
'sort' => 'sort'
'sort' => 'sort',
'image' => 'symfony-file:image'
]
);
}
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
},
"require-dev": {
"mockery/mockery": "^1.5",
"spiral/framework": "3.0.x-dev",
"spiral/framework": "^3.0",
"spiral/http": "^3.0",
"phpunit/phpunit": "^9.5",
"spiral/testing": "^2.0",
"vimeo/psalm": "^4.9"
Expand Down
13 changes: 4 additions & 9 deletions src/Attribute/Input/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

namespace Spiral\Validation\Laravel\Attribute\Input;

use Psr\Http\Message\UploadedFileInterface;
use Spiral\Attributes\NamedArgumentConstructor;
use Spiral\Filters\Attribute\Input\AbstractInput;
use Spiral\Filters\InputInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\UploadedFile as SymfonyUploadedFile;
use Symfony\Bridge\PsrHttpMessage\Factory\UploadedFile;

#[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor]
final class File extends AbstractInput
Expand All @@ -21,17 +20,13 @@ public function __construct(
) {
}

public function getValue(InputInterface $input, \ReflectionProperty $property): ?SymfonyUploadedFile
public function getValue(InputInterface $input, \ReflectionProperty $property): ?UploadedFile
{
/** @var UploadedFileInterface $file */
$file = $input->getValue('file', $this->getKey($property));

return new SymfonyUploadedFile(
$file, static fn(): string => sys_get_temp_dir());
return $input->getValue('symfony-file', $this->getKey($property));
}

public function getSchema(\ReflectionProperty $property): string
{
return 'file:' . $this->getKey($property);
return 'symfony-file:' . $this->getKey($property);
}
}
11 changes: 11 additions & 0 deletions src/Bootloader/ValidatorBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
use Illuminate\Translation\Translator;
use Illuminate\Validation\Factory;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Bootloader\Http\HttpBootloader;
use Spiral\Bootloader\I18nBootloader;
use Spiral\Translator\TranslatorInterface;
use Spiral\Validation\Bootloader\ValidationBootloader;
use Spiral\Validation\Laravel\FilterDefinition;
use Spiral\Validation\Laravel\Http\Request\FilesBag;
use Spiral\Validation\Laravel\LaravelValidation;
use Spiral\Validation\ValidationInterface;
use Spiral\Validation\ValidationProvider;
Expand All @@ -27,6 +29,15 @@ class ValidatorBootloader extends Bootloader
LaravelValidation::class => [self::class, 'initValidation'],
];

public function init(HttpBootloader $http): void
{
$http->addInputBag('symfonyFiles', [
'class' => FilesBag::class,
'source' => 'getUploadedFiles',
'alias' => 'symfony-file'
]);
}

public function boot(ValidationProvider $provider): void
{
$provider->register(
Expand Down
28 changes: 28 additions & 0 deletions src/Http/Request/FilesBag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Spiral\Validation\Laravel\Http\Request;

use Spiral\Http\Request\InputBag;
use Symfony\Bridge\PsrHttpMessage\Factory\UploadedFile;

final class FilesBag extends InputBag
{
public function __construct(array $data, string $prefix = '')
{
foreach ($data as $name => $file) {
$data[$name] = new UploadedFile($file, fn(): string => $this->getTemporaryPath());
}

parent::__construct($data, $prefix);
}

/**
* Gets a temporary file path.
*/
protected function getTemporaryPath(): string
{
return \tempnam(\sys_get_temp_dir(), \uniqid('symfony', true));
}
}
8 changes: 7 additions & 1 deletion tests/app/Filters/CreatePostFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use Spiral\Filters\FilterDefinitionInterface;
use Spiral\Filters\HasFilterDefinition;
use Spiral\Validation\Laravel\FilterDefinition;
use Spiral\Validation\Laravel\Attribute\Input\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;

final class CreatePostFilter extends Filter implements HasFilterDefinition
{
Expand All @@ -21,12 +23,16 @@ final class CreatePostFilter extends Filter implements HasFilterDefinition
#[Post]
public int $sort;

#[File]
public UploadedFile $image;

public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition([
'title' => 'string|required|min:5',
'slug' => 'string|required|min:5',
'sort' => 'integer|required|min:0'
'sort' => 'integer|required|min:0',
'image' => 'required|image'
]);
}
}
6 changes: 4 additions & 2 deletions tests/app/Filters/FilterWithArrayMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ public function filterDefinition(): FilterDefinitionInterface
return new FilterDefinition(
[
'username' => 'string|required',
'email' => 'email|required'
'email' => 'email|required',
'image' => 'required|image'
],
[
'username' => 'username',
'email' => 'email'
'email' => 'email',
'image' => 'symfony-file:image'
]
);
}
Expand Down
Binary file added tests/app/fixtures/sample-1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions tests/src/Functional/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Spiral\Boot\EnvironmentInterface;
use Spiral\Bootloader\Security\FiltersBootloader;
use Spiral\Core\Container;
use Spiral\Http\Bootloader\DiactorosBootloader;
use Spiral\Testing\TestableKernelInterface;
use Spiral\Validation\Bootloader\ValidationBootloader;
use Spiral\Validation\Laravel\Bootloader\ValidatorBootloader;
Expand All @@ -24,6 +25,7 @@ public function defineBootloaders(): array
{
return [
AttributesBootloader::class,
DiactorosBootloader::class,
FiltersBootloader::class,
ValidationBootloader::class,
ValidatorBootloader::class,
Expand Down
66 changes: 35 additions & 31 deletions tests/src/Functional/ValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,36 @@

namespace Spiral\Validation\Laravel\Tests\Functional;

use Nyholm\Psr7\UploadedFile;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Spiral\Filters\Exception\ValidationException;
use Spiral\Filters\InputInterface;
use Spiral\Validation\Laravel\Tests\App\Filters\CreatePostFilter;
use Spiral\Validation\Laravel\Tests\App\Filters\FilterWithArrayMapping;
use Spiral\Validation\Laravel\Tests\App\Filters\SimpleFilter;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyUploadedFile;

final class ValidationTest extends TestCase
{
/** @dataProvider requestsSuccessProvider */
public function testValidationSuccess(string $filterClass, array $data): void
public function testValidationSuccess(string $filterClass, array $data, bool $withFile = false): void
{
$this->getContainer()->bind(InputInterface::class, $this->initInputScope($data));
$this->getContainer()->bind(ServerRequestInterface::class, $this->createRequest($data, $withFile));

$filter = $this->getContainer()->get($filterClass);
$filter = $this->getContainer()->get($filterClass)->getData();

$this->assertSame($data, $filter->getData());
if ($withFile) {
$this->assertInstanceOf(SymfonyUploadedFile::class, $filter['image']);
unset($filter['image']);
}

$this->assertSame($data, $filter);
}

/** @dataProvider requestsErrorProvider */
public function testValidationError(string $filterClass, array $data): void
{
$this->getContainer()->bind(InputInterface::class, $this->initInputScope($data));
$this->getContainer()->bind(ServerRequestInterface::class, $this->createRequest($data));

$this->expectException(ValidationException::class);
$this->getContainer()->get($filterClass);
Expand All @@ -34,8 +42,8 @@ public function testValidationError(string $filterClass, array $data): void
public function requestsSuccessProvider(): \Traversable
{
yield [SimpleFilter::class, ['username' => 'foo', 'email' => '[email protected]']];
yield [FilterWithArrayMapping::class, ['username' => 'foo', 'email' => '[email protected]']];
yield [CreatePostFilter::class, ['title' => 'New post', 'slug' => 'new-post', 'sort' => 1]];
yield [FilterWithArrayMapping::class, ['username' => 'foo', 'email' => '[email protected]'], true];
yield [CreatePostFilter::class, ['title' => 'New post', 'slug' => 'new-post', 'sort' => 1], true];
}

public function requestsErrorProvider(): \Traversable
Expand All @@ -53,29 +61,25 @@ public function requestsErrorProvider(): \Traversable
yield [CreatePostFilter::class, ['title' => 'foo', 'slug' => 'foo', 'sort' => 1]];
}

private function initInputScope(array $data): InputInterface
private function createRequest(array $data, bool $withFile = false): ServerRequestInterface
{
return new class($data) implements InputInterface {

public function __construct(
private array $data
) {
}

public function withPrefix(string $prefix, bool $add = true): InputInterface
{
return $this;
}

public function getValue(string $source, string $name = null): mixed
{
return $this->data[$name] ?? null;
}

public function hasValue(string $source, string $name): bool
{
return isset($this->data);
}
};
$factory = $this->getContainer()->get(ServerRequestFactoryInterface::class);

$request = $factory->createServerRequest('POST', '/foo')->withParsedBody($data);

if ($withFile) {
$path = \dirname(__DIR__, 2) . '/app/fixtures/sample-1.jpg';

$request = $request->withUploadedFiles([
'image' => new UploadedFile(
\fopen($path, 'rb'),
\filesize($path),
0,
$path
)
]);
}

return $request;
}
}

0 comments on commit 68c7db3

Please sign in to comment.