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,60 @@ declare(strict_types=1);
namespace Icefox\DTO;
use Icefox\DTO\Support\RuleFactory;
use Illuminate\Support\Collection;
use phpDocumentor\Reflection\PseudoTypes\Generic;
class Config
{
public static function getCaster(string $className): ?callable
/**
* @param class-string $class
**/
public static function getCaster(string $class): ?callable
{
return config('dto.cast.' . $className, null);
return config('dto.cast.' . $class, null);
}
public static function getRules(string $className): ?callable
/**
* @param class-string $class
**/
public static function getRules(string $class): ?callable
{
return config('dto.rules.' . $className, null);
if ($userDefined = config('dto.rules.' . $class, null)) {
return $userDefined;
}
return match ($class) {
Collection::class => static::rulesIlluminateCollection(...),
default => null,
};
}
/**
* @return array<string,string[]>
*/
private static function rulesIlluminateCollection(ParameterMeta $parameter, RuleFactory $factory): array
{
if (is_null($parameter->tag)) {
return [];
}
$type = $parameter->tag->getType();
if (!$type instanceof Generic) {
return [];
}
$subtypes = $type->getTypes();
if (count($subtypes) == 0) {
return ['' => ['array']];
}
$subtype = count($subtypes) == 1 ? $subtypes[0] : $subtypes[1];
return $factory->mergeRules(
['' => ['array']],
$factory->getRulesFromDocBlock($subtype, '.*'),
);
}
}

View file

@ -4,40 +4,13 @@ declare(strict_types=1);
namespace Icefox\DTO;
use Icefox\DTO\Attributes\FromInput;
use Icefox\DTO\Attributes\CastWith;
use Icefox\DTO\Attributes\FromRouteParameter;
use Icefox\DTO\Attributes\OverwriteRules;
use Icefox\DTO\Support\RuleFactory;
use Icefox\DTO\Support\ValueFactory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Validator;
use ReflectionClass;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
trait DataObject
{
public static function fromRequest(Request $request): mixed
{
$reflection = new ReflectionClass(static::class);
$constructor = $reflection->getConstructor();
$input = [];
foreach ($constructor->getParameters() as $parameter) {
$parameterName = $parameter->getName();
foreach ($parameter->getAttributes(FromRouteParameter::class) as $attr) {
$name = $attr->newInstance()->name;
if ($value = $request->input($name, null)) {
$input[$parameterName] = $value;
continue 2;
}
}
}
return static::fromArray($input);
return DataObjectFactory::fromRequest(static::class, $request);
}
/**
@ -45,73 +18,6 @@ trait DataObject
*/
public static function fromArray(array $input): ?static
{
$logger = new Log();
$parameters = ReflectionHelper::getParametersMeta(static::class);
foreach ($parameters as $parameter) {
$parameterName = $parameter->reflection->getName();
foreach ($parameter->reflection->getAttributes(FromInput::class) as $attr) {
if ($value = $input[$attr->newInstance()->name] ?? null) {
$input[$parameterName] = $value;
continue 2;
}
}
if ($value = $input[$parameterName] ?? null) {
$input[$parameterName] = $value;
continue;
}
if ($parameter->reflection->isDefaultValueAvailable()) {
$input[$parameterName] = $parameter->reflection->getDefaultValue();
continue;
}
}
$logger->inputRaw($input);
$rules = (new RuleFactory($logger))->make(static::class);
$validator = static::withValidator($input, $rules);
if ($validator->fails()) {
$logger->validationErrors($validator->errors()->toArray());
return static::fails($validator);
}
$mappedInput = [];
foreach ($parameters as $parameter) {
$parameterName = $parameter->reflection->getName();
if ($castWith = array_first($parameter->reflection->getAttributes(CastWith::class))) {
$value = App::call(
[App::make($castWith->newInstance()->class), 'cast'],
['value' => $validator->getValue($parameterName)],
);
$mappedInput[$parameterName] = $value;
continue;
}
$mappedInput[$parameterName] = ValueFactory::resolveValue(
$validator->getValue($parameterName),
$parameter->tag instanceof Param ? $parameter->tag->getType() : null,
$parameter->reflection,
);
}
$logger->input($mappedInput);
return App::make(static::class, $mappedInput);
}
public static function fails(Validator $validator): ?static
{
throw new ValidationException($validator);
}
/**
* @param array<string,mixed> $data
* @param array<string,array<int, string|Rule>> $rules
*/
public static function withValidator(array $data, array $rules): Validator
{
return App::makeWith(Validator::class, ['data' => $data, 'rules' => $rules]);
return DataObjectFactory::fromArray(static::class, $input);
}
}

