Skip to content

Commit d5c67a5

Browse files
committed
docs: restructure README and polish documentation
- 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
1 parent be3270b commit d5c67a5

15 files changed

Lines changed: 109 additions & 126 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
run: composer lint
5050

5151
- name: Static analysis
52-
run: composer analyse
52+
run: composer analyze
5353

5454
- name: Tests
5555
run: composer test

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ Core validation logic lives in `src/Lemmon/Validator/`. Tests in `tests/` follow
2525

2626
- `composer test` - Run Pest test suite
2727
- `composer lint` / `composer format` - Mago linting and formatting (dev dependency)
28-
- `composer analyse` - PHPStan at max level
28+
- `composer analyze` - PHPStan at max level
2929
- `composer platform-check` - Verify PHP 8.3 and extension requirements
30-
- `composer check` - Run all checks (platform, format, lint, Prettier, test, analyse); use before PR
30+
- `composer check` - Run all checks (platform, format, lint, Prettier, test, analyze); use before PR
3131
- `npm run format` - Prettier for YAML, JSON, Markdown
3232
- `npm run check` - Alias for `composer check`
3333
- Pre-commit hook (Husky): Prettier check, Mago format --staged, Mago lint --staged
@@ -38,4 +38,4 @@ Dev tooling: symfony/var-dumper, symfony/error-handler, ergebnis/composer-normal
3838

3939
## Coding Style & Formatting
4040

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.

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ All notable changes to this project will be documented in this file.
4444

4545
### Fixed
4646

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
4848
- **Issue**: Chaining `->default($value)->required()` always threw "Value is required" for null inputs because `required` checked for null before `default` was applied
4949
- **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
5050
- **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
5252
- **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
5353

5454
### Changed
5555

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
5757
- Removed 2 duplicated early returns for null + no-pipeline + not-required
5858
- Removed mid-loop `default()` and `required()` checks (were duplicating post-loop logic)
5959
- `default()` now applies in exactly one place: after the pipeline, as the last-resort fallback

README.md

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,6 @@ composer require lemmon/validator
1717

1818
**Requirements:** PHP 8.3 or higher
1919

20-
## About
21-
22-
**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
42-
- **Smart null handling** - validations skip `null` unless `required()`, `transform()`/`pipe()`/`nullifyEmpty()` skip `null` by default
43-
- **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-
5720
## Quick Start
5821

5922
All runtime classes live under the `Lemmon\Validator` namespace.
@@ -63,16 +26,16 @@ use Lemmon\Validator\Validator;
6326

6427
// Simple validation with form-safe coercion
6528
$email = Validator::isString()
66-
->email()
6729
->nullifyEmpty() // Empty strings become null (form-safe)
30+
->email()
6831
->validate('user@example.com');
6932

7033
// Schema validation with custom logic
7134
$userSchema = Validator::isAssociative([
7235
'name' => Validator::isString()->required(),
7336
'age' => Validator::isInt()->min(18)->coerce(),
74-
'email' => Validator::isString()->email()->nullifyEmpty(),
75-
'password' => Validator::isString()->satisfies(fn($v) => strlen($v) >= 8, 'Password too short')
37+
'email' => Validator::isString()->nullifyEmpty()->email(),
38+
'password' => Validator::isString()->minLength(8, 'Password too short')
7639
]);
7740

7841
// Tuple-based validation (no exceptions)
@@ -91,6 +54,26 @@ try {
9154
}
9255
```
9356

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()`)
61+
- **Smart null handling** — validations skip `null` unless `required()`; `transform()`/`pipe()`/`nullifyEmpty()` skip `null` by default
62+
- **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.
76+
9477
## Documentation
9578

