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+ }
0 commit comments