respect-validation/library/Rules/Attributes.php
Henrique Moody 0d4a5fe2be
Enable validating private properties of parent classes
All validators related to object properties only consider the properties
of the object being validated, not those of its parent object. That's
because PHP reflection only gets the properties visible to the current
class (public or protected). The problem with that is that when there's
a private property in the parent class, we're completely unaware of it.

This commit will modify those rules by retrieving properties from the
parent class, ensuring we capture all properties that require
validation.
2025-12-23 16:44:04 +01:00

94 lines
2.8 KiB
PHP

<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use Attribute;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionObject;
use ReflectionProperty;
use Respect\Validation\Id;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use Respect\Validation\Rules\Core\Reducer;
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Attributes implements Rule
{
public function evaluate(mixed $input): Result
{
$id = new Id('attributes');
$objectType = (new ObjectType())->evaluate($input);
if (!$objectType->hasPassed) {
return $objectType->withId($id);
}
$reflection = new ReflectionObject($input);
$rules = [...$this->getClassRules($reflection), ...$this->getPropertyRules($reflection)];
if ($rules === []) {
return (new AlwaysValid())->evaluate($input)->withId($id);
}
return (new Reducer(...$rules))->evaluate($input)->withId($id);
}
/** @return array<Rule> */
private function getClassRules(ReflectionObject $reflection): array
{
$rules = [];
while ($reflection instanceof ReflectionClass) {
foreach ($reflection->getAttributes(Rule::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
$rules[] = $attribute->newInstance();
}
$reflection = $reflection->getParentClass();
}
return $rules;
}
/** @return array<Rule> */
private function getPropertyRules(ReflectionObject $reflection): array
{
$rules = [];
foreach ($this->getProperties($reflection) as $propertyName => $property) {
$propertyRules = [];
foreach ($property->getAttributes(Rule::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
$propertyRules[] = $attribute->newInstance();
}
if ($propertyRules === []) {
continue;
}
$allowsNull = $property->getType()?->allowsNull() ?? false;
$childRule = new Reducer(...$propertyRules);
$rules[] = new Property($propertyName, $allowsNull ? new NullOr($childRule) : $childRule);
}
return $rules;
}
/** @return array<ReflectionProperty> */
private function getProperties(ReflectionObject $reflection): array
{
$properties = [];
while ($reflection instanceof ReflectionClass) {
foreach ($reflection->getProperties() as $property) {
$properties[$property->name] = $property;
}
$reflection = $reflection->getParentClass();
}
return $properties;
}
}