* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\VarExporter\Internal; /** * Stores the state of lazy objects and caches related reflection information. * * As a micro-optimization, this class uses no type declarations. * * @internal */ class LazyObjectRegistry { /** * @var array */ public static array $classReflectors = []; /** * @var array> */ public static array $defaultProperties = []; /** * @var array> */ public static array $classResetters = []; /** * @var array */ public static array $classAccessors = []; /** * @var array */ public static array $parentMethods = []; public static ?\Closure $noInitializerState = null; public static function getClassResetters($class) { $classProperties = []; if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) { $propertyScopes = []; } else { $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); } foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) { $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; if ($k === $key && "\0$class\0lazyObjectState" !== $k) { $classProperties[$readonlyScope ?? $scope][$name] = $key; } } $resetters = []; foreach ($classProperties as $scope => $properties) { $resetters[] = \Closure::bind(static function ($instance, $skippedProperties) use ($properties) { foreach ($properties as $name => $key) { if (!\array_key_exists($key, $skippedProperties)) { unset($instance->$name); } } }, null, $scope); } return $resetters; } public static function getClassAccessors($class) { return \Closure::bind(static fn () => [ 'get' => static function &($instance, $name, $readonly) { if (!$readonly) { return $instance->$name; } $value = $instance->$name; return $value; }, 'set' => static function ($instance, $name, $value) { $instance->$name = $value; }, 'isset' => static fn ($instance, $name) => isset($instance->$name), 'unset' => static function ($instance, $name) { unset($instance->$name); }, ], null, \Closure::class === $class ? null : $class)(); } public static function getParentMethods($class) { $parent = get_parent_class($class); $methods = []; foreach (['set', 'isset', 'unset', 'clone', 'serialize', 'unserialize', 'sleep', 'wakeup', 'destruct', 'get'] as $method) { if (!$parent || !method_exists($parent, '__'.$method)) { $methods[$method] = false; } else { $m = new \ReflectionMethod($parent, '__'.$method); $methods[$method] = !$m->isAbstract() && !$m->isPrivate(); } } $methods['get'] = $methods['get'] ? ($m->returnsReference() ? 2 : 1) : 0; return $methods; } public static function getScope($propertyScopes, $class, $property, $readonlyScope = null) { 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]; if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) { $scope = $frame['object']->class; } if (null === $readonlyScope && '*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { return null; } return $scope; } }