workbench, tests

This commit is contained in:
icefox 2026-02-23 21:09:02 -03:00
parent d83a324eb0
commit 367858c97c
No known key found for this signature in database
27 changed files with 568 additions and 410 deletions

View file

@ -4,15 +4,15 @@ declare(strict_types=1);
namespace Icefox\DTO\Support;
use Icefox\DTO\Attributes\CastWith;
use Icefox\DTO\Attributes\Flat;
use Icefox\DTO\Attributes\OverwriteRules;
use Icefox\DTO\Config;
use Icefox\DTO\Log;
use Icefox\DTO\ParameterMeta;
use Icefox\DTO\ReflectionHelper;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
use Psr\Log\LoggerInterface;
use ReflectionClass;
use ReflectionNamedType;
use ReflectionUnionType;
@ -26,19 +26,19 @@ use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\Object_;
class RuleFactory
final class RuleFactory
{
/**
* @return array<string, array<string|Rule>>
*/
public static function getRulesFromDocBlock(
public function getRulesFromDocBlock(
Type $type,
string $prefix,
): array {
$rules = [];
if ($type instanceof Nullable) {
$rules[$prefix] = ['nullable'];
$rules = array_merge($rules, self::getRulesFromDocBlock($type->getActualType(), $prefix));
$type = $type->getActualType();
} else {
$rules[$prefix] = ['required'];
}
@ -47,7 +47,7 @@ class RuleFactory
$rules[$prefix][] = 'array';
$valueType = $type->getValueType();
$rules = array_merge($rules, self::getRulesFromDocBlock($valueType, $prefix . '.*'));
$rules = $this->mergeRules($rules, $this->getRulesFromDocBlock($valueType, $prefix . '.*'));
}
if ($type instanceof Boolean) {
$rules[$prefix][] = 'boolean';
@ -55,10 +55,7 @@ class RuleFactory
$rules[$prefix][] = 'numeric';
} elseif ($type instanceof Object_) {
$paramsSub = ReflectionHelper::getParametersMeta($type->getFqsen()->__toString());
$rules = array_merge(
$rules,
self::infer($paramsSub, $prefix),
);
$rules = $this->mergeRules($rules, $this->infer($paramsSub, $prefix));
}
return $rules;
}
@ -67,14 +64,14 @@ class RuleFactory
* @param array<ParameterMeta> $parameters
* @return array<string,array<int,string|Rule>>
*/
public static function infer(array $parameters, string $basePrefix): array
public function infer(array $parameters, string $basePrefix): array
{
$rules = [];
foreach ($parameters as $parameter) {
$prefix = $basePrefix
. (empty($basePrefix) ? '' : '.')
. (empty($parameter->reflection->getAttributes(Flat::class)) ? $parameter->reflection->getName() : '');
foreach (self::buildParameterRule($parameter, $prefix) as $key => $newRules) {
foreach ($this->buildParameterRule($parameter, $prefix) as $key => $newRules) {
$rules[$key] = $newRules;
}
}
@ -84,7 +81,7 @@ class RuleFactory
/**
* @return array<string, array<int, string|Rule>>
*/
public static function buildParameterRule(ParameterMeta $parameter, string $prefix): array
public function buildParameterRule(ParameterMeta $parameter, string $prefix): array
{
$type = $parameter->reflection->getType();
@ -93,12 +90,14 @@ class RuleFactory
}
$rules = [$prefix => []];
if ($parameter->reflection->isOptional()) {
$rules[$prefix][] = 'sometimes';
} elseif ($type->allowsNull()) {
$rules[$prefix][] = 'nullable';
} else {
$rules[$prefix][] = 'required';
if (!empty($prefix)) {
if ($parameter->reflection->isOptional()) {
$rules[$prefix][] = 'sometimes';
} elseif ($type->allowsNull()) {
$rules[$prefix][] = 'nullable';
} else {
$rules[$prefix][] = 'required';
}
}
if ($type instanceof ReflectionUnionType) {
@ -108,11 +107,13 @@ class RuleFactory
if ($type instanceof ReflectionNamedType && $name = $type->getName()) {
if ($globalRules = Config::getRules($name)) {
foreach ($globalRules($parameter->reflection, $parameter->tag->getType()) as $scopedPrefix => $values) {
foreach ($globalRules($parameter, $this) as $scopedPrefix => $values) {
$realPrefix = $prefix . $scopedPrefix;
$rules[$realPrefix] = array_values(array_unique(array_merge($rules[$realPrefix] ?? [], $values)));
$rules[$realPrefix] = array_merge($rules[$realPrefix] ?? [], $values);
}
return $rules;
}
if ($name === 'string') {
} elseif ($name === 'bool') {
$rules[$prefix][] = 'boolean';
@ -127,38 +128,24 @@ class RuleFactory
}
} else {
$paramsSub = ReflectionHelper::getParametersMeta($type->getName());
$rules = array_merge(
$rules,
self::infer($paramsSub, $prefix),
);
}
}
foreach ($parameter->reflection->getAttributes(CastWith::class) as $attr) {
$mapperClass = $attr->newInstance()->class;
if (method_exists($mapperClass, 'rules')) {
$subRules = App::call("$mapperClass@rules");
foreach ($subRules as $key => &$value) {
$path = empty($key) ? $prefix : ($prefix . '.' . $key);
$rules[$path] = array_values(array_unique(array_merge($rules[$path] ?? [], $value)));
}
$rules = $this->mergeRules($rules, $this->infer($paramsSub, $prefix));
}
}
if ($parameter->tag instanceof Param) {
$docblockRules = self::getRulesFromDocBlock(
$docblockRules = $this->getRulesFromDocBlock(
$parameter->tag->getType(),
$prefix,
);
foreach ($docblockRules as $key => &$values) {
$rules[$key] = array_values(array_unique(array_merge($rules[$key] ?? [], $values)));
}
$rules = $this->mergeRules($rules, $docblockRules);
}
if (empty($rules[$prefix])) {
unset($rules[$prefix]);
}
return $rules;
}
public function __construct(public Log $log) {}
public function __construct(public LoggerInterface $log) {}
/**
* @param class-string $class
@ -178,21 +165,21 @@ class RuleFactory
$rules = $customRules;
} else {
$inferredRules = RuleFactory::infer($parameters, '');
$rules = self::mergeRules($inferredRules, $customRules);
$rules = $this->mergeRules($inferredRules, $customRules);
}
$this->log->rules($rules);
$this->log->info('Constructed rules for class ' . $class, $rules);
return $rules;
}
/**
* @param array<string,array<int, string>> $inferredRules
* @param array<string,array<int, string>> $customRules
* @param array<string,array<int, string>> $first
* @param array<string,array<int, string>> $second
* @return array<string,array<int, string>>
*/
protected function mergeRules(array $inferredRules, array $customRules): array
public function mergeRules(array $first, array $second): array
{
$merged = $inferredRules;
foreach ($customRules as $key => $rules) {
$merged = $first;
foreach ($second as $key => $rules) {
if (isset($merged[$key])) {
$merged[$key] = array_values(array_unique(array_merge($merged[$key], $rules)));
} else {
@ -201,4 +188,14 @@ class RuleFactory
}
return $merged;
}
private static self $_instance;
public static function instance(?LoggerInterface $log = null): static
{
if (empty(self::$_instance)) {
static::$_instance = new self($log ?? Log::channel(config('dto.logging.channel')));
}
return static::$_instance;
}
}