Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Odata Laravel Package

laravelui5/odata

Laravel UI5 OData adds OData service support to Laravel, letting you expose Eloquent models with query options such as $filter, $select, $expand, $orderby, and $count. It integrates with Laravel routing and is designed for SAPUI5/OpenUI5 frontends.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

Feature and roadmap decisions

  • Ship data-fidelity features with confidence. Metadata now carries Nullable, Precision/Scale and MaxLength from the actual column schema, so Fiori/UI5 controls (notably sap.ui.model.odata.type.Decimal) format and validate against real constraints instead of a bare Edm.Decimal. This is the difference between a demo and a production screen for any customer binding currency, quantity or free-text fields two-way.
  • Support installation-specific business rules without forking. ColumnFacetResolverInterface is a supported extension seam: one binding, applied per column, composed with #[ODataProperty(precision:, scale:, nullable:)] on individual models. Price decimals that differ per installation become configuration, not a code branch. This unlocks multi-tenant/regulatory variants that were previously "no-bid".
  • Unblock enterprise reporting and paging UX. @odata.nextLink now preserves $filter, $orderby, $select, custom options and $count, so Excel "Load more", Power BI and UI5 growing lists return the second page of the same query rather than an unfiltered slice of the whole set. Previously documented workarounds/warnings can be removed from onboarding docs.
  • Count aggregations without extra round-trips. $expand=lines($count=true) now emits [email protected] from a single withCount sub-select at any nesting depth, surviving $select. This removes a strong argument for custom aggregation endpoints.
  • Correctness over silent wrongness is now a product promise. Unsupported $filter constructs answer 501 unsupported_filter with the construct named; malformed ones answer 400 invalid_filter; errors before the first row keep their status code instead of arriving after a 200. That is a supportability win: the ticket says what failed, not "the grid is empty".
  • Polymorphic relations are excluded from discovery by design, with documented explicit alternatives. This is a scoping decision to accept, not fight: metadata no longer advertises navigations whose target varies per row.
  • Build vs. buy. Every release like this (facets, paging links, filter translation, key validation) is spec surface you would otherwise re-implement and regression-test against UI5, Excel and Power BI. Buy, and budget for periodic upgrades.
  • Use cases strongly served: Fiori/UI5 line-of-business apps over Laravel data; Excel and Power BI self-service consumption; partner integrations needing a documented, standards-based read API; internal read models that must not leak unfiltered/full-table results.

Decisions this release forces you to make

  • Cache lifecycle policy. A cached service (odata:cache) now serves byte-identical $metadata warm and cold — but it bakes in the resolver's answer at build time. When an installation's facet facts change, the cache must be rebuilt. Decide who owns that trigger (deploy hook vs. admin action) and document it before customers hit a stale-schema support call.
  • Upgrade risk triage. Clients that were quietly receiving everything or nothing from unsupported filters will now receive errors; clients relying on the old bare ?$skip=n nextLink or on polymorphic navigations appearing in metadata will see behavior change. Plan a release note, a client-compat check and a metadata diff between environments.

When to Consider This Package

Adopt when most of these hold

  • The stack is Laravel + Eloquent and the data model is already expressed as models and relations; discovery is doing the schema work for you.
  • The consumer is a standards-driven OData V4 client — SAP UI5/Fiori, Excel, Power BI, or a partner who expects $metadata, $filter, $expand, $select, $orderby, $count and @odata.nextLink to behave per spec.
  • Read-oriented exposure is the requirement: querying, projection, paging, expanding and counting existing domain data. This release is almost entirely about read correctness.
  • You need tenant- or installation-specific type facets (currency scale, price precision, nullability) and want that as bound configuration rather than per-model edits.
  • You value precise, non-2xx failure signalling — 400 vs. 501 vs. 200-with-wrong-rows — enough to accept that previously tolerated bad requests now error.
  • You can accept schema-visible conventions: polymorphic relations (including morphTo, morphToMany, morphOneOrMany, even with fixed targets) are omitted from discovery by design, and key literals are strictly validated per type, answering 400 invalid_key on a mismatch.

Look elsewhere, or plan extra work, when

  • You are not on Laravel/Eloquent — discovery, casts and the resolver seam are all Eloquent-shaped.
  • You need heavy write/CUD, actions, functions or $batch change sets as the primary use case; read-path maturity is not evidence of write-path maturity.
  • You require polymorphic navigations to be addressable via OData — that is explicitly out of scope; you would need custom controllers or a different projection model.
  • You require computed/virtual navigations to answer $count — they return 501 unsupported_expand by design, because the resolver decides the row set.
  • You cannot operate a cache-invalidation process for odata:cache; the resolver runs at schema build time, so stale caches are a real operational hazard.
  • You need a stable, frozen API contract with no behavioral tightening — this release changes several wrong-answer paths into errors and changes nextLink contents. That is the right direction, but it is not a no-op upgrade.

