diego-ninja / granite
A lightweight zero-dependency PHP library for building immutable, serializable objects with validation capabilities.
Requires
- php: ^8.3|^8.4
Requires (Dev)
- fakerphp/faker: ^1.23
- laravel/pint: ^1.24
- mockery/mockery: ^1.6
- nesbot/carbon: ^3.10
- phpstan/phpstan: ^2
- phpunit/phpunit: ^11.0
- rector/rector: ^2.0
- squizlabs/php_codesniffer: ^3.7
- dev-main
- v1.6.0
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.1
- v1.4.0
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.2
- v1.2.0
- v1.1.0
- v1.0.0
- dev-feature/performance-optimizations
- dev-feature/uuid_hydration
- dev-hotfix/coderabbit_review
- dev-coderabbitai/docstrings/00ed8aa
- dev-feature/peeble_immutable_dto
- dev-feature/hydration_from_plain_object
- dev-feature/comparation_capabilities
- dev-feat/support-default-enum-for-unexpected-values
- dev-feat/deserialization/support-default-case
- dev-feature/maybe_option_monads
This package is auto-updated.
Last update: 2026-03-03 16:13:28 UTC
README
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 - Lightweight Immutable Snapshots
Pebble is a lightweight alternative to Granite for when you need immutable snapshots without validation overhead โ ideal for caching, Eloquent snapshots, and fast comparisons.
$snapshot = Pebble::from($eloquentModel); $snapshot->name; // Magic __get $snapshot['email']; // ArrayAccess $snapshot->equals($other); // O(1) fingerprint comparison
๐ Read Pebble Documentation
โจ Features
- 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 โ Fast path for simple DTOs (~3.5x vs plain PHP), WeakMap caching, direct property access
๐ Quick Start
Installation
composer require diego-ninja/granite
Basic Usage
use 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();
๐ Documentation
Core Concepts
- 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
Guides
- Migration Guide โ Migrate from arrays, stdClass, Doctrine, Laravel
- Troubleshooting โ Common issues and solutions
๐ Performance & Benchmarks
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.
Benchmark Results
Benchmarked on PHP 8.4, comparing Granite against plain PHP constructors and Pebble (Granite's lightweight companion). The test DTO has 6 fields (int, string, string, int, string, bool).
Object Creation
| Benchmark | ยตs/op | vs Plain PHP |
|---|---|---|
| Plain PHP constructor | 0.33 | โ |
Granite::from(array) |
1.14 | 3.5x |
Granite::from(named args) |
1.13 | 3.4x |
Pebble::from(array) |
3.62 | 11.1x |
Nested Object Creation (3 objects)
| Benchmark | ยตs/op | vs Plain PHP |
|---|---|---|
| Plain PHP constructors | 0.78 | โ |
Granite::from(array) |
3.63 | 4.6x |
Pebble::from(array) |
6.80 | 8.7x |
Granite recursively applies the fast path to nested Granite-typed properties, so the overhead scales linearly with object depth rather than exploding through the full hydration pipeline.
Serialization
| Benchmark | ยตs/op | vs Plain PHP |
|---|---|---|
Plain PHP toArray() |
0.19 | โ |
Granite array() |
0.25 | 1.3x |
Plain PHP json_encode(array) |
0.25 | โ |
Granite json() |
0.26 | 1.0x |
Both array() and json() results are cached in a WeakMap. Since Granite objects are readonly, the serialized representation never changes, so repeated calls return instantly. The json() method effectively matches native json_encode performance.
Equality Check
| Benchmark | ยตs/op | vs Plain PHP |
|---|---|---|
Plain array === |
0.12 | โ |
Granite equals() |
0.48 | 4.0x |
Pebble equals() (fingerprint) |
0.31 | 2.6x |
For simple DTOs, equals() compares properties directly without building intermediate arrays, with early exit on the first difference.
Collection (100 items, create from array)
| Benchmark | ยตs/op | vs Plain PHP |
|---|---|---|
Plain PHP array_map + constructors |
31 | โ |
Granite array_map + from() |
106 | 3.4x |
Pebble array_map + from() |
343 | 11.1x |
Property Access
Granite uses native PHP readonly promoted properties โ property access is identical to plain PHP objects, with zero overhead:
| Benchmark | ยตs/op |
|---|---|
| Plain PHP readonly | 0.15 |
| Granite readonly | 0.14 |
Pebble __get() |
1.03 |
How it works
Granite's performance comes from three layers of optimization:
-
Fast path detection (
ClassProfile) โ At class-load time, Granite analyzes each class and determines if it can skip the full hydration pipeline. A class qualifies when all constructor parameters are either primitive types (int,string,float,bool,array) or other Granite subclasses, and the class has no special attributes (#[Hidden],#[SerializedName], validation rules, etc.). -
WeakMap caching โ
array()andjson()results are cached in aWeakMapkeyed by object instance. Since Granite objects are readonly, the cache is always valid. When the object is garbage collected, the cache entry is automatically cleaned up. -
Direct property access โ For serialization and comparison, Granite reads properties directly by name (
$instance->$name) instead of going through reflection, metadata lookups, and type conversion.
Classes that don't qualify for the fast path (those with validation attributes, naming conventions, Carbon dates, etc.) use the standard hydration pipeline, which is still optimized with reflection caching and O(1) hidden property lookups.
Running the Benchmarks
php benchmarks/GraniteBench.php
โ ๏ธ Deprecation Notice
GraniteDTO and GraniteVO are deprecated since 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 { }
๐ง Requirements
- PHP 8.3+ โ Takes advantage of modern PHP features
- No dependencies โ Zero external dependencies for maximum compatibility
๐ฆ Installation
composer require diego-ninja/granite
๐ค Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
๐ License
This package is open-sourced software licensed under the MIT license.
๐ Credits
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