Potential Out-of-Memory Handling
Problem
Doctrine ORM offers extra lazy collections to handle large sets of data. However, only a few methods are extra-lazy-safe. The following are the safe methods that will not trigger the full initialization of the collection, and won't cause out-of-memory errors with an extra-lazy collection:
contains($entity)containsKey($key)(only ifindexByis set)count()get($key)(only ifindexByis set)slice($offset, $length = null)add($entity)offsetSet($key, $entity)(only if$keyis null)offsetExists($key)(only ifindexByis set)offsetGet($key)(only ifindexByis set)matching($criteria)
All the other methods not listed above are not safe. If a non-safe method is called, Doctrine will load the entire collection into memory, potentially causing out-of-memory errors. These errors can be very difficult to debug. It is difficult to catch in CI. And it often gets triggered in seemingly unrelated parts of the application.
And it can be all too easy to call the non-safe methods accidentally, giving us elusive errors that occur only in production, and never in the development environment.
The most common unsafe behaviors include:
- Using
foreachdirectly on the collection. - Calling
toArray(). - Calling
filter(),map()or other similar methods. - Calling
removeElement($entity). This one is usually accidentally called because Symfony MakerBundle generates remover methods that callremoveElement().
Solution
Our classes will pass the safe methods to the underlying collection unchanged.
But if a non-safe method is called, rather than loading the entire set, it adds
a LIMIT clause to the query. If the number of results exceeds 500, it gives a
deprecation warning. If it exceeds 2000, the classes will throw an exception.
This behavior applies to the following classes:
RecollectionDecoratorCriteriaRecollectionAbstractRepositoryQueryRecollection
The thresholds can be changed by specifying the options $softLimit or
$hardLimit in the constructor of the classes that have this behavior.
The default threshold can be changed by setting static properties
$defaultSoftLimit and $defaultHardLimit in the Configuration class.
What to Do After the Threshold is Reached
If you see the warning or exception, it means your code is loading too many entities at once, and you need to use a different approach to the problem.
To start, you can change your code to use the minimal flavor instead:
- Change
RecollectionDecoratortoMinimalRecollectionDecorator - Change
CriteriaRecollectiontoMinimalCriteriaRecollection - Change
AbstractRepositorytoAbstractMinimalRepository - Change
QueryRecollectiontoQueryPageable
These minimal flavors should never trigger full load of the collection.
Then you can run a static analysis tool to find all the places that still call the non-safe methods. You can then refactor the code to use a different approach. If you need to iterate over it, read the Iterating Large Collections section.