Skip to content

Commit d97aa23

Browse files
committed
chore: simplify ServiceProvider and externalize multi-endpoint support
- remove "extra.laravel" from composer.json to disable auto-registration - always boot default Endpoint in ServiceProvider for consistent behavior - drop request path inspection logic for segmented endpoint resolution ==> apps requiring multiple OData endpoints should now implement and register their own ServiceProvider (see docs for example)
1 parent 56ccf68 commit d97aa23

File tree

2 files changed

+131
-40
lines changed

2 files changed

+131
-40
lines changed

doc/getting-started/endpoint.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,135 @@ By default, `flat3/lodata` exposes a **single global service endpoint**. However
99

1010
This is where **service endpoints** come in. They allow you to split your schema into smaller, focused units, each with its own `$metadata` document and queryable surface.
1111

12+
## Replacing the ServiceProvider
13+
14+
To enable multiple service endpoints you need to replace `\Flat3\Lodata\ServiceProvider` with your own implementation that inspects the request path and boots the appropriate endpoint based on your configuration.
15+
16+
Take this sample implementation:
17+
18+
```php
19+
<?php
20+
21+
namespace App\Providers;
22+
23+
use Composer\InstalledVersions;
24+
use Flat3\Lodata\Controller\Monitor;
25+
use Flat3\Lodata\Controller\OData;
26+
use Flat3\Lodata\Controller\ODCFF;
27+
use Flat3\Lodata\Controller\PBIDS;
28+
use Flat3\Lodata\Controller\Response;
29+
use Flat3\Lodata\Helper\Filesystem;
30+
use Flat3\Lodata\Helper\Flysystem;
31+
use Flat3\Lodata\Helper\DBAL;
32+
use Flat3\Lodata\Helper\Symfony;
33+
use Flat3\Lodata\Interfaces\ServiceEndpointInterface;
34+
use Illuminate\Foundation\Application;
35+
use Illuminate\Support\Facades\Route;
36+
use Illuminate\Support\ServiceProvider;
37+
use RuntimeException;
38+
use Symfony\Component\HttpKernel\Kernel;
39+
40+
class YourServiceProvider extends ServiceProvider
41+
{
42+
public function register()
43+
{
44+
$this->mergeConfigFrom(__DIR__.'/../config.php', 'lodata');
45+
}
46+
47+
public function boot()
48+
{
49+
if ($this->app->runningInConsole()) {
50+
$this->publishes([__DIR__.'/../config.php' => config_path('lodata.php')], 'config');
51+
$this->bootServices(new Endpoint(''));
52+
}
53+
else {
54+
$segments = explode('/', request()->path());
55+
56+
if ($segments[0] === config('lodata.prefix')) {
57+
58+
$serviceUris = config('lodata.endpoints', []);
59+
60+
if (0 === sizeof($serviceUris) || count($segments) === 1) {
61+
$service = new Endpoint('');
62+
}
63+
else if (array_key_exists($segments[1], $serviceUris)) {
64+
$clazz = $serviceUris[$segments[1]];
65+
if (!class_exists($clazz)) {
66+
throw new RuntimeException(sprintf('Endpoint class `%s` does not exist', $clazz));
67+
}
68+
if (!is_subclass_of($clazz, ServiceEndpointInterface::class)) {
69+
throw new RuntimeException(sprintf('Endpoint class `%s` must implement Flat3\\Lodata\\Interfaces\\ServiceEndpointInterface', $clazz));
70+
}
71+
$service = new $clazz($segments[1]);
72+
}
73+
else {
74+
$service = new Endpoint('');
75+
}
76+
77+
$this->bootServices($service);
78+
}
79+
}
80+
}
81+
82+
private function bootServices(Endpoint $service): void
83+
{
84+
$this->app->instance(Endpoint::class, $service);
85+
86+
$this->app->bind(DBAL::class, function (Application $app, array $args) {
87+
return version_compare(InstalledVersions::getVersion('doctrine/dbal'), '4.0.0', '>=') ? new DBAL\DBAL4($args['connection']) : new DBAL\DBAL3($args['connection']);
88+
});
89+
90+
$this->loadJsonTranslationsFrom(__DIR__.'/../lang');
91+
92+
$model = $service->discover(new Model());
93+
assert($model instanceof Model);
94+
95+
$this->app->instance(Model::class, $model);
96+
97+
$this->app->alias(Model::class, 'lodata.model');
98+
99+
$this->app->bind(Response::class, function () {
100+
return Kernel::VERSION_ID < 60000 ? new Symfony\Response5() : new Symfony\Response6();
101+
});
102+
103+
$this->app->bind(Filesystem::class, function () {
104+
return class_exists('League\Flysystem\Adapter\Local') ? new Flysystem\Flysystem1() : new Flysystem\Flysystem3();
105+
});
106+
107+
$route = $service->route();
108+
$middleware = config('lodata.middleware', []);
109+
110+
Route::get("{$route}/_lodata/odata.pbids", [PBIDS::class, 'get']);
111+
Route::get("{$route}/_lodata/{identifier}.odc", [ODCFF::class, 'get']);
112+
Route::resource("{$route}/_lodata/monitor", Monitor::class);
113+
Route::any("{$route}{path}", [OData::class, 'handle'])->where('path', '(.*)')->middleware($middleware);
114+
}
115+
}
116+
```
117+
118+
Register your new provider in `bootstrap/providers.php` instead of the original one.
119+
120+
```php
121+
<?php
122+
123+
return [
124+
App\Providers\AppServiceProvider::class,
125+
App\Providers\YourServiceProvider::class, <-- SP here
126+
// more providers if needed
127+
];
128+
```
129+
130+
And define your endpoints in `config/lodata.php`:
131+
132+
```php
133+
'endpoints' => [
134+
'projects' => \App\Endpoints\ProjectEndpoint::class,
135+
'hr' => \App\Endpoints\HrEndpoint::class,
136+
],
137+
```
138+
139+
This setup enables segmented endpoint support for your Laravel app, while keeping you in full control of the boot logic and route behavior.
140+
12141
## Defining Multiple Endpoints
13142

