Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter\Exception;
class ClassNotFoundException extends \Exception implements ExceptionInterface
{
public function __construct(string $class, ?\Throwable $previous = null)
{
parent::__construct(\sprintf('Class "%s" not found.', $class), 0, $previous);
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter\Exception;
interface ExceptionInterface extends \Throwable
{
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter\Exception;
class LogicException extends \LogicException implements ExceptionInterface
{
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter\Exception;
class NotInstantiableTypeException extends \Exception implements ExceptionInterface
{
public function __construct(string $type, ?\Throwable $previous = null)
{
parent::__construct(\sprintf('Type "%s" is not instantiable.', $type), 0, $previous);
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
use Symfony\Component\VarExporter\Internal\Hydrator as InternalHydrator;
/**
* Utility class to hydrate the properties of an object.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class Hydrator
{
/**
* Sets the properties of an object, including private and protected ones.
*
* For example:
*
* // Sets the public or protected $object->propertyName property
* Hydrator::hydrate($object, ['propertyName' => $propertyValue]);
*
* // Sets a private property defined on its parent Bar class:
* Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]);
*
* // Alternative way to set the private $object->privateBarProperty property
* Hydrator::hydrate($object, [], [
* Bar::class => ['privateBarProperty' => $propertyValue],
* ]);
*
* Instances of ArrayObject, ArrayIterator and SplObjectStorage can be hydrated
* by using the special "\0" property name to define their internal value:
*
* // Hydrates an SplObjectStorage where $info1 is attached to $obj1, etc.
* Hydrator::hydrate($object, ["\0" => [$obj1, $info1, $obj2, $info2...]]);
*
* // Hydrates an ArrayObject populated with $inputArray
* Hydrator::hydrate($object, ["\0" => [$inputArray]]);
*
* @template T of object
*
* @param T $instance The object to hydrate
* @param array<string, mixed> $properties The properties to set on the instance
* @param array<class-string, array<string, mixed>> $scopedProperties The properties to set on the instance,
* keyed by their declaring class
*
* @return T
*/
public static function hydrate(object $instance, array $properties = [], array $scopedProperties = []): object
{
if ($properties) {
$class = $instance::class;
$propertyScopes = InternalHydrator::$propertyScopes[$class] ??= InternalHydrator::getPropertyScopes($class);
foreach ($properties as $name => &$value) {
[$scope, $name, $writeScope] = $propertyScopes[$name] ?? [$class, $name, $class];
$scopedProperties[$writeScope ?? $scope][$name] = &$value;
}
unset($value);
}
foreach ($scopedProperties as $scope => $properties) {
if ($properties) {
(InternalHydrator::$simpleHydrators[$scope] ??= InternalHydrator::getSimpleHydrator($scope))($properties, $instance);
}
}
return $instance;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
use Symfony\Component\VarExporter\Exception\ExceptionInterface;
use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
use Symfony\Component\VarExporter\Internal\Registry;
/**
* A utility class to create objects without calling their constructor.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class Instantiator
{
/**
* Creates an object and sets its properties without calling its constructor nor any other methods.
*
* @see Hydrator::hydrate() for examples
*
* @template T of object
*
* @param class-string<T> $class The class of the instance to create
* @param array<string, mixed> $properties The properties to set on the instance
* @param array<class-string, array<string, mixed>> $scopedProperties The properties to set on the instance,
* keyed by their declaring class
*
* @return T
*
* @throws ExceptionInterface When the instance cannot be created
*/
public static function instantiate(string $class, array $properties = [], array $scopedProperties = []): object
{
$reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class);
if (Registry::$cloneable[$class]) {
$instance = clone Registry::$prototypes[$class];
} elseif (Registry::$instantiableWithoutConstructor[$class]) {
$instance = $reflector->newInstanceWithoutConstructor();
} elseif (null === Registry::$prototypes[$class]) {
throw new NotInstantiableTypeException($class);
} elseif ($reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize')) {
$instance = unserialize('C:'.\strlen($class).':"'.$class.'":0:{}');
} else {
$instance = unserialize('O:'.\strlen($class).':"'.$class.'":0:{}');
}
return $properties || $scopedProperties ? Hydrator::hydrate($instance, $properties, $scopedProperties) : $instance;
}
}

View File

@@ -0,0 +1,413 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\NotInstantiableTypeException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Exporter
{
/**
* Prepares an array of values for VarExporter.
*
* For performance this method is public and has no type-hints.
*
* @param array &$values
* @param \SplObjectStorage $objectsPool
* @param array &$refsPool
* @param int &$objectsCount
* @param bool &$valuesAreStatic
*
* @return array
*
* @throws NotInstantiableTypeException When a value cannot be serialized
*/
public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic)
{
$refs = $values;
foreach ($values as $k => $value) {
if (\is_resource($value)) {
throw new NotInstantiableTypeException(get_resource_type($value).' resource');
}
$refs[$k] = $objectsPool;
if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) {
$values[$k] = &$value; // Break hard references to make $values completely
unset($value); // independent from the original structure
$refs[$k] = $value = $values[$k];
if ($value instanceof Reference && 0 > $value->id) {
$valuesAreStatic = false;
++$value->count;
continue;
}
$refsPool[] = [&$refs[$k], $value, &$value];
$refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value);
}
if (\is_array($value)) {
if ($value) {
$value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
}
goto handle_value;
} elseif (!\is_object($value) || $value instanceof \UnitEnum) {
goto handle_value;
}
$valueIsStatic = false;
if (isset($objectsPool[$value])) {
++$objectsCount;
$value = new Reference($objectsPool[$value][0]);
goto handle_value;
}
$class = $value::class;
$reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class);
$properties = [];
$sleep = null;
$proto = Registry::$prototypes[$class];
if ($reflector->hasMethod('__serialize')) {
if (!$reflector->getMethod('__serialize')->isPublic()) {
throw new \Error(\sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class));
}
if (!\is_array($arrayValue = $value->__serialize())) {
throw new \TypeError($class.'::__serialize() must return an array');
}
if ($reflector->hasMethod('__unserialize')) {
$properties = $arrayValue;
goto prepare_value;
}
} elseif (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) {
// ArrayIterator and ArrayObject need special care because their "flags"
// option changes the behavior of the (array) casting operator.
[$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto);
// populates Registry::$prototypes[$class] with a new instance
Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]);
} elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) {
// By implementing Serializable, SplObjectStorage breaks
// internal references; let's deal with it on our own.
foreach (clone $value as $v) {
$properties[] = $v;
$properties[] = $value[$v];
}
$properties = ['SplObjectStorage' => ["\0" => $properties]];
$arrayValue = (array) $value;
} elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod) {
++$objectsCount;
$objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0];
$value = new Reference($id);
goto handle_value;
} else {
if (method_exists($class, '__sleep')) {
if (!\is_array($sleep = $value->__sleep())) {
trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', \E_USER_NOTICE);
$value = null;
goto handle_value;
}
$sleep = array_flip($sleep);
}
$arrayValue = (array) $value;
}
$proto = (array) $proto;
foreach ($arrayValue as $name => $v) {
$i = 0;
$n = (string) $name;
if ('' === $n || "\0" !== $n[0]) {
$parent = $reflector;
do {
$p = $parent->hasProperty($n) ? $parent->getProperty($n) : null;
} while (!$p && $parent = $parent->getParentClass());
$c = $p && (!$p->isPublic() || (\PHP_VERSION_ID >= 80400 ? $p->isProtectedSet() || $p->isPrivateSet() : $p->isReadOnly())) ? $p->class : 'stdClass';
} elseif ('*' === $n[1]) {
$n = substr($n, 3);
$c = $reflector->getProperty($n)->class;
} else {
$i = strpos($n, "\0", 2);
$c = substr($n, 1, $i - 1);
$n = substr($n, 1 + $i);
}
if (null !== $sleep) {
if (!isset($sleep[$name]) && (!isset($sleep[$n]) || ($i && $c !== $class))) {
unset($arrayValue[$name]);
continue;
}
unset($sleep[$name], $sleep[$n]);
}
if ("\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) {
$properties[$c][$n] = $v;
} elseif (!\array_key_exists($name, $proto) || $proto[$name] !== $v) {
$properties[match ($c) {
'Error' => 'TypeError',
'Exception' => 'ErrorException',
default => $c,
}][$n] = $v;
}
}
if ($sleep) {
foreach ($sleep as $n => $v) {
trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE);
}
}
if (method_exists($class, '__unserialize')) {
$properties = $arrayValue;
}
prepare_value:
$objectsPool[$value] = [$id = \count($objectsPool)];
$properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
++$objectsCount;
$objectsPool[$value] = [$id, $class, $properties, method_exists($class, '__unserialize') ? -$objectsCount : (method_exists($class, '__wakeup') ? $objectsCount : 0)];
$value = new Reference($id);
handle_value:
if ($isRef) {
unset($value); // Break the hard reference created above
} elseif (!$valueIsStatic) {
$values[$k] = $value;
}
$valuesAreStatic = $valueIsStatic && $valuesAreStatic;
}
return $values;
}
public static function export($value, $indent = '')
{
switch (true) {
case \is_int($value) || \is_float($value): return var_export($value, true);
case [] === $value: return '[]';
case false === $value: return 'false';
case true === $value: return 'true';
case null === $value: return 'null';
case '' === $value: return "''";
case $value instanceof \UnitEnum: return '\\'.ltrim(var_export($value, true), '\\');
}
if ($value instanceof Reference) {
if (0 <= $value->id) {
return '$o['.$value->id.']';
}
if (!$value->count) {
return self::export($value->value, $indent);
}
$value = -$value->id;
return '&$r['.$value.']';
}
$subIndent = $indent.' ';
if (\is_string($value)) {
$code = \sprintf("'%s'", addcslashes($value, "'\\"));
$code = preg_replace_callback("/((?:[\\0\\r\\n]|\u{202A}|\u{202B}|\u{202D}|\u{202E}|\u{2066}|\u{2067}|\u{2068}|\u{202C}|\u{2069})++)(.)/", function ($m) use ($subIndent) {
$m[1] = \sprintf('\'."%s".\'', str_replace(
["\0", "\r", "\n", "\u{202A}", "\u{202B}", "\u{202D}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{202C}", "\u{2069}", '\n\\'],
['\0', '\r', '\n', '\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}', '\u{2069}', '\n"'."\n".$subIndent.'."\\'],
$m[1]
));
if ("'" === $m[2]) {
return substr($m[1], 0, -2);
}
if (str_ends_with($m[1], 'n".\'')) {
return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2);
}
return $m[1].$m[2];
}, $code, -1, $count);
if ($count && str_starts_with($code, "''.")) {
$code = substr($code, 3);
}
return $code;
}
if (\is_array($value)) {
$j = -1;
$code = '';
foreach ($value as $k => $v) {
$code .= $subIndent;
if (!\is_int($k) || 1 !== $k - $j) {
$code .= self::export($k, $subIndent).' => ';
}
if (\is_int($k) && $k > $j) {
$j = $k;
}
$code .= self::export($v, $subIndent).",\n";
}
return "[\n".$code.$indent.']';
}
if ($value instanceof Values) {
$code = $subIndent."\$r = [],\n";
foreach ($value->values as $k => $v) {
$code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n";
}
return "[\n".$code.$indent.']';
}
if ($value instanceof Registry) {
return self::exportRegistry($value, $indent, $subIndent);
}
if ($value instanceof Hydrator) {
return self::exportHydrator($value, $indent, $subIndent);
}
throw new \UnexpectedValueException(\sprintf('Cannot export value of type "%s".', get_debug_type($value)));
}
private static function exportRegistry(Registry $value, string $indent, string $subIndent): string
{
$code = '';
$serializables = [];
$seen = [];
$prototypesAccess = 0;
$factoriesAccess = 0;
$r = '\\'.Registry::class;
$j = -1;
foreach ($value->classes as $k => $class) {
if (':' === ($class[1] ?? null)) {
$serializables[$k] = $class;
continue;
}
if (!Registry::$instantiableWithoutConstructor[$class]) {
if (is_subclass_of($class, 'Serializable') && !method_exists($class, '__unserialize')) {
$serializables[$k] = 'C:'.\strlen($class).':"'.$class.'":0:{}';
} else {
$serializables[$k] = 'O:'.\strlen($class).':"'.$class.'":0:{}';
}
if (is_subclass_of($class, 'Throwable')) {
$eol = is_subclass_of($class, 'Error') ? "\0Error\0" : "\0Exception\0";
$serializables[$k] = substr_replace($serializables[$k], '1:{s:'.(5 + \strlen($eol)).':"'.$eol.'trace";a:0:{}}', -4);
}
continue;
}
$code .= $subIndent.(1 !== $k - $j ? $k.' => ' : '');
$j = $k;
$eol = ",\n";
$c = '['.self::export($class).']';
if ($seen[$class] ?? false) {
if (Registry::$cloneable[$class]) {
++$prototypesAccess;
$code .= 'clone $p'.$c;
} else {
++$factoriesAccess;
$code .= '$f'.$c.'()';
}
} else {
$seen[$class] = true;
if (Registry::$cloneable[$class]) {
$code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p = &'.$r.'::$prototypes)').$c.' ?? '.$r.'::p';
} else {
$code .= '('.($factoriesAccess++ ? '$f' : '($f = &'.$r.'::$factories)').$c.' ?? '.$r.'::f';
$eol = '()'.$eol;
}
$code .= '('.substr($c, 1, -1).'))';
}
$code .= $eol;
}
if (1 === $prototypesAccess) {
$code = str_replace('($p = &'.$r.'::$prototypes)', $r.'::$prototypes', $code);
}
if (1 === $factoriesAccess) {
$code = str_replace('($f = &'.$r.'::$factories)', $r.'::$factories', $code);
}
if ('' !== $code) {
$code = "\n".$code.$indent;
}
if ($serializables) {
$code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')';
} else {
$code = '['.$code.']';
}
return '$o = '.$code;
}
private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string
{
$code = '';
foreach ($value->properties as $class => $properties) {
$code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n";
}
$code = [
self::export($value->registry, $subIndent),
self::export($value->values, $subIndent),
'' !== $code ? "[\n".$code.$subIndent.']' : '[]',
self::export($value->value, $subIndent),
self::export($value->wakeups, $subIndent),
];
return '\\'.$value::class."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')';
}
/**
* @param \ArrayIterator|\ArrayObject $value
* @param \ArrayIterator|\ArrayObject $proto
*/
private static function getArrayObjectProperties($value, $proto): array
{
$reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject';
$reflector = Registry::$reflectors[$reflector] ??= Registry::getClassReflector($reflector);
$properties = [
$arrayValue = (array) $value,
$reflector->getMethod('getFlags')->invoke($value),
$value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator',
];
$reflector = $reflector->getMethod('setFlags');
$reflector->invoke($proto, \ArrayObject::STD_PROP_LIST);
if ($properties[1] & \ArrayObject::STD_PROP_LIST) {
$reflector->invoke($value, 0);
$properties[0] = (array) $value;
} else {
$reflector->invoke($value, \ArrayObject::STD_PROP_LIST);
$arrayValue = (array) $value;
}
$reflector->invoke($value, $properties[1]);
if ([[], 0, 'ArrayIterator'] === $properties) {
$properties = [];
} else {
if ('ArrayIterator' === $properties[2]) {
unset($properties[2]);
}
$properties = [$reflector->class => ["\0" => $properties]];
}
return [$arrayValue, $properties];
}
}

