Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lazy Objects in PHP and Symfony

Lazy Objects in PHP and Symfony

Nicolas Grekas

August 29, 2024
Tweet

More Decks by Nicolas Grekas

Other Decks in Technology

Transcript

  1. Lazy Loading Can save time and memory Perfect for short-lived

    requests Allows creating circular object graphs (Provides resetting for free)
  2. The 4 kinds of Lazy Loading • Lazy Initialization •

    Value holders • Virtual proxies • Ghost objects
  3. class ClosureHolder { public function __construct( private Closure|string $value )

    { } public function getValue(): string { if ($this->value instanceof Closure) { $this->value = ($this->value)(); } return $this->value; }
  4. class LocatorHolder { public function __construct( #[AutowireLocator('workflow', 'name')] private ContainerInterface

    $workflows ) { } public function getWorkflow(string $name) { return $this->workflows->get($name); }
  5. class IterableHolder { public function __construct( #[AutowireIterator('workflow')] private iterable $workflows

    ) { } public function getWorkflows(): Generator { foreach ($this->workflows as $workflow) { yield $workflow; } }
  6. Virtual Proxies An object with the same interface as the

    real object The real object is created just-in-time
  7. class VirtualChildEntityManager extends EntityManager { private parent $em; private bool

    $isInitialized = false; public function __construct( private Closure $initializer ) { } public function find(string $class, $id) { if (!$this->isInitialized) { ($this->initializer)($this); } return $this->em->find($class, $id); }
  8. class VirtualProxyEntityManager implements EntityManagerInterface { private EntityManagerInterface $em; private bool

    $isInitialized = false; public function __construct( private Closure $initializer ) { } public function find(string $class, $id) { if (!$this->isInitialized) { ($this->initializer)($this); } return $this->em->find($class, $id); }
  9. Virtual Proxies Neither the consumers nor the real object are

    laziness-aware Do work with final classes Can cause identity issues aka break fluent/wither APIs
  10. Ghost Objects The real object without any data The first

    time any methods are called, the ghost populates its state
  11. class GhostEntityManager extends EntityManager { public function __construct( private Closure

    $initializer ) { unset(/* all properties defined by the parent */); } public function __get($name) { // initialize all parent properties } // ...
  12. namespace Proxies\__CG__\App\Entity; use Doctrine\Persistence\Proxy; /** * DO NOT EDIT THIS

    FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR */ class Conference extends \App\Entity\Conference implements Proxy { use \Symfony\Component\VarExporter\LazyGhostTrait
  13. Ghost Objects Neither the consumers nor the real object are

    laziness-aware Don't work with final classes Work with final methods Don't cause identity issues aka work with fluent/wither APIs
  14. class VirtualEntityManager extends EntityManager { private parent $em; public function

    __construct( private Closure $initializer ) { unset(/* all properties defined by the parent */); } public function __get($name) { $this->em ??= ($this->initializer)($this); return $this->em->$name; }
  15. Virtual State Proxies Neither the consumers nor the real object

    are laziness-aware Don't work with final classes Work with final methods Don't cause identity issues aka work with fluent/wither APIs
  16. class ReflectionClass { [...] public int const SKIP_INITIALIZATION_ON_SERIALIZE = 1;

    public int const SKIP_DESTRUCTOR = 2; public function newLazyGhost(callable $initializer, int $options = 0): object; public function newLazyProxy(callable $factory, int $options = 0): object; public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): void; public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): void; public function isUninitializedLazyObject(object $instance): bool; [...]
  17. class ReflectionClass { [...] /** * Initializes a lazy object

    (no-op if the object is already initialized.) * * The backing object is returned, which can be another instance than the lazy object when the virtual strategy is used. */ public function initializeLazyObject(object $object): object; public function isUninitializedLazyObject(object $object): object; public function markLazyObjectAsInitialized(object $object): void; public function getLazyInitializer(object $object): ?callable; }
  18. class ReflectionProperty { [...] /** * Marks a property as

    *not* triggering initialization when being accessed. */ public function skipLazyInitialization(object $object): void; /** * Sets a property *without* triggering initialization while skipping hooks if any. */ public function setRawValueWithoutLazyInitialization(object $object, mixed $value): void; }
  19. Native Lazy Objects Neither the consumers nor the real object

    are laziness-aware Do work with final classes Does works with fluent/wither APIs Stellar performance