A powerful, zero-dependency PHP library for building immutable, serializable objects with validation and mapping capabilities. Perfect for DTOs, Value Objects, API responses, and domain modeling.
Pebble is a lightweight alternative to Granite for defensive, immutable snapshots without validation overhead β ideal for caching, Eloquent snapshots, and fast comparisons. Nested arrays, userland objects, and dates are copied so later source mutations cannot change the snapshot.
$snapshot = Pebble::from($eloquentModel);
$snapshot->name; // Magic __get
$snapshot['email']; // ArrayAccess
$snapshot->equals($other); // O(1) fingerprint comparisonπ Read Pebble Documentation
- Immutable Objects β Read-only DTOs and Value Objects, thread-safe by design
- Flexible
from()Method β Create from arrays, JSON, named parameters, other Granite objects, or mix them - Comprehensive Validation β 30+ built-in rules including Carbon date validation (Age, Future, Past, BusinessDay...)
- ObjectMapper β Convention-based property mapping between objects with custom transformations
- Smart Serialization β Custom property names, naming conventions, hidden fields, Carbon date formats
- Object Comparison β Deep equality with
equals(), detailed diffs withdiffers() - Performance Optimized β Conservative fast path for simple DTOs, deep immutable WeakMap caching, direct property access
composer require diego-ninja/graniteuse Ninja\Granite\Granite;
use Ninja\Granite\Validation\Attributes\Required;
use Ninja\Granite\Validation\Attributes\Email;
use Ninja\Granite\Validation\Attributes\Min;
use Ninja\Granite\Serialization\Attributes\Hidden;
final readonly class User extends Granite
{
public function __construct(
#[Required]
#[Min(2)]
public string $name,
#[Required]
#[Email]
public string $email,
#[Hidden]
public ?string $password = null,
) {}
}
// Create from array
$user = User::from(['name' => 'John Doe', 'email' => 'john@example.com', 'password' => 'secret']);
// Create from named parameters
$user = User::from(name: 'John Doe', email: 'john@example.com');
// Immutable updates
$updated = $user->with(['name' => 'Jane Doe']);
// Serialization (password hidden automatically)
$json = $user->json(); // {"name":"John Doe","email":"john@example.com"}
$array = $user->array();- Enhanced from() Method β Multiple invocation patterns for flexible object creation
- Validation β Comprehensive validation system with 30+ built-in rules including Carbon
- Serialization β Control how objects are converted to/from arrays and JSON with Carbon support
- Object Comparison β Deep equality checks and difference detection
- ObjectMapper β Powerful object-to-object mapping with conventions
- Pebble β Lightweight immutable snapshots with fingerprinting
- Advanced Usage β Patterns for complex applications
- API Reference β Complete API documentation
- Migration Guide β Migrate from arrays, stdClass, Doctrine, Laravel
- Troubleshooting β Common issues and solutions
Granite uses a multi-layer fast path system that detects simple DTOs at class-load time and bypasses the full hydration pipeline (reflection, metadata, type conversion) entirely. For objects that qualify, the overhead vs plain PHP constructors is minimal.
Measured on PHP 8.5.10 with OPcache CLI enabled, using the median of 7 repetitions with 100,000 operations each:
| Scenario | Β΅s/op | Improvement |
|---|---|---|
| Plain PHP constructor | 0.147 | control |
Granite::from(array) |
0.284 | β |
Granite::from(JSON) |
1.019 | 6.11x faster |
Granite::from(object) |
1.000 | 5.59x faster |
Granite::from(Granite) |
0.898 | 7.50x faster |
| Array-property DTO hydration | 0.201 | 11.69x faster |
| Validated DTO hydration | 4.691 | 1.96x faster |
Cached array() |
0.040 | 22.58x faster |
Cached nested array() |
0.040 | 31.56x faster |
| General array serialization | 1.175 | 1.39x faster |
| ObjectMapper to plain object | 1.869 | 1.11x faster |
| ObjectMapper to Granite | 1.550 | 1.14x faster |
Improvements compare the same benchmark against the pre-optimization baseline. Full samples and methodology are recorded in the performance optimization plan.
Granite's performance comes from three layers of optimization:
-
Capability-specific fast paths (
ClassProfile) β Hydration, serialization, and comparison are evaluated independently. Exact array properties can use constructor hydration while still using recursive general serialization. -
WeakMap caching β
array()andjson()results are read before repeating graph analysis and are cached only for deeply immutable graphs. Runtime serialization configuration invalidates cached values. -
Compiled metadata β Reflection objects, validation attributes, validators, class date providers, and property transformers are reused across operations.
Classes that need conversion or custom naming use the general pipeline, with cached metadata and early exits for scalar values.
php benchmarks/GraniteBench.php
composer bench:objects
composer bench:objects -- --json --iterations=100000 --repetitions=7GraniteDTO was already deprecated before v2.0.0, and GraniteVO is deprecated as of v2.0.0, in favor of the unified Granite base class. Both still extend Granite for backward compatibility but will be removed in v3.0.0.
// β Deprecated β use Granite instead
final readonly class User extends GraniteVO { }
// β
final readonly class User extends Granite { }- PHP 8.3+ β Takes advantage of modern PHP features
- No dependencies β Zero external dependencies for maximum compatibility
composer require diego-ninja/graniteContributions are welcome! Please see CONTRIBUTING.md for details.
This package is open-sourced software licensed under the MIT license.
This project is developed and maintained by π₯· Diego Rin in his free time.
If you find this project useful, please consider:
- β Starring the repository
- π Reporting bugs and issues
- π‘ Suggesting new features
- π§ Contributing code improvements
Made with β€οΈ for the PHP community