@@ -181,5 +181,186 @@ populated by using the special ``"\0"`` property name to define their internal v
181
181
182
182
The :class: `Symfony\\ Component\\ VarExporter\\ Hydrator ` was introduced in Symfony 6.2.
183
183
184
+ Creating Lazy Objects
185
+ ---------------------
186
+
187
+ Lazy-objects are objects instantiated empty and populated on-demand. This is
188
+ particularly useful when you have for example properties in your classes that
189
+ requires some heavy computation to determine their value. In this case, you
190
+ may want to trigger the property's value processing only when you actually need
191
+ its value. Thanks to this, the heavy computation won't be done if you never use
192
+ this property. The VarExporter component is bundled with two traits helping
193
+ you implement such mechanism easily in your classes.
194
+
195
+ .. _var-exporter_ghost-objects :
196
+
197
+ LazyGhostTrait
198
+ ~~~~~~~~~~~~~~
199
+
200
+ Ghost objects are empty objects, which see their properties populated the first
201
+ time any method is called. Thanks to :class: `Symfony\\ Component\\ VarExporter\\ LazyGhostTrait `,
202
+ the implementation of the lazy mechanism is eased. In the following example, we are
203
+ defining the ``$hash `` property as lazy. We also declare that the ``MyLazyObject::computeHash() ``
204
+ method should be called only when ``$hash ``'s value need to be known::
205
+
206
+ namespace App\Hash;
207
+
208
+ use Symfony\Component\VarExporter\LazyGhostTrait;
209
+
210
+ class HashProcessor
211
+ {
212
+ use LazyGhostTrait;
213
+ // Because of how the LazyGhostTrait trait works internally, you
214
+ // must add this private property in your class
215
+ private int $lazyObjectId;
216
+
217
+ // This property may require a heavy computation to have its value
218
+ public readonly string $hash;
219
+
220
+ public function __construct()
221
+ {
222
+ self::createLazyGhost(initializer: [
223
+ 'hash' => $this->computeHash(...),
224
+ ], instance: $this);
225
+ }
226
+
227
+ private function computeHash(array $data): string
228
+ {
229
+ // Compute $this->hash value with the passed data
230
+ }
231
+ }
232
+
233
+ :class: `Symfony\\ Component\\ VarExporter\\ LazyGhostTrait ` also allows to
234
+ convert non-lazy classes to lazy ones::
235
+
236
+ namespace App\Hash;
237
+
238
+ use Symfony\Component\VarExporter\LazyGhostTrait;
239
+
240
+ class HashProcessor
241
+ {
242
+ public readonly string $hash;
243
+
244
+ public function __construct(array $data)
245
+ {
246
+ $this->hash = $this->computeHash($data);
247
+ }
248
+
249
+ private function computeHash(array $data): string
250
+ {
251
+ // ...
252
+ }
253
+
254
+ public function validateHash(): bool
255
+ {
256
+ // ...
257
+ }
258
+ }
259
+
260
+ class LazyHashProcessor extends HashProcessor
261
+ {
262
+ use LazyGhostTrait;
263
+ }
264
+
265
+ $processor = LazyHashProcessor::createLazyGhost(initializer: function (HashProcessor $instance): void {
266
+ // Do any operation you need here: call setters, getters, methods to validate the hash, etc.
267
+ $data = /** Retrieve required data to compute the hash */;
268
+ $instance->__construct(...$data);
269
+ $instance->validateHash();
270
+ });
271
+
272
+ While you never query ``$processor->hash `` value, heavy methods will never be triggered.
273
+ But still, the ``$processor `` object exists and can be used in your code, passed to
274
+ methods, functions, etc.
275
+
276
+ Additionally and by adding two arguments to initializer function, it is possible to initialize
277
+ properties one-by-one::
278
+
279
+ $processor = LazyHashProcessor::createLazyGhost(initializer: function (HashProcessor $instance, string $propertyName, ?string $propertyScope): mixed {
280
+ if (HashProcessor::class === $propertyScope && 'hash' === $propertyName) {
281
+ // Return $hash value
282
+ }
283
+
284
+ // Then you can add more logic for the other properties
285
+ });
286
+
287
+ Ghost objects unfortunately can't work with abstract classes but also internal PHP classes.
288
+ Nevertheless, the VarExporter component covers this need with the help of to
289
+ :ref: `Virtual Proxies <var-exporter_virtual-proxies >`.
290
+
291
+ .. versionadded :: 6.2
292
+
293
+ The :class: `Symfony\\ Component\\ VarExporter\\ LazyGhostTrait ` was introduced in Symfony 6.2.
294
+
295
+ .. _var-exporter_virtual-proxies :
296
+
297
+ LazyProxyTrait
298
+ ~~~~~~~~~~~~~~
299
+
300
+ The purpose of virtual proxies in the same one as
301
+ :ref: `ghost objects <var-exporter_ghost-objects >`, but their internal behavior is
302
+ totally different. Where ghost objects requires to extend a base class, virtual
303
+ proxies take advantage of the **Liskov Substitution principle **. This principle
304
+ describes that if two objects are implementing the same interface, you can swap between
305
+ the different implementations without breaking your application. This is what virtual
306
+ proxies take advantage of. To use virtual proxies, you may use
307
+ :class: `Symfony\\ Component\\ VarExporter\\ ProxyHelper ` to generate proxy's class
308
+ code::
309
+
310
+ namespace App\Hash;
311
+
312
+ use Symfony\Component\VarExporter\ProxyHelper;
313
+
314
+ interface ProcessorInterface
315
+ {
316
+ public function getHash(): bool;
317
+ }
318
+
319
+ abstract class AbstractProcessor implements ProcessorInterface
320
+ {
321
+ protected string $hash;
322
+
323
+ public function getHash(): bool
324
+ {
325
+ return $this->hash;
326
+ }
327
+ }
328
+
329
+ class HashProcessor extends AbstractProcessor
330
+ {
331
+ public function __construct(array $data)
332
+ {
333
+ $this->hash = $this->computeHash($data);
334
+ }
335
+
336
+ private function computeHash(array $data): string
337
+ {
338
+ // ...
339
+ }
340
+ }
341
+
342
+ $proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(AbstractProcessor::class));
343
+ // $proxyCode contains the actual proxy and the reference to LazyProxyTrait.
344
+ // In production env, this should be dumped into a file to avoid calling eval().
345
+ eval('class HashProcessorProxy'.$proxyCode);
346
+
347
+ $processor = HashProcessorProxy::createLazyProxy(initializer: function (): ProcessorInterface {
348
+ $data = /** Retrieve required data to compute the hash */;
349
+ $instance = new HashProcessor(...$data);
350
+
351
+ // Do any operation you need here: call setters, getters, methods to validate the hash, etc.
352
+
353
+ return $instance;
354
+ });
355
+
356
+ Just like ghost objects, while you never query ``$processor->hash ``, its value will not be computed.
357
+ The main difference with ghost objects is that this time, we created a proxy of an abstract class.
358
+ This also works with internal PHP class.
359
+
360
+ .. versionadded :: 6.2
361
+
362
+ The :class: `Symfony\\ Component\\ VarExporter\\ LazyProxyTrait ` and
363
+ :class: `Symfony\\ Component\\ VarExporter\\ ProxyHelper ` were introduced in Symfony 6.2.
364
+
184
365
.. _`OPcache` : https://www.php.net/opcache
185
366
.. _`PSR-2` : https://www.php-fig.org/psr/psr-2/
0 commit comments