View File

@@ -0,0 +1,329 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <p@tchwork.com>
*
* @internal
*/
class Hydrator
{
public const PROPERTY_HAS_HOOKS = 1;
public const PROPERTY_NOT_BY_REF = 2;
public static array $hydrators = [];
public static array $simpleHydrators = [];
public static array $propertyScopes = [];
public $registry;
public $values;
public $properties;
public $value;
public $wakeups;
public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups)
{
$this->registry = $registry;
$this->values = $values;
$this->properties = $properties;
$this->value = $value;
$this->wakeups = $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][$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) {
$notByRef = (array) $this;
foreach ($properties as $name => &$value) {
if (!$noRef = $notByRef[$name] ?? false) {
$object->$name = $value;
$object->$name = &$value;
} elseif (true !== $noRef) {
$noRef($object, $value);
} else {
$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[$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()) {
$notByRef = new \stdClass();
foreach ($classReflector->getProperties() as $propertyReflector) {
if ($propertyReflector->isStatic()) {
continue;
}
if (\PHP_VERSION_ID >= 80400 && !$propertyReflector->isAbstract() && $propertyReflector->getHooks()) {
$notByRef->{$propertyReflector->name} = $propertyReflector->setRawValue(...);
} elseif ($propertyReflector->isReadOnly()) {
$notByRef->{$propertyReflector->name} = true;
}
}
return $baseHydrator->bindTo($notByRef, $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;
}
}
};
}
/**
* @return array
*/
public static function getPropertyScopes($class)
{
$propertyScopes = [];
$r = new \ReflectionClass($class);
foreach ($r->getProperties() as $property) {
$flags = $property->getModifiers();
if (\ReflectionProperty::IS_STATIC & $flags) {
continue;
}
$name = $property->name;
$access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0);
if (\PHP_VERSION_ID >= 80400 && !$property->isAbstract() && $h = $property->getHooks()) {
$access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0);
}
if (\ReflectionProperty::IS_PRIVATE & $flags) {
$propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, null, $access, $property];
continue;
}
$propertyScopes[$name] = [$class, $name, null, $access, $property];
if ($flags & (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY)) {
$propertyScopes[$name][2] = $property->class;
}
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) {
$flags = $property->getModifiers();
if (\ReflectionProperty::IS_STATIC & $flags) {
continue;
}
$name = $property->name;
$access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0);
if (\PHP_VERSION_ID >= 80400 && $h = $property->getHooks()) {
$access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0);
}
$propertyScopes["\0$class\0$name"] = [$class, $name, null, $access, $property];
$propertyScopes[$name] ??= $propertyScopes["\0$class\0$name"];
}
}
return $propertyScopes;
}
}

