Skip to content

Commit 88dfdfb

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 88dfdfb

1 file changed

Lines changed: 16 additions & 7 deletions

File tree

src/Validators/Attributes.php

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828
use Respect\Validation\Validators\Attributes\PropertyResolver;
2929
use Respect\Validation\Validators\Core\Reducer;
3030

31-
use function spl_object_id;
32-
3331
#[Composable(without: [All::class, Key::class, Property::class, Not::class, UndefOr::class])]
3432
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
3533
#[Template(
@@ -41,8 +39,8 @@ final class Attributes implements Validator
4139
{
4240
public const string TEMPLATE_CIRCULAR_REFERENCE = '__circular_reference__';
4341

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

4745
private readonly PropertyResolver $propertyResolver;
4846

@@ -53,6 +51,18 @@ public function __construct(
5351
new ExplicitAttributePropertyResolver(),
5452
new DeclaredTypePropertyResolver(),
5553
);
54+
$this->visited = new \WeakMap();
55+
}
56+
57+
/** @return list<string> */
58+
public function __sleep(): array
59+
{
60+
return ['propertyResolver'];
61+
}
62+
63+
public function __wakeup(): void
64+
{
65+
$this->visited = new \WeakMap();
5666
}
5767

5868
public function evaluate(mixed $input): Result
@@ -63,12 +73,11 @@ public function evaluate(mixed $input): Result
6373
return $objectType->withId($id);
6474
}
6575

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

71-
$this->visited[$objectId] = true;
80+
$this->visited[$input] = true;
7281

7382
$reflection = new ReflectionObject($input);
7483
$validators = [...$this->getClassValidators($reflection), ...$this->getPropertyValidators($reflection)];

0 commit comments

Comments
 (0)