greenbean / db-hydration
Lightweight, explicit, type-safe hydration system for mapping database rows into DTOs.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/greenbean/db-hydration
Requires
- php: ^8.3
This package is auto-updated.
Last update: 2025-12-09 15:08:12 UTC
README
A lightweight, explicit, type-safe PHP 8.3 library for hydrating PHP objects from database rows, without requiring an ORM, annotations, attributes, or complex metadata systems.
This library is ideal for applications that:
- Prefer writing SQL rather than generating it
- Use DTOs or readonly value objects
- Want predictable hydration without magical reflection
- Require nested object hydration from JOINed queries
- Want a small, focused library instead of an entire ORM layer
Designed to be framework-agnostic and extremely fast.
Features
- Simple, explicit mappings (no annotations or attributes)
- Hydrator for mapping flat SQL rows to DTOs
- EmbeddedObject for nested hydration (JOIN results)
- DbResults to hydrate directly from a
PDOStatementiterator - IndexedDbResults for key-indexed hydration (e.g.,
$users[$id]) - Zero dependencies
- No reflection
- Supports custom value objects,
fromString(), and callables - Automatic JSON decoding
- Strict error handling for mismatched schemas
Installation
composer require greenbean/db-hydration
Requires PHP 8.3+.
Basic Usage
1. Define a DTO
final readonly class UserDto { public function __construct( public UserId $id, public string $email, public DateTimeImmutable $createdAt, ) {} }
2. Define a mapping
use Greenbean\DbHydration\HydrationMapping; final class UserMapping implements HydrationMapping { public static function map(): array { return [ 'id' => UserId::class, 'email' => 'string', 'created_at' => DateTimeImmutable::class, ]; } }
3. Hydrate rows with the Hydrator
$hydrator = Hydrator::fromMapping(new UserMapping()); $rows = $pdo->query('SELECT id, email, created_at FROM users') ->fetchAll(PDO::FETCH_ASSOC); $users = $hydrator->hydrateRows(UserDto::class, $rows);
Nested Hydration with JOINs
DTOs:
final readonly class ProfileDto { public function __construct( public string $bio, public int $age, ) {} } final readonly class UserWithProfileDto { public function __construct( public UserId $id, public string $email, public ProfileDto $profile, ) {} }
Mapping:
final class ProfileMapping implements HydrationMapping { public static function map(): array { return [ 'bio' => 'string', 'age' => 'int', ]; } }
Create hydrators:
$profileHydrator = Hydrator::fromMapping(new ProfileMapping(), 'profile'); $userHydrator = Hydrator::fromMapping( new UserMapping(), prefix: 'u', injected: [], new EmbeddedObject( name: 'profile', prefix: 'profile', dtoClass: ProfileDto::class, hydrator: $profileHydrator ) );
SQL example:
SELECT u.id AS u_id, u.email AS u_email, p.bio AS profile_bio, p.age AS profile_age FROM users u LEFT JOIN profiles p ON p.user_id = u.id;
Hydrate:
$users = $userHydrator->hydrateRows(UserWithProfileDto::class, $rows);
Streaming Hydration
DbResults
$stmt = $pdo->query('SELECT id, email, created_at FROM users'); $results = new DbResults($hydrator, UserDto::class, $stmt); foreach ($results as $user) { // $user is UserDto }
IndexedDbResults
$stmt = $pdo->query('SELECT id, email, created_at FROM users'); $results = new IndexedDbResults('id', $hydrator, UserDto::class, $stmt); foreach ($results as $id => $user) { // keyed by ID }
Mapping Rules
| Type | Meaning |
|---|---|
int |
Cast to integer |
string |
Cast to string |
bool |
Cast to boolean |
json |
Decode JSON into associative array |
ClassName |
Uses fromString() or constructor |
callable |
Developer-defined custom mapper |
Error Handling
The hydrator throws exceptions for:
- Missing required columns
- Missing embedded columns
- Unrecognized mapping types
- Type conversion failures
Failures are explicit and loud, to catch mistakes early.
Requirements
- PHP 8.3+
- PDO
- Composer
License
MIT License.
Contributing
Pull requests are welcome. Please include:
- A clear summary of the change
- Tests where applicable
- Real-world examples when adding new features