* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\VarExporter\Internal; use Symfony\Component\VarExporter\Exception\ClassNotFoundException; /** * @author Nicolas Grekas * * @internal */ class Hydrator { public static array $hydrators = []; public static array $simpleHydrators = []; public static array $propertyScopes = []; public function __construct( public readonly Registry $registry, public readonly ?Values $values, public readonly array $properties, public readonly mixed $value, public readonly array $wakeups, ) { } public static function hydrate($objects, $values, $properties, $value, $wakeups) { foreach ($properties as $class => $vars) { (self::$hydrators[$class] ??= self::getHydrator($class))($vars, $objects); } foreach ($wakeups as $k => $v) { if (\is_array($v)) { $objects[-$k]->__unserialize($v); } else { $objects[$v]->__wakeup(); } } return $value; } public static function getHydrator($class) { $baseHydrator = self::$hydrators['stdClass'] ??= static function ($properties, $objects) { foreach ($properties as $name => $values) { foreach ($values as $i => $v) { $objects[$i]->$name = $v; } } }; switch ($class) { case 'stdClass': return $baseHydrator; case 'ErrorException': return $baseHydrator->bindTo(null, new class() extends \ErrorException { }); case 'TypeError': return $baseHydrator->bindTo(null, new class() extends \Error { }); case 'SplObjectStorage': return static function ($properties, $objects) { foreach ($properties as $name => $values) { if ("\0" === $name) { foreach ($values as $i => $v) { for ($j = 0; $j < \count($v); ++$j) { $objects[$i]->attach($v[$j], $v[++$j]); } } continue; } foreach ($values as $i => $v) { $objects[$i]->$name = $v; } } }; } if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { throw new ClassNotFoundException($class); } $classReflector = new \ReflectionClass($class); switch ($class) { case 'ArrayIterator': case 'ArrayObject': $constructor = $classReflector->getConstructor()->invokeArgs(...); return static function ($properties, $objects) use ($constructor) { foreach ($properties as $name => $values) { if ("\0" !== $name) { foreach ($values as $i => $v) { $objects[$i]->$name = $v; } } } foreach ($properties["\0"] ?? [] as $i => $v) { $constructor($objects[$i], $v); } }; } if (!$classReflector->isInternal()) { return $baseHydrator->bindTo(null, $class); } if ($classReflector->name !== $class) { return self::$hydrators[$classReflector->name] ??= self::getHydrator($classReflector->name); } $propertySetters = []; foreach ($classReflector->getProperties() as $propertyReflector) { if (!$propertyReflector->isStatic()) { $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); } } if (!$propertySetters) { return $baseHydrator; } return static function ($properties, $objects) use ($propertySetters) { foreach ($properties as $name => $values) { if ($setValue = $propertySetters[$name] ?? null) { foreach ($values as $i => $v) { $setValue($objects[$i], $v); } continue; } foreach ($values as $i => $v) { $objects[$i]->$name = $v; } } }; } public static function getSimpleHydrator($class) { $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) { $readonly = (array) $this; foreach ($properties as $name => &$value) { $object->$name = $value; if (!($readonly[$name] ?? false)) { $object->$name = &$value; } } })->bindTo(new \stdClass()); switch ($class) { case 'stdClass': return $baseHydrator; case 'ErrorException': return $baseHydrator->bindTo(new \stdClass(), new class() extends \ErrorException { }); case 'TypeError': return $baseHydrator->bindTo(new \stdClass(), new class() extends \Error { }); case 'SplObjectStorage': return static function ($properties, $object) { foreach ($properties as $name => &$value) { if ("\0" !== $name) { $object->$name = $value; $object->$name = &$value; continue; } for ($i = 0; $i < \count($value); ++$i) { $object->attach($value[$i], $value[++$i]); } } }; } if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { throw new ClassNotFoundException($class); } $classReflector = new \ReflectionClass($class); switch ($class) { case 'ArrayIterator': case 'ArrayObject': $constructor = $classReflector->getConstructor()->invokeArgs(...); return static function ($properties, $object) use ($constructor) { foreach ($properties as $name => &$value) { if ("\0" === $name) { $constructor($object, $value); } else { $object->$name = $value; $object->$name = &$value; } } }; } if (!$classReflector->isInternal()) { $readonly = new \stdClass(); foreach ($classReflector->getProperties(\ReflectionProperty::IS_READONLY) as $propertyReflector) { if ($class === $propertyReflector->class) { $readonly->{$propertyReflector->name} = true; } } return $baseHydrator->bindTo($readonly, $class); } if ($classReflector->name !== $class) { return self::$simpleHydrators[$classReflector->name] ??= self::getSimpleHydrator($classReflector->name); } $propertySetters = []; foreach ($classReflector->getProperties() as $propertyReflector) { if (!$propertyReflector->isStatic()) { $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); } } if (!$propertySetters) { return $baseHydrator; } return static function ($properties, $object) use ($propertySetters) { foreach ($properties as $name => &$value) { if ($setValue = $propertySetters[$name] ?? null) { $setValue($object, $value); } else { $object->$name = $value; $object->$name = &$value; } } }; } public static function getPropertyScopes($class): array { $propertyScopes = []; $r = new \ReflectionClass($class); foreach ($r->getProperties() as $property) { $flags = $property->getModifiers(); if (\ReflectionProperty::IS_STATIC & $flags) { continue; } $name = $property->name; if (\ReflectionProperty::IS_PRIVATE & $flags) { $readonlyScope = null; if ($flags & \ReflectionProperty::IS_READONLY) { $readonlyScope = $class; } $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, $readonlyScope, $property]; continue; } $readonlyScope = null; if ($flags & \ReflectionProperty::IS_READONLY) { $readonlyScope = $property->class; } $propertyScopes[$name] = [$class, $name, $readonlyScope, $property]; if (\ReflectionProperty::IS_PROTECTED & $flags) { $propertyScopes["\0*\0$name"] = $propertyScopes[$name]; } } while ($r = $r->getParentClass()) { $class = $r->name; foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) { if (!$property->isStatic()) { $name = $property->name; $readonlyScope = $property->isReadOnly() ? $class : null; $propertyScopes["\0$class\0$name"] = [$class, $name, $readonlyScope, $property]; $propertyScopes[$name] ??= [$class, $name, $readonlyScope, $property]; } } } return $propertyScopes; } }