Skip to content
This repository was archived by the owner on Jan 7, 2025. It is now read-only.

Commit e4a1a98

Browse files
Merge pull request #10 from Tivix/feature/rules-messages
Add possibility to add rule message
2 parents 811b6a5 + b8a0b57 commit e4a1a98

File tree

6 files changed

+91
-25
lines changed

6 files changed

+91
-25
lines changed

src/Features/Data/Attributes/Validation/Rule.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
namespace Kellton\Tools\Features\Data\Attributes\Validation;
44

55
use Attribute;
6+
use Illuminate\Contracts\Validation\Rule as ValidationRule;
67

78
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
89
final class Rule extends ValidationAttribute
910
{
1011
/**
1112
* Rule constructor.
1213
*
13-
* @param string $rule
14+
* @param string|ValidationRule $rule
15+
* @param string|null $message
1416
*/
15-
public function __construct(public readonly string $rule)
17+
public function __construct(public readonly string|ValidationRule $rule, public readonly ?string $message = null)
1618
{
1719
}
1820
}

src/Features/Data/Data.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ public static function getValidationRules(): array
4848
/** @var RuleService $service */
4949
$service = app(RuleService::class);
5050

51-
return $service->getByClass(static::class)->toArray();
51+
return $service->getByClass(static::class)->transform(fn ($value) => $value->get('rules'))->toArray();
5252
}
5353
}

src/Features/Data/Services/DataService.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function create(string $class, mixed ...$payload): Data
5757
$payload = array_merge($payload->route()->parameters, $payload->all());
5858
}
5959

60-
if($payload === null) {
60+
if ($payload === null) {
6161
$payload = [];
6262
}
6363

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

96-
$validator = Validator::make($payload->toArray(), $rules->toArray());
96+
$rules = $definitions->map(fn (Collection $value) => $value->get('rules'));
97+
$messages = $definitions->map(fn (Collection $value) => $value->get('messages'));
98+
99+
$validator = Validator::make(
100+
$payload->toArray(),
101+
$rules->toArray(),
102+
$this->parseValidationErrorMessages($messages)
103+
);
97104

98105
$validator->validate();
99106

100107
return collect_all($validator->validated());
101108
}
102109

110+
/**
111+
* Parse validation error messages to array.
112+
*
113+
* @param Collection $validationErrorMessages
114+
*
115+
* @return array
116+
*/
117+
private function parseValidationErrorMessages(Collection $validationErrorMessages): array
118+
{
119+
$validationMessages = [];
120+
121+
$validationErrorMessages->each(function (Collection $errorMessages, $fieldName) use (&$validationMessages) {
122+
if ($errorMessages->isNotEmpty()) {
123+
$errorMessages->each(function ($errorMessage, $ruleName) use ($fieldName, &$validationMessages) {
124+
$keyName = $fieldName . '.' . explode(':', $ruleName)[0];
125+
$validationMessages[$keyName] = $errorMessage;
126+
});
127+
}
128+
});
129+
130+
return $validationMessages;
131+
}
132+
103133
/**
104134
* Resolve map properties.
105135
*

src/Features/Data/Services/RuleService.php

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use BackedEnum;
66
use Carbon\Carbon;
7+
use Kellton\Tools\Features\Data\Attributes\Validation\Rule;
78
use Kellton\Tools\Features\Data\Attributes\Validation\ValidationAttribute;
89
use Kellton\Tools\Features\Data\Definition;
910
use Kellton\Tools\Features\Data\Exceptions\MissingConstructor;
@@ -39,7 +40,9 @@ public function __construct(public readonly DefinitionService $definitionService
3940
*/
4041
public function get(Definition $definition): Collection
4142
{
42-
return $definition->properties->mapWithKeys(fn (Property $property) => $this->resolve($property)->all());
43+
$data = $definition->properties->mapWithKeys(fn (Property $property) => $this->resolve($property)->all());
44+
45+
return $data;
4346
}
4447

