Skip to content

Commit

Permalink
[VarExporter] Do initialize ghost objects when setting one of their p…
Browse files Browse the repository at this point in the history
…roperties
  • Loading branch information
nicolas-grekas committed Oct 2, 2022
1 parent 53277fe commit e6151f9
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 153 deletions.
12 changes: 7 additions & 5 deletions Internal/LazyObjectRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,18 @@ public static function getParentMethods($class)

public static function getScope($propertyScopes, $class, $property, $readonlyScope = null)
{
if (null === $readonlyScope && !isset($propertyScopes["\0$class\0$property"]) && !isset($propertyScopes["\0*\0$property"])) {
if (null === $readonlyScope && !isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) {
return null;
}
$frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];

$scope = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['class'] ?? \Closure::class;

if (null === $readonlyScope && isset($propertyScopes["\0*\0$property"]) && ($class === $scope || is_subclass_of($class, $scope))) {
if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) {
$scope = $frame['object']->class;
}
if (null === $readonlyScope && '*' === $k[1] && ($class === $scope || is_subclass_of($class, $scope))) {
return null;
}

return \ReflectionProperty::class === $scope ? $class : $scope;
return $scope;
}
}
63 changes: 21 additions & 42 deletions Internal/LazyObjectState.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,6 @@ class LazyObjectState
public const STATUS_UNINITIALIZED_FULL = 2;
public const STATUS_INITIALIZED_FULL = 3;

/**
* @var array<class-string|'*', array<string, true>>
*/
public readonly array $preInitUnsetProperties;

/**
* @var array<string, true>
*/
public readonly array $preInitSetProperties;

/**
* @var array<class-string|'*', array<string, true>>
*/
public array $unsetProperties;

/**
* @var array<string, true>
*/
Expand All @@ -56,70 +41,64 @@ public function __construct(public \Closure $initializer, $skippedProperties = [
$this->skippedProperties = $skippedProperties;
}

/**
* @return bool Returns true when fully-initializing, false when partial-initializing
*/
public function initialize($instance, $propertyName, $propertyScope)
{
$properties = null;
$class = $instance::class;

if (!$this->status) {
$this->status = 1 < (new \ReflectionFunction($this->initializer))->getNumberOfRequiredParameters() ? self::STATUS_INITIALIZED_PARTIAL : self::STATUS_UNINITIALIZED_FULL;
$this->preInitUnsetProperties = $this->unsetProperties ??= [];
$properties = (array) $instance;
$this->preInitSetProperties = array_fill_keys(array_keys(array_diff_key($properties, $this->skippedProperties, ["\0{$class}\0lazyObjectId" => true])), true);
$this->status = 4 <= (new \ReflectionFunction($this->initializer))->getNumberOfParameters() ? self::STATUS_INITIALIZED_PARTIAL : self::STATUS_UNINITIALIZED_FULL;

if (null === $propertyName) {
return self::STATUS_INITIALIZED_PARTIAL !== $this->status;
return $this->status;
}
}

if (self::STATUS_INITIALIZED_FULL === $this->status) {
return true;
return self::STATUS_INITIALIZED_FULL;
}

if (self::STATUS_INITIALIZED_PARTIAL === $this->status) {
$value = ($this->initializer)(...[$instance, $propertyName, $propertyScope]);

$class = $instance::class;
$propertyScope ??= $class;
$accessor = LazyObjectRegistry::$classAccessors[$propertyScope] ??= LazyObjectRegistry::getClassAccessors($propertyScope);
$propertyScopes = Hydrator::$propertyScopes[$class];
$propertyScopes[$k = "\0$propertyScope\0$propertyName"] ?? $propertyScopes[$k = "\0*\0$propertyName"] ?? $k = $propertyName;

$value = ($this->initializer)(...[$instance, $propertyName, $propertyScope, LazyObjectRegistry::$defaultProperties[$class][$k] ?? null]);

$accessor = LazyObjectRegistry::$classAccessors[$propertyScope] ??= LazyObjectRegistry::getClassAccessors($propertyScope);
$accessor['set']($instance, $propertyName, $value);

return false;
return self::STATUS_INITIALIZED_PARTIAL;
}

$this->status = self::STATUS_INITIALIZED_FULL;

try {
if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$class], $this->preInitSetProperties, $this->skippedProperties)) {
if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) {
PublicHydrator::hydrate($instance, $defaultProperties);
}

($this->initializer)($instance);
} catch (\Throwable $e) {
$this->status = self::STATUS_UNINITIALIZED_FULL;

if ($defaultProperties) {
self::resetProperties($class, $instance, $defaultProperties);
}
$this->reset($instance);

throw $e;
}

return true;
return self::STATUS_INITIALIZED_FULL;
}

private static function resetProperties($class, $instance, $properties): void
public function reset($instance): void
{
$propertyScopes = Hydrator::$propertyScopes[$class];
$skippedProperties = [];
$class = $instance::class;
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
$skippedProperties = $this->skippedProperties;
$properties = (array) $instance;

foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) {
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;

if (($k === $key && !\array_key_exists($k, $properties)) || null !== $readonlyScope) {
$skippedProperties[$key] = true;
if ($k === $key && (null !== $readonlyScope || !\array_key_exists($k, $properties))) {
$skippedProperties[$k] = true;
}
}

Expand Down
Loading

0 comments on commit e6151f9

Please sign in to comment.