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

0 commit comments

Comments
 (0)