4548
/**
@@ -77,7 +80,7 @@ private function resolve(Property $property): Collection
7780
return $this->getNestedRules($property, $propertyName);
7881
}
7982

80-
return collect([$propertyName => $this->getRulesForProperty($property)]);
83+
return collect([$propertyName => $this->getDataForProperty($property)]);
8184
}
8285

8386
/**
@@ -87,9 +90,14 @@ private function resolve(Property $property): Collection
8790
*
8891
* @return Collection
8992
*/
90-
protected function getRulesForProperty(Property $property): Collection
93+
protected function getDataForProperty(Property $property): Collection
9194
{
92-
$rules = collect();
95+
$data = collect([
96+
'rules' => collect(),
97+
'messages' => collect(),
98+
]);
99+
100+
$rules = $data->get('rules');
93101

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

110118
$this->resolveTypes($property, $rules);
111-
$this->resolveAttributeRules($property, $rules);
119+
$this->resolveAttributeRules($property, $data);
112120

113-
return $rules;
121+
return $data;
114122
}
115123

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

135-
$parentRules = $this->getRulesForProperty($property);
143+
$parentRules = $this->getDataForProperty($property);
136144

137145
$definition = $this->definitionService->get($property->dataClass);
138146
$rules = $this->get($definition);
@@ -179,17 +187,20 @@ private function resolveTypes(Property $property, Collection $rules): void
179187
* Resolve rules for the attributes.
180188
*
181189
* @param Property $property
182-
* @param Collection $rules
190+
* @param Collection $data
183191
*
184192
* @return void
185193
*/
186-
private function resolveAttributeRules(Property $property, Collection $rules): void
194+
private function resolveAttributeRules(Property $property, Collection $data): void
187195
{
188196
$property
189197
->attributes
190198
->filter(fn (object $attribute) => is_subclass_of($attribute, ValidationAttribute::class))
191-
->each(function (ValidationAttribute $rule) use ($rules) {
192-
$rules->add($rule->rule);
199+
->each(function (ValidationAttribute $rule) use ($data) {
200+
$data->get('rules')->add($rule->rule);
201+
if ($rule instanceof Rule && $rule->message !== null) {
202+
$data->get('messages')->put($rule->rule, $rule->message);
203+
}
193204
});
194205
}
195206
}

tests/Data/TestData.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Kellton\Tools\Tests\Data;
4+
5+
use Kellton\Tools\Features\Data\Attributes\Validation\Rule;
6+
use Kellton\Tools\Features\Data\Data;
7+
8+
readonly class TestData extends Data
9+
{
10+
public function __construct(
11+
public string $firstName,
12+
public string $lastName,
13+
#[Rule('email', message: 'Wrong email address format!')]
14+
public string $email
15+
) {
16+
}
17+
}

tests/Feature/DataTest.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace Kellton\Tools\Tests\Feature;
44

5+
use Illuminate\Validation\ValidationException;
56
use Kellton\Tools\Features\Data\Data;
67
use Kellton\Tools\Features\Data\Exceptions\MissingConstructor;
78
use Kellton\Tools\Tests\Data\IndexData;
9+
use Kellton\Tools\Tests\Data\TestData;
810
use Kellton\Tools\Tests\TestCase;
911
use ReflectionException;
1012

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

2931
$validationRules = $data::getValidationRules();
@@ -47,14 +49,18 @@ public function testFiltersDataShouldSucceed(): void
4749
$this->assertIsArray($validationRules);
4850
$this->assertNotEmpty($validationRules);
4951
}
50-
}
5152

52-
/**
53-
* Class TestData is used for testing readonly Data class.
54-
*/
55-
readonly class TestData extends Data
56-
{
57-
public function __construct(public string $firstName, public string $lastName)
53+
public function testRuleMessageShouldSucceed(): void
5854
{
55+
try {
56+
TestData::create([
57+
'firstName' => 'John',
58+
'lastName' => 'Doe',
59+
'email' => 'john',
60+
]);
61+
} catch (ValidationException $e) {
62+
$message = data_get($e->errors(), 'email.0');
63+
$this->assertSame('Wrong email address format!', $message);
64+
}
5965
}
6066
}

0 commit comments

Comments
 (0)