Skip to content

Commit 69fa5a9

Browse files
committed
Initial commit
0 parents  commit 69fa5a9

39 files changed

Lines changed: 2179 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: Validation CI
2+
3+
on:
4+
push:
5+
branches: [ main, dev ]
6+
pull_request:
7+
branches: [ main, dev ]
8+
9+
jobs:
10+
quality:
11+
name: Quality Checks (PHP ${{ matrix.php }})
12+
runs-on: ubuntu-latest
13+
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
php: [ "8.2", "8.3", "8.4" ]
18+
19+
steps:
20+
# -------------------------------------------------
21+
# 1) Checkout
22+
# -------------------------------------------------
23+
- name: Checkout repository
24+
uses: actions/checkout@v4
25+
26+
# -------------------------------------------------
27+
# 2) Setup PHP
28+
# -------------------------------------------------
29+
- name: Setup PHP
30+
uses: shivammathur/setup-php@v2
31+
with:
32+
php-version: ${{ matrix.php }}
33+
tools: composer
34+
coverage: none
35+
36+
# -------------------------------------------------
37+
# 3) Validate composer.json
38+
# -------------------------------------------------
39+
- name: Validate composer.json
40+
run: composer validate --strict
41+
42+
# -------------------------------------------------
43+
# 4) Install dependencies
44+
# -------------------------------------------------
45+
- name: Install dependencies
46+
run: composer install --no-interaction --prefer-dist --no-progress
47+
48+
# -------------------------------------------------
49+
# 5) Dump optimized autoload
50+
# -------------------------------------------------
51+
- name: Optimize autoload
52+
run: composer dump-autoload --optimize
53+
54+
# -------------------------------------------------
55+
# 6) Run PHPStan (Level Max)
56+
# -------------------------------------------------
57+
- name: Run PHPStan
58+
run: composer analyse
59+
60+
# -------------------------------------------------
61+
# 7) Syntax Check (extra safety)
62+
# -------------------------------------------------
63+
- name: Lint PHP files
64+
run: |
65+
find src -type f -name "*.php" -print0 | xargs -0 -n1 php -l

