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