Skip to content

Commit 4ffca13

Browse files
mplement Item and Collection validation features
1 parent 73d74a6 commit 4ffca13

File tree

10 files changed

+442
-23
lines changed

10 files changed

+442
-23
lines changed

README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,81 @@ if ($validation->validateArray($data) === true) {
103103
}
104104
```
105105

106+
#### Example: Validating Nested Objects with `Item`
107+
108+
The `Item` rule allows you to validate nested objects or associative arrays with specific rules for each key.
109+
110+
```php
111+
use PhpDevCommunity\Validator\Validation;
112+
use PhpDevCommunity\Validator\Rules\NotNull;
113+
use PhpDevCommunity\Validator\Rules\Item;
114+
use PhpDevCommunity\Validator\Rules\StringLength;
115+
use PhpDevCommunity\Validator\Rules\Alphabetic;
116+
117+
// Define validation rules for a nested object (e.g., a "person" object)
118+
$validation = new Validation([
119+
'person' => [new NotNull(), new Item([
120+
'first_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)],
121+
'last_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)],
122+
])]
123+
]);
124+
125+
// Example data
126+
$data = [
127+
'person' => [
128+
'first_name' => 'John',
129+
'last_name' => 'Doe'
130+
]
131+
];
132+
133+
// Validate the data
134+
if ($validation->validateArray($data) === true) {
135+
echo "Person object is valid!";
136+
} else {
137+
$errors = $validation->getErrors();
138+
echo "Validation errors: " . json_encode($errors, JSON_PRETTY_PRINT);
139+
}
140+
```
141+
142+
#### Example: Validating Arrays of Items with `Collection`
143+
144+
The `Collection` rule is used to validate arrays where each item in the array must satisfy a set of rules.
145+
146+
```php
147+
use PhpDevCommunity\Validator\Validation;
148+
use PhpDevCommunity\Validator\Rules\NotEmpty;
149+
use PhpDevCommunity\Validator\Rules\Collection;
150+
use PhpDevCommunity\Validator\Rules\Item;
151+
use PhpDevCommunity\Validator\Rules\NotNull;
152+
use PhpDevCommunity\Validator\Rules\StringLength;
153+
154+
// Define validation rules for a collection of articles
155+
$validation = new Validation([
156+
'articles' => [new NotEmpty(), new Collection([
157+
new Item([
158+
'title' => [new NotNull(), (new StringLength())->min(3)],
159+
'body' => [new NotNull(), (new StringLength())->min(10)],
160+
])
161+
])]
162+
]);
163+
164+
// Example data
165+
$data = [
166+
'articles' => [
167+
['title' => 'Article 1', 'body' => 'This is the body of the first article.'],
168+
['title' => 'Article 2', 'body' => 'Second article body here.']
169+
]
170+
];
171+
172+
// Validate the data
173+
if ($validation->validateArray($data) === true) {
174+
echo "Articles are valid!";
175+
} else {
176+
$errors = $validation->getErrors();
177+
echo "Validation errors: " . json_encode($errors, JSON_PRETTY_PRINT);
178+
}
179+
```
180+
106181
#### URL Validation
107182

108183
Validate a URL to ensure it is not null and is a valid URL format.

src/Assert/AbstractValidator.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ protected function error(string $message, array $context): void
3737
$value = method_exists($value, '__toString') ? (string)$value : get_class($value);
3838
} elseif (is_array($value)) {
3939
$value = json_encode($value);
40+
} elseif (is_bool($value)) {
41+
$value = $value ? 'true' : 'false';
4042
} else {
4143
$value = (string)$value;
4244
}

src/Assert/Boolean.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDevCommunity\Validator\Assert;
6+
7+
final class Boolean extends AbstractValidator
8+
{
9+
private string $message = 'This value should be of type {{ type }}.';
10+
11+
public function validate($value): bool
12+
{
13+
if ($value === null) {
14+
return true;
15+
}
16+
17+
if (is_int($value) && ($value == 0 || $value === 1)) {
18+
return true;
19+
}
20+
21+
if (is_bool($value) === false) {
22+
$this->error($this->message, ['value' => $value, 'type' => 'boolean']);
23+
return false;
24+
}
25+
26+
return true;
27+
}
28+
29+
public function message(string $message): self
30+
{
31+
$this->message = $message;
32+
return $this;
33+
}
34+
}

src/Assert/Collection.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDevCommunity\Validator\Assert;
6+
7+
use PhpDevCommunity\Validator\Validation;
8+
use PhpDevCommunity\Validator\ValidationProcessor;
9+
use function ctype_alpha;
10+
11+
final class Collection extends AbstractValidator
12+
{
13+
private string $message = 'This value should be of type {{ type }}.';
14+
15+
/**
16+
* @param array<ValidatorInterface> $validators
17+
*/
18+
private array $validators;
19+
private array $errors = [];
20+
21+
/**
22+
* @param array<ValidatorInterface> $validators
23+
*/
24+
public function __construct(array $validators)
25+
{
26+
foreach ($validators as $validator) {
27+
if ($validator instanceof ValidatorInterface === false) {
28+
throw new \InvalidArgumentException(sprintf('The validator must be an instance of %s', ValidatorInterface::class));
29+
}
30+
}
31+
$this->validators = $validators;
32+
}
33+
public function validate($value): bool
34+
{
35+
if ($value === null) {
36+
return true;
37+
}
38+
39+
if (is_array($value) === false) {
40+
$this->error($this->message, ['value' => $value, 'type' => 'collection']);
41+
return false;
42+
}
43+
44+
$validationProcessor = new ValidationProcessor();
45+
$errors = [];
46+
foreach ($value as $key => $element) {
47+
$errors = array_merge($errors, $validationProcessor->process($this->validators, $key, $element));
48+
}
49+
50+
if ($errors !== []) {
51+
$this->errors = $errors;
52+
return false;
53+
}
54+
55+
return true;
56+
}
57+
58+
public function message(string $message): self
59+
{
60+
$this->message = $message;
61+
return $this;
62+
}
63+
64+
public function getErrors(): array
65+
{
66+
return $this->errors;
67+
}
68+
}

src/Assert/Item.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDevCommunity\Validator\Assert;
6+
7+
use InvalidArgumentException;
8+
use PhpDevCommunity\Validator\Validation;
9+
10+
final class Item extends AbstractValidator
11+
{
12+
private string $message = 'This value should be of type {{ type }}.';
13+
private string $messageKeyNotExist = 'The key {{ value }} does not exist.';
14+
15+
private Validation $validation;
16+
private array $errors = [];
17+
18+
/**
19+
* @param array<string,ValidatorInterface[]> $validators
20+
*/
21+
public function __construct(array $validators)
22+
{
23+
$this->validation = new Validation($validators);
24+
}
25+
26+
public function validate($value): bool
27+
{
28+
if ($value === null) {
29+
return true;
30+
}
31+
32+
if (is_array($value) === false) {
33+
$this->error($this->message, ['value' => $value, 'type' => 'array']);
34+
return false;
35+
}
36+
37+
$this->validation->validateArray($value);
38+
if ($this->validation->getErrors() !== []) {
39+
$this->errors = $this->validation->getErrors();
40+
return false;
41+
}
42+
43+
return true;
44+
}
45+
46+
public function message(string $message): self
47+
{
48+
$this->message = $message;
49+
return $this;
50+
}
51+
52+
public function getErrors(): array
53+
{
54+
return $this->errors;
55+
}
56+
}

src/Assert/NotEmpty.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDevCommunity\Validator\Assert;
6+
7+
final class NotEmpty extends AbstractValidator
8+
{
9+
private string $message = 'This value should not be empty.';
10+
11+
public function validate($value): bool
12+
{
13+
if (empty($value)) {
14+
$this->error($this->message, ['value' => $value]);
15+
return false;
16+
}
17+
18+
return true;
19+
}
20+
21+
public function message(string $message): self
22+
{
23+
$this->message = $message;
24+
return $this;
25+
}
26+
}

src/Assert/StringLength.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
final class StringLength extends AbstractValidator
1111
{
12-
private string $invalidMessage = 'Invalid type given. String expected.';
12+
private string $invalidMessage = '{{ value }} is not a valid string.';
1313
private string $minMessage = '{{ value }} must be at least {{ limit }} characters long';
1414
private string $maxMessage = '{{ value }} cannot be longer than {{ limit }} characters';
1515
private ?int $min = null;

src/Validation.php

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PhpDevCommunity\Validator;
44

5+
use PhpDevCommunity\Validator\Assert\Collection;
6+
use PhpDevCommunity\Validator\Assert\Item;
57
use PhpDevCommunity\Validator\Assert\ValidatorInterface;
68
use InvalidArgumentException;
79
use Psr\Http\Message\ServerRequestInterface;
@@ -17,6 +19,8 @@
1719

1820
final class Validation
1921
{
22+
private ValidationProcessor $processor;
23+
2024
/**
2125
* @var array<string,array>
2226
*/
@@ -31,6 +35,8 @@ final class Validation
3135

3236
public function __construct(array $fieldValidators)
3337
{
38+
$this->processor = new ValidationProcessor();
39+
3440
foreach ($fieldValidators as $field => $validators) {
3541
if (!is_array($validators)) {
3642
$validators = [$validators];
@@ -66,23 +72,23 @@ public function validate(ServerRequestInterface $request): bool
6672
public function validateArray(array $data): bool
6773
{
6874
$this->data = $data;
75+
$this->executeValidators($this->validators, $this->data);
76+
return $this->getErrors() === [];
77+
}
6978

79+
private function executeValidators(array $validatorsByField, array &$data): void
80+
{
7081
/**
7182
* @var $validators array<ValidatorInterface>
7283
*/
73-
foreach ($this->validators as $field => $validators) {
74-
if (!isset($this->data[$field])) {
75-
$this->data[$field] = null;
76-
}
77-
78-
foreach ($validators as $validator) {
79-
if ($validator->validate($this->data[$field]) === false) {
80-
$this->addError($field, (string)$validator->getError());
81-
}
84+
foreach ($validatorsByField as $field => $validators) {
85+
if (!isset($data[$field])) {
86+
$data[$field] = null;
8287
}
8388

89+
$errors = $this->processor->process($validators, $field, $data[$field]);
90+
$this->errors = array_merge($this->errors, $errors);
8491
}
85-
return $this->getErrors() === [];
8692
}
8793

8894
/**
@@ -101,18 +107,6 @@ public function getData(): array
101107
return $this->data;
102108
}
103109

104-
/**
105-
* Add an error for a specific field.
106-
*
107-
* @param string $field The field for which the error occurred
108-
* @param string $message The error message
109-
* @return void
110-
*/
111-
private function addError(string $field, string $message): void
112-
{
113-
$this->errors[$field][] = $message;
114-
}
115-
116110
/**
117111
* Add a validator for a specific field.
118112
*

0 commit comments

Comments
 (0)