Repository navigation
Casts 2.0: Class-based cast hierarchy and schema-to-Row auto-wiring #192
Description
Background
Issue #13 added automated data typing via a callable-based $cast attribute on Column and a $casts array on Row (via Traits\Cast). This is the "wimpy" but practical foundation — any PHP callable works, auto-detection covers common types, and it ships without any schema wiring.
This issue tracks the next-generation design: a proper Cast class hierarchy, parameterized casts, and automatic schema-to-Row wiring so subclasses need zero boilerplate.
1. Casts/ directory
A new directory parallel to Operators/ and Parsers/, with an abstract base and concrete types:
Casts/
Base.php abstract — single public static cast( mixed $value ): mixed
Boolean.php (bool)
DateTime.php returns a DateTime object or formatted string (TBD)
Decimal.php (float) with configurable precision
Integer.php (int)
Json.php json_decode / json_encode
Serialized.php maybe_unserialize / serialize
Text.php (string)
Static cast() methods — unlike Operators, Cast classes carry no state and fire O(rows × columns) times. Static methods avoid per-property instantiation overhead while still being callable via [Integer::class, 'cast'].
Cast classes passed as the $cast attribute or in $casts are automatically recognised and invoked without the caller needing to reference the method name:
new Column( array( 'name' => 'count', 'type' => 'bigint', 'cast' => Casts\Integer::class ) );2. Parameterized casts
Some types need configuration that a bare callable can't carry. A Casts\Base instance (rather than a static call) solves this:
new Column( array(
'name' => 'amount',
'type' => 'decimal',
'cast' => new Casts\Decimal( precision: 2 ),
) );Column::cast() checks instanceof Casts\Base and calls ->cast( $value ) on the instance, falling back to the existing callable path for everything else. Fully backwards-compatible.
3. Schema-to-Row auto-wiring (Phase 2)
The current $casts array on Row requires manual duplication of what the Schema already knows. The end goal is zero boilerplate for standard types:
class Ad extends Row {
protected $schema = Ads_Schema::class;
// $casts auto-populated from Column definitions — no manual array needed
}Row::get_cast_map() resolves casts in priority order:
- Explicit
$castsoverrides on the subclass - Column
$castattributes from$schema::get_columns() - Auto-detected cast from column type (already in place)
4. Validators 2.0 (companion issue)
The write-side $validate callable is intentionally left as-is for now — it needs Column context ($this->default, $this->unsigned, $this->allow_null) that makes a pure static class awkward. A Validators/ hierarchy is worth a separate issue once the Casts pattern is proven, with the key design question being how to thread Column state into validator instances cleanly.
Compatibility
The callable path ('cast' => 'intval', 'cast' => 'maybe_unserialize') must remain fully supported. Cast classes are additive sugar on top of it, not a replacement.
Activity
Make Column type category a first-class, settable property