8000 Merge branch '6.3' into 6.4 · symfony/symfony-docs@b8c79f3 · GitHub
[go: up one dir, main page]

Skip to content

Commit b8c79f3

Browse files
committed
Merge branch '6.3' into 6.4
* 6.3: Tweaks [VarExporter] Add `LazyGhostTrait` and `LazyProxyTrait` documentation
2 parents 7a47892 + 8269a08 commit b8c79f3

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

components/var_exporter.rst

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,5 +181,186 @@ populated by using the special ``"\0"`` property name to define their internal v
181181

182182
The :class:`Symfony\\Component\\VarExporter\\Hydrator` was introduced in Symfony 6.2.
183183

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, the
203+
``$hash`` property is defined as lazy. Also, 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
273+
triggered. But still, the ``$processor`` object exists and can be used in your
274+
code, passed to methods, functions, etc.
275+
276+
Additionally and by adding two arguments to the initializer function, it is
277+
possible to initialize 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 or internal PHP
288+
classes. Nevertheless, the VarExporter component covers this need with the help
289+
of :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
305+
between the different implementations without breaking your application. This is
306+
what virtual 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
357+
will not be computed. The main difference with ghost objects is that this time,
358+
a proxy of an abstract class was created. 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+
184365
.. _`OPcache`: https://www.php.net/opcache
185366
.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/

0 commit comments

Comments
 (0)
0