Lazy Loading
If the specified target is a class, not an existing object, Mapper will attempt to create a lazy object as the target. The benefit is that Mapper will not perform the actual mapping until the target is actually used, or never if it is not used.
If the source object is a Doctrine entity, the mapping will not trigger the hydration of the source; even accessing ID properties on the target will also not trigger the hydration. Only after accessing other properties of the target will the hydration take place.
Under PHP < 8.4, if the target is final
, then lazy-loading is not possible.
There are also cases that can prevent a lazy-loading proxy from being created.
To see if a proxy is being used, or the reason why it is not, you can see that
in the Mapper panel in the Symfony profiler.
Under PHP 8.4 and later, Mapper uses native PHP lazy objects, which support
lazy-loading for final
classes.
Mapping to Doctrine Entities
Doctrine reads properties using Reflection
directly, and therefore will not
trigger the hydration of our proxy objects. To prevent problems while working
with Doctrine entities, Mapper will prevent proxy creation if the target is a
Doctrine entity.
API Platform
With API Platform, if you are using DTOs as ApiResource
, then API Platform
should be able to generate IRIs without causing the hydration of the source (if
the source is a Doctrine entity). The only thing you need to do is to ensure
the source (a Doctrine entity) and the target (an ApiResource
DTO) both use
the same identifier property name. Or better: just use id
as the identifier
everywhere, and be done with it.
Without lazy-loading, API Platform will hydrate everything in the object graph, even when it only needs to generate an IRI.
Read the documentation of api-lite to know more about how we utilize Mapper with API Platform.
Eager Properties
During the mapping, Mapper will try to identify the identifier properties on the
source side. First, it looks for the information in Doctrine's class metadata.
If not found, it will use id
, uuid
, or identifier
if any of those exists
on the source side.
These identifier properties will not be lazy, and will be mapped immediately after the instantiation of the target proxy object. This should not cause the hydration of the source side because a Doctrine proxy already hold the identifier, even when uninitialized.
If your application needs to have a custom logic for determining the identifier
fields, you can create a service implementing
EagerPropertiesResolverInterface
.
If an identifier property maps to a constructor argument on the target side, then everything in the constructor will be mapped eagerly.
PHP 8.4 Lazy Objects
Since Mapper 2.0, if you are using PHP 8.4 or later, Mapper will use native PHP
lazy objects.
There should be no practical difference between the native lazy objects and
older symfony/var-exporter
lazy objects, except that the new mechanism
supports final
classes.
Old-style proxy classes will still be generated during warming up. This is done to anticipate the case where the target environment uses different PHP versions.
Disabling Lazy-Loading
There should be no downside to using a lazy-loading proxy in place of the real object. In most cases, they should be interchangeable. However, a proxy incurs a small overhead, and you may wish to disable it in some cases, for example if you are using the Mapper in a batch process.
Using MapperOptions
If you want to disable lazy-loading for a mapping run, you can set the option
enableLazyLoading
to false in the MapperOptions
object, and add it to the
context:
use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Context\MapperOptions;
$options = new MapperOptions(lazyLoading: false);
$context = Context::create($options);
$target = $this->mapper->map($source, TargetDto::class, $context);
Using Eager
Attribute
To disable proxy creation for a specific class, add the #[Eager]
attribute to
the target class:
use Rekalogika\Mapper\Attribute\Eager;
#[Eager]
class TargetDto
{
// ...
}
Other Ways of Disabling Lazy-Loading
- You can instantiate manually, and pass the object as the mapper's target.
- You can decorate
ProxyFactoryInterface
, and throwProxyNotSupportedException
if it asks for your specific class. ReadDoctrineProxyFactory
for an example.