View File

@@ -0,0 +1,177 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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<class-string, \ReflectionClass>
*/
public static array $classReflectors = [];
/**
* @var array<class-string, array<string, mixed>>
*/
public static array $defaultProperties = [];
/**
* @var array<class-string, list<\Closure>>
*/
public static array $classResetters = [];
/**
* @var array<class-string, array{get: \Closure, set: \Closure, isset: \Closure, unset: \Closure}>
*/
public static array $classAccessors = [];
/**
* @var array<class-string, array{set: bool, isset: bool, unset: bool, clone: bool, serialize: bool, unserialize: bool, sleep: bool, wakeup: bool, destruct: bool, get: int}>
*/
public static array $parentMethods = [];
public static ?\Closure $noInitializerState = null;
public static function getClassResetters($class)
{
$classProperties = [];
$hookedProperties = [];
if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) {
$propertyScopes = [];
} else {
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
}
foreach ($propertyScopes as $key => [$scope, $name, $writeScope, $access]) {
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
if ($k !== $key || "\0$class\0lazyObjectState" === $k) {
continue;
}
if ($access & Hydrator::PROPERTY_HAS_HOOKS) {
$hookedProperties[$k] = true;
} else {
$classProperties[$writeScope ?? $scope][$name] = $key;
}
}
$resetters = [];
foreach ($classProperties as $scope => $properties) {
$resetters[] = \Closure::bind(static function ($instance, $skippedProperties, $onlyProperties = null) use ($properties) {
foreach ($properties as $name => $key) {
if (!\array_key_exists($key, $skippedProperties) && (null === $onlyProperties || \array_key_exists($key, $onlyProperties))) {
unset($instance->$name);
}
}
}, null, $scope);
}
$resetters[] = static function ($instance, $skippedProperties, $onlyProperties = null) use ($hookedProperties) {
foreach ((array) $instance as $name => $value) {
if ("\0" !== ($name[0] ?? '')
&& !\array_key_exists($name, $skippedProperties)
&& (null === $onlyProperties || \array_key_exists($name, $onlyProperties))
&& !isset($hookedProperties[$name])
) {
unset($instance->$name);
}
}
};
return $resetters;
}
public static function getClassAccessors($class)
{
return \Closure::bind(static fn () => [
'get' => static function &($instance, $name, $notByRef) {
if (!$notByRef) {
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 getScopeForRead($propertyScopes, $class, $property)
{
if (!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 ('*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) {
return null;
}
return $scope;
}
public static function getScopeForWrite($propertyScopes, $class, $property, $flags)
{
if (!($flags & (\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_READONLY | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET | \ReflectionProperty::IS_PROTECTED_SET : 0)))) {
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 ($flags & (\ReflectionProperty::IS_PRIVATE | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY))) {
return $scope;
}
if ($flags & (\ReflectionProperty::IS_PROTECTED | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PROTECTED_SET : 0)) && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) {
return null;
}
return $scope;
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\Hydrator as PublicHydrator;
/**
* Keeps the state of lazy objects.
*
* As a micro-optimization, this class uses no type declarations.
*
* @internal
*/
class LazyObjectState
{
public const STATUS_UNINITIALIZED_FULL = 1;
public const STATUS_UNINITIALIZED_PARTIAL = 2;
public const STATUS_INITIALIZED_FULL = 3;
public const STATUS_INITIALIZED_PARTIAL = 4;
/**
* @var array<string, true>
*/
public readonly array $skippedProperties;
/**
* @var self::STATUS_*
*/
public int $status = 0;
public object $realInstance;
public function __construct(public readonly \Closure|array $initializer, $skippedProperties = [])
{
$this->skippedProperties = $skippedProperties;
$this->status = \is_array($initializer) ? self::STATUS_UNINITIALIZED_PARTIAL : self::STATUS_UNINITIALIZED_FULL;
}
public function initialize($instance, $propertyName, $writeScope)
{
if (self::STATUS_INITIALIZED_FULL === $this->status) {
return self::STATUS_INITIALIZED_FULL;
}
if (\is_array($this->initializer)) {
$class = $instance::class;
$writeScope ??= $class;
$propertyScopes = Hydrator::$propertyScopes[$class];
$propertyScopes[$k = "\0$writeScope\0$propertyName"] ?? $propertyScopes[$k = "\0*\0$propertyName"] ?? $k = $propertyName;
if ($initializer = $this->initializer[$k] ?? null) {
$value = $initializer(...[$instance, $propertyName, $writeScope, LazyObjectRegistry::$defaultProperties[$class][$k] ?? null]);
$accessor = LazyObjectRegistry::$classAccessors[$writeScope] ??= LazyObjectRegistry::getClassAccessors($writeScope);
$accessor['set']($instance, $propertyName, $value);
return $this->status = self::STATUS_INITIALIZED_PARTIAL;
}
if ($initializer = $this->initializer["\0"] ?? null) {
if (!\is_array($values = $initializer($instance, LazyObjectRegistry::$defaultProperties[$class]))) {
throw new \TypeError(\sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values)));
}
$properties = (array) $instance;
foreach ($values as $key => $value) {
if (!\array_key_exists($key, $properties) && [$scope, $name, $writeScope] = $propertyScopes[$key] ?? null) {
$scope = $writeScope ?? $scope;
$accessor = LazyObjectRegistry::$classAccessors[$scope] ??= LazyObjectRegistry::getClassAccessors($scope);
$accessor['set']($instance, $name, $value);
if ($k === $key) {
$this->status = self::STATUS_INITIALIZED_PARTIAL;
}
}
}
}
return $this->status;
}
if (self::STATUS_INITIALIZED_PARTIAL === $this->status) {
return self::STATUS_INITIALIZED_PARTIAL;
}
$this->status = self::STATUS_INITIALIZED_PARTIAL;
try {
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;
$this->reset($instance);
throw $e;
}
return $this->status = self::STATUS_INITIALIZED_FULL;
}
public function reset($instance): void
{
$class = $instance::class;
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
$skippedProperties = $this->skippedProperties;
$properties = (array) $instance;
$onlyProperties = \is_array($this->initializer) ? $this->initializer : null;
foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
if ($k === $key && ($access & Hydrator::PROPERTY_HAS_HOOKS || ($access >> 2) & \ReflectionProperty::IS_READONLY || !\array_key_exists($k, $properties))) {
$skippedProperties[$k] = true;
}
}
foreach (LazyObjectRegistry::$classResetters[$class] as $reset) {
$reset($instance, $skippedProperties, $onlyProperties);
}
$this->status = self::STATUS_INITIALIZED_FULL === $this->status ? self::STATUS_UNINITIALIZED_FULL : self::STATUS_UNINITIALIZED_PARTIAL;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\Serializer\Attribute\Ignore;
if (\PHP_VERSION_ID >= 80300) {
/**
* @internal
*/
trait LazyObjectTrait
{
#[Ignore]
private readonly LazyObjectState $lazyObjectState;
}
} else {
/**
* @internal
*/
trait LazyObjectTrait
{
#[Ignore]
private LazyObjectState $lazyObjectState;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter\Internal;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Reference
{
public int $count = 0;
public function __construct(
public readonly int $id,
public readonly mixed $value = null,
) {
}
}

View File

@@ -0,0 +1,144 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Registry
{
public static array $reflectors = [];
public static array $prototypes = [];
public static array $factories = [];
public static array $cloneable = [];
public static array $instantiableWithoutConstructor = [];
public $classes = [];
public function __construct(array $classes)
{
$this->classes = $classes;
}
public static function unserialize($objects, $serializables)
{
$unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector');
try {
foreach ($serializables as $k => $v) {
$objects[$k] = unserialize($v);
}
} finally {
ini_set('unserialize_callback_func', $unserializeCallback);
}
return $objects;
}
public static function p($class)
{
self::getClassReflector($class, true, true);
return self::$prototypes[$class];
}
public static function f($class)
{
$reflector = self::$reflectors[$class] ??= self::getClassReflector($class, true, false);
return self::$factories[$class] = [$reflector, 'newInstanceWithoutConstructor'](...);
}
public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null)
{
if (!($isClass = class_exists($class)) && !interface_exists($class, false) && !trait_exists($class, false)) {
throw new ClassNotFoundException($class);
}
$reflector = new \ReflectionClass($class);
if ($instantiableWithoutConstructor) {
$proto = $reflector->newInstanceWithoutConstructor();
} elseif (!$isClass || $reflector->isAbstract()) {
throw new NotInstantiableTypeException($class);
} elseif ($reflector->name !== $class) {
$reflector = self::$reflectors[$name = $reflector->name] ??= self::getClassReflector($name, false, $cloneable);
self::$cloneable[$class] = self::$cloneable[$name];
self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name];
self::$prototypes[$class] = self::$prototypes[$name];
return $reflector;
} else {
try {
$proto = $reflector->newInstanceWithoutConstructor();
$instantiableWithoutConstructor = true;
} catch (\ReflectionException) {
$proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:';
if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) {
$proto = null;
} else {
try {
$proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}');
} catch (\Exception $e) {
if (__FILE__ !== $e->getFile()) {
throw $e;
}
throw new NotInstantiableTypeException($class, $e);
}
if (false === $proto) {
throw new NotInstantiableTypeException($class);
}
}
}
if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !method_exists($class, '__sleep') && !method_exists($class, '__serialize')) {
try {
serialize($proto);
} catch (\Exception $e) {
throw new NotInstantiableTypeException($class, $e);
}
}
}
if (null === $cloneable) {
if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !method_exists($proto, '__wakeup') && !method_exists($class, '__unserialize'))) {
throw new NotInstantiableTypeException($class);
}
$cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone');
}
self::$cloneable[$class] = $cloneable;
self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor;
self::$prototypes[$class] = $proto;
if ($proto instanceof \Throwable) {
static $setTrace;
if (null === $setTrace) {
$setTrace = [
new \ReflectionProperty(\Error::class, 'trace'),
new \ReflectionProperty(\Exception::class, 'trace'),
];
$setTrace[0] = $setTrace[0]->setValue(...);
$setTrace[1] = $setTrace[1]->setValue(...);
}
$setTrace[$proto instanceof \Exception]($proto, []);
}
return $reflector;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter\Internal;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Values
{
public $values;
public function __construct(array $values)
{
$this->values = $values;
}
}

19
vendor/symfony/var-exporter/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2018-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,409 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
use Symfony\Component\Serializer\Attribute\Ignore;
use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
use Symfony\Component\VarExporter\Internal\LazyObjectState;
use Symfony\Component\VarExporter\Internal\LazyObjectTrait;
trait LazyGhostTrait
{
use LazyObjectTrait;
/**
* Creates a lazy-loading ghost instance.
*
* Skipped properties should be indexed by their array-cast identifier, see
* https://php.net/manual/language.types.array#language.types.array.casting
*
* @param (\Closure(static):void $initializer The closure should initialize the object it receives as argument
* @param array<string, true>|null $skippedProperties An array indexed by the properties to skip, a.k.a. the ones
* that the initializer doesn't initialize, if any
* @param static|null $instance
*/
public static function createLazyGhost(\Closure|array $initializer, ?array $skippedProperties = null, ?object $instance = null): static
{
if (\is_array($initializer)) {
trigger_deprecation('symfony/var-exporter', '6.4', 'Per-property lazy-initializers are deprecated and won\'t be supported anymore in 7.0, use an object initializer instead.');
}
$onlyProperties = null === $skippedProperties && \is_array($initializer) ? $initializer : null;
if (self::class !== $class = $instance ? $instance::class : static::class) {
$skippedProperties["\0".self::class."\0lazyObjectState"] = true;
} elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
}
$instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor();
Registry::$defaultProperties[$class] ??= (array) $instance;
$instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []);
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
$reset($instance, $skippedProperties, $onlyProperties);
}
return $instance;
}
/**
* Returns whether the object is initialized.
*
* @param $partial Whether partially initialized objects should be considered as initialized
*/
#[Ignore]
public function isLazyObjectInitialized(bool $partial = false): bool
{
if (!$state = $this->lazyObjectState ?? null) {
return true;
}
if (!\is_array($state->initializer)) {
return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status;
}
$class = $this::class;
$properties = (array) $this;
if ($partial) {
return (bool) array_intersect_key($state->initializer, $properties);
}
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
foreach ($state->initializer as $key => $initializer) {
if (!\array_key_exists($key, $properties) && isset($propertyScopes[$key])) {
return false;
}
}
return true;
}
/**
* Forces initialization of a lazy object and returns it.
*/
public function initializeLazyObject(): static
{
if (!$state = $this->lazyObjectState ?? null) {
return $this;
}
if (!\is_array($state->initializer)) {
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
$state->initialize($this, '', null);
}
return $this;
}
$values = isset($state->initializer["\0"]) ? null : [];
$class = $this::class;
$properties = (array) $this;
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
foreach ($state->initializer as $key => $initializer) {
if (\array_key_exists($key, $properties) || ![$scope, $name, $writeScope] = $propertyScopes[$key] ?? null) {
continue;
}
$scope = $writeScope ?? $scope;
if (null === $values) {
if (!\is_array($values = ($state->initializer["\0"])($this, Registry::$defaultProperties[$class]))) {
throw new \TypeError(\sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values)));
}
if (\array_key_exists($key, $properties = (array) $this)) {
continue;
}
}
if (\array_key_exists($key, $values)) {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['set']($this, $name, $properties[$key] = $values[$key]);
} else {
$state->initialize($this, $name, $scope);
$properties = (array) $this;
}
}
return $this;
}
/**
* @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
*/
public function resetLazyObject(): bool
{
if (!$state = $this->lazyObjectState ?? null) {
return false;
}
if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $state->status) {
$state->reset($this);
}
return true;
}
public function &__get($name): mixed
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$notByRef = 0;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
$state = $this->lazyObjectState ?? null;
if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) {
$notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF;
if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) {
// Work around php/php-src#12695
$property = null === $scope ? $name : "\0$scope\0$name";
$property = $propertyScopes[$property][4]
?? Hydrator::$propertyScopes[$this::class][$property][4] = new \ReflectionProperty($scope ?? $class, $name);
} else {
$property = null;
}
if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) {
$scope ??= $writeScope;
}
if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope)) {
goto get_in_scope;
}
}
}
if ($parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']) {
if (2 === $parent) {
return parent::__get($name);
}
$value = parent::__get($name);
return $value;
}
if (null === $class) {
$frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $this::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE);
}
get_in_scope:
try {
if (null === $scope) {
if (!$notByRef) {
return $this->$name;
}
$value = $this->$name;
return $value;
}
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
return $accessor['get']($this, $name, $notByRef);
} catch (\Error $e) {
if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
throw $e;
}
try {
if (null === $scope) {
$this->$name = [];
return $this->$name;
}
$accessor['set']($this, $name, []);
return $accessor['get']($this, $name, $notByRef);
} catch (\Error) {
if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) {
throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious());
}
throw $e;
}
}
}
public function __set($name, $value): void
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
$state = $this->lazyObjectState ?? null;
if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"]))
&& LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status
) {
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
$state->initialize($this, $name, $writeScope ?? $scope);
}
goto set_in_scope;
}
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) {
parent::__set($name, $value);
return;
}
set_in_scope:
if (null === $scope) {
$this->$name = $value;
} else {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['set']($this, $name, $value);
}
}
public function __isset($name): bool
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
if ([$class, , $writeScope] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
$state = $this->lazyObjectState ?? null;
if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))
&& LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status
&& LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope)
) {
goto isset_in_scope;
}
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) {
return parent::__isset($name);
}
isset_in_scope:
if (null === $scope) {
return isset($this->$name);
}
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
return $accessor['isset']($this, $name);
}
public function __unset($name): void
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
$state = $this->lazyObjectState ?? null;
if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"]))
&& LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status
) {
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
$state->initialize($this, $name, $writeScope ?? $scope);
}
goto unset_in_scope;
}
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) {
parent::__unset($name);
return;
}
unset_in_scope:
if (null === $scope) {
unset($this->$name);
} else {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['unset']($this, $name);
}
}
public function __clone(): void
{
if ($state = $this->lazyObjectState ?? null) {
$this->lazyObjectState = clone $state;
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
parent::__clone();
}
}
public function __serialize(): array
{
$class = self::class;
if ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) {
$properties = parent::__serialize();
} else {
$this->initializeLazyObject();
$properties = (array) $this;
}
unset($properties["\0$class\0lazyObjectState"]);
if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
return $properties;
}
$scope = get_parent_class($class);
$data = [];
foreach (parent::__sleep() as $name) {
$value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null;
if (null === $k) {
trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE);
} else {
$data[$k] = $value;
}
}
return $data;
}
public function __destruct()
{
$state = $this->lazyObjectState ?? null;
if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) {
return;
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
parent::__destruct();
}
}
#[Ignore]
private function setLazyObjectAsInitialized(bool $initialized): void
{
$state = $this->lazyObjectState ?? null;
if ($state && !\is_array($state->initializer)) {
$state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL;
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
interface LazyObjectInterface
{
/**
* Returns whether the object is initialized.
*
* @param $partial Whether partially initialized objects should be considered as initialized
*/
public function isLazyObjectInitialized(bool $partial = false): bool;
/**
* Forces initialization of a lazy object and returns it.
*/
public function initializeLazyObject(): object;
/**
* @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
*/
public function resetLazyObject(): bool;
}

View File

@@ -0,0 +1,355 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
use Symfony\Component\Serializer\Attribute\Ignore;
use Symfony\Component\VarExporter\Hydrator as PublicHydrator;
use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
use Symfony\Component\VarExporter\Internal\LazyObjectState;
use Symfony\Component\VarExporter\Internal\LazyObjectTrait;
trait LazyProxyTrait
{
use LazyObjectTrait;
/**
* Creates a lazy-loading virtual proxy.
*
* @param \Closure():object $initializer Returns the proxied object
* @param static|null $instance
*/
public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static
{
if (self::class !== $class = $instance ? $instance::class : static::class) {
$skippedProperties = ["\0".self::class."\0lazyObjectState" => true];
} elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
}
$instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor();
$instance->lazyObjectState = new LazyObjectState($initializer);
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
$reset($instance, $skippedProperties ??= []);
}
return $instance;
}
/**
* Returns whether the object is initialized.
*
* @param $partial Whether partially initialized objects should be considered as initialized
*/
#[Ignore]
public function isLazyObjectInitialized(bool $partial = false): bool
{
return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer;
}
/**
* Forces initialization of a lazy object and returns it.
*/
public function initializeLazyObject(): parent
{
if ($state = $this->lazyObjectState ?? null) {
return $state->realInstance ??= ($state->initializer)();
}
return $this;
}
/**
* @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
*/
public function resetLazyObject(): bool
{
if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) {
return false;
}
unset($this->lazyObjectState->realInstance);
return true;
}
public function &__get($name): mixed
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$instance = $this;
$notByRef = 0;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF;
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
}
if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) {
$scope ??= $writeScope;
}
$parent = 2;
goto get_in_scope;
}
}
$parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get'];
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
} else {
if (2 === $parent) {
return parent::__get($name);
}
$value = parent::__get($name);
return $value;
}
if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) {
$frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE);
}
get_in_scope:
$notByRef = $notByRef || 1 === $parent;
try {
if (null === $scope) {
if (!$notByRef) {
return $instance->$name;
}
$value = $instance->$name;
return $value;
}
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
return $accessor['get']($instance, $name, $notByRef);
} catch (\Error $e) {
if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
throw $e;
}
try {
if (null === $scope) {
$instance->$name = [];
return $instance->$name;
}
$accessor['set']($instance, $name, []);
return $accessor['get']($instance, $name, $notByRef);
} catch (\Error) {
throw $e;
}
}
}
public function __set($name, $value): void
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$instance = $this;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
}
goto set_in_scope;
}
}
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
} elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) {
parent::__set($name, $value);
return;
}
set_in_scope:
if (null === $scope) {
$instance->$name = $value;
} else {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['set']($instance, $name, $value);
}
}
public function __isset($name): bool
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$instance = $this;
if ([$class] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
}
goto isset_in_scope;
}
}
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
} elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) {
return parent::__isset($name);
}
isset_in_scope:
if (null === $scope) {
return isset($instance->$name);
}
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
return $accessor['isset']($instance, $name);
}
public function __unset($name): void
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$instance = $this;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
}
goto unset_in_scope;
}
}
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
} elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) {
parent::__unset($name);
return;
}
unset_in_scope:
if (null === $scope) {
unset($instance->$name);
} else {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['unset']($instance, $name);
}
}
public function __clone(): void
{
if (!isset($this->lazyObjectState)) {
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
parent::__clone();
}
return;
}
$this->lazyObjectState = clone $this->lazyObjectState;
if (isset($this->lazyObjectState->realInstance)) {
$this->lazyObjectState->realInstance = clone $this->lazyObjectState->realInstance;
}
}
public function __serialize(): array
{
$class = self::class;
$state = $this->lazyObjectState ?? null;
if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) {
$properties = parent::__serialize();
} else {
$properties = (array) $this;
if ($state) {
unset($properties["\0$class\0lazyObjectState"]);
$properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)();
}
}
if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
return $properties;
}
$scope = get_parent_class($class);
$data = [];
foreach (parent::__sleep() as $name) {
$value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null;
if (null === $k) {
trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE);
} else {
$data[$k] = $value;
}
}
return $data;
}
public function __unserialize(array $data): void
{
$class = self::class;
if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) {
unset($data["\0$class\0lazyObjectReal"]);
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
$reset($this, $data);
}
if ($data) {
PublicHydrator::hydrate($this, $data);
}
$this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.'));
$this->lazyObjectState->realInstance = $instance;
} elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) {
parent::__unserialize($data);
} else {
PublicHydrator::hydrate($this, $data);
if (Registry::$parentMethods[$class]['wakeup']) {
parent::__wakeup();
}
}
}
public function __destruct()
{
if (isset($this->lazyObjectState)) {
return;
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
parent::__destruct();
}
}
}