How to Pitch It (Stakeholders)

For executives (one paragraph). This release turns the OData layer from "works for the demo" into "survives the customer's spreadsheet." Screens and reports now receive the real shape of the data — nullability, decimal precision and scale, string length — so UI5 controls format currency correctly and enforce the same rules the database does. Excel and Power BI "load more" now fetch the next page of the query the user built, not a fresh unfiltered slice, which removes a class of "the totals are wrong" incidents. Requests the layer cannot honour now fail loudly with a specific status and reason instead of quietly returning all rows or no rows. And a supported extension point lets a single installation's pricing and precision conventions be configured rather than coded. Net effect: fewer correctness defects in reporting and forms, less bespoke integration code, and faster sign-off on Fiori/Power BI rollouts — with two things we must manage: a metadata cache rebuild when those configured facts change, and a client-compatibility pass at upgrade.

For engineering leads (bullets).

  • Metadata fidelity: discoverModel() reads Nullable, Precision/Scale, MaxLength from Schema::getColumns(); facets are only applied where the final Edm type can carry them, so a #[ODataProperty(type:)] override never inherits the source column's length or scale. Key properties are forced non-nullable; SQLite's nullable-integer-PK report is ignored. Facet-free columns serialize exactly as before.
  • Cache parity: EdmxWriter previously rebuilt properties as new Property(name, type), dropping facets, the collection flag and defaults — warm and cold $metadata diverged. Fixed, with a test asserting warm/cold identity. Note the known prior instance of this bug class (enum types, 3.0.3).
  • Override order per column: schema → ColumnFacetResolverInterface → attribute; key stays non-nullable. One binding via bindIf, so an app-level resolver wins regardless of provider order — and there is exactly one resolver, not a chain. TypeFacets gains withNullable()/withMaxLength()/withPrecision()/withScale()/isDefault() for surgical overrides.
  • Fail-fast validation: a LogicException at schema build time when facets don't fit the type (scale on non-decimal, precision outside decimal/temporal, maxLength outside string/binary, scale > precision) — this is a startup failure, so it surfaces in CI rather than in a customer's browser.
  • Expand counting: $count=true inside $expand emits <nav>@odata.count computed by one withCount sub-select per counted navigation at any depth, respects the expand's $filter but not its $top/$skip, and survives $select. Single-valued → 400 invalid_expand; virtual → 501 unsupported_expand.
  • Filter translation unified: both translators now extend AbstractFilterTranslator so they cannot drift. Unsupported functions/operators/lambdas → 501 unsupported_filter naming the construct; property-less comparisons, null with anything but eq/ne, or a non-Boolean standalone → 400 invalid_filter. tolower/toupper map to LOWER()/UPPER() (UI5 case-insensitive filters work), contains/startswith/endswith escape wildcards as $search does, 3 lt id is mirrored, bare Boolean properties filter, $filter=false returns nothing, and in no longer collapses to [].
  • Parser: in handled per ABNF (RWS "in" RWS), plus optional whitespace after (, before ), around ,, and after a lambda's :, so id in (1,3), nav/any(p: p/name eq 'x'), ( id eq 1 ) and contains( name , 'x' ) all parse.
  • Streaming vs. status codes: EntitySetHandler ran the query lazily inside the stream callback, so rejected filters and SQL errors landed after 200. It now executes up to the first row before committing the response; rows still stream.
  • Pagination: nextLink is rebuilt from the request (same path, byte-for-byte query string, only $skip replaced), preserving custom options and $count; $batch parts use their own URL. Do not round-trip through Symfony's normalised query string.
  • Discovery exclusions: the whole polymorphic family is checked before the regular relation arms and skipped — MorphTo extends BelongsTo and MorphToMany extends BelongsToMany meant they were silently wired as ordinary navigations with an arbitrary empty-model target, and a type-column join cannot be expressed as a ReferentialConstraint.
  • Key validation: the planner no longer casts blindly (Products(abc) → Products(0)). Integer ranges, decimals, doubles, true/false, Guid, Date, DateTimeOffset, TimeOfDay and Duration are validated per family; misfits answer 400 invalid_key. Decimal, temporal and Guid keys remain strings to stay exact.
  • Upgrade checklist for the team: diff $metadata pre/post upgrade; identify clients that depended on the old all-or-nothing filter behaviour or bare nextLinks; confirm no client consumed polymorphic navigations; rebuild odata:cache after upgrading and whenever a bound resolver's inputs change.

Risks to track (PM-owned). Cache staleness after facet configuration changes (owner and trigger undefined today); behavioral tightening in $filter status codes that will surface as new client errors; removal of polymorphic navigations from metadata being a visible schema break for existing consumers; and the recurring pattern of EdmxWriter dropping metadata (now fixed twice — worth a standing warm-vs-cold parity test across every future metadata feature).

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource