potagerphp / grape
Type-safe schema validation and data sanitization for PHP 8.2+.
Package info
pkg:composer/potagerphp/grape
Requires
- php: >=8.2
Requires (Dev)
- mockery/mockery: ^1.6
- pestphp/pest: ^3.8
- phpstan/phpstan: ^2.1
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
potager / grape
Type-safe schema validation, data sanitization, and structural contracts for PHP.
Grape is a zero-dependency schema validation and sanitization library for PHP 8.2+. Inspired by declarative schema ecosystems like Zod, Grape allows you to define declarative contracts, validate untrusted inputs, sanitize values in-place, and receive strongly-typed output with precise, nested error trees.
Highlights
- Zero Dependencies — Pure, lightweight PHP with no third-party runtime overhead.
- Fluent & Type-Safe — Full IDE autocompletion with chainable, expressive constraint rules.
- In-Place Sanitization — Transform data during parsing (
trim(),lowercase(),clamp(),compact()). - Flexible Flow Control — Choose between traditional exception throwing or functional non-throwing tuples
[$error, $data]. - Dual Coercion Modes — Loose mode (default) casts HTTP strings (e.g.
"42"→42,"true"→true), while strict mode enforces exact native types. - First-Class Composition — Nest complex schemas, homogeneous collections, and fixed-size tuples with deduplication and index normalization.
- Customizable Error Collectors — Output error trees, flat maps, or fast-fail on the first encountered error.
Installation
Install Grape via Composer:
composer require potagerphp/grape
Quickstart
1. Basic Schema & In-Place Sanitization
use Potager\Grape\Grape; // Define your validation contract $userSchema = Grape::schema([ 'name' => Grape::string()->trim()->minLength(2)->maxLength(50)->required(), 'email' => Grape::string()->trim()->lowercase()->email()->required(), 'age' => Grape::integer()->min(18)->max(120)->optional(), 'role' => Grape::literal('admin', 'editor', 'viewer')->default('viewer'), 'metadata' => Grape::schema([ 'tags' => Grape::collection(Grape::string())->distinct(), 'active' => Grape::boolean()->default(true), ])->optional(), ]); // Parse & sanitize untrusted payload $cleanData = $userSchema->validate($_POST);
2. Functional Non-Throwing Flow (check())
If you prefer to avoid try / catch blocks in your controllers or middleware, use the functional check() method:
[$error, $data] = $userSchema->check($requestPayload); if ($error !== null) { // $error->getMessages() returns a structured error array return response()->json([ 'success' => false, 'errors' => $error->getMessages(), ], 422); } // $data is guaranteed clean, sanitized, and typed $userService->register($data);
Core Capabilities
Strict vs. Loose Type Coercion
By default, Grape operates in loose mode, making it seamless to validate HTTP form submissions where numbers and booleans arrive as strings:
// Loose mode (Default) Grape::integer()->validate("42"); // Returns (int) 42 Grape::boolean()->validate("true"); // Returns (bool) true // Strict mode (Ideal for pure JSON APIs) Grape::integer(strict: true)->validate("42"); // Throws ValidationException Grape::integer(strict: true)->validate(42); // Returns 42
Homogeneous Collections & Tuples
// Homogeneous List with sanitization & deduplication $tagsValidator = Grape::collection(Grape::string()->trim()->lowercase()) ->min(1) ->max(10) ->distinct() ->skipInvalids(); // Fixed-size, ordered Tuple (e.g., Geo Coordinates: [lat, lng]) $coordinatesValidator = Grape::tuple([ Grape::float()->min(-90)->max(90), Grape::float()->min(-180)->max(180), ]);
Custom Error Messages & Dynamic Placeholders
Customize feedback per rule with dynamic interpolation tokens:
$validator = Grape::string() ->minLength(5, 'The :field is too short (min :min characters, got :value).') ->email('Please provide a valid corporate email address.');
Supported placeholders include:
:field— The attribute path (e.g.user.emailoritems.0.sku):value— The actual input value provided:min/:max/:length— Boundary parameters for the active rule
Error Collectors
Choose how errors are gathered and formatted:
// 1. Nested Tree Collector (Default) - mirrors input shape Grape::useTreeCollector(); // 2. Flat Dot-Notation Collector - e.g. ['user.email' => [...]] Grape::useFlatCollector(); // 3. First Error Collector - stops validation at first error for maximum performance Grape::useFirstErrorCollector();
Supported Types Reference
| Type Validator | Description | Key Methods |
|---|---|---|
Grape::string() |
String validation & string sanitization | minLength(), maxLength(), email(), url(), uuid(), regex(), trim(), lowercase(), uppercase() |
Grape::number() |
Unified numeric validation (int or float) | min(), max(), positive(), negative(), clamp() |
Grape::integer() |
Exact integer numbers | min(), max(), even(), odd(), clamp() |
Grape::float() |
Floating-point numbers | min(), max(), precision(), clamp() |
Grape::boolean() |
Boolean flag parsing | truthy(), falsy(), customizable boolean-like mappings |
Grape::accepted() |
Terms / agreement checkboxes | yes, on, 1, true acceptance |
Grape::schema() |
Associative object/dictionary schemas | strict(), ignoreExtraKeys(), presence contracts |
Grape::collection() |
Homogeneous indexed arrays | distinct(), skipInvalids(), min(), max() |
Grape::tuple() |
Fixed-length ordered heterogeneous arrays | Exact index-by-index positional validation |
Grape::literal() |
Exact scalar value matching (Enums / Discriminated Unions) | literal('draft', 'published', 'archived') |
Grape::null() |
Explicit null value validation |
Ensures target value is strictly null |
Grape::mixed() |
Untyped pass-through with custom constraints | Custom closures and pre-conditions |
Testing
Grape is tested with Pest and statically analyzed with PHPStan:
# Run test suite composer test # Run static analysis composer analyse
License
Grape is open-sourced software licensed under the MIT license.
Part of the Potager PHP project.