refactor
This commit is contained in:
parent
367858c97c
commit
6b1a385292
13 changed files with 191 additions and 80 deletions
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Icefox\DTO;
|
namespace Icefox\DTO;
|
||||||
|
|
||||||
use Icefox\DTO\Support\RuleFactory;
|
use Icefox\DTO\RuleFactory;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use phpDocumentor\Reflection\PseudoTypes\Generic;
|
use phpDocumentor\Reflection\PseudoTypes\Generic;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Icefox\DTO\Factories;
|
namespace Icefox\DTO;
|
||||||
|
|
||||||
use Icefox\DTO\ParameterMeta;
|
use Icefox\DTO\RuleFactory;
|
||||||
use Icefox\DTO\Support\RuleFactory;
|
|
||||||
use phpDocumentor\Reflection\PseudoTypes\Generic;
|
use phpDocumentor\Reflection\PseudoTypes\Generic;
|
||||||
|
|
||||||
class CollectionFactory
|
class CustomHandlers
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return array<string,string[]>
|
* @return array<string,string[]>
|
||||||
*/
|
*/
|
||||||
public static function rules(ParameterMeta $parameter, RuleFactory $factory): array
|
public static function CollectionRules(ParameterMeta $parameter, RuleFactory $factory): array
|
||||||
{
|
{
|
||||||
if (is_null($parameter->tag)) {
|
if (is_null($parameter->tag)) {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -36,3 +35,4 @@ class CollectionFactory
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4,14 +4,14 @@ namespace Icefox\DTO;
|
||||||
|
|
||||||
use Icefox\DTO\Attributes\FromInput;
|
use Icefox\DTO\Attributes\FromInput;
|
||||||
use Icefox\DTO\Attributes\FromRouteParameter;
|
use Icefox\DTO\Attributes\FromRouteParameter;
|
||||||
use Icefox\DTO\Support\RuleFactory;
|
use Icefox\DTO\RuleFactory;
|
||||||
use Icefox\DTO\Support\ValueFactory;
|
use Icefox\DTO\ValueFactory;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Route;
|
use Illuminate\Routing\Route;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\Validation\Validator;
|
use Illuminate\Validation\Validator;
|
||||||
use ReflectionClass;
|
|
||||||
|
|
||||||
class DataObjectFactory
|
class DataObjectFactory
|
||||||
{
|
{
|
||||||
|
|
@ -31,7 +31,7 @@ class DataObjectFactory
|
||||||
*/
|
*/
|
||||||
public static function fromArray(string $class, array $input, array $routeParameters): ?object
|
public static function fromArray(string $class, array $input, array $routeParameters): ?object
|
||||||
{
|
{
|
||||||
$logger = new Log();
|
$logger = Log::channel('dto');
|
||||||
$parameters = ReflectionHelper::getParametersMeta($class);
|
$parameters = ReflectionHelper::getParametersMeta($class);
|
||||||
foreach ($parameters as $parameter) {
|
foreach ($parameters as $parameter) {
|
||||||
$parameterName = $parameter->reflection->getName();
|
$parameterName = $parameter->reflection->getName();
|
||||||
|
|
@ -60,7 +60,7 @@ class DataObjectFactory
|
||||||
// continue;
|
// continue;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
$logger->inputRaw($input);
|
$logger->debug('input', $input);
|
||||||
|
|
||||||
$rules = (new RuleFactory($logger))->make($class);
|
$rules = (new RuleFactory($logger))->make($class);
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ class DataObjectFactory
|
||||||
: App::makeWith(Validator::class, ['data' => $input, 'rules' => $rules]);
|
: App::makeWith(Validator::class, ['data' => $input, 'rules' => $rules]);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
$logger->validationErrors($validator->errors()->toArray());
|
$logger->warning('validation error', $validator->errors()->toArray());
|
||||||
if (method_exists($class, 'fails')) {
|
if (method_exists($class, 'fails')) {
|
||||||
return App::call("$class::fails", ['validator' => $validator ]);
|
return App::call("$class::fails", ['validator' => $validator ]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Icefox\DTO;
|
|
||||||
|
|
||||||
use Icefox\DTO\Attributes\FromInput;
|
|
||||||
use Icefox\DTO\Attributes\FromRouteParameter;
|
|
||||||
|
|
||||||
class InputFactory
|
|
||||||
{
|
|
||||||
public function __construct(public readonly Log $log) {}
|
|
||||||
|
|
||||||
public function make(string $class): array
|
|
||||||
{
|
|
||||||
$map = [];
|
|
||||||
$parameters = ReflectionHelper::getParametersMeta($class);
|
|
||||||
foreach ($parameters as $parameter) {
|
|
||||||
$name = $parameter->reflection->getName();
|
|
||||||
|
|
||||||
foreach ($parameter->reflection->getAttributes(FromRouteParameter::class) as $attr) {
|
|
||||||
$map[$name][] = 'route_' . $attr->newInstance()->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($parameter->reflection->getAttributes(FromInput::class) as $attr) {
|
|
||||||
$map[$name][] = $attr->newInstance()->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
$map[$name][] = $name;
|
|
||||||
}
|
|
||||||
return $map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static self $_instance;
|
|
||||||
|
|
||||||
public static function instance(): self
|
|
||||||
{
|
|
||||||
if (empty(self::$_instance)) {
|
|
||||||
self::$_instance = new self(new Log());
|
|
||||||
}
|
|
||||||
return self::$_instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -45,4 +45,3 @@ class ReflectionHelper
|
||||||
return self::$cache[$class];
|
return self::$cache[$class];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Icefox\DTO\Support;
|
namespace Icefox\DTO;
|
||||||
|
|
||||||
use Icefox\DTO\Attributes\Flat;
|
use Icefox\DTO\Attributes\Flat;
|
||||||
use Icefox\DTO\Attributes\OverwriteRules;
|
use Icefox\DTO\Attributes\OverwriteRules;
|
||||||
use Icefox\DTO\Config;
|
use Icefox\DTO\Config;
|
||||||
use Icefox\DTO\ParameterMeta;
|
use Icefox\DTO\ParameterMeta;
|
||||||
use Icefox\DTO\ReflectionHelper;
|
use Icefox\DTO\ReflectionHelper;
|
||||||
|
use Icefox\DTO\RuleFactory;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
@ -2,16 +2,14 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Icefox\DTO\Support;
|
namespace Icefox\DTO;
|
||||||
|
|
||||||
use Icefox\DTO\Attributes\CastWith;
|
use Icefox\DTO\Attributes\CastWith;
|
||||||
use Icefox\DTO\Attributes\Flat;
|
use Icefox\DTO\Attributes\Flat;
|
||||||
use Icefox\DTO\Config;
|
use Icefox\DTO\Config;
|
||||||
use Icefox\DTO\ParameterMeta;
|
|
||||||
use Icefox\DTO\ReflectionHelper;
|
use Icefox\DTO\ReflectionHelper;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use ReflectionNamedType;
|
use ReflectionNamedType;
|
||||||
use ReflectionParameter;
|
|
||||||
use phpDocumentor\Reflection\DocBlock\Tags\Param;
|
use phpDocumentor\Reflection\DocBlock\Tags\Param;
|
||||||
use phpDocumentor\Reflection\PseudoTypes\Generic;
|
use phpDocumentor\Reflection\PseudoTypes\Generic;
|
||||||
use phpDocumentor\Reflection\Type;
|
use phpDocumentor\Reflection\Type;
|
||||||
|
|
@ -102,6 +100,9 @@ class ValueFactory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string,mixed> $input
|
||||||
|
*/
|
||||||
public static function make(string $class, array $input): object
|
public static function make(string $class, array $input): object
|
||||||
{
|
{
|
||||||
$parameters = ReflectionHelper::getParametersMeta($class);
|
$parameters = ReflectionHelper::getParametersMeta($class);
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
use Icefox\DTO\Log;
|
use Icefox\DTO\Log;
|
||||||
use Icefox\DTO\Support\RuleFactory;
|
use Icefox\DTO\RuleFactory;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Tests\Classes\ArrayDataObject;
|
use Tests\Classes\ArrayDataObject;
|
||||||
use Tests\Classes\CollectionDataObject;
|
use Tests\Classes\CollectionDataObject;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace Tests\Rules;
|
||||||
|
|
||||||
use Icefox\DTO\Log;
|
use Icefox\DTO\Log;
|
||||||
use Icefox\DTO\ReflectionHelper;
|
use Icefox\DTO\ReflectionHelper;
|
||||||
use Icefox\DTO\Support\RuleFactory;
|
use Icefox\DTO\RuleFactory;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Tests\Rules\WithEmptyOverwriteRules;
|
use Tests\Rules\WithEmptyOverwriteRules;
|
||||||
use Tests\Rules\WithMergedRules;
|
use Tests\Rules\WithMergedRules;
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,8 @@ declare(strict_types=1);
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
use Icefox\DTO\Attributes\Flat;
|
use Icefox\DTO\Attributes\Flat;
|
||||||
use Icefox\DTO\Support\RuleFactory;
|
use Icefox\DTO\RuleFactory;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
|
|
||||||
readonly class BasicPrimitives
|
readonly class BasicPrimitives
|
||||||
{
|
{
|
||||||
|
|
|
||||||
165
tests/ValuesTest.php
Normal file
165
tests/ValuesTest.php
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
use Icefox\DTO\Attributes\Flat;
|
||||||
|
use Icefox\DTO\RuleFactory;
|
||||||
|
use Icefox\DTO\ValueFactory;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
readonly class BasicPrimitives
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $text,
|
||||||
|
public int $number,
|
||||||
|
public bool $flag,
|
||||||
|
public ?array $items,
|
||||||
|
public float $floating = 0.0,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('required rules', function () {
|
||||||
|
$object = ValueFactory::make(BasicPrimitives::class, [
|
||||||
|
'text' => 'abc',
|
||||||
|
'number' => 42,
|
||||||
|
'flag' => false,
|
||||||
|
'items' => ['a', 2, true],
|
||||||
|
'floating' => 4.2,
|
||||||
|
]);
|
||||||
|
expect($object->text)->toBe('abc');
|
||||||
|
expect($object->number)->toBe(42);
|
||||||
|
expect($object->flag)->toBe(false);
|
||||||
|
expect($object->items)->toBe(['a', 2, true]);
|
||||||
|
expect($object->floating)->toEqualWithDelta(4.2, 0.000001);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly class AnnotatedArray
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<int,float> $items
|
||||||
|
*/
|
||||||
|
public function __construct(public array $items) {}
|
||||||
|
}
|
||||||
|
test('annotated array', function () {
|
||||||
|
expect(RuleFactory::instance()->make(AnnotatedArray::class))->toBe([
|
||||||
|
'items' => ['required', 'array'],
|
||||||
|
'items.*' => ['required', 'numeric'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly class AnnotatedArrayNullableValue
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<?float> $items
|
||||||
|
*/
|
||||||
|
public function __construct(public array $items) {}
|
||||||
|
}
|
||||||
|
test('annotated array with nullable items', function () {
|
||||||
|
expect(RuleFactory::instance()->make(AnnotatedArrayNullableValue::class))->toBe([
|
||||||
|
'items' => ['required', 'array'],
|
||||||
|
'items.*' => ['nullable', 'numeric'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly class PlainLeaf
|
||||||
|
{
|
||||||
|
public function __construct(public string $name) {}
|
||||||
|
}
|
||||||
|
readonly class PlainRoot
|
||||||
|
{
|
||||||
|
public function __construct(public int $value, public PlainLeaf $leaf) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('plain nesting', function () {
|
||||||
|
expect(RuleFactory::instance()->make(PlainRoot::class))->toBe([
|
||||||
|
'value' => ['required', 'numeric'],
|
||||||
|
'leaf' => ['required'],
|
||||||
|
'leaf.name' => ['required'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
readonly class AnnotatedArrayItem
|
||||||
|
{
|
||||||
|
public function __construct(public int $value) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly class AnnotatedArrayObject
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param ?array<AnnotatedArrayItem> $items
|
||||||
|
*/
|
||||||
|
public function __construct(public ?array $items) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('annotated array with object', function () {
|
||||||
|
expect(RuleFactory::instance()->make(AnnotatedArrayObject::class))->toBe([
|
||||||
|
'items' => ['nullable', 'array'],
|
||||||
|
'items.*' => ['required'],
|
||||||
|
'items.*.value' => ['required', 'numeric'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly class FlattenedLeaf
|
||||||
|
{
|
||||||
|
public function __construct(public ?bool $flag) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly class NotFlattenedLeaf
|
||||||
|
{
|
||||||
|
public function __construct(public string $description) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly class FlattenedNode
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $id,
|
||||||
|
public NotFlattenedLeaf $leaf,
|
||||||
|
#[Flat]
|
||||||
|
public FlattenedLeaf $squish,
|
||||||
|
public int $level = 1,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly class FlattenedRoot
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $value,
|
||||||
|
#[Flat]
|
||||||
|
public FlattenedNode $node,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('flattened basic', function () {
|
||||||
|
expect(RuleFactory::instance()->make(FlattenedRoot::class))->toBe([
|
||||||
|
'value' => ['required', 'numeric'],
|
||||||
|
'id' => ['required' ],
|
||||||
|
'leaf' => ['required'],
|
||||||
|
'leaf.description' => ['required'],
|
||||||
|
'flag' => ['nullable', 'boolean'],
|
||||||
|
'level' => ['sometimes', 'numeric'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly class AnnotatedCollectionItem
|
||||||
|
{
|
||||||
|
public function __construct(public int $value) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly class AnnotatedCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Collection<AnnotatedCollectionItem> $group
|
||||||
|
*/
|
||||||
|
public function __construct(public Collection $group) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('annotated collection', function () {
|
||||||
|
expect(RuleFactory::instance()->make(AnnotatedCollection::class))->toBe([
|
||||||
|
'group' => ['required', 'array'],
|
||||||
|
'group.*' => ['required'],
|
||||||
|
'group.*.value' => ['required', 'numeric'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Icefox\DTO\CustomHandlers;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'rules' => [
|
||||||
|
Collection::class => CustomHandlers::CollectionRules(...),
|
||||||
|
],
|
||||||
'logging' => [
|
'logging' => [
|
||||||
'channel' => 'dto',
|
'channel' => 'dto',
|
||||||
'context' => [
|
'context' => [
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
|
||||||
bootstrap="vendor/autoload.php"
|
|
||||||
colors="true"
|
|
||||||
>
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="root">
|
|
||||||
<directory>tests</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
<source>
|
|
||||||
<include>
|
|
||||||
<directory>app</directory>
|
|
||||||
</include>
|
|
||||||
</source>
|
|
||||||
</phpunit>
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue