v0.3.0
Released on the main branch as Composer tag v0.3.0.
Upgrade summary
{
"require": {
"better-route/better-route": "^0.3.0"
}
}
Most consumers can upgrade with composer update better-route/better-route. The only behavior change that is likely to require an explicit decision is the OpenAPI document endpoint default permission — see the breaking change list below.
Security and hardening
- Route
idfrom URL preferred over merged params. Resource and Woo handlers readidviaget_url_params()first; query/bodyidis only used when the URL does not provide one. - Hardened error responses. For non-
ApiExceptionfailures with status >= 500, the response is normalized to"Unexpected error."with emptydetails— internal exception class and message no longer leak. - JWT hardening.
Hs256JwtVerifiernow requiresexpby default (requireExpiration: true), supports optionalexpectedIssuer,expectedAudience,maxLifetimeSeconds, and amaxTokenLength(default 8192 bytes). WpClaimsUserMapperno longer mapssubby default. DefaultidClaimsis['user_id', 'uid', 'wp_user_id']. Re-add'sub'explicitly if you depend on it.- Custom table resources are deny-by-default. Table resources now require an explicit
policy()(orResourcePolicypreset) before any read or write succeeds. CPT resources keep WordPress visibility for reads. - Identity-aware default keys.
CachingMiddleware,IdempotencyMiddleware, andRateLimitMiddlewarederive default keys fromauth.userId/auth.subject, falling back to client IP (rate-limit only) or'guest'. Existing customkeyResolvers are unaffected. - Woo customer endpoints are customer-only.
assertCustomerUser()rejects users without thecustomerrole. Create/update/delete on customers requirescreate_users/edit_user/delete_user. - Protected Woo meta keys (
_...) are not writable or returned by default. Pass$allowProtected = trueonly when you need it. - CPT writes go through WP capability checks. Publish/status/author/delete operations are gated by core capabilities.
WpdbAdapterrejects cross-database table names (containing.) and structured write payloads.- OpenAPI document endpoint defaults to
manage_options. Previously public. Pass'permissionCallback' => static fn (): bool => trueto keep it public. X-Request-IDis sanitized to^[A-Za-z0-9._:-]{1,128}$. Anything else is replaced with a generated id.
New features
Resource layer
Resource::writeSchema()/Resource::payloadSchema()for write validation. Supports types (int,float,bool,string,date,email,url,enum,array,object),required,min/max,minLength/maxLength,regex,enum.values,sanitize,nullable. Validation failure returns400 validation_failedwithdetails.fieldErrors.Resource::fieldPolicy()for field-level write authorization (per action, per field).ResourcePolicypresets:ResourcePolicy::adminOnly(string $cap = 'manage_options')ResourcePolicy::publicReadPrivateWrite(string|array $writeCap = 'manage_options')ResourcePolicy::capabilities(array $permissions)ResourcePolicy::callbacks(array $callbacks)
deleteMode('trash'|'force')for CPT resources (default:'force').
WooCommerce
deleteModeoption ('force'or'trash') for orders, products, and coupons.- Capability-gated writes; protected meta keys (
_...) hidden from output by default.
OpenAPI
strictSchemas => truecausesOpenApiExporter::export()to throwInvalidArgumentExceptionon missing component references instead of substituting a permissive placeholder.
HTTP infrastructure
BetterRoute\Http\ClientIpResolverwith trusted-proxy + trusted-header support (defaults toHTTP_X_FORWARDED_FOR,HTTP_CF_CONNECTING_IP,HTTP_X_REAL_IP).BetterRoute\Middleware\Cache\ETagMiddlewarewithIf-None-Match/304 Not Modifiedand optional weak validators.
Stores and limiters
BetterRoute\Middleware\RateLimit\WpObjectCacheRateLimiter— uses the WP object cache. ThrowsRuntimeExceptionifwp_cache_*is unavailable.BetterRoute\Middleware\Write\WpdbIdempotencyStore—wpdb-backed idempotency store with auto-installSchema().
Developer experience
- Composer scripts run tools through
php vendor/bin/...so missing executable bits on Windows / shared mounts do not break runs. - Expanded regression coverage for security defaults, resource validation, OpenAPI strict mode, ETag handling, and WP-backed stores.
Breaking change checklist
| Change | Action |
|---|---|
OpenAPI doc default manage_options | Add 'permissionCallback' => static fn (): bool => true if the doc must stay public |
| Custom table resources deny-by-default | Add ->policy(ResourcePolicy::publicReadPrivateWrite()) (or another preset) for affected table resources |
JWT exp required | Either include exp in tokens, or pass requireExpiration: false to Hs256JwtVerifier |
sub removed from WpClaimsUserMapper defaults | Re-add 'sub' to idClaims if your tokens rely on it |
| Woo customer endpoints customer-only | Confirm callers operate on customer-role users; update pipelines that touched non-customer users |
| Protected Woo meta keys hidden | Read explicitly with $allowProtected = true if you stored business data under _... keys |
| Identity-aware default cache/idempotency/rate-limit keys | If your existing keys assumed pre-v0.3.0 defaults, expect a one-time cache miss; pass an explicit keyResolver to keep keys stable |
X-Request-ID sanitization | Clients sending arbitrary characters will receive a generated id instead — acceptable in almost all cases |