bd-data-object
better-data: Adding a DataObject
For library maintainers and downstream contributors who add or modify a DataObject subclass inside better-data. Every typed shape — production DTOs in src/, test fixtures in tests/Fixtures/, plugin-level DTOs in the companion testbed — extends the abstract DataObject (src/DataObject.php:36) and is the foundation that every engine (sources, sinks, validation, Presenter, REST schema, better-route bridge) reads against.
Misconception this skill corrects
"I'll just declare the constructor parameters with the types I want and set the values when I instantiate the class — defaults are optional."
In better-data, defaults are load-bearing. The hydration entry point DataObject::fromArray (src/DataObject.php:47-79) iterates ReflectionParameters and treats any parameter without isDefaultValueAvailable() (and without allowsNull()) as REQUIRED — throwing MissingRequiredFieldException. PHP's Reflection silently demotes earlier-positioned defaults to "required" if a later parameter has no default — so a single missing default at the end cascades and breaks ::fromArray for the whole DTO.
Other AI-prone misconceptions:
- "I'll add
encrypt: trueto theMetaKeyand store the property as a plainstring." Wrong shape —#[Encrypted]writes ciphertext but the in-memory value is still a plain string that leaks viavar_dump/print_r/serialize. UseSecretas the property type. - "I'll add a public mutator method (
setEmail()) to make consumer code more ergonomic." Wrong — every DTO isfinal readonly class. Mutation is$dto->with(['email' => 'new@example.com'])which returns a NEW instance. Mutators break the immutability contract thatSecret, route-side projection, and Presenter caching all depend on.
When to use this skill
Trigger when ANY of the following is true: