LazyObjectState.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\VarExporter\Internal;
  11. use Symfony\Component\VarExporter\Hydrator as PublicHydrator;
  12. /**
  13. * Keeps the state of lazy objects.
  14. *
  15. * As a micro-optimization, this class uses no type declarations.
  16. *
  17. * @internal
  18. */
  19. class LazyObjectState
  20. {
  21. public const STATUS_UNINITIALIZED_FULL = 1;
  22. public const STATUS_UNINITIALIZED_PARTIAL = 2;
  23. public const STATUS_INITIALIZED_FULL = 3;
  24. public const STATUS_INITIALIZED_PARTIAL = 4;
  25. /**
  26. * @var self::STATUS_*
  27. */
  28. public int $status = self::STATUS_UNINITIALIZED_FULL;
  29. public object $realInstance;
  30. public object $cloneInstance;
  31. /**
  32. * @param array<string, true> $skippedProperties
  33. */
  34. public function __construct(
  35. public ?\Closure $initializer = null,
  36. public array $skippedProperties = [],
  37. ) {
  38. }
  39. public function initialize($instance, $propertyName, $writeScope)
  40. {
  41. if (self::STATUS_UNINITIALIZED_FULL !== $this->status) {
  42. return $this->status;
  43. }
  44. $this->status = self::STATUS_INITIALIZED_PARTIAL;
  45. try {
  46. if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) {
  47. PublicHydrator::hydrate($instance, $defaultProperties);
  48. }
  49. ($this->initializer)($instance);
  50. } catch (\Throwable $e) {
  51. $this->status = self::STATUS_UNINITIALIZED_FULL;
  52. $this->reset($instance);
  53. throw $e;
  54. }
  55. return $this->status = self::STATUS_INITIALIZED_FULL;
  56. }
  57. public function reset($instance): void
  58. {
  59. $class = $instance::class;
  60. $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
  61. $skippedProperties = $this->skippedProperties;
  62. $properties = (array) $instance;
  63. foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
  64. $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
  65. if ($k === $key && ($access & Hydrator::PROPERTY_HAS_HOOKS || ($access >> 2) & \ReflectionProperty::IS_READONLY || !\array_key_exists($k, $properties))) {
  66. $skippedProperties[$k] = true;
  67. }
  68. }
  69. foreach (LazyObjectRegistry::$classResetters[$class] as $reset) {
  70. $reset($instance, $skippedProperties);
  71. }
  72. foreach ((array) $instance as $name => $value) {
  73. if ("\0" !== ($name[0] ?? '') && !\array_key_exists($name, $skippedProperties)) {
  74. unset($instance->$name);
  75. }
  76. }
  77. $this->status = self::STATUS_UNINITIALIZED_FULL;
  78. }
  79. public function __clone()
  80. {
  81. if (isset($this->cloneInstance)) {
  82. try {
  83. $this->realInstance = $this->cloneInstance;
  84. } finally {
  85. unset($this->cloneInstance);
  86. }
  87. } elseif (isset($this->realInstance)) {
  88. $this->realInstance = clone $this->realInstance;
  89. }
  90. }
  91. public function __get($name)
  92. {
  93. if ('realInstance' !== $name) {
  94. throw new \BadMethodCallException(\sprintf('No such property "%s::$%s"', self::class, $name));
  95. }
  96. return $this->realInstance = ($this->initializer)();
  97. }
  98. }