@@ -128,5 +128,186 @@ created by using the special ``"\0"`` property name to define their internal val
128
128
"\0" => [$inputArray],
129
129
]);
130
130
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 (): AbstractProcessor {
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
+
131
312
.. _`OPcache` : https://www.php.net/opcache
132
313
.. _`PSR-2` : https://www.php-fig.org/psr/psr-2/
0 commit comments