Skip to content
This repository was archived by the owner on Jan 7, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Features/Data/Attributes/Validation/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
namespace Kellton\Tools\Features\Data\Attributes\Validation;

use Attribute;
use Illuminate\Contracts\Validation\Rule as ValidationRule;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Rule extends ValidationAttribute
{
/**
* Rule constructor.
*
* @param string $rule
* @param string|ValidationRule $rule
* @param string|null $message
*/
public function __construct(public readonly string $rule)
public function __construct(public readonly string|ValidationRule $rule, public readonly ?string $message = null)
{
}
}
2 changes: 1 addition & 1 deletion src/Features/Data/Data.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ public static function getValidationRules(): array
/** @var RuleService $service */
$service = app(RuleService::class);

return $service->getByClass(static::class)->toArray();
return $service->getByClass(static::class)->transform(fn ($value) => $value->get('rules'))->toArray();
}
}
36 changes: 33 additions & 3 deletions src/Features/Data/Services/DataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function create(string $class, mixed ...$payload): Data
$payload = array_merge($payload->route()->parameters, $payload->all());
}

if($payload === null) {
if ($payload === null) {
$payload = [];
}

Expand Down Expand Up @@ -91,15 +91,45 @@ public function create(string $class, mixed ...$payload): Data
*/
private function validate(Definition $definition, Collection $payload): Collection
{
$rules = $this->ruleService->get($definition);
$definitions = $this->ruleService->get($definition);

$validator = Validator::make($payload->toArray(), $rules->toArray());
$rules = $definitions->map(fn (Collection $value) => $value->get('rules'));
$messages = $definitions->map(fn (Collection $value) => $value->get('messages'));

$validator = Validator::make(
$payload->toArray(),
$rules->toArray(),
$this->parseValidationErrorMessages($messages)
);

$validator->validate();

return collect_all($validator->validated());
}

/**
* Parse validation error messages to array.
*
* @param Collection $validationErrorMessages
*
* @return array
*/
private function parseValidationErrorMessages(Collection $validationErrorMessages): array
{
$validationMessages = [];

$validationErrorMessages->each(function (Collection $errorMessages, $fieldName) use (&$validationMessages) {
if ($errorMessages->isNotEmpty()) {
$errorMessages->each(function ($errorMessage, $ruleName) use ($fieldName, &$validationMessages) {
$keyName = $fieldName . '.' . explode(':', $ruleName)[0];
$validationMessages[$keyName] = $errorMessage;
});
}
});

return $validationMessages;
}

/**
* Resolve map properties.
*
Expand Down
33 changes: 22 additions & 11 deletions src/Features/Data/Services/RuleService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use BackedEnum;
use Carbon\Carbon;
use Kellton\Tools\Features\Data\Attributes\Validation\Rule;
use Kellton\Tools\Features\Data\Attributes\Validation\ValidationAttribute;
use Kellton\Tools\Features\Data\Definition;
use Kellton\Tools\Features\Data\Exceptions\MissingConstructor;
Expand Down Expand Up @@ -39,7 +40,9 @@ public function __construct(public readonly DefinitionService $definitionService
*/
public function get(Definition $definition): Collection
{
return $definition->properties->mapWithKeys(fn (Property $property) => $this->resolve($property)->all());
$data = $definition->properties->mapWithKeys(fn (Property $property) => $this->resolve($property)->all());

return $data;
}

/**
Expand Down Expand Up @@ -77,7 +80,7 @@ private function resolve(Property $property): Collection
return $this->getNestedRules($property, $propertyName);
}

return collect([$propertyName => $this->getRulesForProperty($property)]);
return collect([$propertyName => $this->getDataForProperty($property)]);
}

/**
Expand All @@ -87,9 +90,14 @@ private function resolve(Property $property): Collection
*
* @return Collection
*/
protected function getRulesForProperty(Property $property): Collection
protected function getDataForProperty(Property $property): Collection
{
$rules = collect();
$data = collect([
'rules' => collect(),
'messages' => collect(),
]);

$rules = $data->get('rules');

if ($property->isNullable) {
$rules->add('nullable');
Expand All @@ -108,9 +116,9 @@ protected function getRulesForProperty(Property $property): Collection
}

$this->resolveTypes($property, $rules);
$this->resolveAttributeRules($property, $rules);
$this->resolveAttributeRules($property, $data);

return $rules;
return $data;
}

/**
Expand All @@ -132,7 +140,7 @@ protected function getNestedRules(Property $property, string $propertyName): Col
default => throw new TypeError()
};

$parentRules = $this->getRulesForProperty($property);
$parentRules = $this->getDataForProperty($property);

$definition = $this->definitionService->get($property->dataClass);
$rules = $this->get($definition);
Expand Down Expand Up @@ -179,17 +187,20 @@ private function resolveTypes(Property $property, Collection $rules): void
* Resolve rules for the attributes.
*
* @param Property $property
* @param Collection $rules
* @param Collection $data
*
* @return void
*/
private function resolveAttributeRules(Property $property, Collection $rules): void
private function resolveAttributeRules(Property $property, Collection $data): void
{
$property
->attributes
->filter(fn (object $attribute) => is_subclass_of($attribute, ValidationAttribute::class))
->each(function (ValidationAttribute $rule) use ($rules) {
$rules->add($rule->rule);
->each(function (ValidationAttribute $rule) use ($data) {
$data->get('rules')->add($rule->rule);
if ($rule instanceof Rule && $rule->message !== null) {
$data->get('messages')->put($rule->rule, $rule->message);
}
});
}
}
17 changes: 17 additions & 0 deletions tests/Data/TestData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Kellton\Tools\Tests\Data;

use Kellton\Tools\Features\Data\Attributes\Validation\Rule;
use Kellton\Tools\Features\Data\Data;

readonly class TestData extends Data
{
public function __construct(
public string $firstName,
public string $lastName,
#[Rule('email', message: 'Wrong email address format!')]
public string $email
) {
}
}
22 changes: 14 additions & 8 deletions tests/Feature/DataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace Kellton\Tools\Tests\Feature;

use Illuminate\Validation\ValidationException;
use Kellton\Tools\Features\Data\Data;
use Kellton\Tools\Features\Data\Exceptions\MissingConstructor;
use Kellton\Tools\Tests\Data\IndexData;
use Kellton\Tools\Tests\Data\TestData;
use Kellton\Tools\Tests\TestCase;
use ReflectionException;

Expand All @@ -23,7 +25,7 @@ class DataTest extends TestCase
*/
public function testCreateShouldSucceed(): void
{
$data = new TestData('John', 'Doe');
$data = new TestData('John', 'Doe', '[email protected]');
$this->assertInstanceOf(Data::class, $data);

$validationRules = $data::getValidationRules();
Expand All @@ -47,14 +49,18 @@ public function testFiltersDataShouldSucceed(): void
$this->assertIsArray($validationRules);
$this->assertNotEmpty($validationRules);
}
}

/**
* Class TestData is used for testing readonly Data class.
*/
readonly class TestData extends Data
{
public function __construct(public string $firstName, public string $lastName)
public function testRuleMessageShouldSucceed(): void
{
try {
TestData::create([
'firstName' => 'John',
'lastName' => 'Doe',
'email' => 'john',
]);
} catch (ValidationException $e) {
$message = data_get($e->errors(), 'email.0');
$this->assertSame('Wrong email address format!', $message);
}
}
}