Skip to content

Commit b2f7e6f

Browse files
committed
Replace Attributes::$visited array with WeakMap
The circular-reference guard keyed visited objects by spl_object_id(), which is recycled once an object is garbage-collected. On a reused validator instance this caused two problems: unbounded growth of the visited map across calls, and false circular-reference failures when a fresh object inherited a freed ID. WeakMap is keyed by object identity, so distinct objects can never collide, and entries are reclaimed automatically when the key object is garbage-collected, no manual reset is required. Since WeakMap is not serializable, __sleep excludes the field and __wakeup reinitializes it, which also avoids carrying stale IDs across unserialize.
1 parent f2ab556 commit b2f7e6f

1 file changed

Lines changed: 17 additions & 7 deletions

File tree

src/Validators/Attributes.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
use Respect\Validation\Validators\Attributes\ExplicitAttributePropertyResolver;
2828
use Respect\Validation\Validators\Attributes\PropertyResolver;
2929
use Respect\Validation\Validators\Core\Reducer;
30-
31-
use function spl_object_id;
30+
use WeakMap;
3231

3332
#[Composable(without: [All::class, Key::class, Property::class, Not::class, UndefOr::class])]
3433
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
@@ -41,8 +40,8 @@ final class Attributes implements Validator
4140
{
4241
public const string TEMPLATE_CIRCULAR_REFERENCE = '__circular_reference__';
4342

44-
/** @var array<int, true> */
45-
private array $visited = [];
43+
/** @var WeakMap<object, true> */
44+
private WeakMap $visited;
4645

4746
private readonly PropertyResolver $propertyResolver;
4847

@@ -53,6 +52,7 @@ public function __construct(
5352
new ExplicitAttributePropertyResolver(),
5453
new DeclaredTypePropertyResolver(),
5554
);
55+
$this->visited = new WeakMap();
5656
}
5757

5858
public function evaluate(mixed $input): Result
@@ -63,12 +63,11 @@ public function evaluate(mixed $input): Result
6363
return $objectType->withId($id);
6464
}
6565

66-
$objectId = spl_object_id($input);
67-
if (isset($this->visited[$objectId])) {
66+
if ($this->visited->offsetExists($input)) {
6867
return Result::failed($input, $this, [], self::TEMPLATE_CIRCULAR_REFERENCE)->withId($id);
6968
}
7069

71-
$this->visited[$objectId] = true;
70+
$this->visited[$input] = true;
7271

7372
$reflection = new ReflectionObject($input);
7473
$validators = [...$this->getClassValidators($reflection), ...$this->getPropertyValidators($reflection)];
@@ -127,4 +126,15 @@ private function getProperties(ReflectionObject $reflection): array
127126

128127
return $properties;
129128
}
129+
130+
/** @return list<string> */
131+
public function __sleep(): array
132+
{
133+
return ['propertyResolver'];
134+
}
135+
136+
public function __wakeup(): void
137+
{
138+
$this->visited = new WeakMap();
139+
}
130140
}

0 commit comments

Comments
 (0)