[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/vendor/ocramius/proxy-manager/docs/ -> lazy-loading-ghost-object.md (source)

   1  ---
   2  title: Lazy Loading Ghost Object Proxies
   3  ---
   4  
   5  # Lazy Loading Ghost Object Proxies
   6  
   7  A Lazy Loading Ghost is a type of proxy object.
   8  
   9  More specifically, it is a fake object that looks exactly like an object
  10  that you want to interact with, but is actually just an empty instance
  11  that gets all properties populated as soon as they are needed.
  12  
  13  Those properties do not really exist until the ghost object is actually
  14  initialized.
  15  
  16  ## Lazy loading with the Ghost Object
  17  
  18  In pseudo-code, in userland, [lazy loading](http://www.martinfowler.com/eaaCatalog/lazyLoad.html) in a ghost object
  19  looks like following:
  20  
  21  ```php
  22  class MyObjectProxy
  23  {
  24      private $initialized = false;
  25      private $name;
  26      private $surname;
  27  
  28      public function doFoo()
  29      {
  30          $this->init();
  31  
  32          // Perform doFoo routine using loaded variables
  33      }
  34  
  35      private function init()
  36      {
  37          if (! $this->initialized) {
  38              $data          = some_logic_that_loads_data();
  39  
  40              $this->name    = $data['name'];
  41              $this->surname = $data['surname'];
  42  
  43              $this->initialized = true;
  44          }
  45      }
  46  }
  47  ```
  48  
  49  Ghost objects work similarly to virtual proxies, but since they don't wrap around a "real" instance of the proxied
  50  subject, they are better suited for representing dataset rows.
  51  
  52  ## When do I use a ghost object?
  53  
  54  You usually need a ghost object in cases where following applies:
  55  
  56   * you are building a small data-mapper and want to lazily load data across associations in your object graph;
  57   * you want to initialize objects representing rows in a large dataset;
  58   * you want to compare instances of lazily initialized objects without the risk of comparing a proxy with a real subject;
  59   * you are aware of the internal state of the object and are confident in working with its internals via reflection
  60     or direct property access.
  61  
  62  ## Usage examples
  63  
  64  [ProxyManager](https://github.com/Ocramius/ProxyManager) provides a factory that creates lazy loading ghost objects.
  65  To use it, follow these steps:
  66  
  67  First, define your object's logic without taking care of lazy loading:
  68  
  69  ```php
  70  namespace MyApp;
  71  
  72  class Customer
  73  {
  74      private $name;
  75      private $surname;
  76  
  77      // just write your business logic or generally logic
  78      // don't worry about how complex this object will be!
  79      // don't code lazy-loading oriented optimizations in here!
  80      public function getName() { return $this->name; }
  81      public function setName($name) { $this->name = (string) $name; }
  82      public function getSurname() { return $this->surname; }
  83      public function setSurname($surname) { $this->surname = (string) $surname; }
  84  }
  85  ```
  86  
  87  Then, use the proxy manager to create a ghost object of it.
  88  You will be responsible for setting its state during lazy loading:
  89  
  90  ```php
  91  namespace MyApp;
  92  
  93  use ProxyManager\Factory\LazyLoadingGhostFactory;
  94  use ProxyManager\Proxy\GhostObjectInterface;
  95  
  96  require_once  __DIR__ . '/vendor/autoload.php';
  97  
  98  $factory     = new LazyLoadingGhostFactory();
  99  $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).


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1