14143
You can define service endpoints by registering them in your `config/lodata.php` configuration file:

src/ServiceProvider.php

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Flat3\Lodata;
66

7-
use RuntimeException;
87
use Composer\InstalledVersions;
98
use Flat3\Lodata\Controller\Monitor;
109
use Flat3\Lodata\Controller\OData;
@@ -15,7 +14,6 @@
1514
use Flat3\Lodata\Helper\Flysystem;
1615
use Flat3\Lodata\Helper\DBAL;
1716
use Flat3\Lodata\Helper\Symfony;
18-
use Flat3\Lodata\Interfaces\ServiceEndpointInterface;
1917
use Illuminate\Foundation\Application;
2018
use Illuminate\Support\Facades\Route;
2119
use Symfony\Component\HttpKernel\Kernel;
@@ -45,47 +43,11 @@ public function boot()
4543
{
4644
if ($this->app->runningInConsole()) {
4745
$this->publishes([__DIR__.'/../config.php' => config_path('lodata.php')], 'config');
48-
$this->bootServices(new Endpoint(''));
49-
}
50-
else {
51-
// Let’s examine the request path
52-
$segments = explode('/', request()->path());
53-
54-
// we only kick off operation when path prefix is configured in lodata.php
55-
// and bypass all other routes for performance
56-
if ($segments[0] === config('lodata.prefix')) {
57-
58-
// next look up the configured service endpoints
59-
$serviceUris = config('lodata.endpoints', []);
60-
61-
if (0 === sizeof($serviceUris) || count($segments) === 1) {
62-
// when no locators are defined, or the global locator ist requested,
63-
// enter global mode; this will ensure compatibility with prior
64-
// versions of this package
65-
$service = new Endpoint('');
66-
}
67-
else if (array_key_exists($segments[1], $serviceUris)) {
68-
$clazz = $serviceUris[$segments[1]];
69-
if (!class_exists($clazz)) {
70-
throw new RuntimeException(sprintf('Endpoint class `%s` does not exist', $clazz));
71-
}
72-
if (!is_subclass_of($clazz, ServiceEndpointInterface::class)) {
73-
throw new RuntimeException(sprintf('Endpoint class `%s` must implement Flat3\\Lodata\\Interfaces\\ServiceEndpointInterface', $clazz));
74-
}
75-
$service = new $clazz($segments[1]);
76-
}
77-
else {
78-
// when no service definition could be found for the path segment,
79-
// we assume global scope
80-
$service = new Endpoint('');
81-
}
82-
83-
$this->bootServices($service);
84-
}
8546
}
47+
$this->bootServices(new Endpoint(''));
8648
}
8749

88-
private function bootServices($service): void
50+
private function bootServices(Endpoint $service): void
8951
{
9052
// register the $service, which is a singleton, with the container; this allows us
9153
// to fulfill all old ServiceProvider::route() and ServiceProvider::endpoint()

0 commit comments

Comments
 (0)