10000 Merge branch '6.4' into 7.2 · symfony/symfony@6471d4a · GitHub
[go: up one dir, main page]

Skip to content

Commit 6471d4a

Browse files
Merge branch '6.4' into 7.2
* 6.4: [VarExporter] Fix support for hooks and asymmetric visibility fix(process): use a pipe for stderr in pty mode to avoid mixed output between stdout and stderr [Cache] fix data collector
2 parents e7af9b8 + bb07d18 commit 6471d4a

22 files changed

+263
-128
lines changed

src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public function lateCollect(): void
6060

6161
$this->data['instances']['statistics'] = $this->calculateStatistics();
6262
$this->data['total']['statistics'] = $this->calculateTotalStatistics();
63+
$this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']);
6364
}
6465

6566
public function getName(): string

src/Symfony/Component/Cache/Tests/DataCollector/CacheDataCollectorTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Cache\DataCollector\CacheDataCollector;
1818
use Symfony\Component\HttpFoundation\Request;
1919
use Symfony\Component\HttpFoundation\Response;
20+
use Symfony\Component\VarDumper\Cloner\Data;
2021

2122
class CacheDataCollectorTest extends TestCase
2223
{
@@ -122,6 +123,7 @@ public function testLateCollect()
122123
$this->assertEquals($stats[self::INSTANCE_NAME]['hits'], 0, 'hits');
123124
$this->assertEquals($stats[self::INSTANCE_NAME]['misses'], 1, 'misses');
124125
$this->assertEquals($stats[self::INSTANCE_NAME]['calls'], 1, 'calls');
126+
$this->assertInstanceOf(Data::class, $collector->getCalls());
125127
}
126128

127129
private function getCacheDataCollectorStatisticsFromEvents(array $traceableAdapterEvents)

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class WitherProxy1991f2a extends \Symfony\Component\DependencyInjection\Tests\Co
7676
use \Symfony\Component\VarExporter\LazyProxyTrait;
7777

7878
private const LAZY_OBJECT_PROPERTY_SCOPES = [
79-
'foo' => [parent::class, 'foo', null],
79+
'foo' => [parent::class, 'foo', null, 4],
8080
];
8181
}
8282

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class WitherProxyE94fdba extends \Symfony\Component\DependencyInjection\Tests\Co
7878
use \Symfony\Component\VarExporter\LazyProxyTrait;
7979

8080
private const LAZY_OBJECT_PROPERTY_SCOPES = [
81-
'foo' => [parent::class, 'foo', null],
81+
'foo' => [parent::class, 'foo', null, 4],
8282
];
8383
}
8484

src/Symfony/Component/DependencyInjection/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"psr/container": "^1.1|^2.0",
2121
"symfony/deprecation-contracts": "^2.5|^3",
2222
"symfony/service-contracts": "^3.5",
23-
"symfony/var-exporter": "^6.4|^7.0"
23+
"symfony/var-exporter": "^6.4.20|^7.2.5"
2424
},
2525
"require-dev": {
2626
"symfony/yaml": "^6.4|^7.0",

src/Symfony/Component/Process/Pipes/UnixPipes.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function getDescriptors(): array
7070
return [
7171
['pty'],
7272
['pty'],
73-
['pty'],
73+
['pipe', 'w'], // stderr needs to be in a pipe to correctly split error and output, since PHP will use the same stream for both
7474
];
7575
}
7676

src/Symfony/Component/Process/Tests/ProcessTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,20 @@ public function testExitCodeTextIsNullWhenExitCodeIsNull()
559559
$this->assertNull($process->getExitCodeText());
560560
}
561561

