Final Class Yiisoft\Validator\Helper\ObjectParser
| Inheritance | Yiisoft\Validator\Helper\ObjectParser |
|---|
A helper class used to parse rules from PHP attributes (attached to class properties and class itself) and data from object properties. The attributes introduced in PHP 8 simplify rules' configuration process, especially for nested data and relations. This way the validated structures can be presented as DTO classes with references to each other.
An example of parsed object with both one-to-one (requires PHP > 8.0) and one-to-many (requires PHP > 8.1) relations:
final class Post
{
#[Length(max: 255)]
public string $title = '';
#[Nested]
public Author|null $author = null;
// Passing instances is available only since PHP 8.1.
#[Each(new Nested(File::class))]
public array $files = [];
public function __construct()
{
$this->author = new Author();
}
}
final class Author
{
#[Length(min: 1)]
public string $name = '';
}
// Some rules, like "Nested" can be also configured through the class attribute.
#[Nested(['url' => new Url()])]
final class File
{
public string $url = '';
}
$post = new Post(title: 'Yii3 Overview 3', author: 'Dmitriy');
$parser = new ObjectParser($post);
$rules = $parser->getRules();
$data = $parser->getData();
The parsed $rules will contain:
[
new Nested([
'title' => [new Length(max: 255)],
'author' => new Nested([
'name' => [new Length(min: 1)],
]),
'files' => new Each([
new Nested([
'url' => [new Url()],
]),
]),
]);
];
And the result of $data will be:
[
'title' => 'Yii3 Overview 3',
'author' => 'John',
'files' => [],
];
A class name string is valid as a source too. This way only rules will be parsed:
$parser = new ObjectParser(Post::class);
$rules = $parser->getRules(); // The result is the same as in previous example.
$data = $parser->getData(); // Returns empty array.
Please refer to the guide for more examples.
Note that the rule attributes can be combined with others without affecting parsing. Which properties to parse can be configured via {@see \Yiisoft\Validator\Helper\ObjectParser::$propertyVisibility} and {@see \Yiisoft\Validator\Helper\ObjectParser::$skipStaticProperties} options.
Uses Reflection for getting object data and metadata. Supports caching for Reflection of a class / an obhect with properties and rules which can be disabled on demand.
Psalm Types
| Name | Value |
|---|---|
| RulesCacheItem | array{0: Yiisoft\Validator\RuleInterface, 1: \Attribute::TARGET_*} |
Public Methods
| Method | Description | Defined By |
|---|---|---|
| __construct() | Yiisoft\Validator\Helper\ObjectParser | |
| getData() | Returns the parsed object's data as a whole in a form of associative array. | Yiisoft\Validator\Helper\ObjectParser |
| getLabels() | Parses labels specified via {@see Label} attributes attached to class properties. | Yiisoft\Validator\Helper\ObjectParser |
| getPropertyTranslator() | An optional property names translator. It's taken from the {@see $source} object when
{@see PropertyTranslatorProviderInterface} is implemented. In case of it's missing or {@see $source} being a
class name string, a null value is returned. |
Yiisoft\Validator\Helper\ObjectParser |
| getPropertyValue() | Returns a property value of the parsed object. | Yiisoft\Validator\Helper\ObjectParser |
| getReflectionProperties() | Returns Reflection properties parsed from {@see $source} in accordance with {@see $propertyVisibility} and {@see $skipStaticProperties} values. Repetitive calls utilize cache if it's enabled in {@see $useCache}. | Yiisoft\Validator\Helper\ObjectParser |
| getRules() | Parses rules specified via attributes attached to class properties and class itself. Repetitive calls utilize cache if it's enabled in {@see $useCache}. | Yiisoft\Validator\Helper\ObjectParser |
| hasProperty() | Whether the parsed object has the property with a given name. Note that this means existence only and properties with empty values are treated as present too. | Yiisoft\Validator\Helper\ObjectParser |
Method Details
| public mixed __construct ( string|object $source, integer $propertyVisibility = ReflectionProperty::IS_PRIVATE | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC, boolean $skipStaticProperties = false, boolean $useCache = true ) | ||
| $source | string|object | |
| $propertyVisibility | integer | |
| $skipStaticProperties | boolean | |
| $useCache | boolean | |
| throws | InvalidArgumentException |
If a class name string provided in {@see $source} refers to a non-existing class. |
|---|---|---|
public function __construct(
/**
* @var object|string A source for parsing rules and data. Can be either a class name string or an
* instance.
*
* @psalm-var class-string|object
*/
private readonly string|object $source,
/**
* @var int Visibility levels the parsed properties must have. For example: public and protected only, this
* means that the rest (private ones) will be skipped. Defaults to all visibility levels (public, protected and
* private).
*
* @psalm-var int-mask-of<ReflectionProperty::IS_*>
*/
private readonly int $propertyVisibility = ReflectionProperty::IS_PRIVATE
| ReflectionProperty::IS_PROTECTED
| ReflectionProperty::IS_PUBLIC,
/**
* @var bool Whether the properties with "static" modifier must be skipped.
*/
private readonly bool $skipStaticProperties = false,
/**
* @var bool Whether some results of parsing (Reflection of a class / an object with properties and rules) must
* be cached.
*/
bool $useCache = true,
) {
/** @var object|string $source */
if (is_string($source) && !class_exists($source)) {
throw new InvalidArgumentException(
sprintf('Class "%s" not found.', $source),
);
}
if ($useCache) {
$this->cacheKey = (is_object($source) ? $source::class : $source)
. '_' . $this->propertyVisibility
. '_' . (int) $this->skipStaticProperties;
}
}
Returns the parsed object's data as a whole in a form of associative array.
If a {@see $source} is a class name string, an empty array is always returned.
| public array getData ( ) | ||
| return | array |
A mapping between property names and their values. |
|---|---|---|
public function getData(): array
{
if (!is_object($this->source)) {
return [];
}
$data = [];
foreach ($this->getReflectionProperties() as $name => $property) {
if (!$property->isInitialized($this->source)) {
continue;
}
/** @var mixed */
$data[$name] = $property->getValue($this->source);
}
return $data;
}
Parses labels specified via {@see Label} attributes attached to class properties.
| public array<string, string> getLabels ( ) |
public function getLabels(): array
{
if ($this->hasCacheItem('labels')) {
/** @var array<string, string> */
return $this->getCacheItem('labels');
}
$labels = [];
foreach ($this->getReflectionProperties() as $property) {
$attributes = $property->getAttributes(Label::class, ReflectionAttribute::IS_INSTANCEOF);
foreach ($attributes as $attribute) {
/** @var Label $instance */
$instance = $attribute->newInstance();
$labels[$property->getName()] = $instance->getLabel();
}
}
$this->setCacheItem('labels', $labels);
return $labels;
}
An optional property names translator. It's taken from the {@see $source} object when
{@see PropertyTranslatorProviderInterface} is implemented. In case of it's missing or {@see $source} being a
class name string, a null value is returned.
| public Yiisoft\Validator\PropertyTranslatorInterface|null getPropertyTranslator ( ) | ||
| return | Yiisoft\Validator\PropertyTranslatorInterface|null |
A property translator instance or `null if it was not provided. |
|---|---|---|
public function getPropertyTranslator(): ?PropertyTranslatorInterface
{
return $this->source instanceof PropertyTranslatorProviderInterface
? $this->source->getPropertyTranslator()
: null;
}
Returns a property value of the parsed object.
Note that in case of non-existing property a default null value is returned. If you need to check the presence
of a property or return a different default value, use {@see \Yiisoft\Validator\Helper\hasProperty()} instead.
If a {@see $source} is a class name string, null value is always returned.
| public mixed getPropertyValue ( string $property ) | ||
| $property | string |
Property name. |
| return | mixed |
Property value. |
|---|---|---|
public function getPropertyValue(string $property): mixed
{
if (!is_object($this->source)) {
return null;
}
$reflectionProperty = $this->getReflectionProperties()[$property] ?? null;
if ($reflectionProperty === null || !$reflectionProperty->isInitialized($this->source)) {
return null;
}
return $reflectionProperty->getValue($this->source);
}
Returns Reflection properties parsed from {@see $source} in accordance with {@see $propertyVisibility} and {@see $skipStaticProperties} values. Repetitive calls utilize cache if it's enabled in {@see $useCache}.
See also https://github.com/yiisoft/form for usage in form collector.
| public array<string, ReflectionProperty> getReflectionProperties ( ) | ||
| return | array<string, ReflectionProperty> |
A mapping between Reflection property names and their values. |
|---|---|---|
public function getReflectionProperties(): array
{
if ($this->hasCacheItem('reflectionProperties')) {
/** @var array<string, ReflectionProperty> */
return $this->getCacheItem('reflectionProperties');
}
$reflectionProperties = [];
foreach ($this->getReflectionSource()->getProperties($this->propertyVisibility) as $property) {
if ($this->skipStaticProperties && $property->isStatic()) {
continue;
}
$reflectionProperties[$property->getName()] = $property;
}
$this->setCacheItem('reflectionProperties', $reflectionProperties);
return $reflectionProperties;
}
Parses rules specified via attributes attached to class properties and class itself. Repetitive calls utilize cache if it's enabled in {@see $useCache}.
| public array<integer, Yiisoft\Validator\RuleInterface>|array<string, list<Yiisoft\Validator\RuleInterface>> getRules ( ) | ||
| return | array<integer, Yiisoft\Validator\RuleInterface>|array<string, list<Yiisoft\Validator\RuleInterface>> |
The resulting rules array with the following structure:
|
|---|---|---|
public function getRules(): array
{
if ($this->hasCacheItem('rules')) {
/** @var array $rules */
$rules = $this->getCacheItem('rules');
return $this->prepareRules($rules);
}
$rules = [];
// Class rules
$attributes = $this
->getReflectionSource()
->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
foreach ($attributes as $attribute) {
$rules[] = [$attribute->newInstance(), Attribute::TARGET_CLASS];
}
// Properties rules
foreach ($this->getReflectionProperties() as $property) {
// TODO: use Generator to collect attributes.
$attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
foreach ($attributes as $attribute) {
/** @psalm-suppress UndefinedInterfaceMethod */
$rules[$property->getName()][] = [$attribute->newInstance(), Attribute::TARGET_PROPERTY];
}
}
$this->setCacheItem('rules', $rules);
return $this->prepareRules($rules);
}
Whether the parsed object has the property with a given name. Note that this means existence only and properties with empty values are treated as present too.
If a {@see $source} is a class name string, false value is always returned.
| public boolean hasProperty ( string $property ) | ||
| $property | string | |
| return | boolean |
Whether the property exists: |
|---|---|---|
public function hasProperty(string $property): bool
{
return is_object($this->source) && array_key_exists($property, $this->getReflectionProperties());
}
Signup or Login in order to comment.