View File

@@ -0,0 +1,553 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
use Symfony\Component\VarExporter\Exception\LogicException;
use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
final class ProxyHelper
{
/**
* Helps generate lazy-loading ghost objects.
*
* @throws LogicException When the class is incompatible with ghost objects
*/
public static function generateLazyGhost(\ReflectionClass $class): string
{
if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class->isReadOnly()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost with PHP < 8.3: class "%s" is readonly.', $class->name));
}
if ($class->isFinal()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name));
}
if ($class->isInterface() || $class->isAbstract() || $class->isTrait()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name));
}
if (\stdClass::class !== $class->name && $class->isInternal()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name));
}
if ($class->hasMethod('__get') && 'mixed' !== (self::exportType($class->getMethod('__get')) ?? 'mixed')) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name));
}
static $traitMethods;
$traitMethods ??= (new \ReflectionClass(LazyGhostTrait::class))->getMethods();
foreach ($traitMethods as $method) {
if ($class->hasMethod($method->name) && $class->getMethod($method->name)->isFinal()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: method "%s::%s()" is final.', $class->name, $method->name));
}
}
$parent = $class;
while ($parent = $parent->getParentClass()) {
if (\stdClass::class !== $parent->name && $parent->isInternal()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name));
}
}
$hooks = '';
$propertyScopes = Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name);
foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
$flags = $access >> 2;
if ($k !== $key || !($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) {
continue;
}
if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: property "%s::$%s" is final or private(set).', $class->name, $name));
}
$p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name);
$type = self::exportType($p);
$hooks .= "\n "
.($p->isProtected() ? 'protected' : 'public')
.($p->isProtectedSet() ? ' protected(set)' : '')
." {$type} \${$name}"
.($p->hasDefaultValue() ? ' = '.VarExporter::export($p->getDefaultValue()) : '')
." {\n";
foreach ($p->getHooks() as $hook => $method) {
if ('get' === $hook) {
$ref = ($method->returnsReference() ? '&' : '');
$hooks .= " {$ref}get { \$this->initializeLazyObject(); return parent::\${$name}::get(); }\n";
} elseif ('set' === $hook) {
$parameters = self::exportParameters($method, true);
$arg = '$'.$method->getParameters()[0]->name;
$hooks .= " set({$parameters}) { \$this->initializeLazyObject(); parent::\${$name}::set({$arg}); }\n";
} else {
throw new LogicException(\sprintf('Cannot generate lazy ghost: hook "%s::%s()" is not supported.', $class->name, $method->name));
}
}
$hooks .= " }\n";
}
$propertyScopes = self::exportPropertyScopes($class->name, $propertyScopes);
return <<<EOPHP
extends \\{$class->name} implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyGhostTrait;
private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
{$hooks}}
// Help opcache.preload discover always-needed symbols
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
EOPHP;
}
/**
* Helps generate lazy-loading virtual proxies.
*
* @param \ReflectionClass[] $interfaces
*
* @throws LogicException When the class is incompatible with virtual proxies
*/
public static function generateLazyProxy(?\ReflectionClass $class, array $interfaces = []): string
{
if (!class_exists($class?->name ?? \stdClass::class, false)) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not a class.', $class->name));
}
if ($class?->isFinal()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name));
}
if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class?->isReadOnly()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name));
}
$propertyScopes = $class ? Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name) : [];
$abstractProperties = [];
$hookedProperties = [];
if (\PHP_VERSION_ID >= 80400 && $class) {
foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
$flags = $access >> 2;
if ($k !== $key) {
continue;
}
if ($flags & \ReflectionProperty::IS_ABSTRACT) {
$abstractProperties[$name] = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name);
continue;
}
$abstractProperties[$name] = false;
if (!($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) {
continue;
}
if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: property "%s::$%s" is final or private(set).', $class->name, $name));
}
$p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name);
$hookedProperties[$name] = [$p, $p->getHooks()];
}
}
$methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []];
foreach ($interfaces as $interface) {
if (!$interface->isInterface()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name));
}
$methodReflectors[] = $interface->getMethods();
if (\PHP_VERSION_ID >= 80400) {
foreach ($interface->getProperties() as $p) {
$abstractProperties[$p->name] ??= $p;
$hookedProperties[$p->name] ??= [$p, []];
$hookedProperties[$p->name][1] += $p->getHooks();
}
}
}
$hooks = '';
foreach (array_filter($abstractProperties) as $name => $p) {
$type = self::exportType($p);
$hooks .= "\n "
.($p->isProtected() ? 'protected' : 'public')
.($p->isProtectedSet() ? ' protected(set)' : '')
." {$type} \${$name};\n";
}
foreach ($hookedProperties as $name => [$p, $methods]) {
$type = self::exportType($p);
$hooks .= "\n "
.($p->isProtected() ? 'protected' : 'public')
.($p->isProtectedSet() ? ' protected(set)' : '')
." {$type} \${$name} {\n";
foreach ($methods as $hook => $method) {
if ('get' === $hook) {
$ref = ($method->returnsReference() ? '&' : '');
$hooks .= <<<EOPHP
{$ref}get {
if (isset(\$this->lazyObjectState)) {
return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$p->name};
}
return parent::\${$p->name}::get();
}
EOPHP;
} elseif ('set' === $hook) {
$parameters = self::exportParameters($method, true);
$arg = '$'.$method->getParameters()[0]->name;
$hooks .= <<<EOPHP
set({$parameters}) {
if (isset(\$this->lazyObjectState)) {
\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)();
\$this->lazyObjectState->realInstance->{$p->name} = {$arg};
}
parent::\${$p->name}::set({$arg});
}
EOPHP;
} else {
throw new LogicException(\sprintf('Cannot generate lazy proxy: hook "%s::%s()" is not supported.', $class->name, $method->name));
}
}
$hooks .= " }\n";
}
$extendsInternalClass = false;
if ($parent = $class) {
do {
$extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal();
} while (!$extendsInternalClass && $parent = $parent->getParentClass());
}
$methodsHaveToBeProxied = $extendsInternalClass;
$methods = [];
$methodReflectors = array_merge(...$methodReflectors);
foreach ($methodReflectors as $method) {
if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) {
continue;
}
$methodsHaveToBeProxied = true;
$trait = new \ReflectionMethod(LazyProxyTrait::class, '__get');
$body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine());
$body[0] = str_replace('): mixed', '): '.$type, $body[0]);
$methods['__get'] = strtr(implode('', $body).' }', [
'Hydrator' => '\\'.Hydrator::class,
'Registry' => '\\'.LazyObjectRegistry::class,
]);
break;
}
foreach ($methodReflectors as $method) {
if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) {
continue;
}
if ($method->isFinal()) {
if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name));
}
continue;
}
if (method_exists(LazyProxyTrait::class, $method->name) || ($method->isProtected() && !$method->isAbstract())) {
continue;
}
$signature = self::exportSignature($method, true, $args);
$parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})";
if ($method->isStatic()) {
$body = " $parentCall;";
} elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) {
$body = <<<EOPHP
if (isset(\$this->lazyObjectState)) {
(\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args});
} else {
{$parentCall};
}
EOPHP;
} else {
if (!$methodsHaveToBeProxied && !$method->isAbstract()) {
// Skip proxying methods that might return $this
foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) {
if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) {
continue 2;
}
foreach ([$class, ...$interfaces] as $r) {
if ($r && is_a($r->name, $type, true)) {
continue 3;
}
}
}
}
$body = <<<EOPHP
if (isset(\$this->lazyObjectState)) {
return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args});
}
return {$parentCall};
EOPHP;
}
$methods[$lcName] = " {$signature}\n {\n{$body}\n }";
}
$types = $interfaces = array_unique(array_column($interfaces, 'name'));
$interfaces[] = LazyObjectInterface::class;
$interfaces = implode(', \\', $interfaces);
$parent = $class ? ' extends \\'.$class->name : '';
array_unshift($types, $class ? 'parent' : '');
$type = ltrim(implode('&\\', $types), '&');
if (!$class) {
$trait = new \ReflectionMethod(LazyProxyTrait::class, 'initializeLazyObject');
$body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine());
$body[0] = str_replace('): parent', '): '.$type, $body[0]);
$methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods;
}
$body = $methods ? "\n".implode("\n\n", $methods)."\n" : '';
$propertyScopes = $class ? self::exportPropertyScopes($class->name, $propertyScopes) : '[]';
if (
$class?->hasMethod('__unserialize')
&& !$class->getMethod('__unserialize')->getParameters()[0]->getType()
) {
// fix contravariance type problem when $class declares a `__unserialize()` method without typehint.
$lazyProxyTraitStatement = <<<EOPHP
use \Symfony\Component\VarExporter\LazyProxyTrait {
__unserialize as private __doUnserialize;
}
EOPHP;
$body .= <<<EOPHP
public function __unserialize(\$data): void
{
\$this->__doUnserialize(\$data);
}
EOPHP;
} else {
$lazyProxyTraitStatement = <<<EOPHP
use \Symfony\Component\VarExporter\LazyProxyTrait;
EOPHP;
}
return <<<EOPHP
{$parent} implements \\{$interfaces}
{
{$lazyProxyTraitStatement}
private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
{$hooks}{$body}}
// Help opcache.preload discover always-needed symbols
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
EOPHP;
}
public static function exportParameters(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string
{
$byRefIndex = 0;
$args = '';
$param = null;
$parameters = [];
$namespace = $function instanceof \ReflectionMethod ? $function->class : $function->getNamespaceName().'\\';
$namespace = substr($namespace, 0, strrpos($namespace, '\\') ?: 0);
foreach ($function->getParameters() as $param) {
$parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '')
.($withParameterTypes && $param->hasType() ? self::exportType($param).' ' : '')
.($param->isPassedByReference() ? '&' : '')
.($param->isVariadic() ? '...' : '').'$'.$param->name
.($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param, $namespace) : '');
if ($param->isPassedByReference()) {
$byRefIndex = 1 + $param->getPosition();
}
$args .= ($param->isVariadic() ? '...$' : '$').$param->name.', ';
}
if (!$param || !$byRefIndex) {
$args = '...\func_get_args()';
} elseif ($param->isVariadic()) {
$args = substr($args, 0, -2);
} else {
$args = explode(', ', $args, 1 + $byRefIndex);
$args[$byRefIndex] = \sprintf('...\array_slice(\func_get_args(), %d)', $byRefIndex);
$args = implode(', ', $args);
}
return implode(', ', $parameters);
}
public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string
{
$parameters = self::exportParameters($function, $withParameterTypes, $args);
$signature = 'function '.($function->returnsReference() ? '&' : '')
.($function->isClosure() ? '' : $function->name).'('.$parameters.')';
if ($function instanceof \ReflectionMethod) {
$signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private '))
.($function->isStatic() ? 'static ' : '').$signature;
}
if ($function->hasReturnType()) {
$signature .= ': '.self::exportType($function);
}
static $getPrototype;
$getPrototype ??= (new \ReflectionMethod(\ReflectionMethod::class, 'getPrototype'))->invoke(...);
while ($function) {
if ($function->hasTentativeReturnType()) {
return '#[\ReturnTypeWillChange] '.$signature;
}
try {
$function = $function instanceof \ReflectionMethod && $function->isAbstract() ? false : $getPrototype($function);
} catch (\ReflectionException) {
break;
}
}
return $signature;
}
public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, ?\ReflectionType $type = null): ?string
{
if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) {
return null;
}
$class = null;
$types = [];
if ($type instanceof \ReflectionUnionType) {
$reflectionTypes = $type->getTypes();
$glue = '|';
} elseif ($type instanceof \ReflectionIntersectionType) {
$reflectionTypes = $type->getTypes();
$glue = '&';
} else {
$reflectionTypes = [$type];
$glue = null;
}
foreach ($reflectionTypes as $type) {
if ($type instanceof \ReflectionIntersectionType) {
if ('' !== $name = '('.self::exportType($owner, $noBuiltin, $type).')') {
$types[] = $name;
}
continue;
}
$name = $type->getName();
if ($noBuiltin && $type->isBuiltin()) {
continue;
}
if (\in_array($name, ['parent', 'self'], true) && $class ??= $owner->getDeclaringClass()) {
$name = 'parent' === $name ? ($class->getParentClass() ?: null)?->name ?? 'parent' : $class->name;
}
$types[] = ($noBuiltin || $type->isBuiltin() || 'static' === $name ? '' : '\\').$name;
}
if (!$types) {
return '';
}
if (null === $glue) {
$defaultNull = $owner instanceof \ReflectionParameter && 'NULL' === rtrim(substr(explode('$'.$owner->name.' = ', (string) $owner, 2)[1] ?? '', 0, -2));
return (!$noBuiltin && ($type->allowsNull() || $defaultNull) && !\in_array($name, ['mixed', 'null'], true) ? '?' : '').$types[0];
}
sort($types);
return implode($glue, $types);
}
private static function exportPropertyScopes(string $parent, array $propertyScopes): string
{
uksort($propertyScopes, 'strnatcmp');
foreach ($propertyScopes as $k => $v) {
unset($propertyScopes[$k][4]);
}
$propertyScopes = VarExporter::export($propertyScopes);
$propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes);
$propertyScopes = preg_replace("/(?|(,)\n( ) |\n |,\n (\]))/", '$1$2', $propertyScopes);
$propertyScopes = str_replace("\n", "\n ", $propertyScopes);
return $propertyScopes;
}
private static function exportDefault(\ReflectionParameter $param, $namespace): string
{
$default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2));
if (\in_array($default, ['<default>', 'NULL'], true)) {
return 'null';
}
if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) {
return VarExporter::export($param->getDefaultValue());
}
$regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/";
$parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
$regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(\(?)(?!: )/';
$callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass())
? fn ($m) => $m[1].match ($m[2]) {
'new', 'false', 'true', 'null' => $m[2],
'NULL' => 'null',
'self' => '\\'.$class->name,
'namespace\\parent',
'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent',
default => self::exportSymbol($m[2], '(' !== $m[3], $namespace),
}.$m[3]
: fn ($m) => $m[1].match ($m[2]) {
'new', 'false', 'true', 'null', 'self', 'parent' => $m[2],
'NULL' => 'null',
default => self::exportSymbol($m[2], '(' !== $m[3], $namespace),
}.$m[3];
return implode('', array_map(fn ($part) => match ($part[0]) {
'"' => $part, // for internal classes only
"'" => false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part,
default => preg_replace_callback($regexp, $callback, $part),
}, $parts));
}
private static function exportSymbol(string $symbol, bool $mightBeRootConst, string $namespace): string
{
if (!$mightBeRootConst
|| false === ($ns = strrpos($symbol, '\\'))
|| substr($symbol, 0, $ns) !== $namespace
|| \defined($symbol)
|| !\defined(substr($symbol, $ns + 1))
) {
return '\\'.$symbol;
}
return '\\'.substr($symbol, $ns + 1);
}
}

