N°8910 - Upgrade Symfony packages

This commit is contained in:
Benjamin Dalsass
2026-02-19 09:18:56 +01:00
committed by GitHub
parent d5706fcbef
commit d2f67dcb3c
126 changed files with 1107 additions and 2067 deletions

View File

@@ -28,19 +28,19 @@ final class ProxyHelper
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: class "%s" is readonly.', $class->name));
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));
throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name));
}
if ($class->isInterface() || $class->isAbstract()) {
throw new LogicException(sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $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));
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));
throw new LogicException(\sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name));
}
static $traitMethods;
@@ -48,17 +48,58 @@ final class ProxyHelper
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));
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));
throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name));
}
}
$propertyScopes = self::exportPropertyScopes($class->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
@@ -66,7 +107,7 @@ final class ProxyHelper
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);
@@ -86,23 +127,113 @@ final class ProxyHelper
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));
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));
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: class "%s" is readonly.', $class->name));
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));
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";
}
$methodReflectors = array_merge(...$methodReflectors);
$extendsInternalClass = false;
if ($parent = $class) {
@@ -112,6 +243,7 @@ final class ProxyHelper
}
$methodsHaveToBeProxied = $extendsInternalClass;
$methods = [];
$methodReflectors = array_merge(...$methodReflectors);
foreach ($methodReflectors as $method) {
if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) {
@@ -134,7 +266,7 @@ final class ProxyHelper
}
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));
throw new LogicException(\sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name));
}
continue;
}
@@ -195,15 +327,40 @@ final class ProxyHelper
$methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods;
}
$body = $methods ? "\n".implode("\n\n", $methods)."\n" : '';
$propertyScopes = $class ? self::exportPropertyScopes($class->name) : '[]';
$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}
{
use \Symfony\Component\VarExporter\LazyProxyTrait;
{$lazyProxyTraitStatement}
private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
{$body}}
{$hooks}{$body}}
// Help opcache.preload discover always-needed symbols
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
@@ -213,18 +370,20 @@ final class ProxyHelper
EOPHP;
}
public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, string &$args = null): string
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) : '');
.($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param, $namespace) : '');
if ($param->isPassedByReference()) {
$byRefIndex = 1 + $param->getPosition();
}
@@ -237,12 +396,19 @@ final class ProxyHelper
$args = substr($args, 0, -2);
} else {
$args = explode(', ', $args, 1 + $byRefIndex);
$args[$byRefIndex] = sprintf('...\array_slice(\func_get_args(), %d)', $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).'('.implode(', ', $parameters).')';
.($function->isClosure() ? '' : $function->name).'('.$parameters.')';
if ($function instanceof \ReflectionMethod) {
$signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private '))
@@ -270,7 +436,7 @@ final class ProxyHelper
return $signature;
}
public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, \ReflectionType $type = null): ?string
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;
@@ -311,19 +477,20 @@ final class ProxyHelper
return '';
}
if (null === $glue) {
return (!$noBuiltin && $type->allowsNull() && 'mixed' !== $name ? '?' : '').$types[0];
$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): string
private static function exportPropertyScopes(string $parent, array $propertyScopes): string
{
$propertyScopes = Hydrator::$propertyScopes[$parent] ??= Hydrator::getPropertyScopes($parent);
uksort($propertyScopes, 'strnatcmp');
foreach ($propertyScopes as $k => $v) {
unset($propertyScopes[$k][3]);
unset($propertyScopes[$k][4]);
}
$propertyScopes = VarExporter::export($propertyScopes);
$propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes);
@@ -333,7 +500,7 @@ final class ProxyHelper
return $propertyScopes;
}
private static function exportDefault(\ReflectionParameter $param): string
private static function exportDefault(\ReflectionParameter $param, $namespace): string
{
$default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2));
@@ -347,7 +514,7 @@ final class ProxyHelper
$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]++)*+)(?!: )/';
$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],
@@ -355,13 +522,13 @@ final class ProxyHelper
'self' => '\\'.$class->name,
'namespace\\parent',
'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent',
default => '\\'.$m[2],
}
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 => '\\'.$m[2],
};
default => self::exportSymbol($m[2], '(' !== $m[3], $namespace),
}.$m[3];
return implode('', array_map(fn ($part) => match ($part[0]) {
'"' => $part, // for internal classes only
@@ -369,4 +536,18 @@ final class ProxyHelper
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);
}
}