Releases: std-out/simple-data-objects
Release list
v1.11.0
v1.10.0
v1.9.0
Added
LowercaseValuePipe/UppercaseValuePipe— case normalization pipes for#[Pipe]-attributed properties, e.g. emails or country/currency codes.
Non-string values pass through untouched (same contract asTrimValuePipe).
Changed
- Documentation site: custom VitePress theme with breadcrumb navigation.
Full Changelog: v1.8.0...v1.9.0
v1.8.0
Slimmer installs
📦 Changed
- Dev-only files (tests, docs, CI config, Docker setup) are now excluded from Composer dist archives via
.gitattributesexport-ignore —composer require std-out/simple-data-objectsdownloads only what runs in production.
Full Changelog: v1.7.2...v1.8.0
v1.7.2
Universal from() + lazy hydration
✨ Added
- Universal
from()— one factory now accepts arrays (unchanged fast path), Eloquent models and anyArrayable,stdClass,JsonSerializable, anyTraversable, JSON strings, plain objects with public properties, and same-class instances (returned as-is). All detection lives on the cold path — the hot array path executes the same opcodes as before. BaseData::fromLazy()— lazy hydration built on native PHP 8.4 lazy ghosts; hydration runs on first property access. With ~10% of objects actually read: ~3× faster on cast-heavy DTOs, ~6× with nested collections.- Integrations docs — Plain PHP, Laravel, Symfony, Slim/PSR-7, plus an
opcache.preloadrecipe.
⚡ Changed
- Compiled hydrator hoisted out of collection loops (
TypedDataCollection::of(),lazyCollection()): +19% on collection hydration (220k → 267k ops/s). fromJson()is now an explicit alias offrom().
🐛 Fixed
HydratorCompiler::compile()fails fast when given a non-BaseDataclass.
Full Changelog: v1.4.3...v1.7.2
v1.4.3
Full Changelog: v1.1.15...v1.4.3
feat: compile per-class hydrators/serializers, warmable cache, lazy collections
Performance refactor: from() and toArray() now execute a specialized
closure generated per data class (plain properties become inline array
reads; casts, enums, nested DTOs, collections, and pipes delegate to the
existing runtime via captured metadata — behavior is unchanged).
Steady-state throughput: hydration ~2.6x, serialization ~2.2x over the
interpreted path.
- Add HydratorCompiler and SerializerCompiler (eval once per class per
process, in-memory registries, flush hooks); remove the interpreted
Hydrator; extract ValueNormalizer for compiled serializers - Precompute ParameterMeta::$isPlain so hot paths skip ValueCaster;
inline the is_array() input check; use it in with() overrides too - Cache format v2: .meta.php files now carry the compiled hydrator and
serializer alongside the metadata — a warmed FPM worker pays neither
reflection nor eval (opcache serves the whole file); legacy v1 files
still load - Add vendor/bin/sdo-warm + Support\CacheWarmer: scans sources (PSR-4
dirs from composer.json by default) for concrete BaseData subclasses
and pre-builds the cache on deploy; fails fast on invalid DTO
definitions, reports non-exportable classes as skipped - Add BaseData::lazyCollection() for streaming large iterables with a
flat memory profile (~0.26 MB peak for 50k rows vs ~13 MB materialized) - Docs: compiled hot path, pre-warming guide, streaming collections,
README performance section
245 tests, 100% coverage.
v1.1.15
Hardened enums & encryption coverage
✨ Added
- Test coverage for
EncryptedCast(XSalsa20-Poly1305) and expanded enum-handling scenarios.
⚡ Changed
- Improved enum handling and metadata caching.
- CI: dynamic coverage badge, GitHub Actions bumped to latest versions.
Full Changelog: v1.0.0...v1.1.15
v1.0.0
What's New
🔄 Pipes
Transform incoming values before hydration using the new #[Pipe] attribute. Apply pipes per-property or globally via class-level #[Pipe]:
#[Pipe(TrimStringsPipe::class, NullifyEmptyStringsPipe::class)]
class ContactData extends BaseData
{
public function __construct(
#[Pipe(UpperCasePipe::class)]
public readonly string $name,
public readonly ?string $comment,
) {}
}
- Built-in pipes:
TrimStringsPipe,NullifyEmptyStringsPipe,TrimValuePipe,NullifyEmptyStringValuePipe. - Implement
ValuePipeto create custom pipes.
🔀 Key Transformation
Automatically remap camelCase input keys to snake_case, kebab-case or STUDLY_CASE using #[TransformKeys]:
#[TransformKeys(TransformKeys::SNAKE_CASE)]
class UserData extends BaseData { ... }
💾 File-based Metadata Cache
Cache resolved class metadata to disk for near-zero overhead in production:
MetadataRegistry::setStoragePath(storage_path('framework/sdo'));
- Supports atomic writes and
__set_state-based serialization. - Skips non-exportable classes gracefully.
🛠️ New BaseData & Collection Methods
| Method | Description |
|---|---|
tryFrom(mixed $data) |
Returns null instead of throwing on failure |
with(...$overrides) |
Returns a new instance with overridden fields |
equals(self $other) |
Compares two instances by value |
diff(self $other) |
Returns array of changed fields |
only(string ...$keys) |
Returns a subset of fields |
except(string ...$keys) |
Returns all fields except specified |
fromValidated(mixed $data) |
Hydrates after Laravel validation |
validate(mixed $data) |
Runs validation without hydration |
TypedDataCollection
$collection->last(); // returns last item or null
🧪 100% Test Coverage
All classes, methods, and lines are covered. Enforced in CI via pcov.