9679
### Getting Started
@@ -101,12 +84,12 @@ try {
10184

10285
### Validation Guides
10386

104-
- [String Validation](docs/guides/string-validation.md) -- Email, URL, patterns, length constraints
105-
- [Numeric Validation](docs/guides/numeric-validation.md) -- Integers, floats, ranges, constraints
106-
- [Array Validation](docs/guides/array-validation.md) -- Indexed arrays and item validation
107-
- [Object & Schema Validation](docs/guides/object-validation.md) -- Complex nested structures
108-
- [Custom Validation](docs/guides/custom-validation.md) -- User-defined functions and business logic
109-
- [Error Handling](docs/guides/error-handling.md) -- Working with validation errors
87+
- [String Validation](docs/guides/string-validation.md) Email, URL, patterns, length constraints
88+
- [Numeric Validation](docs/guides/numeric-validation.md) Integers, floats, ranges, constraints
89+
- [Array Validation](docs/guides/array-validation.md) Indexed arrays and item validation
90+
- [Object & Schema Validation](docs/guides/object-validation.md) Complex nested structures
91+
- [Custom Validation](docs/guides/custom-validation.md) User-defined functions and business logic
92+
- [Error Handling](docs/guides/error-handling.md) Working with validation errors
11093

11194
### API Reference
11295

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@
5050
"sort-packages": true
5151
},
5252
"scripts": {
53-
"analyse": "phpstan analyse",
53+
"analyze": "phpstan analyse",
5454
"check": [
5555
"@platform-check",
5656
"mago format --check",
5757
"@lint",
5858
"npm run format:check",
5959
"@test",
60-
"@analyse"
60+
"@analyze"
6161
],
6262
"format": "mago format",
6363
"lint": "mago lint",

docs/api-reference/validator-factory.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ $validator = Validator::isObject()->defaultUsing(
732732
$result = $validator->validate(null); // Returns a new stdClass each time
733733
```
734734

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.
736736

737737
---
738738

docs/examples/form-validation.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ class FileUploadValidator
883883

884884
## Next Steps
885885

886-
- [String Validation Guide](../guides/string-validation.md) -- Advanced string validation patterns
887-
- [Numeric Validation Guide](../guides/numeric-validation.md) -- Numeric validation techniques
888-
- [Object & Schema Validation](../guides/object-validation.md) -- Complex nested structure validation
889-
- [Error Handling Guide](../guides/error-handling.md) -- Advanced error handling techniques
886+
- [String Validation Guide](../guides/string-validation.md) Advanced string validation patterns
887+
- [Numeric Validation Guide](../guides/numeric-validation.md) Numeric validation techniques
888+
- [Object & Schema Validation](../guides/object-validation.md) Complex nested structure validation
889+
- [Error Handling Guide](../guides/error-handling.md) Advanced error handling techniques

docs/getting-started/basic-usage.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ $result = $safeQuantity->validate('5'); // Returns: 5
154154

155155
### Execution Order Matters
156156

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.
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.
158158

159159
```php
160160
// Order matters for transformations
@@ -347,7 +347,7 @@ $withFallback = Validator::isString()
347347

348348
## Next Steps
349349

350-
- [Core Concepts](core-concepts.md) -- Understand the architecture
351-
- [String Validation Guide](../guides/string-validation.md) -- Detailed string validation
352-
- [Numeric Validation Guide](../guides/numeric-validation.md) -- Integer and float validation
353-
- [Custom Validation Guide](../guides/custom-validation.md) -- Create your own validation rules
350+
- [Core Concepts](core-concepts.md) Understand the architecture
351+
- [String Validation Guide](../guides/string-validation.md) Detailed string validation
352+
- [Numeric Validation Guide](../guides/numeric-validation.md) Integer and float validation
353+
- [Custom Validation Guide](../guides/custom-validation.md) Create your own validation rules

docs/getting-started/core-concepts.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,12 @@ Validator::isObject($schema) // stdClass object with schema
124124
Understanding the validation flow helps debug and optimize your validators:
125125

126126
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)
128128
3. **Pipeline Execution** - Validations and transformations run in the order written (fail-fast per field)
129129
4. **Default** - Last-resort fallback: if the result is null and a default exists, apply it
130130
5. **Required** - Single check, always last: if the value is still null after default, fail
131131

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.
133133

134134
## Data Transformations
135135

@@ -258,7 +258,7 @@ $processed = Validator::isArray()
258258

259259
### Transformation Pipeline Order
260260

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:
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:
262262

263263
```php
264264
// Execution order: trim → nullifyEmpty
@@ -498,7 +498,7 @@ $email2 = $emailValidator->validate('user2@example.com');
498498

499499
Now that you understand the core concepts:
500500

501-
- [String Validation Guide](../guides/string-validation.md) -- Master string validation
502-
- [Numeric Validation Guide](../guides/numeric-validation.md) -- Work with numbers
503-
- [Object & Schema Validation](../guides/object-validation.md) -- Handle complex structures
504-
- [Custom Validation Guide](../guides/custom-validation.md) -- Create custom business rules
501+
- [String Validation Guide](../guides/string-validation.md) Master string validation
502+
- [Numeric Validation Guide](../guides/numeric-validation.md) Work with numbers
503+
- [Object & Schema Validation](../guides/object-validation.md) Handle complex structures
504+
- [Custom Validation Guide](../guides/custom-validation.md) Create custom business rules

docs/getting-started/installation.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ composer lint
7878
composer fix
7979

8080
# Run static analysis
81-
composer analyse
81+
composer analyze
8282
```
8383

8484
## Next Steps
8585

86-
- [Basic Usage](basic-usage.md) -- Learn the fundamentals
87-
- [Core Concepts](core-concepts.md) -- Understand the architecture
88-
- [String Validation Guide](../guides/string-validation.md) -- Start with string validation
86+
- [Basic Usage](basic-usage.md) Learn the fundamentals
87+
- [Core Concepts](core-concepts.md) Understand the architecture
88+
- [String Validation Guide](../guides/string-validation.md) Start with string validation

0 commit comments

Comments
 (0)