Multi-Service Routing
By default, a single OData service handles all requests under the configured prefix. To host multiple independent services -- each with its own schema, entity types, and resolvers -- implement a custom service registry.
The service registry interface
interface ODataServiceRegistryInterface
{
/**
* Return all registered services (used by odata:cache and odata:clear).
*/
public function services(): array;
/**
* Route a request path to the appropriate service.
*/
public function resolve(string $fullPath): ODataServiceInterface;
}Example: path-based routing
<?php
namespace App\OData;
use LaravelUi5\OData\Service\Contracts\ODataServiceInterface;
use LaravelUi5\OData\Service\Contracts\ODataServiceRegistryInterface;
class ServiceRegistry implements ODataServiceRegistryInterface
{
public function resolve(string $fullPath): ODataServiceInterface
{
return match (true) {
str_starts_with($fullPath, 'partners') => new PartnerService(),
str_starts_with($fullPath, 'reporting') => new ReportingService(),
default => new PartnerService(), // fallback
};
}
public function services(): array
{
return [
new PartnerService(),
new ReportingService(),
];
}
}With this registry, the URLs resolve as:
GET /odata/partners/Partners → PartnerService
GET /odata/partners/$metadata → PartnerService metadata
GET /odata/reporting/SalesReport → ReportingService
GET /odata/reporting/$metadata → ReportingService metadataRegistration
Update config/odata.php:
'service_registry' => App\OData\ServiceRegistry::class,The service provider binds the registry as a singleton via the container.
Host application integration
When a host application (e.g. LaravelUi5/Core) provides its own routing, set register_routes to false and configure a custom service_registry:
// config/odata.php
'register_routes' => false,
'service_registry' => \App\OData\MyServiceRegistry::class,The host's service provider re-uses the package's route file, wrapping it with its own prefix and middleware. This keeps a single middleware config:
// In the host's ServiceProvider::boot()
Route::prefix('odata')
->middleware(config('my-app.odata_middleware'))
->group(base_path('vendor/laravelui5/odata/routes/odata.php'));The route file defines a catch-all {path?} that dispatches to the OData controller. The custom ODataServiceRegistryInterface implementation resolves the correct ODataService from the request path (e.g. extracting {namespace}@{version}). The controller then strips the service's route() prefix to obtain the clean OData resource path (/, $metadata, Flights, etc.).
Service isolation
Each service is fully self-contained:
- Its own
namespace()for$metadata - Its own
configure()with its own entity types and sets - Its own
registerBindings()andbindFunctions()with its own resolvers - Its own generated
Edm/cache directory (when usingodata:cache)
Services do not share types, entity sets, or resolvers.
Caching
The odata:cache and odata:clear commands iterate services() to cache or clear every registered service. Make sure services() returns all services you want to be cacheable.