Design Decisions
Non-Goals
These features are intentionally out of scope:
- ORM/Database integration - Keep library focused on data transfer
- Request handling - Framework-specific, use adapters
- Dependency injection - Use framework DI containers
- Runtime reflection - Defeats purpose of code generation
Validation
The library provides minimal built-in validation:
- Required Fields
- Type Checking
For more complex validation, you can use a service like Respect/Validation.
We dont want to bloat the library with complex validation rules that are better handled by dedicated libraries.
Best Practices
Keep DTOs simple - DTOs are data containers, not business logic holders. Consider using a separate validation service for complex rules.
Fail fast - Use
requiredfor fields that must always be present. This catches errors at construction time.Use type hints - PHP's type system already validates types. A
stringfield won't accept an integer.Validate at boundaries - Validate input at API/form boundaries, not deep in business logic.
Don't over-validate - Internal DTOs used between trusted services don't need the same validation as user input DTOs.
Test validation - Write unit tests for your validation rules.
Custom Casters / Transformers
Current features are sufficient:
// Input transformation
->factory('fromArray') // Calls ClassName::fromArray($value)
->factory('fromString') // Calls ClassName::fromString($value)
->factory('External::create') // Calls External::create($value)
// Output transformation
->serialize('array') // Calls $obj->toArray()
->serialize('string') // Calls $obj->__toString()
->serialize('FromArrayToArray') // Full round-tripPlus auto-detection for:
- FromArrayToArrayInterface
- JsonSerializable
- Enums (backed/unit)
Additional casters add complexity without real benefit.
Readonly Properties (PHP 8.1+)
- Generate truly readonly DTO properties.
Low value for this library because:
- Breaks current patterns - fromArray(), setFromArray(), touched tracking don't work with readonly
- Already have immutable DTOs - with*() pattern works and is more flexible
- Public properties - Forces public visibility (debatable if good, we don't think so)
Verdict: Nope. The current immutable DTO with with*() methods is more flexible for this use case.
Best Practices
- Use immutable DTOs for readonly behavior.
- Avoid mixing mutable and immutable DTOs.
Additional Serialization Formats (XML, CSV)
Not at this point.
Reason: Out of scope. Use symfony/serializer for complex serialization needs.
Advanced Type Constraints (positive-int, non-empty-string)
Not at this point.
Reason: Validation concern, not type concern. Use validation rules instead.
Computed Properties
No need on the generated part.
Reason: Use traits instead.
// config
Dto::create('User')->traits(\App\Traits\UserComputedTrait::class)->fields(...)
// trait
trait UserComputedTrait {
public function getFullName(): string {
return $this->firstName . ' ' . $this->lastName;
}
}Lifecycle Hooks
Not needed.
Reason: Use traits instead.
class UserDto extends AbstractUserDto {
protected function setFromArray(...): static {
// pre-processing
parent::setFromArray(...);
// post-processing
return $this;
}
}Constructor promotion (PHP 8.0+)
The real value of constructor promotion:
- Reduces boilerplate in hand-written code
- Our code is generated - verbosity isn't a maintenance burden
Verdict: Not useful for this library. The flexibility of optional fields and incremental population is more valuable than the syntactic sugar of constructor promotion.