View File

@@ -0,0 +1,114 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
use Symfony\Component\VarExporter\Exception\ExceptionInterface;
use Symfony\Component\VarExporter\Internal\Exporter;
use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\Registry;
use Symfony\Component\VarExporter\Internal\Values;
/**
* Exports serializable PHP values to PHP code.
*
* VarExporter allows serializing PHP data structures to plain PHP code (like var_export())
* while preserving all the semantics associated with serialize() (unlike var_export()).
*
* By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize().
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class VarExporter
{
/**
* Exports a serializable PHP value to PHP code.
*
* @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise
* @param array &$foundClasses Classes found in the value are added to this list as both keys and values
*
* @throws ExceptionInterface When the provided value cannot be serialized
*/
public static function export(mixed $value, ?bool &$isStaticValue = null, array &$foundClasses = []): string
{
$isStaticValue = true;
if (!\is_object($value) && !(\is_array($value) && $value) && !\is_resource($value) || $value instanceof \UnitEnum) {
return Exporter::export($value);
}
$objectsPool = new \SplObjectStorage();
$refsPool = [];
$objectsCount = 0;
try {
$value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0];
} finally {
$references = [];
foreach ($refsPool as $i => $v) {
if ($v[0]->count) {
$references[1 + $i] = $v[2];
}
$v[0] = $v[1];
}
}
if ($isStaticValue) {
return Exporter::export($value);
}
$classes = [];
$values = [];
$states = [];
foreach ($objectsPool as $i => $v) {
[, $class, $values[], $wakeup] = $objectsPool[$v];
$foundClasses[$class] = $classes[] = $class;
if (0 < $wakeup) {
$states[$wakeup] = $i;
} elseif (0 > $wakeup) {
$states[-$wakeup] = [$i, array_pop($values)];
$values[] = [];
}
}
ksort($states);
$wakeups = [null];
foreach ($states as $v) {
if (\is_array($v)) {
$wakeups[-$v[0]] = $v[1];
} else {
$wakeups[] = $v;
}
}
if (null === $wakeups[0]) {
unset($wakeups[0]);
}
$properties = [];
foreach ($values as $i => $vars) {
foreach ($vars as $class => $values) {
foreach ($values as $name => $v) {
$properties[$class][$name][$i] = $v;
}
}
}
if ($classes || $references) {
$value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups);
} else {
$isStaticValue = true;
}
return Exporter::export($value);
}
}