Skip to content

Commit a27ea6a

Browse files
committed
Change how to stringify objects
I'm matching the latest changes in format, using "{}" for objects, but I'm making many improvements to the `ObjectStringifier`. When stringifying objects, we have access to the protected and private properties. I made it more familiar as well, prefixing the variables with a "$", which is how we describe variables, and using the symbols "+", "#" and "-", which is how UML represents visibility. I also fixed a bug because getting the value of non-initialized properties raises an error, which we didn't consider before. With this commit, I also introduced a maximum number of properties to prevent longer strings and a maximum depth to infinite recursion. Since now we always represent objects with "{}", we don't need to prefix it with "object" anymore. Signed-off-by: Henrique Moody <[email protected]>
1 parent 61f99a4 commit a27ea6a

File tree

8 files changed

+297
-72
lines changed

8 files changed

+297
-72
lines changed

src/Stringifiers/ClusterStringifier.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ final class ClusterStringifier implements Stringifier
1818
{
1919
private const MAXIMUM_DEPTH = 3;
2020
private const MAXIMUM_NUMBER_OF_ITEMS = 5;
21+
private const MAXIMUM_NUMBER_OF_PROPERTIES = self::MAXIMUM_NUMBER_OF_ITEMS;
2122

2223
/**
2324
* @var Stringifier[]
@@ -46,7 +47,7 @@ public static function createDefault(): self
4647
new ThrowableObjectStringifier($jsonEncodableStringifier, $quoter),
4748
new StringableObjectStringifier($jsonEncodableStringifier, $quoter),
4849
new JsonSerializableObjectStringifier($jsonEncodableStringifier, $quoter),
49-
new ObjectStringifier($stringifier, $quoter),
50+
new ObjectStringifier($stringifier, $quoter, self::MAXIMUM_DEPTH, self::MAXIMUM_NUMBER_OF_PROPERTIES),
5051
new ArrayStringifier($stringifier, $quoter, self::MAXIMUM_DEPTH, self::MAXIMUM_NUMBER_OF_ITEMS),
5152
new InfiniteNumberStringifier($quoter),
5253
new NotANumberStringifier($quoter),

src/Stringifiers/ObjectStringifier.php

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,26 @@
1010

1111
namespace Respect\Stringifier\Stringifiers;
1212

13+
use ReflectionObject;
14+
use ReflectionProperty;
1315
use Respect\Stringifier\Quoter;
1416
use Respect\Stringifier\Stringifier;
1517

16-
use function get_object_vars;
18+
use function count;
19+
use function implode;
1720
use function is_object;
1821
use function sprintf;
22+
use function trim;
1923

2024
final class ObjectStringifier implements Stringifier
2125
{
26+
private const LIMIT_EXCEEDED_PLACEHOLDER = '...';
27+
2228
public function __construct(
2329
private readonly Stringifier $stringifier,
24-
private readonly Quoter $quoter
30+
private readonly Quoter $quoter,
31+
private readonly int $maximumDepth,
32+
private readonly int $maximumNumberOfProperties
2533
) {
2634
}
2735

@@ -31,13 +39,61 @@ public function stringify(mixed $raw, int $depth): ?string
3139
return null;
3240
}
3341

34-
return $this->quoter->quote(
35-
sprintf(
36-
'[object] (%s: %s)',
37-
$raw::class,
38-
$this->stringifier->stringify(get_object_vars($raw), $depth + 1)
39-
),
40-
$depth
41-
);
42+
if ($depth >= $this->maximumDepth) {
43+
return $this->quoter->quote(sprintf('%s { %s }', $raw::class, self::LIMIT_EXCEEDED_PLACEHOLDER), $depth);
44+
}
45+
46+
$properties = $this->getProperties(new ReflectionObject($raw), $raw, $depth + 1);
47+
if (count($properties) === 0) {
48+
return $this->quoter->quote(sprintf('%s {}', $raw::class), $depth);
49+
}
50+
51+
return $this->quoter->quote(sprintf('%s { %s }', $raw::class, implode(' ', $properties)), $depth);
52+
}
53+
54+
/**
55+
* @return array<int, string>
56+
*/
57+
private function getProperties(ReflectionObject $reflectionObject, object $object, int $depth): array
58+
{
59+
$reflectionProperties = $reflectionObject->getProperties();
60+
if (count($reflectionProperties) === 0) {
61+
return [];
62+
}
63+
64+
$properties = [];
65+
foreach ($reflectionProperties as $reflectionProperty) {
66+
if (count($properties) >= $this->maximumNumberOfProperties) {
67+
$properties[] = self::LIMIT_EXCEEDED_PLACEHOLDER;
68+
break;
69+
}
70+
71+
$properties[] = trim(sprintf(
72+
'%s$%s=%s',
73+
match (true) {
74+
$reflectionProperty->isPrivate() => '-',
75+
$reflectionProperty->isProtected() => '#',
76+
default => '+',
77+
},
78+
$reflectionProperty->getName(),
79+
$this->getPropertyValue($reflectionProperty, $object, $depth)
80+
));
81+
}
82+
83+
return $properties;
84+
}
85+
86+
private function getPropertyValue(ReflectionProperty $reflectionProperty, object $object, int $depth): ?string
87+
{
88+
if (!$reflectionProperty->isInitialized($object)) {
89+
return '*uninitialized*';
90+
}
91+
92+
$value = $reflectionProperty->getValue($object);
93+
if (is_object($value)) {
94+
return $this->stringify($value, $depth);
95+
}
96+
97+
return $this->stringifier->stringify($value, $depth);
4298
}
4399
}

tests/fixtures/WithProperties.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Respect/Stringifier.
5+
* Copyright (c) Henrique Moody <[email protected]>
6+
* SPDX-License-Identifier: MIT
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
12+
13+
class WithProperties
14+
{
15+
public bool $publicProperty = true;
16+
17+
protected int $protectedProperty = 42;
18+
19+
private string $privateProperty = 'something'; // @phpstan-ignore-line
20+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Respect/Stringifier.
5+
* Copyright (c) Henrique Moody <[email protected]>
6+
* SPDX-License-Identifier: MIT
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
12+
13+
final class WithUninitializedProperties
14+
{
15+
public string $uninitializedProperty;
16+
}

tests/integration/stringify-array.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ output([
2525
]);
2626
?>
2727
--EXPECT--
28-
`[1, null, [1.0, [resource <stream>, ..., []]], false, [object] (stdClass: []), ...]`
28+
`[1, null, [1.0, [resource <stream>, ..., []]], false, stdClass {}, ...]`

tests/integration/stringify-object.phpt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ declare(strict_types=1);
55

66
require 'vendor/autoload.php';
77

8-
output(new Respect\Stringifier\Test\MyObject());
8+
outputMultiple(
9+
new stdClass(),
10+
new WithProperties(),
11+
new WithUninitializedProperties(),
12+
);
913
?>
1014
--EXPECT--
11-
`[object] (Respect\Stringifier\Test\MyObject: ["foo": true])`
15+
`stdClass {}`
16+
`WithProperties { +$publicProperty=true #$protectedProperty=42 -$privateProperty="something" }`
17+
`WithUninitializedProperties { +$uninitializedProperty=*uninitialized* }`

tests/src/MyObject.php

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)