Skip to content

Commit 42bf5fc

Browse files
committed
feat: Support numbered arguments
1 parent 11ca654 commit 42bf5fc

File tree

3 files changed

+49
-9
lines changed

3 files changed

+49
-9
lines changed

src/Validatable.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Attributes\Validation;
1010

11+
use ArrayAccess;
1112
use Attributes\Validation\Exceptions\ValidationException;
1213

1314
interface Validatable
@@ -21,12 +22,12 @@ interface Validatable
2122
public function validate(array $data, object $model): object;
2223

2324
/**
24-
* @param array $data - Data to be validated
25+
* @param array|ArrayAccess $data - Data to be validated
2526
* @param callable $call - Callable to be validated
2627
*
2728
* @returns array - Arguments in a sequence order for the given function
2829
*
2930
* @throws ValidationException - If the validation fails
3031
*/
31-
public function validateCallable(array $data, callable $call): array;
32+
public function validateCallable(array|ArrayAccess $data, callable $call): array;
3233
}

src/Validator.php

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

55
namespace Attributes\Validation;
66

7+
use ArrayAccess;
78
use Attributes\Options;
89
use Attributes\Options\Exceptions\InvalidOptionException;
910
use Attributes\Validation\Exceptions\ContextPropertyException;
@@ -51,7 +52,7 @@ public function __construct(?PropertyValidator $validator = null, bool $stopFirs
5152
/**
5253
* Validates a given data according to a given model
5354
*
54-
* @param array $data - Data to validate
55+
* @param array|ArrayAccess $data - Data to validate
5556
* @param string|object $model - Model to validate against
5657
* @return object - Model populated with the validated data
5758
*
@@ -60,7 +61,7 @@ public function __construct(?PropertyValidator $validator = null, bool $stopFirs
6061
* @throws ReflectionException
6162
* @throws InvalidOptionException
6263
*/
63-
public function validate(array $data, string|object $model): object
64+
public function validate(array|ArrayAccess $data, string|object $model): object
6465
{
6566
$currentLevel = $this->context->getOptional('internal.recursionLevel', 0);
6667
$maxRecursionLevel = $this->context->getOptional('internal.maxRecursionLevel', 30);
@@ -127,7 +128,7 @@ public function validate(array $data, string|object $model): object
127128
/**
128129
* Validates a given data according to a given model
129130
*
130-
* @param array $data - Data to validate
131+
* @param array|ArrayAccess $data - Data to validate
131132
* @param callable $call - Callable to validate data against
132133
* @return array - Returns an array with the necessary arguments for the callable
133134
*
@@ -136,14 +137,14 @@ public function validate(array $data, string|object $model): object
136137
* @throws ReflectionException
137138
* @throws InvalidOptionException
138139
*/
139-
public function validateCallable(array $data, callable $call): array
140+
public function validateCallable(array|ArrayAccess $data, callable $call): array
140141
{
141142
$arguments = [];
142143
$reflectionFunction = new ReflectionFunction($call);
143144
$errorInfo = $this->context->getOptional(ErrorHolder::class) ?: new ErrorHolder($this->context);
144145
$this->context->set(ErrorHolder::class, $errorInfo, override: true);
145146
$defaultAliasGenerator = $this->getDefaultAliasGenerator($reflectionFunction);
146-
foreach ($reflectionFunction->getParameters() as $parameter) {
147+
foreach ($reflectionFunction->getParameters() as $index => $parameter) {
147148
if (! $this->isToValidate($parameter)) {
148149
continue;
149150
}
@@ -152,7 +153,7 @@ public function validateCallable(array $data, callable $call): array
152153
$aliasName = $this->getAliasName($parameter, $defaultAliasGenerator);
153154
$this->context->push('internal.currentProperty', $propertyName);
154155

155-
if (! array_key_exists($aliasName, $data)) {
156+
if (! array_key_exists($aliasName, $data) && ! array_key_exists($index, $data)) {
156157
if (! $parameter->isDefaultValueAvailable()) {
157158
try {
158159
$errorInfo->addError("Missing required argument '$aliasName'");
@@ -166,7 +167,7 @@ public function validateCallable(array $data, callable $call): array
166167
continue;
167168
}
168169

169-
$propertyValue = $data[$aliasName];
170+
$propertyValue = $data[$index] ?? $data[$aliasName];
170171
$property = new Property($parameter, $propertyValue);
171172
$this->context->set(Property::class, $property, override: true);
172173

tests/Integration/ValidateCallableTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,41 @@
138138
]],
139139
])
140140
->group('validator', 'validate-callable', 'error-handling');
141+
142+
// Numbered arguments
143+
144+
test('Numbered arguments - validate callable', function (bool $isStrict) {
145+
$validator = new Validator(strict: $isStrict);
146+
$rawData = [
147+
'fullName' => 'My full name',
148+
1 => [
149+
'my_post' => [
150+
'postId' => 1,
151+
'myTitle' => 'My Post Title',
152+
],
153+
],
154+
'profile' => [], // Numbered arguments should have precedence
155+
'default' => 5,
156+
];
157+
$call = function (string $fullName, Models\Complex\Profile $profile, int $default = 1) {
158+
return 'success';
159+
};
160+
$args = $validator->validateCallable($rawData, $call);
161+
expect($args)
162+
->toBeArray()
163+
->toHaveCount(3)
164+
->toMatchArray([
165+
'fullName' => 'My full name',
166+
'default' => 5,
167+
])
168+
->and($args['profile'])
169+
->toBeInstanceOf(Models\Complex\Profile::class)
170+
->and($args['profile']->my_post)
171+
->toBeInstanceOf(Models\Complex\Post::class)
172+
->and($args['profile']->my_post->my_post_id)
173+
->toBe(1)
174+
->and($args['profile']->my_post->my_title)
175+
->toBe('My Post Title');
176+
})
177+
->with([true, false])
178+
->group('validator', 'validate-callable', 'numbered-arguments');

0 commit comments

Comments
 (0)