562+
public function testStderrNotMixedWithStdout()
563+
{
564+
if (!Process::isPtySupported()) {
565+
$this->markTestSkipped('PTY is not supported on this operating system.');
566+
}
567+
568+
$process = $this->getProcess('echo "foo" && echo "bar" >&2');
569+
$process->setPty(true);
570+
$process->run();
571+
572+
$this->assertSame("foo\r\n", $process->getOutput());
573+
$this->assertSame("bar\n", $process->getErrorOutput());
574+
}
575+
562576
public function testPTYCommand()
563577
{
564578
if (!Process::isPtySupported()) {

src/Symfony/Component/VarExporter/Internal/Exporter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
144144
$i = 0;
145145
$n = (string) $name;
146146
if ('' === $n || "\0" !== $n[0]) {
147-
$c = $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass';
147+
$p = $reflector->hasProperty($n) ? $reflector->getProperty($n) : null;
148+
$c = $p && (\PHP_VERSION_ID >= 80400 ? $p->isProtectedSet() || $p->isPrivateSet() : $p->isReadOnly()) ? $p->class : 'stdClass';
148149
} elseif ('*' === $n[1]) {
149150
$n = substr($n, 3);
150151
$c = $reflector->getProperty($n)->class;

src/Symfony/Component/VarExporter/Internal/Hydrator.php

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
*/
2121
class Hydrator
2222
{
23+
public const PROPERTY_HAS_HOOKS = 1;
24+
public const PROPERTY_NOT_BY_REF = 2;
25+
2326
public static array $hydrators = [];
2427
public static array $simpleHydrators = [];
2528
public static array $propertyScopes = [];
@@ -150,13 +153,16 @@ public static function getHydrator($class)
150153
public static function getSimpleHydrator($class)
151154
{
152155
$baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) {
153-
$readonly = (array) $this;
156+
$notByRef = (array) $this;
154157

155158
foreach ($properties as $name => &$value) {
156-
$object->$name = $value;
157-
158-
if (!($readonly[$name] ?? false)) {
159+
if (!$noRef = $notByRef[$name] ?? false) {
160+
$object->$name = $value;
159161
$object->$name = &$value;
162+
} elseif (true !== $noRef) {
163+
$notByRef($object, $value);
164+
} else {
165+
$object->$name = $value;
160166
}
161167
}
162168
})->bindTo(new \stdClass());
@@ -211,14 +217,19 @@ public static function getSimpleHydrator($class)
211217
}
212218

213219
if (!$classReflector->isInternal()) {
214-
$readonly = new \stdClass();
215-
foreach ($classReflector->getProperties(\ReflectionProperty::IS_READONLY) as $propertyReflector) {
216-
if ($class === $propertyReflector->class) {
217-
$readonly->{$propertyReflector->name} = true;
220+
$notByRef = new \stdClass();
221+
foreach ($classReflector->getProperties() as $propertyReflector) {
222+
if ($propertyReflector->isStatic()) {
223+
continue;
224+
}
225+
if (\PHP_VERSION_ID >= 80400 && !$propertyReflector->isAbstract() && $propertyReflector->getHooks()) {
226+
$notByRef->{$propertyReflector->name} = $propertyReflector->setRawValue(...);
227+
} elseif ($propertyReflector->isReadOnly()) {
228+
$notByRef->{$propertyReflector->name} = true;
218229
}
219230
}
220231

221-
return $baseHydrator->bindTo($readonly, $class);
232+
return $baseHydrator->bindTo($notByRef, $class);
222233
}
223234

224235
if ($classReflector->name !== $class) {
@@ -260,42 +271,47 @@ public static function getPropertyScopes($class): array
260271
continue;
261272
}
262273
$name = $property->name;
263-
$writeScope = null;
274+
$access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0);
275+
276+
if (\PHP_VERSION_ID >= 80400 && !$property->isAbstract() && $h = $property->getHooks()) {
277+
$access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0);
278+
}
264279

265280
if (\ReflectionProperty::IS_PRIVATE & $flags) {
266-
if (\PHP_VERSION_ID >= 80400 ? $property->isPrivateSet() : ($flags & \ReflectionProperty::IS_READONLY)) {
267-
$writeScope = $class;
268-
}
269-
$propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, $writeScope, $property];
281+
$propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, null, $access, $property];
270282

271283
continue;
272284
}
273-
if (\PHP_VERSION_ID >= 80400 ? $property->isProtectedSet() || $property->isPrivateSet() : ($flags & \ReflectionProperty::IS_READONLY)) {
274-
$writeScope = $property->class;
285+
286+
$propertyScopes[$name] = [$class, $name, null, $access, $property];
287+
288+
if ($flags & (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY)) {
289+
$propertyScopes[$name][2] = $property->class;
275290
}
276-
$propertyScopes[$name] = [$class, $name, $writeScope, $property];
277291

278292
if (\ReflectionProperty::IS_PROTECTED & $flags) {
279293
$propertyScopes["\0*\0$name"] = $propertyScopes[$name];
280-
} elseif (\PHP_VERSION_ID >= 80400 && $property->getHooks()) {
281-
$propertyScopes[$name][4] = true;
282294
}
283295
}
284296

285297
while ($r = $r->getParentClass()) {
286298
$class = $r->name;
287299

288300
foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) {
289-
if (!$property->isStatic()) {
290-
$name = $property->name;
291-
if (\PHP_VERSION_ID < 80400) {
292-
$writeScope = $property->isReadOnly() ? $class : null;
293-
} else {
294-
$writeScope = $property->isPrivateSet() ? $class : null;
295-
}
296-
$propertyScopes["\0$class\0$name"] = [$class, $name, $writeScope, $property];
297-
$propertyScopes[$name] ??= [$class, $name, $writeScope, $property];
301+
$flags = $property->getModifiers();
302+
303+
if (\ReflectionProperty::IS_STATIC & $flags) {
304+
continue;
305+
}
306+
$name = $property->name;
307+
$access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0);
308+
309+
if (\PHP_VERSION_ID >= 80400 && $h = $property->getHooks()) {
310+
$access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0);
298311
}
312+
313+
$propertyScopes["\0$class\0$name"] = [$class, $name, null, $access, $property];
314+
$propertyScopes[$name] ??= $propertyScopes["\0$class\0$name"];
299315
}
300316
}
301317

