Skip to content

Commit 0512dc3

Browse files
wip
- Updated the config file - Added new route discovery trait - Updated service provider
1 parent 686c16c commit 0512dc3

File tree

4 files changed

+302
-0
lines changed

4 files changed

+302
-0
lines changed

config/orion.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,16 @@
5151
],
5252

5353
'use_validated' => false,
54+
55+
'route_discovery' => [
56+
'enabled' => true,
57+
'paths' => [
58+
app_path('Http/Controllers/Api'),
59+
],
60+
'route_prefix' => 'api',
61+
'route_name_prefix' => 'api',
62+
'route_middleware' => [
63+
// Add custom middleware here - eg: 'auth:sanctum',
64+
],
65+
],
5466
];
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<?php
2+
3+
namespace Orion\Concerns;
4+
5+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
6+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7+
use Illuminate\Database\Eloquent\Relations\HasMany;
8+
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
9+
use Illuminate\Database\Eloquent\Relations\HasOne;
10+
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
11+
use Illuminate\Database\Eloquent\Relations\MorphMany;
12+
use Illuminate\Database\Eloquent\Relations\MorphOne;
13+
use Illuminate\Database\Eloquent\Relations\MorphTo;
14+
use Illuminate\Database\Eloquent\Relations\MorphToMany;
15+
use Illuminate\Database\Eloquent\SoftDeletes;
16+
use Illuminate\Support\Arr;
17+
use Illuminate\Support\Facades\Route;
18+
use Orion\Exceptions\RouteDiscoveryException;
19+
use Orion\Facades\Orion;
20+
use Orion\Http\Controllers\RelationController;
21+
22+
trait HandlesRouteDiscovery
23+
{
24+
protected static $slug = null;
25+
protected static $routePrefix = null;
26+
protected static $routeNamePrefix = null;
27+
protected static $routeMiddleware = [];
28+
protected static $withoutRouteMiddleware = [];
29+
30+
public static function registerRoutes(): void
31+
{
32+
if (static::isRelationController()) {
33+
static::registerRelationRoutes();
34+
} else {
35+
static::registerResourceRoutes();
36+
}
37+
}
38+
39+
protected static function registerResourceRoutes(): void
40+
{
41+
$slug = static::getSlug();
42+
43+
Route::middleware(static::getRouteMiddleware())
44+
->withoutMiddleware(static::getWithoutRouteMiddleware())
45+
->prefix(static::getRoutePrefix())
46+
->name(static::getRouteNamePrefix() . '.')
47+
->group(function () use ($slug) {
48+
$controller = static::class;
49+
$route = Orion::resource($slug, $controller);
50+
51+
if (static::usesSoftDeletes($controller)) {
52+
$route->withSoftDeletes();
53+
}
54+
});
55+
}
56+
57+
protected static function registerRelationRoutes(): void
58+
{
59+
$controller = static::class;
60+
$instance = app($controller);
61+
62+
$model = isset($instance->model) ? $instance->model : null;
63+
$relation = isset($instance->relation) ? $instance->relation : null;
64+
$type = isset($instance->resourceType) ? $instance->resourceType : static::detectRelationType($model, $relation);
65+
66+
if (! $model || ! $relation || ! $type) {
67+
throw new RouteDiscoveryException("Cannot register relation route: model [$model], relation [$relation], type [$type]");
68+
}
69+
70+
$parentSlug = str(class_basename($model))->kebab()->plural();
71+
72+
Route::middleware(static::getRouteMiddleware())
73+
->withoutMiddleware(static::getWithoutRouteMiddleware())
74+
->prefix(static::getRoutePrefix())
75+
->name(static::getRouteNamePrefix() . '.')
76+
->group(function () use ($type, $parentSlug, $relation, $controller, $instance) {
77+
$route = null;
78+
79+
switch ($type) {
80+
case 'hasOne':
81+
$route = Orion::hasOneResource($parentSlug, $relation, $controller);
82+
break;
83+
case 'hasMany':
84+
$route = Orion::hasManyResource($parentSlug, $relation, $controller);
85+
break;
86+
case 'belongsTo':
87+
$route = Orion::belongsToResource($parentSlug, $relation, $controller);
88+
break;
89+
case 'belongsToMany':
90+
$route = Orion::belongsToManyResource($parentSlug, $relation, $controller);
91+
break;
92+
case 'hasOneThrough':
93+
$route = Orion::hasOneThroughResource($parentSlug, $relation, $controller);
94+
break;
95+
case 'hasManyThrough':
96+
$route = Orion::hasManyThroughResource($parentSlug, $relation, $controller);
97+
break;
98+
case 'morphOne':
99+
$route = Orion::morphOneResource($parentSlug, $relation, $controller);
100+
break;
101+
case 'morphMany':
102+
$route = Orion::morphManyResource($parentSlug, $relation, $controller);
103+
break;
104+
case 'morphTo':
105+
$route = Orion::morphToResource($parentSlug, $relation, $controller);
106+
break;
107+
case 'morphToMany':
108+
$route = Orion::morphToManyResource($parentSlug, $relation, $controller);
109+
break;
110+
case 'morphedByMany':
111+
$route = Orion::morphedByManyResource($parentSlug, $relation, $controller);
112+
break;
113+
default:
114+
throw new RouteDiscoveryException("Unsupported relation type [$type] on [$parentSlug -> $relation]");
115+
}
116+
117+
if (static::usesRelatedSoftDeletes($instance)) {
118+
$route->withSoftDeletes();
119+
}
120+
});
121+
}
122+
123+
protected static function isRelationController(): bool
124+
{
125+
return is_subclass_of(static::class, RelationController::class);
126+
}
127+
128+
protected static function detectRelationType($model, $relation): ?string
129+
{
130+
if (! method_exists($model, $relation)) {
131+
return null;
132+
}
133+
134+
$instance = new $model;
135+
$relationInstance = $instance->{$relation}();
136+
137+
$map = [
138+
HasOne::class => 'hasOne',
139+
HasOneThrough::class => 'hasOneThrough',
140+
MorphOne::class => 'morphOne',
141+
BelongsTo::class => 'belongsTo',
142+
MorphTo::class => 'morphTo',
143+
HasMany::class => 'hasMany',
144+
HasManyThrough::class => 'hasManyThrough',
145+
MorphMany::class => 'morphMany',
146+
BelongsToMany::class => 'belongsToMany',
147+
MorphToMany::class => static::isMorphedByMany($model, $relation) ? 'morphedByMany' : 'morphToMany',
148+
];
149+
150+
foreach ($map as $class => $type) {
151+
if ($relationInstance instanceof $class) {
152+
return $type;
153+
}
154+
}
155+
156+
return null;
157+
}
158+
159+
protected static function isMorphedByMany($model, $relation): bool
160+
{
161+
$instance = new $model;
162+
163+
if (! method_exists($instance, $relation)) {
164+
return false;
165+
}
166+
167+
$relationInstance = $instance->{$relation}();
168+
169+
return $relationInstance instanceof MorphToMany && $relationInstance->getInverse();
170+
}
171+
172+
protected static function usesSoftDeletes($controller): bool
173+
{
174+
$instance = app($controller);
175+
176+
if (! method_exists($instance, 'resolveResourceModelClass')) {
177+
return false;
178+
}
179+
180+
$modelClass = $instance->resolveResourceModelClass();
181+
182+
return class_exists($modelClass)
183+
&& in_array(SoftDeletes::class, class_uses_recursive($modelClass));
184+
}
185+
186+
protected static function usesRelatedSoftDeletes($controller): bool
187+
{
188+
$model = $controller->model ?? null;
189+
$relation = $controller->relation ?? null;
190+
191+
if (! $model || ! method_exists($model, $relation)) {
192+
return false;
193+
}
194+
195+
$related = $model::query()->getModel()->{$relation}()->getRelated();
196+
197+
return in_array(SoftDeletes::class, class_uses_recursive($related));
198+
}
199+
200+
public static function getSlug(): string
201+
{
202+
if (! empty(static::$slug)) {
203+
return static::$slug;
204+
}
205+
206+
return (string) str(class_basename(static::class))
207+
->beforeLast('Controller')
208+
->kebab()
209+
->plural();
210+
}
211+
212+
public static function getRoutePrefix(): string
213+
{
214+
return static::$routePrefix ?: config('orion.route_discovery.route_prefix', 'api');
215+
}
216+
217+
public static function getRouteNamePrefix(): string
218+
{
219+
return static::$routeNamePrefix ?: config('orion.route_discovery.route_name_prefix', 'api');
220+
}
221+
222+
public static function getRouteName(): string
223+
{
224+
return static::getRouteNamePrefix() . '.' . static::getRelativeRouteName();
225+
}
226+
227+
public static function getRoutePath(): string
228+
{
229+
return '/' . static::getSlug();
230+
}
231+
232+
public static function getRelativeRouteName(): string
233+
{
234+
return (string) str(static::getSlug())->replace('/', '.');
235+
}
236+
237+
public static function getRouteMiddleware()
238+
{
239+
return array_merge(
240+
config('orion.route_discovery.route_middleware', []),
241+
Arr::wrap(static::$routeMiddleware)
242+
);
243+
}
244+
245+
public static function getWithoutRouteMiddleware(): array
246+
{
247+
return Arr::wrap(static::$withoutRouteMiddleware);
248+
}
249+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Orion\Exceptions;
4+
5+
class RouteDiscoveryException extends \Exception
6+
{
7+
8+
}

src/OrionServiceProvider.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Orion;
44

5+
use Illuminate\Support\Facades\File;
56
use Illuminate\Support\ServiceProvider;
67
use Orion\Commands\BuildSpecsCommand;
78
use Orion\Contracts\ComponentsResolver;
@@ -60,5 +61,37 @@ public function boot()
6061
]
6162
);
6263
}
64+
65+
if (config('orion.route_discovery.enabled', false)) {
66+
$this->discoverAndRegisterControllers();
67+
}
68+
}
69+
70+
protected function discoverAndRegisterControllers(): void
71+
{
72+
$paths = config('orion.route_discovery.paths', []);
73+
$baseNamespace = trim(config('orion.namespaces.controllers', 'App\\Http\\Controllers\\'), '\\');
74+
75+
foreach ($paths as $path) {
76+
if (! is_dir($path)) {
77+
continue;
78+
}
79+
80+
foreach (File::allFiles($path) as $file) {
81+
$relativePath = str($file->getRelativePathname())
82+
->replace(DIRECTORY_SEPARATOR, '\\')
83+
->replace('.php', '')
84+
->value();
85+
86+
$class = "$baseNamespace\\$relativePath";
87+
88+
if (
89+
class_exists($class) &&
90+
method_exists($class, 'registerRoutes')
91+
) {
92+
$class::registerRoutes();
93+
}
94+
}
95+
}
6396
}
6497
}

0 commit comments

Comments
 (0)