HOW_TO_USE.md

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
# HOW_TO_USE — Validation Module
2+
3+
This guide explains **how to use the Validation module** in controllers and
4+
application flow.
5+
6+
It assumes:
7+
- The Validation module is available under the project namespace `App\Modules\Validation`
8+
- `respect/validation` is installed
9+
- PHP 8.2+
10+
- PHPStan level max compatibility is required
11+
12+
---
13+
14+
## 1️⃣ Basic Usage Pattern (API Controller)
15+
16+
### Step 1 — Choose the Schema
17+
Each endpoint must have **one schema** representing its input.
18+
19+
Example:
20+
- Login → `AuthLoginSchema`
21+
- Create Admin → `AdminCreateSchema`
22+
23+
---
24+
25+
### Step 2 — Validate the Input
26+
27+
```php
28+
use App\Modules\Validation\Validator\RespectValidator;
29+
use app\Modules\Validation\Schemas\AuthLoginSchema;
30+
use app\Modules\Validation\ErrorMapper\SystemApiErrorMapper;
31+
32+
/** @var array<string, mixed> $input */
33+
$input = (array) $request->getParsedBody();
34+
35+
$validator = new RespectValidator();
36+
$schema = new AuthLoginSchema();
37+
38+
$result = $validator->validate($schema, $input);
39+
```
40+
41+
📌 Notes:
42+
43+
* Validation **never throws** for invalid input
44+
* All errors are structured and typed
45+
* HTTP status is always `400` for validation errors
46+
(by design — `422` is reserved for non-validation semantic failures)
47+
* ❌ Validation does **not** perform input sanitization (e.g., `trim`, `normalize`)
48+
– handle sanitization explicitly in the controller or input factory if needed
49+
50+
---
51+
52+
### Step 3 — Handle Validation Failure
53+
54+
```php
55+
if (!$result->isValid()) {
56+
$errorMapper = new SystemApiErrorMapper();
57+
$errorResponse = $errorMapper->mapValidationErrors(
58+
$result->getErrors()
59+
);
60+
61+
return $response
62+
->withStatus($errorResponse->getStatus())
63+
->withJson($errorResponse->toArray());
64+
}
65+
```
66+
67+
---
68+
69+
### Step 4 — Continue Normal Flow
70+
71+
```php
72+
// Input is valid here
73+
// Call Service / Domain layer safely
74+
```
75+
76+
---
77+
78+
## 2️⃣ Adding a New Schema
79+
80+
### Step 1 — Create Schema Class
81+
82+
All schemas **must extend `AbstractSchema`**.
83+
84+
```php
85+
use app\Modules\Validation\Schemas\AbstractSchema;
86+
use app\Modules\Validation\Rules\RequiredStringRule;
87+
use app\Modules\Validation\Enum\ValidationErrorCodeEnum;
88+
89+
final class ExampleSchema extends AbstractSchema
90+
{
91+
protected function rules(): array
92+
{
93+
return [
94+
'title' => [
95+
RequiredStringRule::rule(3, 100),
96+
ValidationErrorCodeEnum::REQUIRED_FIELD,
97+
],
98+
];
99+
}
100+
}
101+
```
102+
103+
📌 Rules format:
104+
105+
```php
106+
'field_name' => [Validatable, ValidationErrorCodeEnum]
107+
```
108+
109+
---
110+
111+
## 3️⃣ Adding a New Rule
112+
113+
Rules are **thin wrappers** around Respect validators.
114+
115+
Example:
116+
117+
```php
118+
use Respect\Validation\Validator as v;
119+
use Respect\Validation\Validatable;
120+
121+
final class SlugRule
122+
{
123+
/**
124+
* @return Validatable
125+
*/
126+
public static function rule()
127+
{
128+
return v::stringType()->regex('/^[a-z0-9-]+$/');
129+
}
130+
}
131+
```
132+
133+
Rules:
134+
135+
* Must not know about Schemas
136+
* Must not throw custom exceptions
137+
* Must return `Validatable` (via docblock)
138+
139+
---
140+
141+
## 4️⃣ Validation Error Codes (Enums)
142+
143+
### Validation Errors
144+
145+
All validation errors use:
146+
147+
```php
148+
ValidationErrorCodeEnum
149+
```
150+
151+
Example:
152+
153+
```php
154+
ValidationErrorCodeEnum::INVALID_EMAIL
155+
```
156+
157+
❌ Never use strings directly.
158+
159+
---
160+
161+
### Auth / Permission Errors
162+
163+
Used by Guards (not Validation):
164+
165+
```php
166+
AuthErrorCodeEnum
167+
```
168+
169+
Example:
170+
171+
```php
172+
AuthErrorCodeEnum::STEP_UP_REQUIRED
173+
```
174+
175+
---
176+
177+
## 5️⃣ Error Mapping (System-Level)
178+
179+
All errors are converted to API responses through:
180+
181+
```php
182+
SystemApiErrorMapper
183+
```
184+
185+
### Validation Mapping
186+
187+
```php
188+
$errorMapper->mapValidationErrors($errors);
189+
```
190+
191+
### Auth Mapping (used in exception handlers)
192+
193+
```php
194+
$errorMapper->mapAuthError(AuthErrorCodeEnum::NOT_AUTHORIZED);
195+
```
196+
197+
---
198+
199+
## 6️⃣ ApiErrorResponseDTO
200+
201+
All error responses are returned as:
202+
203+
```php
204+
ApiErrorResponseDTO
205+
```
206+
207+
### Accessors
208+
209+
```php
210+
$errorResponse->getStatus(); // HTTP status code
211+
$errorResponse->toArray(); // API-safe payload
212+
```
213+
214+
Payload format:
215+
216+
```json
217+
{
218+
"code": "INPUT_INVALID",
219+
"errors": {
220+
"email": ["invalid_email"]
221+
}
222+
}
223+
```
224+
225+
---
226+
227+
## 7️⃣ What NOT To Do ❌
228+
229+
* ❌ Do not validate inside Domain services
230+
* ❌ Do not throw validation exceptions
231+
* ❌ Do not log validation errors
232+
* ❌ Do not return arrays from ErrorMappers
233+
* ❌ Do not use strings instead of Enums
234+
* ❌ Do not mix validation with authorization
235+
236+
---
237+
238+
## 8️⃣ Common Mistakes
239+
240+
| Mistake | Why It’s Wrong |
241+
|--------------------------|-----------------------------------|
242+
| Validating in Service | Breaks separation of concerns |
243+
| Using strings for errors | Breaks type-safety |
244+
| try/catch per field | Duplication (use AbstractSchema) |
245+
| HTTP logic in Schema | Schema must be framework-agnostic |
246+
247+
---
248+
249+
## 9️⃣ Static Analysis Notes
250+
251+
* Return types for Respect validators are declared via **docblocks**
252+
* This is intentional for PHPStan compatibility
253+
* Do not add strict return types to Rule methods
254+
255+
---
256+
257+
## 🔒 Final Rule (LOCKED)
258+
259+
> **Every request must be validated using a Schema.
260+
> Every validation error must be expressed as an Enum.
261+
> Every error response must be returned as a DTO.**
262+
263+
---
264+
265+
## ✅ Status
266+
267+
* Usage pattern: **LOCKED**
268+
* API contract: **STABLE**
269+
* PHPStan: **PASS (level max)**
270+
271+
---

0 commit comments

Comments
 (0)