@@ -128,5 +128,186 @@ created by using the special ``"\0"`` property name to define their internal val
128128 "\0" => [$inputArray],
129129 ]);
130130
131+ Creating Lazy Objects
132+ ---------------------
133+
134+ Lazy-objects are objects instantiated empty and populated on-demand. This is
135+ particularly useful when you have for example properties in your classes that
136+ requires some heavy computation to determine their value. In this case, you
137+ may want to trigger the property's value processing only when you actually need
138+ its value. Thanks to this, the heavy computation won't be done if you never use
139+ this property. The VarExporter component is bundled with two traits helping
140+ you implement such mechanism easily in your classes.
141+
142+ .. _var-exporter_ghost-objects :
143+
144+ LazyGhostTrait
145+ ~~~~~~~~~~~~~~
146+
147+ Ghost objects are empty objects, which see their properties populated the first
148+ time any method is called. Thanks to:class: `Symfony\\ Component\\ VarExporter\\ LazyGhostTrait `,
149+ the implementation of the lazy mechanism is eased. In the following example, we are
150+ defining the ``$hash `` property as lazy. We also declare that the ``MyLazyObject::computeHash() ``
151+ method should be called only when ``$hash ``'s value need to be known::
152+
153+ namespace App\Hash;
154+
155+ use Symfony\Component\VarExporter\LazyGhostTrait;
156+
157+ class HashProcessor
158+ {
159+ use LazyGhostTrait;
160+ // Because of how the LazyGhostTrait trait works internally, you
161+ // must add this private property in your class
162+ private int $lazyObjectId;
163+
164+ // This property may require a heavy computation to have its value
165+ public readonly string $hash;
166+
167+ public function __construct()
168+ {
169+ self::createLazyGhost(initializer: [
170+ 'hash' => $this->computeHash(...),
171+ ], instance: $this);
172+ }
173+
174+ private function computeHash(array $data): string
175+ {
176+ // Compute $this->hash value with the passed data
177+ }
178+ }
179+
180+ :class: `Symfony\\ Component\\ VarExporter\\ LazyGhostTrait ` also allows to
181+ convert non-lazy classes to lazy ones::
182+
183+ namespace App\Hash;
184+
185+ use Symfony\Component\VarExporter\LazyGhostTrait;
186+
187+ class HashProcessor
188+ {
189+ public readonly string $hash;
190+
191+ public function __construct(array $data)
192+ {
193+ $this->hash = $this->computeHash($data);
194+ }
195+
196+ private function computeHash(array $data): string
197+ {
198+ // ...
199+ }
200+
201+ public function validateHash(): bool
202+ {
203+ // ...
204+ }
205+ }
206+
207+ class LazyHashProcessor extends HashProcessor
208+ {
209+ use LazyGhostTrait;
210+ }
211+
212+ $processor = LazyHashProcessor::createLazyGhost(initializer: function (HashProcessor $instance): void {
213+ // Do any operation you need here: call setters, getters, methods to validate the hash, etc.
214+ $data = /** Retrieve required data to compute the hash */;
215+ $instance->__construct(...$data);
216+ $instance->validateHash();
217+ });
218+
219+ While you never query ``$processor->hash `` value, heavy methods will never be triggered.
220+ But still, the ``$processor `` object exists and can be used in your code, passed to
221+ methods, functions, etc.
222+
223+ Additionally and by adding two arguments to initializer function, it is possible to initialize
224+ properties one-by-one::
225+
226+ $processor = LazyHashProcessor::createLazyGhost(initializer: function (HashProcessor $instance, string $propertyName, ?string $propertyScope): mixed {
227+ if (HashProcessor::class === $propertyScope && 'hash' === $propertyName) {
228+ // Return $hash value
229+ }
230+
231+ // Then you can add more logic for the other properties
232+ });
233+
234+ Ghost objects unfortunately can't work with abstract classes but also internal PHP classes.
235+ Nevertheless, the VarExporter component covers this need with the help of to
236+ :ref: `Virtual Proxies <var-exporter_virtual-proxies >`.
237+
238+ ..versionadded ::6.2
239+
240+ The:class: `Symfony\\ Component\\ VarExporter\\ LazyGhostTrait ` was introduced in Symfony 6.2.
241+
242+ .. _var-exporter_virtual-proxies :
243+
244+ LazyProxyTrait
245+ ~~~~~~~~~~~~~~
246+
247+ The purpose of virtual proxies in the same one as
248+ :ref: `ghost objects <var-exporter_ghost-objects >`, but their internal behavior is
249+ totally different. Where ghost objects requires to extend a base class, virtual
250+ proxies take advantage of the **Liskov Substitution principle **. This principle
251+ describes that if two objects are implementing the same interface, you can swap between
252+ the different implementations without breaking your application. This is what virtual
253+ proxies take advantage of. To use virtual proxies, you may use
254+ :class: `Symfony\\ Component\\ VarExporter\\ ProxyHelper ` to generate proxy's class
255+ code::
256+
257+ namespace App\Hash;
258+
259+ use Symfony\Component\VarExporter\ProxyHelper;
260+
261+ interface ProcessorInterface
262+ {
263+ public function getHash(): bool;
264+ }
265+
266+ abstract class AbstractProcessor implements ProcessorInterface
267+ {
268+ protected string $hash;
269+
270+ public function getHash(): bool
271+ {
272+ return $this->hash;
273+ }
274+ }
275+
276+ class HashProcessor extends AbstractProcessor
277+ {
278+ public function __construct(array $data)
279+ {
280+ $this->hash = $this->computeHash($data);
281+ }
282+
283+ private function computeHash(array $data): string
284+ {
285+ // ...
286+ }
287+ }
288+
289+ $proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(AbstractProcessor::class));
290+ // $proxyCode contains the actual proxy and the reference to LazyProxyTrait.
291+ // In production env, this should be dumped into a file to avoid calling eval().
292+ eval('class HashProcessorProxy'.$proxyCode);
293+
294+ $processor = HashProcessorProxy::createLazyProxy(initializer: function (): ProcessorInterface {
295+ $data = /** Retrieve required data to compute the hash */;
296+ $instance = new HashProcessor(...$data);
297+
298+ // Do any operation you need here: call setters, getters, methods to validate the hash, etc.
299+
300+ return $instance;
301+ });
302+
303+ Just like ghost objects, while you never query ``$processor->hash ``, its value will not be computed.
304+ The main difference with ghost objects is that this time, we created a proxy of an abstract class.
305+ This also works with internal PHP class.
306+
307+ ..versionadded ::6.2
308+
309+ The:class: `Symfony\\ Component\\ VarExporter\\ LazyProxyTrait ` and
310+ :class: `Symfony\\ Component\\ VarExporter\\ ProxyHelper ` were introduced in Symfony 6.2.
311+
131312.. _`OPcache` :https://www.php.net/opcache
132313.. _`PSR-2` :https://www.php-fig.org/psr/psr-2/