81
src/DataObjectFactory.php Normal file
View file

@ -0,0 +1,81 @@
<?php
namespace Icefox\DTO;
use Icefox\DTO\Attributes\FromInput;
use Icefox\DTO\Attributes\FromRouteParameter;
use Icefox\DTO\Support\RuleFactory;
use Icefox\DTO\Support\ValueFactory;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\App;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Validator;
use ReflectionClass;
class DataObjectFactory
{
/**
* @param class-string $class
*/
public static function fromRequest(string $class, Request $request): ?object
{
$routeParameters = $request->route() instanceof Route ? $request->route()->parameters() : [];
return static::fromArray($class, $request->input(), $routeParameters);
}
/**
* @param class-string $class
* @param array<string,mixed> $input
* @param array<string,mixed> $routeParameters
*/
public static function fromArray(string $class, array $input, array $routeParameters): ?object
{
$logger = new Log();
$parameters = ReflectionHelper::getParametersMeta($class);
foreach ($parameters as $parameter) {
$parameterName = $parameter->reflection->getName();
foreach ($parameter->reflection->getAttributes(FromRouteParameter::class) as $fromRouteParameter) {
if ($value = $routeParameters[$fromRouteParameter->newInstance()->name] ?? null) {
$input[$parameterName] = $value;
continue 2;
}
}
foreach ($parameter->reflection->getAttributes(FromInput::class) as $attr) {
if ($value = $input[$attr->newInstance()->name] ?? null) {
$input[$parameterName] = $value;
continue 2;
}
}
if ($value = $input[$parameterName] ?? null) {
$input[$parameterName] = $value;
continue;
}
// if ($parameter->reflection->isDefaultValueAvailable()) {
// $input[$parameterName] = $parameter->reflection->getDefaultValue();
// continue;
// }
}
$logger->inputRaw($input);
$rules = (new RuleFactory($logger))->make($class);
$validator = method_exists($class, 'withValidator')
? App::call("$class::withValidator", ['data' => $input, 'rules' => $rules])
: App::makeWith(Validator::class, ['data' => $input, 'rules' => $rules]);
if ($validator->fails()) {
$logger->validationErrors($validator->errors()->toArray());
if (method_exists($class, 'fails')) {
return App::call("$class::fails", ['validator' => $validator ]);
}
throw new ValidationException($validator);
}
return ValueFactory::make($class, $validator->validated());
}
}

View file

@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
namespace Icefox\DTO;
use Icefox\DTO\Support\DataObjectRegistrar;
use Illuminate\Support\ServiceProvider;
class DataObjectServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
$this->mergeConfigFrom(__DIR__ . '/config/dto.php', 'dto');
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}

View file

@ -2,24 +2,22 @@
namespace Icefox\DTO\Factories;
use Icefox\DTO\ParameterMeta;
use Icefox\DTO\Support\RuleFactory;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
use ReflectionParameter;
use phpDocumentor\Reflection\PseudoTypes\Generic;
use phpDocumentor\Reflection\Type;
class CollectionFactory
{
/**
* @return array<string,string|Rule[]>
* @return array<string,string[]>
*/
public static function rules(ReflectionParameter $parameter, ?Type $type): array
public static function rules(ParameterMeta $parameter, RuleFactory $factory): array
{
if (is_null($type)) {
if (is_null($parameter->tag)) {
return [];
}
$type = $parameter->tag->getType();
if (!$type instanceof Generic) {
return [];
}
@ -27,14 +25,14 @@ class CollectionFactory
$subtypes = $type->getTypes();
if (count($subtypes) == 0) {
return [];
return ['' => ['array']];
}
$subtype = count($subtypes) == 1 ? $subtypes[0] : $subtypes[1];
return array_merge(
return $factory->mergeRules(
['' => ['array']],
RuleFactory::getRulesFromDocBlock($subtype, '.*'),
$factory->getRulesFromDocBlock($subtype, '.*'),
);
}
}

41
src/InputFactory.php Normal file
View file

@ -0,0 +1,41 @@
<?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;
}
}

