Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

lazy-loading-ghost-object.md

Zuletzt modifiziert: 02.04.2025, 15:02 - Dateigröße: 12.52 KiB


001  ---
002  title: Lazy Loading Ghost Object Proxies
003  ---
004   
005  # Lazy Loading Ghost Object Proxies
006   
007  A Lazy Loading Ghost is a type of proxy object.
008   
009  More specifically, it is a fake object that looks exactly like an object
010  that you want to interact with, but is actually just an empty instance
011  that gets all properties populated as soon as they are needed.
012   
013  Those properties do not really exist until the ghost object is actually
014  initialized.
015   
016  ## Lazy loading with the Ghost Object
017   
018  In pseudo-code, in userland, [lazy loading](http://www.martinfowler.com/eaaCatalog/lazyLoad.html) in a ghost object
019  looks like following:
020   
021  ```php
022  class MyObjectProxy
023  {
024      private $initialized = false;
025      private $name;
026      private $surname;
027   
028      public function doFoo()
029      {
030          $this->init();
031   
032          // Perform doFoo routine using loaded variables
033      }
034   
035      private function init()
036      {
037          if (! $this->initialized) {
038              $data          = some_logic_that_loads_data();
039   
040              $this->name    = $data['name'];
041              $this->surname = $data['surname'];
042   
043              $this->initialized = true;
044          }
045      }
046  }
047  ```
048   
049  Ghost objects work similarly to virtual proxies, but since they don't wrap around a "real" instance of the proxied
050  subject, they are better suited for representing dataset rows.
051   
052  ## When do I use a ghost object?
053   
054  You usually need a ghost object in cases where following applies:
055   
056   * you are building a small data-mapper and want to lazily load data across associations in your object graph;
057   * you want to initialize objects representing rows in a large dataset;
058   * you want to compare instances of lazily initialized objects without the risk of comparing a proxy with a real subject;
059   * you are aware of the internal state of the object and are confident in working with its internals via reflection
060     or direct property access.
061   
062  ## Usage examples
063   
064  [ProxyManager](https://github.com/Ocramius/ProxyManager) provides a factory that creates lazy loading ghost objects.
065  To use it, follow these steps:
066   
067  First, define your object's logic without taking care of lazy loading:
068   
069  ```php
070  namespace MyApp;
071   
072  class Customer
073  {
074      private $name;
075      private $surname;
076   
077      // just write your business logic or generally logic
078      // don't worry about how complex this object will be!
079      // don't code lazy-loading oriented optimizations in here!
080      public function getName() { return $this->name; }
081      public function setName($name) { $this->name = (string) $name; }
082      public function getSurname() { return $this->surname; }
083      public function setSurname($surname) { $this->surname = (string) $surname; }
084  }
085  ```
086   
087  Then, use the proxy manager to create a ghost object of it.
088  You will be responsible for setting its state during lazy loading:
089   
090  ```php
091  namespace MyApp;
092   
093  use ProxyManager\Factory\LazyLoadingGhostFactory;
094  use ProxyManager\Proxy\GhostObjectInterface;
095   
096  require_once __DIR__ . '/vendor/autoload.php';
097   
098  $factory     = new LazyLoadingGhostFactory();
099  $initializer = function (
100      GhostObjectInterface $ghostObject,
101      string $method,
102      array $parameters,
103      & $initializer,
104      array $properties
105  ) {
106      $initializer   = null; // disable initialization
107   
108      // load data and modify the object here
109      $properties["\0MyApp\\Customer\0name"]    = 'Agent';
110      $properties["\0MyApp\\Customer\0surname"] = 'Smith'; 
111      
112      // you may also call methods on the object, but remember that
113      // the constructor was not called yet:
114      $ghostObject->setSurname('Smith');
115   
116      return true; // confirm that initialization occurred correctly
117  };
118   
119  $ghostObject = $factory->createProxy(\MyApp\Customer::class, $initializer);
120  ```
121   
122  You can now use your object as before:
123   
124  ```php
125  // this will work as before
126  echo $ghostObject->getName() . ' ' . $ghostObject->getSurname(); // Agent Smith
127  ```
128   
129  ## Lazy Initialization
130   
131  We use a closure to handle lazy initialization of the proxy instance at runtime.
132  The initializer closure signature for ghost objects is:
133   
134  ```php
135  /**
136   * @var object  $ghostObject   The instance of the ghost object proxy that is being initialized.
137   * @var string  $method        The name of the method that triggered lazy initialization.
138   * @var array   $parameters    An ordered list of parameters passed to the method that
139   *                             triggered initialization, indexed by parameter name.
140   * @var Closure $initializer   A reference to the property that is the initializer for the
141   *                             proxy. Set it to null to disable further initialization.
142   * @var array   $properties    By-ref array with the properties defined in the object, with their
143   *                             default values pre-assigned. Keys are in the same format that
144   *                             an (array) cast of an object would provide:
145   *                              - `"\0Ns\\ClassName\0propertyName"` for `private $propertyName`
146   *                                defined on `Ns\ClassName`
147   *                              - `"\0Ns\\ClassName\0propertyName"` for `protected $propertyName`
148   *                                defined in any level of the hierarchy
149   *                              - `"propertyName"` for `public $propertyName`
150   *                                defined in any level of the hierarchy
151   *
152   * @return bool true on success
153   */
154  $initializer = function (
155      \ProxyManager\Proxy\GhostObjectInterface $ghostObject,
156      string $method,
157      array $parameters,
158      & $initializer,
159      array $properties
160  ) {};
161  ```
162   
163  The initializer closure should usually be coded like following:
164   
165  ```php
166  $initializer = function (
167      \ProxyManager\Proxy\GhostObjectInterface $ghostObject,
168      string $method,
169      array $parameters,
170      & $initializer,
171      array $properties
172  ) {
173      $initializer = null; // disable initializer for this proxy instance
174   
175      // initialize properties (please read further on)
176      $properties["\0ClassName\0foo"] = 'foo';
177      $properties["\0ClassName\0bar"] = 'bar'; 
178   
179      return true; // report success
180  };
181  ```
182   
183  ### Lazy initialization `$properties` explained
184   
185  The assignments to properties in this closure use unusual `"\0"` sequences.
186  This is to be consistent with how PHP represents private and protected properties when
187  casting an object to an array.
188  `ProxyManager` simply copies a reference to the properties into the `$properties` array passed to the
189  initializer, which allows you to set the state of the object without accessing any of its public
190  API. (This is a very important detail for mapper implementations!)
191   
192  Specifically:
193   
194   * `"\0Ns\\ClassName\0propertyName"` means `private $propertyName` defined in `Ns\ClassName`;
195   * `"\0*\0propertyName"` means `protected $propertyName` defined in any level of the class 
196     hierarchy;
197   * `"propertyName"` means `public $propertyName` defined in any level of the class hierarchy.
198   
199  Therefore, given this class:
200   
201  ```php
202  namespace MyNamespace;
203   
204  class MyClass
205  {
206      private $property1;
207      protected $property2;
208      public $property3;
209  }
210  ```
211   
212  Its appropriate initialization code would be:
213   
214  ```php
215  namespace MyApp;
216   
217  use ProxyManager\Factory\LazyLoadingGhostFactory;
218  use ProxyManager\Proxy\GhostObjectInterface;
219   
220  require_once __DIR__ . '/vendor/autoload.php';
221   
222  $factory     = new LazyLoadingGhostFactory();
223  $initializer = function (
224      GhostObjectInterface $ghostObject,
225      string $method,
226      array $parameters,
227      & $initializer,
228      array $properties
229  ) {
230      $initializer = null;
231   
232      $properties["\0MyNamespace\\MyClass\0property1"] = 'foo';  //private property of MyNamespace\MyClass
233      $properties["\0*\0property2"]                    = 'bar';  //protected property in MyClass's hierarchy
234      $properties["property3"]                         = 'baz';  //public property in MyClass's hierarchy
235   
236      return true;
237  };
238   
239  $instance = $factory->createProxy(\MyNamespace\MyClass::class, $initializer);
240  ```
241   
242  This code would initialize `$property1`, `$property2` and `$property3`
243  respectively to `"foo"`, `"bar"` and `"baz"`.
244   
245  You may read the default values for those properties by reading the respective array keys.
246   
247  Although it is possible to initialize the object by interacting with its public API, it is
248  not safe to do so, because the object only contains default property values since its constructor was not called.
249   
250  ## Proxy implementation
251   
252  The
253  [`ProxyManager\Factory\LazyLoadingGhostFactory`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Factory/LazyLoadingGhostFactory.php)
254  produces proxies that implement the
255  [`ProxyManager\Proxy\GhostObjectInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Proxy/GhostObjectInterface.php).
256   
257  At any point in time, you can set a new initializer for the proxy:
258   
259  ```php
260  $ghostObject->setProxyInitializer($initializer);
261  ```
262   
263  In your initializer, you **MUST** turn off any further initialization:
264   
265  ```php
266  $ghostObject->setProxyInitializer(null);
267  ```
268   
269  or
270   
271  ```php
272  $initializer = null; // if you use the initializer passed by reference to the closure
273  ```
274   
275  Remember to call `$ghostObject->setProxyInitializer(null);`, or to set `$initializer = null` inside your
276  initializer closure to disable initialization of your proxy, or else initialization will trigger
277  more than once.
278   
279  ## Triggering Initialization
280   
281  A lazy loading ghost object is initialized whenever you access any of its properties.
282  Any of the following interactions would trigger lazy initialization:
283   
284  ```php
285  // calling a method (only if the method accesses internal state)
286  $ghostObject->someMethod();
287   
288  // reading a property
289  echo $ghostObject->someProperty;
290   
291  // writing a property
292  $ghostObject->someProperty = 'foo';
293   
294  // checking for existence of a property
295  isset($ghostObject->someProperty);
296   
297  // removing a property
298  unset($ghostObject->someProperty);
299   
300  // accessing a property via reflection
301  $reflection = new \ReflectionProperty($ghostObject, 'someProperty');
302  $reflection->setAccessible(true);
303  $reflection->getValue($ghostObject);
304   
305  // cloning the entire proxy
306  clone $ghostObject;
307   
308  // serializing the proxy
309  $unserialized = unserialize(serialize($ghostObject));
310  ```
311   
312  A method like following would never trigger lazy loading, in the context of a ghost object:
313   
314  ```php
315  public function sayHello() : string
316  {
317      return 'Look ma! No property accessed!';
318  }
319  ```
320   
321  ## Skipping properties (properties that should not be initialized)
322   
323  In some contexts, you may want some properties to be completely ignored by the lazy-loading
324  system.
325   
326  An example for that (in data mappers) is entities with identifiers: an identifier is usually
327   
328   * lightweight
329   * known at all times
330   
331  This means that it can be set in our object at all times, and we never need to lazy-load
332  it. Here is a typical example:
333   
334  ```php
335  namespace MyApp;
336   
337  class User
338  {
339      private $id;
340      private $username;
341      private $passwordHash;
342      private $email;
343      private $address;
344      // ...
345      
346      public function getId() : int
347      {
348          return $this->id;
349      }
350  }
351  ```
352   
353  If we want to skip the property `$id` from lazy-loading, we might want to tell that to
354  the `LazyLoadingGhostFactory`. Here is a longer example, with a more near-real-world
355  scenario:
356   
357  ```php
358  namespace MyApp;
359   
360  use ProxyManager\Factory\LazyLoadingGhostFactory;
361  use ProxyManager\Proxy\GhostObjectInterface;
362   
363  require_once __DIR__ . '/vendor/autoload.php';
364   
365  $factory     = new LazyLoadingGhostFactory();
366  $initializer = function (
367      GhostObjectInterface $ghostObject,
368      string $method,
369      array $parameters,
370      & $initializer,
371      array $properties
372  ) {
373      $initializer = null;
374   
375      // note that `getId` won't initialize our proxy here
376      $properties["\0MyApp\\User\0username"]     = $db->fetchField('users', 'username', $ghostObject->getId();
377      $properties["\0MyApp\\User\0passwordHash"] = $db->fetchField('users', 'passwordHash', $ghostObject->getId();
378      $properties["\0MyApp\\User\0email"]        = $db->fetchField('users', 'email', $ghostObject->getId();
379      $properties["\0MyApp\\User\0address"]      = $db->fetchField('users', 'address', $ghostObject->getId();
380   
381      return true;
382  };
383  $proxyOptions = [
384      'skippedProperties' => [
385          "\0MyApp\\User\0id",
386      ],
387  ];
388   
389  $instance = $factory->createProxy(User::class, $initializer, $proxyOptions);
390   
391  $idReflection = new \ReflectionProperty(User::class, 'id');
392   
393  $idReflection->setAccessible(true);
394   
395  // write the identifier into our ghost object (assuming `setId` doesn't exist)
396  $idReflection->setValue($instance, 1234);
397  ```
398   
399  In this example, we pass a `skippedProperties` array to our proxy factory. Note the use of the `"\0"` parameter syntax as described above.
400   
401  ## Proxying interfaces
402   
403  A lazy loading ghost object cannot proxy an interface directly, as it operates directly around
404  the state of an object. Use a [Virtual Proxy](lazy-loading-value-holder.md) for that instead.
405   
406  ## Tuning performance for production
407   
408  See [Tuning ProxyManager for Production](tuning-for-production.md).
409