Releases: laravelui5/odata
`odata:cache` entity-set / type name collision
EdmxWriter::writeEntitySet() emitted each cached entity set as use {ns}\Types\{TypeName}; followed by final readonly class {SetName} implements EntitySetInterface, then called {TypeName}::instance() by short name. When an entity set's name equalled its entity type's name, the import and the class declaration collided and PHP fataled Cannot redeclare class … (previously declared as local import) at autoload time — taking the whole app down, not just OData.
Fix: writeEntitySet() now references the type by FQN — \{$typeFqcn}::instance() (the FQN was already computed locally) — and drops the use {ns}\Types\{TypeName}; line from the generated template. This aligns the method with the rest of EdmxWriter, which already references types by FQN in every other emitter (singleton / type / complex-type / nav-init).
Generator-only change: no runtime behaviour, EDMX shape, or wire format moves — the regenerated classes are byte-identical except for the type reference. Consumers regenerate their Edm/ caches after the bump.
PHP backed enum → Edm.EnumType
Note: this release is additive for primitive-only consumers and surfaces a new opt-in feature for backed-enum columns. It ships as a patch alongside v1.0.2/v1.0.3 because all three current consumers (
laravelui5/core,laravelui5/sdk,pragmatiqu/timesheet.biz) are in-house. SemVer credit is spent here deliberately; the next release that crosses the public funnel boundary should bump the major.
Added
- PHP backed enum →
Edm.EnumTypebridge. Declare a column as a PHP int-backed enum class-string and the engine reflects it into an<EnumType>element in$metadataand emits the symbolic member name on the wire ("tier":"Single").
public function columns(): array
{
return [
'id' => EdmPrimitiveType::Int64,
'tier' => LicenseTier::class, // ← new
];
}Implementation lives across EnumType::fromBackedEnum(), AbstractEntitySet::entityType(), EdmBuilder::addEntityType() (now auto-registers enum types it discovers), EdmBuilder::addEnumType() (now idempotent on identical re-registration, throws on same-qualified-name collisions with different definitions), and Protocol\Execution\RowCoercion (per-property value→name lookup, unknown ints fall through unchanged).
Changed
EdmBuilder::addEntityType()now walks declared properties to auto-register anyEnumTypeInterfaceit finds on the schema. Consumers never touchaddEnumType()directly. Latent inconsistencies (manually pre-registered enum + entity-type-carried enum with diverging definitions) now throw\LogicExceptionat build time.
Migration
No code changes required — composer update picks up the bridge.
EdmPrimitiveType column declarations continue to work exactly as before.
To adopt for an enum-typed column: swap 'tier' => EdmPrimitiveType::Int64 for 'tier' => LicenseTier::class, then drop any UI5 formatter that was reconstructing the label client-side. The new wire payload is what sap.ui.model.odata.type.Enum parses by default.
Out of scope (deferred)
String-backed enums, IsFlags, complex types, POST/PATCH deserialization ("Single" → 1). Open them as new atoms when a consumer asks.
RFC 3339 wire coercion for temporal types
Note: this release contains a wire-format change (UI5 consumers using
targetType: 'any'workarounds will see different bytes on the wire). It ships as a patch because all three current consumers (laravelui5/core,laravelui5/sdk,pragmatiqu/timesheet.biz) are in-house and patched in lock-step with the release. SemVer credit is spent here deliberately; a future release that crosses the public funnel boundary should bump the major.
Fixed
Edm.DateTimeOffset/Edm.Date/Edm.TimeOfDaywire format. Row-emission path now coerces declared temporal columns to OData v4 / RFC 3339 strings before JSON encoding. Previously, MySQLDATETIME/DATE/TIMEvalues (e.g.2026-05-05 12:34:56) passed through unchanged — invalid per the spec, returningNaNfromDate.parse()in Safari and silently coerced to local time in Chrome.
Coercion happens via Carbon::parse(...):
Edm.DateTimeOffset→->toRfc3339String()Edm.Date→->toDateString()(Y-m-d)Edm.TimeOfDay→->format('H:i:s')
Null values on nullable columns pass through. Already-correct strings round-trip cleanly. Implementation lives in Protocol\Execution\RowCoercion; both EntitySetHandler and EntityHandler apply it.
Migration
No code changes required in consumer PHP. composer update picks up the fix; columns declared as EdmPrimitiveType::DateTimeOffset (or Date / TimeOfDay) start emitting spec-compliant strings automatically.
UI5 frontends should drop two now-obsolete workarounds:
targetType: 'any'on bindings against temporal columns — the default type parsers now work correctly.- Custom formatters that re-parse MySQL-style strings — the wire is already RFC 3339.
grep -rn "targetType.*'any'" <ui5-source-roots> is a reasonable starting survey.
Edm namespace cleanup
Note: this release is technically API-breaking (enum rename + interface relocations). It ships as a patch because all three current consumers (
laravelui5/core,laravelui5/sdk,pragmatiqu/timesheet.biz) are in-house and patched in lock-step with the release. SemVer credit is spent here deliberately; a future release that crosses the public funnel boundary should bump the major.
Changed (breaking)
- Renamed
LaravelUi5\OData\Edm\Contracts\Container\PrimitiveTypeEnum→LaravelUi5\OData\Edm\EdmPrimitiveType. The new name describes what the type is (the EDM primitive type) rather than what PHP shape it happens to take (an enum). Drops the deadEnumsuffix; gains theEdmprefix shared withEdm.String,Edm.EnumType, etc. Lives flat underEdm\because vocabulary enums are PHP vehicles, not contracts. - Relocated three type-level contracts misfiled under
Edm\Contracts\Container\. CSDL §13 reservesEntityContainerfor
EntitySet,Singleton,ActionImport,FunctionImportonly; type-level constructs are Schema-level peers ofEntityContainer, not children of it. Moved:Contracts\Container\PrimitiveTypeInterface→Contracts\Type\PrimitiveTypeInterfaceContracts\Container\EnumTypeInterface→Contracts\Type\EnumTypeInterfaceContracts\Container\EnumMemberInterface→Contracts\Type\EnumMemberInterface
After this move Edm\Contracts\Container\ is aligned with the CSDL §13 surface (Container children only) — symmetric with Edm\Container\ on the implementation side.
- No deprecation alias is shipped. Consumers update
usestatements andPrimitiveTypeEnum::*references in the same release dance.
Migration
find <consumer-source-roots> -name '*.php' -exec sed -i '' \
-e 's|LaravelUi5\\OData\\Edm\\Contracts\\Container\\PrimitiveTypeEnum|LaravelUi5\\OData\\Edm\\EdmPrimitiveType|g' \
-e 's|LaravelUi5\\OData\\Edm\\Contracts\\Container\\PrimitiveTypeInterface|LaravelUi5\\OData\\Edm\\Contracts\\Type\\Primi
tiveTypeInterface|g' \
-e 's|LaravelUi5\\OData\\Edm\\Contracts\\Container\\EnumTypeInterface|LaravelUi5\\OData\\Edm\\Contracts\\Type\\EnumTypeIn
terface|g' \
-e 's|LaravelUi5\\OData\\Edm\\Contracts\\Container\\EnumMemberInterface|LaravelUi5\\OData\\Edm\\Contracts\\Type\\EnumMemb
erInterface|g' \
-e 's|PrimitiveTypeEnum|EdmPrimitiveType|g' \
{} \; Afterwards grep for PHP-escaped class-string literals — sed's single-backslash pattern misses 'LaravelUi5\OData\…' (double-backslash in source). The pattern that catches them:
grep -rn 'Contracts\\\\Container\\\\\(EdmPrimitiveType\|PrimitiveTypeInterface\|EnumTypeInterface\|EnumMemberInterface\)' <consumer-source-roots>Support Laravel 13.x
v1.0.1 WIP allow Laravel 13
🎉 LaravelUi5 OData 1.0.0 — First Stable Release
v1.0.0 Public release