src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ public static function getClassResetters($class)
5858
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
5959
}
6060

61-
foreach ($propertyScopes as $key => [$scope, $name, $writeScope]) {
61+
foreach ($propertyScopes as $key => [$scope, $name, $writeScope, $access]) {
6262
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
6363

6464
if ($k !== $key || "\0$class\0lazyObjectState" === $k) {
6565
continue;
6666
}
6767

68-
if ($k === $name && ($propertyScopes[$k][4] ?? false)) {
68+
if ($access & Hydrator::PROPERTY_HAS_HOOKS) {
6969
$hookedProperties[$k] = true;
7070
} else {
7171
$classProperties[$writeScope ?? $scope][$name] = $key;
@@ -89,8 +89,8 @@ public static function getClassResetters($class)
8989
public static function getClassAccessors($class)
9090
{
9191
return \Closure::bind(static fn () => [
92-
'get' => static function &($instance, $name, $readonly) {
93-
if (!$readonly) {
92+
'get' => static function &($instance, $name, $notByRef) {
93+
if (!$notByRef) {
9494
return $instance->$name;
9595
}
9696
$value = $instance->$name;
@@ -126,17 +126,37 @@ public static function getParentMethods($class)
126126
return $methods;
127127
}
128128

129-
public static function getScope($propertyScopes, $class, $property, $writeScope = null)
129+
public static function getScopeForRead($propertyScopes, $class, $property)
130130
{
131-
if (null === $writeScope && !isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) {
131+
if (!isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) {
132132
return null;
133133
}
134134
$frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
135135

136136
if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) {
137137
$scope = $frame['object']->class;
138138
}
139-
if (null === $writeScope && '*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) {
139+
if ('*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) {
140+
return null;
141+
}
142+
143+
return $scope;
144+
}
145+
146+
public static function getScopeForWrite($propertyScopes, $class, $property, $flags)
147+
{
148+
if (!($flags & (\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_READONLY | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET | \ReflectionProperty::IS_PROTECTED_SET : 0)))) {
149+
return null;
150+
}
151+
$frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
152+
153+
if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) {
154+
$scope = $frame['object']->class;
155+
}
156+
if ($flags & (\ReflectionProperty::IS_PRIVATE | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY))) {
157+
return $scope;
158+
}
159+
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"])))) {
140160
return null;
141161
}
142162

src/Symfony/Component/VarExporter/Internal/LazyObjectState.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function __construct(
4343
) {
4444
}
4545

46-
public function initialize($instance, $propertyName, $propertyScope)
46+
public function initialize($instance, $propertyName, $writeScope)
4747
{
4848
if (self::STATUS_UNINITIALIZED_FULL !== $this->status) {
4949
return $this->status;
@@ -74,10 +74,10 @@ public function reset($instance): void
7474
$skippedProperties = $this->skippedProperties;
7575
$properties = (array) $instance;
7676

77-
foreach ($propertyScopes as $key => [$scope, $name, $writeScope]) {
77+
foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
7878
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
7979

80-
if ($k === $key && (null !== $writeScope || !\array_key_exists($k, $properties))) {
80+
if ($k === $key && ($access & Hydrator::PROPERTY_HAS_HOOKS || ($access >> 2) & \ReflectionProperty::IS_READONLY || !\array_key_exists($k, $properties))) {
8181
$skippedProperties[$k] = true;
8282
}
8383
}

src/Symfony/Component/VarExporter/LazyGhostTrait.php

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,20 +122,26 @@ public function &__get($name): mixed
122122
{
123123
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
124124
$scope = null;
125+
$notByRef = 0;
125126

126-
if ([$class, , $writeScope] = $propertyScopes[$name] ?? null) {
127-
$scope = Registry::getScope($propertyScopes, $class, $name);
127+
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
128+
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
128129
$state = $this->lazyObjectState ?? null;
129130

130131
if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) {
132+
$notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF;
133+
131134
if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) {
132135
// Work around php/php-src#12695
133136
$property = null === $scope ? $name : "\0$scope\0$name";
134-
$property = $propertyScopes[$property][3]
135-
?? Hydrator::$propertyScopes[$this::class][$property][3] = new \ReflectionProperty($scope ?? $class, $name);
137+
$property = $propertyScopes[$property][4]
138+
?? Hydrator::$propertyScopes[$this::class][$property][4] = new \ReflectionProperty($scope ?? $class, $name);
136139
} else {
137140
$property = null;
138141
}
142+
if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) {
143+
$scope ??= $writeScope;
144+
}
139145

140146
if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope)) {
141147
goto get_in_scope;
@@ -161,7 +167,7 @@ public function &__get($name): mixed
161167

162168
try {
163169
if (null === $scope) {
164-
if (null === $writeScope) {
170+
if (!$notByRef) {
165171
return $this->$name;
166172
}
167173
$value = $this->$name;
@@ -170,7 +176,7 @@ public function &__get($name): mixed
170176
}
171177
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
172178

173-
return $accessor['get']($this, $name, null !== $writeScope);
179+
return $accessor['get']($this, $name, $notByRef);
174180
} catch (\Error $e) {
175181
if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
176182
throw $e;
@@ -185,7 +191,7 @@ public function &__get($name): mixed
185191

186192
$accessor['set']($this, $name, []);
187193

188-
return $accessor['get']($this, $name, null !== $writeScope);
194+
return $accessor['get']($this, $name, $notByRef);
189195
} catch (\Error) {
190196
if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) {
191197
throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious());
@@ -201,8 +207,8 @@ public function __set($name, $value): void
201207
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
202208
$scope = null;
203209

204-
if ([$class, , $writeScope] = $propertyScopes[$name] ?? null) {
205-
$scope = Registry::getScope($propertyScopes, $class, $name, $writeScope);
210+
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
211+
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
206212
$state = $this->lazyObjectState ?? null;
207213

208214
if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"]))
@@ -237,7 +243,7 @@ public function __isset($name): bool
237243
$scope = null;
238244

239245
if ([$class, , $writeScope] = $propertyScopes[$name] ?? null) {
240-
$scope = Registry::getScope($propertyScopes, $class, $name);
246+
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
241247
$state = $this->lazyObjectState ?? null;
242248

243249
if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))
@@ -267,8 +273,8 @@ public function __unset($name): void
267273
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
268274
$scope = null;
269275

270-
if ([$class, , $writeScope] = $propertyScopes[$name] ?? null) {
271-
$scope = Registry::getScope($propertyScopes, $class, $name, $writeScope);
276+
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
277+
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
272278
$state = $this->lazyObjectState ?? null;
273279

274280
if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"]))

0 commit comments

Comments
 (0)
0