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