View file

@ -1,67 +0,0 @@
<?php
namespace Icefox\DTO;
use Illuminate\Support\Facades\App;
use Illuminate\Validation\Rule;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
readonly class Log
{
public LoggerInterface $logger;
public function __construct()
{
$raw = config('dto.log.logger');
if (is_callable($raw)) {
$this->logger = App::call($raw);
return;
}
if (is_object($raw)) {
$this->logger = $raw;
return;
}
if (is_string($raw) && class_exists($raw)) {
$this->logger = App::make($raw);
return;
}
$this->logger = new NullLogger();
}
/**
* @param array<string,array<int, string|Rule>> $rules
*/
public function rules(array $rules): void
{
$level = config('dto.log.rules') ?? LogLevel::DEBUG;
$this->logger->log($level, print_r($rules, true));
}
/**
* @param array<string,mixed> $input
*/
public function input(array $input): void
{
$level = config('dto.log.input') ?? LogLevel::DEBUG;
$this->logger->log($level, print_r($input, true));
}
/**
* @param array<string, null|int|float|string|array> $input
*/
public function inputRaw(array $input): void
{
$level = config('dto.log.raw_input') ?? LogLevel::DEBUG;
$this->logger->log($level, print_r($input, true));
}
/**
* @param array<string, array<int, string>> $errors
*/
public function validationErrors(array $errors): void
{
$level = config('dto.log.validation_errors') ?? LogLevel::INFO;
$this->logger->log($level, print_r($errors, true));
}
}

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;
}
}

View file

