Closes #90
This commit is contained in:
parent
99dffe1f0c
commit
d5f1896389
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
|
||||
|
||||
## [7.0.3] - 2026-MM-DD
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#90](https://github.com/sebastianbergmann/exporter/issues/90): `Exporter::toArray()` silently drops a private property that is redeclared in a derived class
|
||||
|
||||
## [7.0.2] - 2025-09-24
|
||||
|
||||
### Changed
|
||||
|
|
@ -20,6 +26,7 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt
|
|||
|
||||
* This component is no longer supported on PHP 8.2
|
||||
|
||||
[7.0.3]: https://github.com/sebastianbergmann/exporter/compare/7.0.2...7.0
|
||||
[7.0.2]: https://github.com/sebastianbergmann/exporter/compare/7.0.1...7.0.2
|
||||
[7.0.1]: https://github.com/sebastianbergmann/exporter/compare/7.0.0...7.0.1
|
||||
[7.0.0]: https://github.com/sebastianbergmann/exporter/compare/6.3...7.0.0
|
||||
|
|
|
|||
|
|
@ -183,29 +183,43 @@ final readonly class Exporter
|
|||
return (array) $value;
|
||||
}
|
||||
|
||||
$array = [];
|
||||
$properties = (array) $value;
|
||||
$shadowed = $this->shadowedPropertyNames($properties);
|
||||
$array = [];
|
||||
|
||||
foreach ($properties as $key => $val) {
|
||||
$key = (string) $key;
|
||||
|
||||
foreach ((array) $value as $key => $val) {
|
||||
// Exception traces commonly reference hundreds to thousands of
|
||||
// objects currently loaded in memory. Including them in the result
|
||||
// has a severe negative performance impact.
|
||||
if ("\0Error\0trace" === $key || "\0Exception\0trace" === $key) {
|
||||
if ($key === "\0Error\0trace" || $key === "\0Exception\0trace") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// properties are transformed to keys in the following way:
|
||||
// private $propertyName => "\0ClassName\0propertyName"
|
||||
// protected $propertyName => "\0*\0propertyName"
|
||||
// public $propertyName => "propertyName"
|
||||
if (preg_match('/\0.+\0(.+)/', (string) $key, $matches) === 1) {
|
||||
$key = $matches[1];
|
||||
}
|
||||
|
||||
// See https://github.com/php/php-src/commit/5721132
|
||||
if ($key === "\0gcdata") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Properties are transformed to keys in the following way:
|
||||
// private $propertyName => "\0DeclaringClassName\0propertyName"
|
||||
// protected $propertyName => "\0*\0propertyName"
|
||||
// public $propertyName => "propertyName"
|
||||
//
|
||||
// A private property that is redeclared in a derived class and the
|
||||
// private property of the same name that it shadows both exist,
|
||||
// independently of each other. To keep one from overwriting the
|
||||
// other, the name of a shadowed private property is prefixed with
|
||||
// the name of the class that declares it.
|
||||
if (preg_match('/^\0([^\0]+)\0([^\0]+)$/', $key, $matches) === 1) {
|
||||
if ($matches[1] !== '*' && isset($shadowed[$matches[2]])) {
|
||||
$key = $matches[1] . '::' . $matches[2];
|
||||
} else {
|
||||
$key = $matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
$array[$key] = $val;
|
||||
}
|
||||
|
||||
|
|
@ -242,6 +256,43 @@ final readonly class Exporter
|
|||
return count((new ReflectionObject($value))->getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, as keys of the returned array, the names of properties that
|
||||
* are declared more than once in the inheritance chain of an object.
|
||||
*
|
||||
* This can only happen when a derived class redeclares a private property
|
||||
* that one of its parent classes also declares.
|
||||
*
|
||||
* @param array<array-key, mixed> $properties
|
||||
*
|
||||
* @return array<string, true>
|
||||
*/
|
||||
private function shadowedPropertyNames(array $properties): array
|
||||
{
|
||||
$seen = [];
|
||||
$shadowed = [];
|
||||
|
||||
foreach ($properties as $key => $unused) {
|
||||
$key = (string) $key;
|
||||
|
||||
if ($key === "\0Error\0trace" || $key === "\0Exception\0trace" || $key === "\0gcdata") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/^\0[^\0]+\0([^\0]+)$/', $key, $matches) === 1) {
|
||||
$key = $matches[1];
|
||||
}
|
||||
|
||||
if (isset($seen[$key])) {
|
||||
$shadowed[$key] = true;
|
||||
}
|
||||
|
||||
$seen[$key] = true;
|
||||
}
|
||||
|
||||
return $shadowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
* @param positive-int $maxLengthForStrings
|
||||
|
|
|
|||
|
|
@ -544,6 +544,19 @@ EOF;
|
|||
$this->assertEquals([], (new Exporter)->toArray((object) $array));
|
||||
}
|
||||
|
||||
public function testPrivatePropertyRedeclaredInDerivedClassIsNotOverwritten(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
[
|
||||
ParentClassWithPrivateProperty::class . '::property' => 'parent value',
|
||||
ChildClassWithPrivateProperty::class . '::property' => 'child value',
|
||||
],
|
||||
(new Exporter)->toArray(
|
||||
new ChildClassWithPrivateProperty('parent value', 'child value'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $value
|
||||
* @param non-negative-int $limit
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
final class ChildClassWithPrivateProperty extends ParentClassWithPrivateProperty
|
||||
{
|
||||
/**
|
||||
* @phpstan-ignore property.onlyWritten
|
||||
*/
|
||||
private string $property;
|
||||
|
||||
public function __construct(string $parentProperty, string $childProperty)
|
||||
{
|
||||
parent::__construct($parentProperty);
|
||||
|
||||
$this->property = $childProperty;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
abstract class ParentClassWithPrivateProperty
|
||||
{
|
||||
/**
|
||||
* @phpstan-ignore property.onlyWritten
|
||||
*/
|
||||
private string $property;
|
||||
|
||||
public function __construct(string $property)
|
||||
{
|
||||
$this->property = $property;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue