v1.0.0
First stable release of better-data — PHP 8.3+ DTO and Presenter library for WordPress. Released on the main branch as Composer tag v1.0.0.
Install
{
"require": {
"better-data/better-data": "^1.0"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/Lonsdale201/better-data"
}
],
"prefer-stable": true
}
Highlights
DataObject layer
final readonly classDTOs with constructor-promoted properties- Type coercion:
string → int/float/bool, ISO strings →DateTimeImmutable, scalar →BackedEnum, array → nested DataObject, string →Secret, arrays → typed lists via#[ListOf] with()for immutable updates that preserveSecret/DateTime/Enumrich types- Attribute-driven validation:
Required,Email,Url,Uuid,Regex,Min/Max,MinLength/MaxLength,OneOf,Callback - Pluggable
ValidationEngineInterfacefor swapping in Symfony Validator / Respect / Laravel
Sources (read path)
PostSource,UserSource,TermSource,OptionSource,RowSource,RequestSource- WP-independent engine — DTO hydration is unit-testable without bootstrapping WordPress
- Bulk hydration helpers (
fromPosts,fromUsers,fromTerms,fromRows) prewarm post + meta in two SQL queries RequestSourceguards:requireNonce,requireCapability,bodyOnly/jsonOnly/queryOnly/urlOnly,noCollisionfor route-vs-body collision detection
Sinks (write path)
PostSink,UserSink,TermSink,OptionSink,RowSink- Projection-first API (
toArgs/toMeta/toArray) for unit tests - Partial writes via
only: [...]; strict whitelist (UnknownFieldExceptionon typos);skipNullDeletesfor PATCH semantics - Convenience methods slash payloads via
wp_slash(); projection methods leave values raw UserSinkexcludesuser_pass,user_activation_key;TermSinkexcludesterm_taxonomy_id,count
Presenter
Presenter::for($dto)->context(PresentationContext::rest())andPresenter::forCollection- Fluent
only/hide/hideUnlessCan/showOnlyFor/rename/compute/preset/formatDate/formatCurrency/includeSensitive - Locale-aware (
switch_to_locale()for date/currency formatting) - Subclassable: override
configure()for reusable presenters
Security primitives
Secret— leak-proof in-memory container (blocks__toString,json_encode,var_dump,print_r,serialize)#[Sensitive]— Presenter-level redaction for plain-string PII (includeSensitive()opt-in)#[Encrypted]— at-rest AES-256-GCM for meta and option values; transparent decrypt on read- Key rotation via
BETTER_DATA_ENCRYPTION_KEY+BETTER_DATA_ENCRYPTION_KEY_PREVIOUS - Loud failures:
MissingEncryptionKeyException,DecryptionFailedExceptionwith no oracle leak
Registration
MetaKeyRegistry::register(Dto::class, 'post', 'subtype')— drivesregister_meta()from DTO shapetoJsonSchema($dto)— root-object JSON Schema for OpenAPI componentstoRestArgs($dto)— flat per-field map forregister_rest_route(['args' => …])_doing_it_wrongguards: protected-metashowInRestwithoutauthCapability,encrypt: true+showInRest: true
better-route bridge
BetterData\Route\BetterRouteBridge— hydrates and validates DTOs from query / JSON / body / URL params- Marks route-owned fields as URL-authoritative; rejects body/query collisions
- Feeds
MetaKeyRegistry::toRestArgs()into the route builder'sargs() - Feeds generated
requestSchema,responseSchema, tags, scopes into the route builder'smeta() - Presents returned
DataObjectvalues throughPresenterwithPresentationContext::rest() - Optional —
better-routeis not a hard Composer dependency
Requirements
- PHP 8.3+ (readonly classes, typed class constants,
#[\Override],json_validate()) - WordPress (no version floor; sources fall back gracefully when optional WP functions are absent)
ext-opensslonly when using#[Encrypted]
Versioning
Semver from v1.0 onwards. The public API surface — DataObject, attributes, sources, sinks, Presenter, MetaKeyRegistry, BetterRouteBridge — is stable. Breaking changes require a major bump and migration notes.
Source
- Repo: github.com/Lonsdale201/better-data
- Release: v1.0.0