You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Put Quick Start before Features; condense feature list and add Philosophy section
- Rename composer analyse script to analyze (CI and AGENTS.md updated)
- Use em dashes in Markdown; limit ASCII punctuation rule to PHP code in AGENTS.md
- Fix README examples (nullifyEmpty before email, minLength for passwords)
- Replace satisfies() strlen examples with digit-count custom code examples
@@ -38,4 +38,4 @@ Dev tooling: symfony/var-dumper, symfony/error-handler, ergebnis/composer-normal
38
38
39
39
## Coding Style & Formatting
40
40
41
-
PSR-12 for PHP. Mago for lint/format (`composer lint`, `composer format`). Prettier (`.prettierrc`) for YAML, JSON, Markdown. Add PHPDoc where behavior is non-obvious. Stick to ASCII punctuation in code and docs (e.g. `--` not em dash) so diffs stay predictable. Emojis sparingly.
41
+
PSR-12 for PHP. Mago for lint/format (`composer lint`, `composer format`). Prettier (`.prettierrc`) for YAML, JSON, Markdown. Add PHPDoc where behavior is non-obvious. Stick to ASCII punctuation in PHP code (e.g. `--`, not an em dash); Markdown and other docs may use proper typographic characters.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -44,16 +44,16 @@ All notable changes to this project will be documented in this file.
44
44
45
45
### Fixed
46
46
47
-
-**CRITICAL BUG**: Fixed `default()` and `required()` interaction --`required()` was rejecting null values before `default()` had a chance to provide a fallback
47
+
-**CRITICAL BUG**: Fixed `default()` and `required()` interaction —`required()` was rejecting null values before `default()` had a chance to provide a fallback
48
48
-**Issue**: Chaining `->default($value)->required()` always threw "Value is required" for null inputs because `required` checked for null before `default` was applied
49
49
-**Root Cause**: `tryValidate()` had a scattered, multi-branch architecture with `default` and `required` checks duplicated across 5+ locations (early returns, pre-pipeline, mid-loop, post-loop), making the interaction order unpredictable
50
50
-**Fix**: Simplified `tryValidate()` to a clean linear 5-step flow: coerce, type check, pipeline, default, required
51
-
-**Impact**: `default()` and `required()` now work cooperatively --`default()` is the last-resort fallback for null, `required()` only fails if the value is still null after default
51
+
-**Impact**: `default()` and `required()` now work cooperatively —`default()` is the last-resort fallback for null, `required()` only fails if the value is still null after default
52
52
-**Real-World Benefit**: Enables patterns like `->pipe(fn($x) => null)->default($fallback)->required()` where a pipeline step intentionally nullifies a value and `default()` catches it before `required()` enforces presence
53
53
54
54
### Changed
55
55
56
-
-**Simplified `tryValidate()` pipeline architecture**-- reduced from 67 lines with 5+ branch points to 33 lines with a linear flow
56
+
-**Simplified `tryValidate()` pipeline architecture**— reduced from 67 lines with 5+ branch points to 33 lines with a linear flow
57
57
- Removed 2 duplicated early returns for null + no-pipeline + not-required
**Philosophy: "Simple and Minimal with Extensibility Over Reinvention"**
23
-
24
-
Rather than reimplementing every possible transformation or validation rule, Lemmon Validator provides a solid foundation with generic `transform()` and `pipe()` methods that integrate seamlessly with PHP's ecosystem. Need complex string transformations? Plug in Laravel's `Str` class through our transformation system. Need advanced array operations? Connect Laravel Collections via our fluent API. The library focuses on what it does best: type-safe validation with excellent developer experience, while enabling you to leverage the entire PHP ecosystem.
25
-
26
-
**Key Design Principles:**
27
-
28
-
-**Type-Safe Architecture**: Modern PHP 8.1+ enums provide IDE autocomplete, refactoring safety, and eliminate magic strings throughout the codebase
29
-
-**Smart Null Handling**: Validations skip `null` unless `required()`. `transform()` and `pipe()` skip `null` by default for type safety. Use `transform($fn, skipNull: false)` to process null values.
30
-
-**Form Safety First**: Empty strings coerce to `null` (not dangerous `0`/`false`) to prevent real-world issues like accidental zero bank balances
31
-
-**Fluent API with Execution Order Guarantee**: Validation rules read like natural language and execute in the exact order written -- `Validator::isString()->pipe('trim')->nullifyEmpty()->required()`
32
-
-**Last-Resort Default**: `default()` is a flag applied after the pipeline -- fills in null only as a last resort, before `required()` enforces presence
33
-
-**Fail-Fast Per Field**: Each validator stops at the first failing rule, while schema validation still aggregates errors across fields
34
-
-**API-Friendly Error Format**: Flattened errors with field paths (`'_root'` for root-level, dot notation for nested) perfect for frontend consumption
35
-
-**Type-Aware Transformations**: Intelligent transformation system that maintains type context and handles coercion automatically
36
-
-**Extensible Architecture**: Generic transformation methods work with any PHP callable or external library
37
-
-**Strict Typing**: All files use `declare(strict_types=1);`, keeping internal type hints strict; opt into `coerce()` when you need form-friendly conversions.
38
-
39
-
## Features
40
-
41
-
-**Type-safe architecture** - PHP 8.1+ enums with IDE autocomplete, refactoring safety, and zero magic strings
-**Type-safe validation** for strings, integers, floats, arrays, and objects
44
-
-**Fluent, chainable API** with guaranteed execution order -- methods execute exactly as written in the chain
45
-
-**Schema-level error aggregation** with fail-fast behavior per field for clear, early feedback
46
-
-**Structured errors** - `getStructuredErrors()` returns `ValidationError` objects with a stable `code` (see `ValidationCode`), dotted `path`, `message`, and `params` for programmatic handling and i18n; match on codes rather than message text
47
-
-**API-friendly flattened errors** with field paths for easy frontend integration (`getFlattenedErrors()`, `ValidationException::flattenErrors()`)
48
-
-**Intuitive custom validation** with `satisfies()` method and optional error messages
49
-
-**Single-value validation** with `const()` for exact value matching (available on all validators)
50
-
-**PHP enum validation** with `enum()` for `BackedEnum` (backed values) and `UnitEnum` (case instances or string case names) (available on all validators)
51
-
-**Logical combinators** (`Validator::allOf()`, `Validator::anyOf()`, `Validator::not()`) for complex validation logic
52
-
-**Form-safe coercion** - empty strings become `null` (not dangerous `0`/`false`) for real-world safety
53
-
-**Accurate schema validation** - results only include provided fields and fields with defaults (no unexpected properties)
54
-
-**Universal transformations** (`transform()`, `pipe()`) for post-validation data processing
55
-
-**Null-safe operations** with `nullifyEmpty()` method for consistent empty value handling
56
-
57
20
## Quick Start
58
21
59
22
All runtime classes live under the `Lemmon\Validator` namespace.
@@ -63,16 +26,16 @@ use Lemmon\Validator\Validator;
63
26
64
27
// Simple validation with form-safe coercion
65
28
$email = Validator::isString()
66
-
->email()
67
29
->nullifyEmpty() // Empty strings become null (form-safe)
'password' => Validator::isString()->minLength(8, 'Password too short')
76
39
]);
77
40
78
41
// Tuple-based validation (no exceptions)
@@ -91,6 +54,26 @@ try {
91
54
}
92
55
```
93
56
57
+
## Features
58
+
59
+
-**Type-safe validation** for strings, integers, floats, arrays, and objects, powered by PHP enums for IDE autocomplete, refactoring safety, and zero magic strings
60
+
-**Fluent, chainable API** with a guaranteed execution order — methods run exactly as written (`->pipe('trim')->nullifyEmpty()->required()`)
-**Form-safe coercion** — empty strings become `null` (not dangerous `0`/`false`); opt into `coerce()` for form-friendly type conversions
63
+
-**Universal transformations** (`transform()`, `pipe()`) that plug in any PHP callable or external library
64
+
-**Custom validation** with `satisfies()`, plus logical combinators (`satisfiesAll()`/`satisfiesAny()`/`satisfiesNone()`)
65
+
-**Exact-value and enum matching** — `const()` for single values, `enum()` for `BackedEnum`/`UnitEnum` (available on all validators)
66
+
-**Last-resort defaults** — `default()` fills `null` after the pipeline, before `required()` enforces presence
67
+
-**Structured errors** — `getStructuredErrors()` returns `ValidationError` objects with a stable `code` (see `ValidationCode`), dotted `path`, `message`, and `params` for programmatic handling and i18n
68
+
-**Fail-fast per field, aggregated per schema** — each validator stops at its first failure while schema validation collects errors across all fields
69
+
-**API-friendly flattened errors** with field paths (`_root` for root, dot notation for nested) via `getFlattenedErrors()`
70
+
-**Accurate schema results** — output includes only provided fields and fields with defaults, never unexpected properties
71
+
-**Strict typing throughout** — every file uses `declare(strict_types=1);`
72
+
73
+
## Philosophy
74
+
75
+
**Simple and minimal, with extensibility over reinvention.** Rather than reimplementing every possible transformation or validation rule, Lemmon Validator provides a solid foundation with generic `transform()` and `pipe()` methods that integrate seamlessly with PHP's ecosystem. Need complex string transformations? Plug in Laravel's `Str` class. Need advanced array operations? Connect Laravel Collections. The library focuses on what it does best — type-safe validation with excellent developer experience — while letting you leverage the entire PHP ecosystem.
$result = $validator->validate(null); // Returns a new stdClass each time
733
733
```
734
734
735
-
Use this whenever the default contains mutable objects -- whether a plain object or an array with nested objects. Each call to `validate()` will invoke the factory and receive a fresh instance.
735
+
Use this whenever the default contains mutable objects — whether a plain object or an array with nested objects. Each call to `validate()` will invoke the factory and receive a fresh instance.
**Critical**: Pipeline steps execute in the **exact order written**. This matters for `pipe()`, `transform()`, and `nullifyEmpty()`. Both `required()` and `default()` are flags -- their position does not change execution order. `default()` fills in null after the pipeline as a last resort; `required()` enforces presence at the very end.
157
+
**Critical**: Pipeline steps execute in the **exact order written**. This matters for `pipe()`, `transform()`, and `nullifyEmpty()`. Both `required()` and `default()` are flags — their position does not change execution order. `default()` fills in null after the pipeline as a last resort; `required()` enforces presence at the very end.
Copy file name to clipboardExpand all lines: docs/getting-started/core-concepts.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -124,12 +124,12 @@ Validator::isObject($schema) // stdClass object with schema
124
124
Understanding the validation flow helps debug and optimize your validators:
125
125
126
126
1.**Type Coercion** - If enabled, attempt type conversion (empty strings become `null` for primitives)
127
-
2.**Type Validation** - Check value type (skipped for null -- lets the pipeline and default/required handle it)
127
+
2.**Type Validation** - Check value type (skipped for null — lets the pipeline and default/required handle it)
128
128
3.**Pipeline Execution** - Validations and transformations run in the order written (fail-fast per field)
129
129
4.**Default** - Last-resort fallback: if the result is null and a default exists, apply it
130
130
5.**Required** - Single check, always last: if the value is still null after default, fail
131
131
132
-
`default()` and `required()` are both flags -- their position in the fluent chain does not change when they are evaluated. `default()` always applies after the pipeline as the last-resort fallback for null. `required()` always enforces presence at the very end, after default has had its chance.
132
+
`default()` and `required()` are both flags — their position in the fluent chain does not change when they are evaluated. `default()` always applies after the pipeline as the last-resort fallback for null. `required()` always enforces presence at the very end, after default has had its chance.
Pipeline methods (`pipe()`, `transform()`, `nullifyEmpty()`, `satisfies()`, etc.) execute in their written order. `required()` and `default()` are flags -- their position does not change execution order:
261
+
Pipeline methods (`pipe()`, `transform()`, `nullifyEmpty()`, `satisfies()`, etc.) execute in their written order. `required()` and `default()` are flags — their position does not change execution order:
0 commit comments