22
33namespace Illuminate \Http \Resources \JsonApi \Concerns ;
44
5+ use Generator ;
56use Illuminate \Contracts \Support \Arrayable ;
67use Illuminate \Database \Eloquent \Model ;
78use Illuminate \Database \Eloquent \Relations \AsPivot ;
1617use Illuminate \Http \Resources \MissingValue ;
1718use Illuminate \Support \Arr ;
1819use Illuminate \Support \Collection ;
20+ use Illuminate \Support \LazyCollection ;
1921use Illuminate \Support \Str ;
2022use JsonSerializable ;
21- use WeakMap ;
2223
2324trait ResolvesJsonApiElements
2425{
@@ -35,9 +36,9 @@ trait ResolvesJsonApiElements
3536 /**
3637 * Cached loaded relationships map.
3738 *
38- * @var \WeakMap |null
39+ * @var array<int, array{0: \Illuminate\Http\Resources\JsonApi\JsonApiResource, 1: string, 2: string, 3: bool} |null
3940 */
40- protected $ loadedRelationshipsMap ;
41+ public $ loadedRelationshipsMap ;
4142
4243 /**
4344 * Cached loaded relationships identifers.
@@ -104,7 +105,7 @@ protected function resolveResourceObject(JsonApiRequest $request): array
104105 *
105106 * @throws ResourceIdentificationException
106107 */
107- protected function resolveResourceIdentifier (JsonApiRequest $ request ): string
108+ public function resolveResourceIdentifier (JsonApiRequest $ request ): string
108109 {
109110 if (! is_null ($ resourceId = $ this ->toId ($ request ))) {
110111 return $ resourceId ;
@@ -123,7 +124,7 @@ protected function resolveResourceIdentifier(JsonApiRequest $request): string
123124 *
124125 * @throws ResourceIdentificationException
125126 */
126- protected function resolveResourceType (JsonApiRequest $ request ): string
127+ public function resolveResourceType (JsonApiRequest $ request ): string
127128 {
128129 if (! is_null ($ resourceType = $ this ->toType ($ request ))) {
129130 return $ resourceType ;
@@ -195,7 +196,7 @@ protected function resolveResourceRelationshipIdentifiers(JsonApiRequest $reques
195196 */
196197 protected function compileResourceRelationships (JsonApiRequest $ request ): void
197198 {
198- if ($ this ->loadedRelationshipsMap instanceof WeakMap ) {
199+ if (! is_null ( $ this ->loadedRelationshipsMap ) ) {
199200 return ;
200201 }
201202
@@ -205,74 +206,124 @@ protected function compileResourceRelationships(JsonApiRequest $request): void
205206 };
206207
207208 $ resourceRelationships = (new Collection ($ this ->toRelationships ($ request )))
208- ->mapWithKeys (function ($ value , $ key ) {
209- $ relationResolver = is_int ($ key ) ? new RelationResolver ($ value ) : new RelationResolver ($ key , $ value );
210-
211- return [$ relationResolver ->relationName => $ relationResolver ];
212- })->filter (fn ($ value , $ key ) => in_array ($ key , $ sparseIncluded ));
209+ ->transform (fn ($ value , $ key ) => is_int ($ key ) ? new RelationResolver ($ value ) : new RelationResolver ($ key , $ value ))
210+ ->mapWithKeys (fn ($ relationResolver ) => [$ relationResolver ->relationName => $ relationResolver ])
211+ ->filter (fn ($ value , $ key ) => in_array ($ key , $ sparseIncluded ));
213212
214213 $ resourceRelationshipKeys = $ resourceRelationships ->keys ();
215214
216215 $ this ->resource ->loadMissing ($ resourceRelationshipKeys ->all () ?? []);
217216
218- $ this ->loadedRelationshipsMap = new WeakMap ;
217+ $ this ->loadedRelationshipsMap = [] ;
219218
220- $ this ->loadedRelationshipIdentifiers = $ resourceRelationships ->mapWithKeys (function (RelationResolver $ relationResolver , $ key ) use ($ request ) {
221- $ relatedModels = $ relationResolver ->handle ($ this ->resource );
222- $ relatedResourceClass = $ relationResolver ->resourceClass ();
219+ $ this ->loadedRelationshipIdentifiers = (new LazyCollection (function () use ($ request , $ resourceRelationships ) {
220+ foreach ($ resourceRelationships as $ relationName => $ relationResolver ) {
221+ $ relatedModels = $ relationResolver ->handle ($ this ->resource );
222+ $ relatedResourceClass = $ relationResolver ->resourceClass ();
223223
224- if (! is_null ($ relatedModels )) {
225- $ relatedModels ->loadMissing ($ request ->sparseIncluded ($ key ));
226- }
224+ if (! is_null ($ relatedModels )) {
225+ $ relatedModels ->loadMissing ($ request ->sparseIncluded ($ relationName ));
226+ }
227227
228- // Relationship is a collection of models...
229- if ($ relatedModels instanceof Collection) {
230- $ relatedModels = $ relatedModels ->values ();
228+ yield from $ this ->compileResourceRelationshipUsingResolver (
229+ $ this ->resource ,
230+ $ relationResolver ,
231+ $ relatedModels ,
232+ $ request
233+ );
234+ }
235+ }))->all ();
236+ }
231237
232- if ($ relatedModels ->isEmpty ()) {
233- return [$ key => ['data ' => $ relatedModels ]];
234- }
238+ /**
239+ * Compile resource relations.
240+ */
241+ protected function compileResourceRelationshipUsingResolver (
242+ mixed $ resource ,
243+ RelationResolver $ relationResolver ,
244+ Collection |Model |null $ relatedModels ,
245+ JsonApiRequest $ request
246+ ): Generator {
247+ $ relationName = $ relationResolver ->relationName ;
248+ $ resourceClass = $ relationResolver ->resourceClass ();
249+
250+ // Relationship is a collection of models...
251+ if ($ relatedModels instanceof Collection) {
252+ $ relatedModels = $ relatedModels ->values ();
253+
254+ if ($ relatedModels ->isEmpty ()) {
255+ yield $ relationName => ['data ' => $ relatedModels ];
256+
257+ return ;
258+ }
235259
236- $ relationship = $ this ->resource ->{$ key }();
260+ $ relationship = $ resource ->{$ relationName }();
261+ $ isUnique = ! $ relationship instanceof BelongsToMany;
237262
238- $ isUnique = ! $ relationship instanceof BelongsToMany;
263+ yield $ relationName => ['data ' => $ relatedModels ->map (function ($ relatedModel ) use ($ request , $ resourceClass , $ isUnique ) {
264+ $ relatedResource = rescue (fn () => $ relatedModel ->toResource ($ resourceClass ), new JsonApiResource ($ relatedModel ));
239265
240- $ key = $ relationResolver ->resourceType ($ relatedModels , $ request );
266+ return transform (
267+ [$ relatedResource ->resolveResourceType ($ request ), $ relatedResource ->resolveResourceIdentifier ($ request )],
268+ function ($ uniqueKey ) use ($ request , $ relatedModel , $ relatedResource , $ isUnique ) {
269+ $ this ->loadedRelationshipsMap [] = [$ relatedResource , ...$ uniqueKey , $ isUnique ];
241270
242- return [$ key => ['data ' => $ relatedModels ->map (function ($ relation ) use ($ key , $ relatedResourceClass , $ isUnique ) {
243- return transform ([$ key , static ::resourceIdFromModel ($ relation )], function ($ uniqueKey ) use ($ relation , $ relatedResourceClass , $ isUnique ) {
244- $ this ->loadedRelationshipsMap [$ relation ] = [...$ uniqueKey , $ relatedResourceClass , $ isUnique ];
271+ $ this ->compileIncludedNestedRelationshipsMap ($ relatedModel , $ relatedResource , $ request );
245272
246273 return [
247274 'id ' => $ uniqueKey [1 ],
248275 'type ' => $ uniqueKey [0 ],
249276 ];
250- });
251- })]] ;
252- }
277+ }
278+ ) ;
279+ })-> all ()];
253280
254- // Relationship is a single model...
255- $ relatedModel = $ relatedModels ;
281+ return ;
282+ }
256283
257- if (is_null ($ relatedModel )) {
258- return [$ key => null ];
259- } elseif ($ relatedModel instanceof Pivot ||
260- in_array (AsPivot::class, class_uses_recursive ($ relatedModel ), true )) {
261- return [$ key => new MissingValue ];
262- }
284+ // Relationship is a single model...
285+ $ relatedModel = $ relatedModels ;
263286
264- return [$ key => ['data ' => transform (
265- [$ relationResolver ->resourceType ($ relatedModel , $ request ), static ::resourceIdFromModel ($ relatedModel )],
266- function ($ uniqueKey ) use ($ relatedModel , $ relatedResourceClass ) {
267- $ this ->loadedRelationshipsMap [$ relatedModel ] = [...$ uniqueKey , $ relatedResourceClass , true ];
287+ if (is_null ($ relatedModel )) {
288+ yield $ relationName => null ;
268289
269- return [
270- 'id ' => $ uniqueKey [1 ],
271- 'type ' => $ uniqueKey [0 ],
272- ];
273- }
274- )]];
275- })->all ();
290+ return ;
291+ } elseif ($ relatedModel instanceof Pivot ||
292+ in_array (AsPivot::class, class_uses_recursive ($ relatedModel ), true )) {
293+ yield $ relationName => new MissingValue ;
294+
295+ return ;
296+ }
297+
298+ $ relatedResource = rescue (fn () => $ relatedModel ->toResource ($ resourceClass ), new JsonApiResource ($ relatedModel ));
299+
300+ yield $ relationName => ['data ' => transform (
301+ [$ relatedResource ->resolveResourceType ($ request ), $ relatedResource ->resolveResourceIdentifier ($ request )],
302+ function ($ uniqueKey ) use ($ relatedModel , $ relatedResource , $ request ) {
303+ $ this ->loadedRelationshipsMap [] = [$ relatedResource , ...$ uniqueKey , true ];
304+
305+ $ this ->compileIncludedNestedRelationshipsMap ($ relatedModel , $ relatedResource , $ request );
306+
307+ return [
308+ 'id ' => $ uniqueKey [1 ],
309+ 'type ' => $ uniqueKey [0 ],
310+ ];
311+ }
312+ )];
313+ }
314+
315+ /**
316+ * Compile included relationships map.
317+ */
318+ protected function compileIncludedNestedRelationshipsMap (Model $ relation , JsonApiResource $ resource , JsonApiRequest $ request ): void
319+ {
320+ (new Collection ($ resource ->toRelationships ($ request )))
321+ ->transform (fn ($ value , $ key ) => is_int ($ key ) ? new RelationResolver ($ value ) : new RelationResolver ($ key , $ value ))
322+ ->mapWithKeys (fn ($ relationResolver ) => [$ relationResolver ->relationName => $ relationResolver ])
323+ ->filter (fn ($ value , $ key ) => in_array ($ key , array_keys ($ relation ->getRelations ())))
324+ ->each (function ($ relationResolver , $ key ) use ($ relation , $ request ) {
325+ $ this ->compileResourceRelationshipUsingResolver ($ relation , $ relationResolver , $ relation ->getRelation ($ key ), $ request );
326+ });
276327 }
277328
278329 /**
@@ -288,10 +339,10 @@ public function resolveIncludedResources(JsonApiRequest $request): array
288339
289340 $ relations = new Collection ;
290341
291- foreach ($ this ->loadedRelationshipsMap as $ relation => $ value ) {
292- [$ type , $ id , $ relatedResourceClass , $ isUnique ] = $ value ;
342+ $ index = 0 ;
293343
294- $ resourceInstance = rescue (fn () => $ relation ->toResource ($ relatedResourceClass ), new JsonApiResource ($ relation ), false );
344+ while ($ index < count ($ this ->loadedRelationshipsMap )) {
345+ [$ resourceInstance , $ type , $ id , $ isUnique ] = $ this ->loadedRelationshipsMap [$ index ];
295346
296347 if (! $ resourceInstance instanceof JsonApiResource &&
297348 $ resourceInstance instanceof JsonResource) {
@@ -300,6 +351,8 @@ public function resolveIncludedResources(JsonApiRequest $request): array
300351
301352 $ relationsData = $ resourceInstance ->withoutRequestQueryString ()->withIncludedFromLoadedRelationships ()->resolve ($ request );
302353
354+ array_push ($ this ->loadedRelationshipsMap , ...$ resourceInstance ->loadedRelationshipsMap );
355+
303356 $ relations ->push (array_filter ([
304357 'id ' => $ id ,
305358 'type ' => $ type ,
@@ -309,6 +362,8 @@ public function resolveIncludedResources(JsonApiRequest $request): array
309362 'links ' => Arr::get ($ relationsData , 'data.links ' ),
310363 'meta ' => Arr::get ($ relationsData , 'data.meta ' ),
311364 ]));
365+
366+ $ index ++;
312367 }
313368
314369 return $ relations ->uniqueStrict (fn ($ relation ) => $ relation ['_uniqueKey ' ])
0 commit comments