Sub resources are lazy loaded (this throws a Laravel error)
API Platform version(s) affected: 4.1
Description
In a Laravel application, if the strict mode (https://laravel-news.com/shouldbestrict) is applied to models, the collection endpoints throws the following error:
Attempted to lazy load [...] on model [App\\Models\\...] but lazy loading is disabled.
How to reproduce
Create a Laravel application, add a model, a second model with a foreign key and add Model::shouldBeStrict() within the boot method of the service provider.
Possible Solution
Trying to not lazy load the sub resources?
Additional Context
I'm not sure how we could not lazy load, do you have a stack trace?
Hey,
The stack trace:
[2025-05-22 08:08:40] local.ERROR: Attempted to lazy load [executables] on model [App\Models\Package] but lazy loading is disabled. {"exception":"[object] (Illuminate\\Database\\LazyLoadingViolationException(code: 0): Attempted to lazy load [executables] on model [App\\Models\\Package] but lazy loading is disabled. at /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php:595)
[stacktrace]
#0 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(554): Illuminate\\Database\\Eloquent\\Model->handleLazyLoadingViolation()
#1 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(485): Illuminate\\Database\\Eloquent\\Model->getRelationValue()
#2 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(2260): Illuminate\\Database\\Eloquent\\Model->getAttribute()
#3 /home/esteban/dev/p/maiamix/vendor/api-platform/laravel/Eloquent/PropertyAccess/PropertyAccessor.php(54): Illuminate\\Database\\Eloquent\\Model->__get()
#4 /home/esteban/dev/p/maiamix/vendor/api-platform/serializer/AbstractItemNormalizer.php(688): ApiPlatform\\Laravel\\Eloquent\\PropertyAccess\\PropertyAccessor->getValue()
#5 /home/esteban/dev/p/maiamix/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(197): ApiPlatform\\Serializer\\AbstractItemNormalizer->getAttributeValue()
#6 /home/esteban/dev/p/maiamix/vendor/api-platform/serializer/AbstractItemNormalizer.php(155): Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer->normalize()
#7 /home/esteban/dev/p/maiamix/vendor/api-platform/jsonld/Serializer/ItemNormalizer.php(134): ApiPlatform\\Serializer\\AbstractItemNormalizer->normalize()
#8 /home/esteban/dev/p/maiamix/vendor/symfony/serializer/Serializer.php(152): ApiPlatform\\JsonLd\\Serializer\\ItemNormalizer->normalize()
#9 /home/esteban/dev/p/maiamix/vendor/api-platform/hydra/Serializer/CollectionNormalizer.php(92): Symfony\\Component\\Serializer\\Serializer->normalize()
#10 /home/esteban/dev/p/maiamix/vendor/api-platform/serializer/AbstractCollectionNormalizer.php(94): ApiPlatform\\Hydra\\Serializer\\CollectionNormalizer->getItemsData()
#11 /home/esteban/dev/p/maiamix/vendor/api-platform/hydra/Serializer/CollectionFiltersNormalizer.php(76): ApiPlatform\\Serializer\\AbstractCollectionNormalizer->normalize()
#12 /home/esteban/dev/p/maiamix/vendor/api-platform/hydra/Serializer/PartialCollectionViewNormalizer.php(53): ApiPlatform\\Hydra\\Serializer\\CollectionFiltersNormalizer->normalize()
#13 /home/esteban/dev/p/maiamix/vendor/symfony/serializer/Serializer.php(152): ApiPlatform\\Hydra\\Serializer\\PartialCollectionViewNormalizer->normalize()
#14 /home/esteban/dev/p/maiamix/vendor/symfony/serializer/Serializer.php(131): Symfony\\Component\\Serializer\\Serializer->normalize()
#15 /home/esteban/dev/p/maiamix/vendor/api-platform/state/Processor/SerializeProcessor.php(74): Symfony\\Component\\Serializer\\Serializer->serialize()
#16 /home/esteban/dev/p/maiamix/vendor/api-platform/state/Processor/WriteProcessor.php(51): ApiPlatform\\State\\Processor\\SerializeProcessor->process()
#17 /home/esteban/dev/p/maiamix/vendor/api-platform/hydra/State/HydraLinkProcessor.php(58): ApiPlatform\\State\\Processor\\WriteProcessor->process()
#18 /home/esteban/dev/p/maiamix/vendor/api-platform/laravel/Controller/ApiPlatformController.php(96): ApiPlatform\\Hydra\\State\\HydraLinkProcessor->process()
#19 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): ApiPlatform\\Laravel\\Controller\\ApiPlatformController->__invoke()
#20 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(44): Illuminate\\Routing\\Controller->callAction()
#21 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/Route.php(266): Illuminate\\Routing\\ControllerDispatcher->dispatch()
#22 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/Route.php(212): Illuminate\\Routing\\Route->runController()
#23 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/Router.php(808): Illuminate\\Routing\\Route->run()
#24 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(170): Illuminate\\Routing\\Router->{closure:Illuminate\\Routing\\Router::runRouteWithinStack():807}()
#25 /home/esteban/dev/p/maiamix/vendor/api-platform/laravel/ApiPlatformMiddleware.php(47): Illuminate\\Pipeline\\Pipeline->{closure:Illuminate\\Pipeline\\Pipeline::prepareDestination():168}()
#26 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): ApiPlatform\\Laravel\\ApiPlatformMiddleware->handle()
#27 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(127): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#28 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/Router.php(807): Illuminate\\Pipeline\\Pipeline->then()
#29 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/Router.php(786): Illuminate\\Routing\\Router->runRouteWithinStack()
#30 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/Router.php(750): Illuminate\\Routing\\Router->runRoute()
#31 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Routing/Router.php(739): Illuminate\\Routing\\Router->dispatchToRoute()
#32 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(201): Illuminate\\Routing\\Router->dispatch()
#33 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(170): Illuminate\\Foundation\\Http\\Kernel->{closure:Illuminate\\Foundation\\Http\\Kernel::dispatchToRouter():198}()
#34 /home/esteban/dev/p/maiamix/vendor/livewire/livewire/src/Features/SupportDisablingBackButtonCache/DisableBackButtonCacheMiddleware.php(19): Illuminate\\Pipeline\\Pipeline->{closure:Illuminate\\Pipeline\\Pipeline::prepareDestination():168}()
#35 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Livewire\\Features\\SupportDisablingBackButtonCache\\DisableBackButtonCacheMiddleware->handle()
#36 /home/esteban/dev/p/maiamix/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php(66): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#37 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle()
#38 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#39 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#40 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()
#41 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#42 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#43 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()
#44 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#45 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Illuminate\\Http\\Middleware\\ValidatePostSize->handle()
#46 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(110): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#47 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()
#48 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(62): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#49 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Illuminate\\Http\\Middleware\\HandleCors->handle()
#50 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(58): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#51 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Illuminate\\Http\\Middleware\\TrustProxies->handle()
#52 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php(22): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#53 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(209): Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks->handle()
#54 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(127): Illuminate\\Pipeline\\Pipeline->{closure:{closure:Illuminate\\Pipeline\\Pipeline::carry():184}:185}()
#55 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Pipeline\\Pipeline->then()
#56 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(145): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
#57 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1220): Illuminate\\Foundation\\Http\\Kernel->handle()
#58 /home/esteban/dev/p/maiamix/public/index.php(19): Illuminate\\Foundation\\Application->handleRequest()
#59 /home/esteban/dev/p/maiamix/vendor/laravel/framework/src/Illuminate/Foundation/resources/server.php(23): require_once('...')
#60 {main}
"}
I see but I'm not sure how we can do otherwise, if you set the correct groups the property will not be read:
https://github.com/api-platform/core/blob/79edced67ccca1a7b80455dd94203501d9c4fa89/src/Laravel/Eloquent/PropertyAccess/PropertyAccessor.php#L53-L55
What do you mean by setting the correct group? I don't understand.
By default all the properties of your Eloquent model will be serialized, therefore we try to access their value. To pick a set of values there are a few solutions:
#[ApiResource(normalizationContext: ['groups' => ['book:read']])]
#[ApiProperty(serialize: new Groups(['book:read']), property: 'title')]
#[ApiProperty(serialize: new Groups(['book:read']), property: 'description')]
#[ApiProperty(serialize: new Groups(['book:read']), property: 'author')]
Or set attributes
#[ApiResource(normalizationContext: ['attributes' => ['title', 'description', 'author']])]
I'd like to understand your full use case though as I'd like for this mode to work it's better for performances.
@Barbapapazes with 4.1.17, we have updated the way Link queries are formed - they now use Eloquent's relations natively under the hood. This means that you can just define the $with = ['relation'] on the model now, or enable automatic eager loading in L12, and you should be good to go.
thanks @jonerickson