@ -5,10 +5,14 @@ declare(strict_types=1);
namespace Icefox\DTO\Support;
use Icefox\DTO\Attributes\CastWith;
use Icefox\DTO\Attributes\Flat;
use Icefox\DTO\Config;
use Icefox\DTO\ParameterMeta;
use Icefox\DTO\ReflectionHelper;
use Illuminate\Support\Facades\App;
use ReflectionNamedType;
use ReflectionParameter;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\PseudoTypes\Generic;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\AbstractList;
@ -35,31 +39,37 @@ class ValueFactory
return App::makeWith($className, $rawValue);
}
public static function resolveTypedValue(mixed $rawValue, Type $type): mixed
public static function resolveAnnotatedValue(Type $type, mixed $rawValue): mixed
{
if ($type instanceof Nullable) {
$type = $type->getActualType();
}
if ($type instanceof Generic) {
$types = $type->getTypes();
$innerType = count($types) === 2 ? $types[1] : $types[0];
$result = [];
foreach ($rawValue as $key => $value) {
$result[$key] = self::resolveTypedValue($value, $innerType);
}
return new ($type->getFqsen()->__toString())($result);
}
if ($type instanceof AbstractList) {
$innerType = $type->getValueType();
$result = [];
foreach ($rawValue as $key => $value) {
$result[$key] = self::resolveTypedValue($value, $innerType);
$result[$key] = self::resolveAnnotatedValue($innerType, $value);
}
return $result;
}
if ($type instanceof Generic) {
$types = $type->getTypes();
$innerType = count($types) === 2 ? $types[1] : $types[0];
if (is_array($rawValue)) {
$innerValues = [];
foreach ($rawValue as $key => $value) {
$innerValues[$key] = self::resolveAnnotatedValue($innerType, $value);
}
return array_key_exists(0, $rawValue)
? new ($type->getFqsen()->__toString())($innerValues)
: App::makeWith($type->getFqsen()->__toString(), $innerValues);
}
$value = self::resolveAnnotatedValue($innerType, $rawValue);
return new ($type->getFqsen()->__toString())($value);
}
if ($type instanceof Boolean) {
return boolval($rawValue);
}
@ -73,44 +83,70 @@ class ValueFactory
}
if ($type instanceof Object_) {
return self::constructObject($type->getFqsen()->__toString(), $rawValue);
return self::make($type->getFqsen()->__toString(), $rawValue);
}
return $rawValue;
}
public static function resolveValue(mixed $rawValue, ?Type $type, ReflectionParameter $reflection): mixed
public static function resolveDeclaredTypeValue(ReflectionNamedType $parameter, mixed $rawValue): mixed
{
if ($reflection->allowsNull() && is_null($rawValue)) {
return null;
}
return match ($parameter->getName()) {
'string' => $rawValue,
'bool' => boolval($rawValue),
'int' => intval($rawValue),
'float' => floatval($rawValue),
'array' => $rawValue,
default => self::make($parameter->getName(), $rawValue),
$castWithAttrs = $reflection->getAttributes(CastWith::class);
if ($withCast = $reflection->getAttributes(CastWith::class)[0] ?? null) {
$caster = $withCast->newInstance()->class;
return App::call("$caster@cast", ['value' => $rawValue]);
}
};
}
if (!is_null($type)) {
return self::resolveTypedValue($rawValue, $type);
}
public static function make(string $class, array $input): object
{
$parameters = ReflectionHelper::getParametersMeta($class);
$arguments = [];
foreach ($parameters as $parameter) {
$name = $parameter->reflection->getName();
$reflectedType = $reflection->getType();
if ($reflectedType instanceof ReflectionNamedType && $name = $reflectedType->getName()) {
if ($caster = Config::getCaster($name)) {
return App::call($caster, ['value' => $rawValue]);
$parameterArgs = empty($parameter->reflection->getAttributes(Flat::class)) ? ($input[$name] ?? null) : $input;
if (is_null($parameterArgs)) {
if ($parameter->reflection->allowsNull()) {
$arguments[$name] = null;
}
continue;
}
return match ($name) {
'string' => $rawValue,
'bool' => boolval($rawValue),
'int' => intval($rawValue),
'float' => floatval($rawValue),
'array' => $rawValue,
default => self::constructObject($name, $rawValue),
};
}
if ($caster = $parameter->reflection->getAttributes(CastWith::class)[0] ?? null) {
$caster = $caster->newInstance()->class;
$arguments[$name] = App::call("$caster@cast", ['data' => $parameterArgs]);
continue;
}
return $rawValue;
$parameterClass = $parameter->reflection->getClass()?->getName();
$type = $parameter->tag?->getType();
if (empty($parameterClass) && $type instanceof Object_) {
$parameterClass = $type->getFqsen();
}
if (!empty($parameterClass) && $caster = config('dto.cast.' . $parameterClass, null)) {
$arguments[$name] = App::call($caster, ['data' => $parameterArgs]);
continue;
}
if ($parameter->tag instanceof Param) {
$arguments[$name] = self::resolveAnnotatedValue($type, $parameterArgs);
continue;
}
if ($parameter->reflection->getType() instanceof ReflectionNamedType) {
$arguments[$name] = self::resolveDeclaredTypeValue($parameter->reflection->getType(), $parameterArgs);
continue;
}
$arguments[$name] = $parameterArgs;
}
return App::makeWith($class, $arguments);
}
}

View file

@ -1,21 +0,0 @@
<?php
use Icefox\DTO\Factories\CollectionFactory;
use Illuminate\Support\Collection;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
return [
'cast' => [],
'rules' => [
Collection::class => CollectionFactory::rules(...),
],
'log' => [
'logger' => NullLogger::class,
'internal' => LogLevel::WARNING,
'rules' => LogLevel::DEBUG,
'input' => LogLevel::DEBUG,
'raw_input' => LogLevel::DEBUG,
'validation_errors' => LogLevel::INFO,
],
];