|
4 | 4 | [](https://packagist.org/packages/lemmon/validator) |
5 | 5 | [](LICENSE) |
6 | 6 |
|
7 | | -A comprehensive, fluent validation library for PHP, inspired by Valibot and Zod. Build type-safe, readable validation schemas with chainable methods and intelligent error handling. |
| 7 | +> [!NOTE] |
| 8 | +> This library is in active development. The API may change in future versions as we refine and improve the developer experience based on real-world usage and feedback. |
8 | 9 |
|
9 | | -## ✨ Features |
| 10 | +Lemmon Validator is a comprehensive, fluent validation and data processing library for PHP that prioritizes developer experience, type safety, and real-world practicality. Inspired by modern validation libraries like Valibot and Zod, it brings a chainable, readable API to PHP for validation, transformation, and sanitization with intelligent error handling and form-safe defaults. |
10 | 11 |
|
11 | | -- 🔒 **Type-safe validation** for strings, integers, floats, arrays, and objects |
12 | | -- 🔗 **Fluent, chainable API** for readable and maintainable validation rules |
13 | | -- 📋 **Comprehensive error collection** with detailed, structured feedback |
14 | | -- ⚙️ **Intuitive custom validation** with `satisfies()` method and optional error messages |
15 | | -- 🧩 **Logical combinators** (`Validator::allOf()`, `Validator::anyOf()`, `Validator::not()`) for complex validation logic |
16 | | -- 🔄 **Form-safe coercion** - empty strings become `null` (not dangerous `0`/`false`) for real-world safety |
17 | | -- 🎯 **Schema validation** for nested data structures |
18 | | -- ⚡ **Universal transformations** (`transform()`, `pipe()`) for post-validation data processing |
| 12 | +## Installation |
19 | 13 |
|
20 | | -## 🚀 Quick Start |
| 14 | +```bash |
| 15 | +composer require lemmon/validator |
| 16 | +``` |
| 17 | + |
| 18 | +**Requirements:** PHP 8.1 or higher |
| 19 | + |
| 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 | +- **Form Safety First**: Empty strings coerce to `null` (not dangerous `0`/`false`) to prevent real-world issues like accidental zero bank balances |
| 29 | +- **Fluent API**: Validation rules read like natural language - `Validator::isString()->email()->required()` |
| 30 | +- **Comprehensive Error Collection**: All validation errors are collected, not just the first failure |
| 31 | +- **Type-Aware Transformations**: Intelligent transformation system that maintains type context and handles coercion automatically |
| 32 | +- **Extensible Architecture**: Generic transformation methods work with any PHP callable or external library |
| 33 | + |
| 34 | +## Features |
| 35 | + |
| 36 | +- **Type-safe validation** for strings, integers, floats, arrays, and objects |
| 37 | +- **Fluent, chainable API** for readable and maintainable validation rules |
| 38 | +- **Comprehensive error collection** with detailed, structured feedback |
| 39 | +- **Intuitive custom validation** with `satisfies()` method and optional error messages |
| 40 | +- **Logical combinators** (`Validator::allOf()`, `Validator::anyOf()`, `Validator::not()`) for complex validation logic |
| 41 | +- **Form-safe coercion** - empty strings become `null` (not dangerous `0`/`false`) for real-world safety |
| 42 | +- **Schema validation** for nested data structures |
| 43 | +- **Universal transformations** (`transform()`, `pipe()`) for post-validation data processing |
| 44 | +- **Null-safe operations** with `nullifyEmpty()` method for consistent empty value handling |
| 45 | + |
| 46 | +## Quick Start |
21 | 47 |
|
22 | 48 | ```php |
23 | 49 | use Lemmon\Validator; |
24 | 50 |
|
25 | | -// Simple validation |
| 51 | +// Simple validation with form-safe coercion |
26 | 52 | $email = Validator::isString() |
27 | 53 | ->email() |
| 54 | + ->nullifyEmpty() // Empty strings become null (form-safe) |
28 | 55 | ->validate('user@example.com'); |
29 | 56 |
|
30 | | -// Schema validation |
| 57 | +// Schema validation with custom logic |
31 | 58 | $userSchema = Validator::isAssociative([ |
32 | 59 | 'name' => Validator::isString()->required(), |
33 | | - 'age' => Validator::isInt()->min(18), |
34 | | - 'email' => Validator::isString()->email(), |
35 | | - 'preferences' => Validator::isObject([ |
36 | | - 'theme' => Validator::isString()->oneOf(['light', 'dark'])->default('light'), |
37 | | - 'notifications' => Validator::isBool()->default(true) |
38 | | - ]) |
| 60 | + 'age' => Validator::isInt()->min(18)->coerce(), |
| 61 | + 'email' => Validator::isString()->email()->nullifyEmpty(), |
| 62 | + 'password' => Validator::isString()->satisfies(fn($v) => strlen($v) >= 8, 'Password too short') |
39 | 63 | ]); |
40 | 64 |
|
| 65 | +// Tuple-based validation (no exceptions) |
41 | 66 | [$valid, $user, $errors] = $userSchema->tryValidate($input); |
| 67 | + |
| 68 | +// Exception-based validation |
| 69 | +$user = $userSchema->validate($input); // Throws ValidationException on failure |
42 | 70 | ``` |
43 | 71 |
|
44 | | -## 📚 Documentation |
| 72 | +## Documentation |
45 | 73 |
|
46 | 74 | ### Getting Started |
47 | | -- 📖 [Installation & Setup](docs/getting-started/installation.md) |
48 | | -- 🎯 [Basic Usage](docs/getting-started/basic-usage.md) |
49 | | -- 💡 [Core Concepts](docs/getting-started/core-concepts.md) |
| 75 | +- [Installation & Setup](docs/getting-started/installation.md) |
| 76 | +- [Basic Usage](docs/getting-started/basic-usage.md) |
| 77 | +- [Core Concepts](docs/getting-started/core-concepts.md) |
50 | 78 |
|
51 | 79 | ### Validation Guides |
52 | | -- 🔤 [String Validation](docs/guides/string-validation.md) - Email, URL, patterns, length constraints |
53 | | -- 🔢 [Numeric Validation](docs/guides/numeric-validation.md) - Integers, floats, ranges, constraints |
54 | | -- 📋 [Array Validation](docs/guides/array-validation.md) - Indexed arrays and item validation |
55 | | -- 🏗️ [Object & Schema Validation](docs/guides/object-validation.md) - Complex nested structures |
56 | | -- ⚙️ [Custom Validation](docs/guides/custom-validation.md) - User-defined functions and business logic |
57 | | -- ❌ [Error Handling](docs/guides/error-handling.md) - Working with validation errors |
| 80 | +- [String Validation](docs/guides/string-validation.md) - Email, URL, patterns, length constraints |
| 81 | +- [Numeric Validation](docs/guides/numeric-validation.md) - Integers, floats, ranges, constraints |
| 82 | +- [Array Validation](docs/guides/array-validation.md) - Indexed arrays and item validation |
| 83 | +- [Object & Schema Validation](docs/guides/object-validation.md) - Complex nested structures |
| 84 | +- [Custom Validation](docs/guides/custom-validation.md) - User-defined functions and business logic |
| 85 | +- [Error Handling](docs/guides/error-handling.md) - Working with validation errors |
58 | 86 |
|
59 | 87 | ### API Reference |
60 | | -- 🏭 [Validator Factory](docs/api-reference/validator-factory.md) |
| 88 | +- [Validator Factory](docs/api-reference/validator-factory.md) |
61 | 89 |
|
62 | 90 | ### Examples |
63 | | -- 📝 [Form Validation](docs/examples/form-validation.md) |
64 | | - |
65 | | -## 📦 Installation |
66 | | - |
67 | | -```bash |
68 | | -composer require lemmon/validator |
69 | | -``` |
70 | | - |
71 | | -**Requirements:** PHP 8.1 or higher |
72 | | - |
73 | | -## 🏃♂️ Quick Examples |
74 | | - |
75 | | -### String Validation with Formats |
76 | | -```php |
77 | | -// Email validation |
78 | | -$email = Validator::isString()->email()->validate('user@example.com'); |
79 | | - |
80 | | -// URL with custom message |
81 | | -$url = Validator::isString() |
82 | | - ->url('Please provide a valid URL') |
83 | | - ->validate('https://example.com'); |
84 | | - |
85 | | -// Pattern matching |
86 | | -$code = Validator::isString() |
87 | | - ->pattern('/^[A-Z]{2}\d{4}$/', 'Code must be 2 letters + 4 digits') |
88 | | - ->validate('AB1234'); |
89 | | -``` |
90 | | - |
91 | | -### Numeric Validation |
92 | | -```php |
93 | | -// Integer with constraints |
94 | | -$age = Validator::isInt() |
95 | | - ->min(18) |
96 | | - ->max(120) |
97 | | - ->validate(25); |
98 | | - |
99 | | -// Form-safe coercion (BREAKING CHANGE in latest version) |
100 | | -$quantity = Validator::isInt() |
101 | | - ->coerce() // Empty strings become null (not dangerous 0) |
102 | | - ->validate(''); // Returns: null |
103 | | - |
104 | | -// Float with precision |
105 | | -$price = Validator::isFloat() |
106 | | - ->positive() |
107 | | - ->multipleOf(0.01) // Cents precision |
108 | | - ->coerce() // Empty strings become null (not dangerous 0.0) |
109 | | - ->validate(19.99); |
110 | | -``` |
111 | | - |
112 | | -### Array Validation |
113 | | -```php |
114 | | -// Array filtering with auto-reindexing |
115 | | -$tags = Validator::isArray() |
116 | | - ->filterEmpty() // Removes '', null but keeps 0, false, [] |
117 | | - ->validate(['php', '', 'javascript', null, 'react']); |
118 | | -// Returns: ['php', 'javascript', 'react'] (properly indexed) |
119 | | - |
120 | | -// With item validation |
121 | | -$numbers = Validator::isArray() |
122 | | - ->items(Validator::isInt()->positive()) |
123 | | - ->filterEmpty() |
124 | | - ->validate([1, '', 2, null, 3]); |
125 | | -// Returns: [1, 2, 3] |
126 | | -``` |
127 | | - |
128 | | -### Data Transformations |
129 | | -```php |
130 | | -// Type-preserving transformations with pipe() |
131 | | -$name = Validator::isString() |
132 | | - ->pipe('trim', 'strtoupper') // Maintains string type |
133 | | - ->validate(' john doe '); // Returns: "JOHN DOE" |
134 | | - |
135 | | -// Type-changing transformations with transform() |
136 | | -$count = Validator::isString() |
137 | | - ->transform(fn($v) => explode(',', $v)) // String → Array |
138 | | - ->pipe('array_unique') // Array operations (auto-reindexed) |
139 | | - ->transform('count') // Array → Int |
140 | | - ->validate('a,b,a,c'); // Returns: 3 |
141 | | - |
142 | | -// Complex multi-type chains |
143 | | -$result = Validator::isArray() |
144 | | - ->pipe('array_unique', 'array_reverse') // Array operations |
145 | | - ->transform(fn($v) => implode(',', $v)) // Array → String |
146 | | - ->pipe('trim', 'strtoupper') // String operations |
147 | | - ->transform('strlen') // String → Int |
148 | | - ->validate(['a', 'b', 'a']); // Returns: 3 |
149 | | -``` |
150 | | - |
151 | | -### Custom Validation |
152 | | -```php |
153 | | -// Context-aware validation with optional error message |
154 | | -$passwordConfirm = Validator::isString()->satisfies( |
155 | | - function ($value, $key, $input) { |
156 | | - return isset($input['password']) && $value === $input['password']; |
157 | | - }, |
158 | | - 'Password confirmation must match password' |
159 | | -); |
160 | | - |
161 | | -// Shorter syntax with default error message |
162 | | -$positiveNumber = Validator::isInt()->satisfies(fn($v) => $v > 0); |
163 | | -``` |
164 | | - |
165 | | -### Advanced Logic |
166 | | -```php |
167 | | -// Logical combinators |
168 | | -$flexibleId = Validator::anyOf([ |
169 | | - Validator::isInt()->positive(), |
170 | | - Validator::isString()->uuid() |
171 | | -]); |
172 | | - |
173 | | -$strictUser = Validator::allOf([ |
174 | | - Validator::isAssociative(['name' => Validator::isString()]), |
175 | | - Validator::isAssociative(['email' => Validator::isString()->email()]) |
176 | | -]); |
177 | | - |
178 | | -$notBanned = Validator::not( |
179 | | - Validator::isString()->oneOf(['banned', 'suspended']), |
180 | | - 'User cannot be banned or suspended' |
181 | | -); |
182 | | -``` |
| 91 | +- [Form Validation](docs/examples/form-validation.md) |
183 | 92 |
|
184 | | -## 🤝 Contributing |
| 93 | +## Contributing |
185 | 94 |
|
186 | 95 | We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. |
187 | 96 |
|
188 | | -## 📄 License |
| 97 | +## License |
189 | 98 |
|
190 | 99 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. |
191 | 100 |
|
192 | | -## 🔗 Links |
| 101 | +## Links |
193 | 102 |
|
194 | 103 | - [Packagist](https://packagist.org/packages/lemmon/validator) |
195 | 104 | - [GitHub Repository](https://github.com/lemmon/validator-php) |
|
0 commit comments