PropertyAccess 组件
PropertyAccess 组件提供了使用简单字符串表示法从对象或数组读取和写入的功能。
安装
1
$ composer require symfony/property-access
注意
如果你在 Symfony 应用程序之外安装此组件,你必须在你的代码中引入 vendor/autoload.php
文件,以启用 Composer 提供的类自动加载机制。阅读 这篇文章了解更多详情。
用法
此组件的入口点是 createPropertyAccessor() 工厂。此工厂将使用默认配置创建一个新的 PropertyAccessor 类的实例
1 2 3
use Symfony\Component\PropertyAccess\PropertyAccess;
$propertyAccessor = PropertyAccess::createPropertyAccessor();
从数组读取
你可以使用 getValue() 方法读取数组。这是使用 PHP 中使用的索引表示法完成的
1 2 3 4 5 6 7
// ...
$person = [
'first_name' => 'Wouter',
];
var_dump($propertyAccessor->getValue($person, '[first_name]')); // 'Wouter'
var_dump($propertyAccessor->getValue($person, '[age]')); // null
正如你所看到的,如果索引不存在,该方法将返回 null
。但是你可以使用 enableExceptionOnInvalidIndex() 方法来更改此行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// ...
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
->enableExceptionOnInvalidIndex()
->getPropertyAccessor();
$person = [
'first_name' => 'Wouter',
];
// instead of returning null, the code now throws an exception of type
// Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
$value = $propertyAccessor->getValue($person, '[age]');
// You can avoid the exception by adding the nullsafe operator
$value = $propertyAccessor->getValue($person, '[age?]');
你也可以使用多维数组
1 2 3 4 5 6 7 8 9 10 11 12
// ...
$persons = [
[
'first_name' => 'Wouter',
],
[
'first_name' => 'Ryan',
],
];
var_dump($propertyAccessor->getValue($persons, '[0][first_name]')); // 'Wouter'
var_dump($propertyAccessor->getValue($persons, '[1][first_name]')); // 'Ryan'
提示
如果数组的键包含点 .
或左方括号 [
,你必须使用反斜杠转义这些字符。在上面的例子中,如果数组键是 first.name
而不是 first_name
,你应该按如下方式访问它的值
1 2
var_dump($propertyAccessor->getValue($persons, '[0][first\.name]')); // 'Wouter'
var_dump($propertyAccessor->getValue($persons, '[1][first\.name]')); // 'Ryan'
右方括号 ]
在数组键中不需要转义。
从对象读取
getValue()
方法是一个非常健壮的方法,当处理对象时,你可以看到它的所有功能。
访问公共属性
要从属性读取,请使用 “点” 表示法
1 2 3 4 5 6 7 8 9 10 11
// ...
$person = new Person();
$person->firstName = 'Wouter';
var_dump($propertyAccessor->getValue($person, 'firstName')); // 'Wouter'
$child = new Person();
$child->firstName = 'Bar';
$person->children = [$child];
var_dump($propertyAccessor->getValue($person, 'children[0].firstName')); // 'Bar'
警告
访问公共属性是 PropertyAccessor
使用的最后一个选项。它会先尝试使用以下方法访问值,然后再直接使用属性。例如,如果你有一个具有 getter 方法的公共属性,它将使用 getter。
使用 Getter
getValue()
方法也支持使用 getter 进行读取。该方法将使用 getter 的通用命名约定创建。它将属性名称转换为 camelCase (first_name
变为 FirstName
) 并在其前面加上 get
前缀。所以实际的方法变为 getFirstName()
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// ...
class Person
{
private string $firstName = 'Wouter';
public function getFirstName(): string
{
return $this->firstName;
}
}
$person = new Person();
var_dump($propertyAccessor->getValue($person, 'first_name')); // 'Wouter'
使用 Hassers/Issers
甚至不止于此。如果没有找到 getter,访问器将查找 isser 或 hasser。此方法的创建方式与 getter 相同,这意味着你可以执行以下操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// ...
class Person
{
private bool $author = true;
private array $children = [];
public function isAuthor(): bool
{
return $this->author;
}
public function hasChildren(): bool
{
return 0 !== count($this->children);
}
}
$person = new Person();
if ($propertyAccessor->getValue($person, 'author')) {
var_dump('This person is an author');
}
if ($propertyAccessor->getValue($person, 'children')) {
var_dump('This person has children');
}
这将产生:This person is an author
访问不存在的属性路径
默认情况下,如果传递给 getValue() 的属性路径不存在,则会抛出 NoSuchPropertyException 异常。你可以使用 disableExceptionOnInvalidPropertyPath() 方法来更改此行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// ...
class Person
{
public string $name;
}
$person = new Person();
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
->disableExceptionOnInvalidPropertyPath()
->getPropertyAccessor();
// instead of throwing an exception the following code returns null
$value = $propertyAccessor->getValue($person, 'birthday');
访问可为空的属性路径
考虑以下 PHP 代码
1 2 3 4 5 6 7 8 9 10 11 12
class Person
{
}
class Comment
{
public ?Person $person = null;
public string $message;
}
$comment = new Comment();
$comment->message = 'test';
假设 $person
是可为空的,像 comment.person.profile
这样的对象图在 $person
属性为 null
时会触发异常。解决方案是用 nullsafe 运算符 (?
) 标记所有可为空的属性
1 2 3 4 5 6 7
// This code throws an exception of type
// Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
var_dump($propertyAccessor->getValue($comment, 'person.firstname'));
// If a property marked with the nullsafe operator is null, the expression is
// no longer evaluated and null is returned immediately without throwing an exception
var_dump($propertyAccessor->getValue($comment, 'person?.firstname')); // null
魔术方法 __get()
getValue()
方法也可以使用魔术方法 __get()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// ...
class Person
{
private array $children = [
'Wouter' => [...],
];
public function __get($id): mixed
{
return $this->children[$id];
}
public function __isset($id): bool
{
return isset($this->children[$id]);
}
}
$person = new Person();
var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...]
警告
当实现魔术方法 __get()
时,你还需要实现 __isset()
。
魔术方法 __call()
最后,getValue()
可以使用魔术方法 __call()
,但是你需要使用 PropertyAccessorBuilder 启用此功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// ...
class Person
{
private array $children = [
'wouter' => [...],
];
public function __call($name, $args): mixed
{
$property = lcfirst(substr($name, 3));
if ('get' === substr($name, 0, 3)) {
return $this->children[$property] ?? null;
} elseif ('set' === substr($name, 0, 3)) {
$value = 1 == count($args) ? $args[0] : null;
$this->children[$property] = $value;
}
}
}
$person = new Person();
// enables PHP __call() magic method
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
->enableMagicCall()
->getPropertyAccessor();
var_dump($propertyAccessor->getValue($person, 'wouter')); // [...]
警告
__call()
功能默认是禁用的,你可以通过调用 enableMagicCall() 来启用它,请参阅 启用其他功能。
写入数组
PropertyAccessor
类不仅可以读取数组,还可以写入数组。这可以使用 setValue() 方法来实现
1 2 3 4 5 6 7 8
// ...
$person = [];
$propertyAccessor->setValue($person, '[first_name]', 'Wouter');
var_dump($propertyAccessor->getValue($person, '[first_name]')); // 'Wouter'
// or
// var_dump($person['first_name']); // 'Wouter'
写入对象
setValue()
方法与 getValue()
方法具有相同的功能。你可以使用 setter、魔术方法 __set()
方法或属性来设置值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// ...
class Person
{
public string $firstName;
private string $lastName;
private array $children = [];
public function setLastName($name): void
{
$this->lastName = $name;
}
public function getLastName(): string
{
return $this->lastName;
}
public function getChildren(): array
{
return $this->children;
}
public function __set($property, $value): void
{
$this->$property = $value;
}
}
$person = new Person();
$propertyAccessor->setValue($person, 'firstName', 'Wouter');
$propertyAccessor->setValue($person, 'lastName', 'de Jong'); // setLastName is called
$propertyAccessor->setValue($person, 'children', [new Person()]); // __set is called
var_dump($person->firstName); // 'Wouter'
var_dump($person->getLastName()); // 'de Jong'
var_dump($person->getChildren()); // [Person()];
你也可以使用 __call()
来设置值,但是你需要启用该功能,请参阅 启用其他功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// ...
class Person
{
private array $children = [];
public function __call($name, $args): mixed
{
$property = lcfirst(substr($name, 3));
if ('get' === substr($name, 0, 3)) {
return $this->children[$property] ?? null;
} elseif ('set' === substr($name, 0, 3)) {
$value = 1 == count($args) ? $args[0] : null;
$this->children[$property] = $value;
}
}
}
$person = new Person();
// Enable magic __call
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
->enableMagicCall()
->getPropertyAccessor();
$propertyAccessor->setValue($person, 'wouter', [...]);
var_dump($person->getWouter()); // [...]
注意
默认情况下启用 __set()
方法支持。如果你想禁用它,请参阅 启用其他功能。
写入数组属性
PropertyAccessor
类允许通过 adder 和 remover 方法更新存储在属性中的数组内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// ...
class Person
{
/**
* @var string[]
*/
private array $children = [];
public function getChildren(): array
{
return $this->children;
}
public function addChild(string $name): void
{
$this->children[$name] = $name;
}
public function removeChild(string $name): void
{
unset($this->children[$name]);
}
}
$person = new Person();
$propertyAccessor->setValue($person, 'children', ['kevin', 'wouter']);
var_dump($person->getChildren()); // ['kevin', 'wouter']
PropertyAccess 组件检查名为 add<SingularOfThePropertyName>()
和 remove<SingularOfThePropertyName>()
的方法。这两个方法都必须定义。例如,在前面的示例中,组件查找 addChild()
和 removeChild()
方法来访问 children
属性。String 组件 inflector 用于查找属性名称的单数形式。
如果可用,adder 和 remover 方法优先于 setter 方法。
使用非标准的 adder/remover 方法
有时,adder 和 remover 方法不使用标准的 add
或 remove
前缀,例如在这个例子中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// ...
class Team
{
// ...
public function joinTeam(string $person): void
{
$this->team[] = $person;
}
public function leaveTeam(string $person): void
{
foreach ($this->team as $id => $item) {
if ($person === $item) {
unset($this->team[$id]);
break;
}
}
}
}
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyAccess\PropertyAccessor;
$list = new Team();
$reflectionExtractor = new ReflectionExtractor(null, null, ['join', 'leave']);
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH, null, $reflectionExtractor, $reflectionExtractor);
$propertyAccessor->setValue($person, 'team', ['kevin', 'wouter']);
var_dump($person->getTeam()); // ['kevin', 'wouter']
PropertyAccess 组件将调用 join<SingularOfThePropertyName>()
和 leave<SingularOfThePropertyName>()
方法,而不是调用 add<SingularOfThePropertyName>()
和 remove<SingularOfThePropertyName>()
。
检查属性路径
当你想检查是否可以安全地调用 getValue() 而无需实际调用该方法时,你可以使用 isReadable() 代替
1 2 3 4 5
$person = new Person();
if ($propertyAccessor->isReadable($person, 'firstName')) {
// ...
}
对于 setValue() 也是如此:调用 isWritable() 方法来找出是否可以更新属性路径
1 2 3 4 5
$person = new Person();
if ($propertyAccessor->isWritable($person, 'firstName')) {
// ...
}
混合对象和数组
你也可以混合使用对象和数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// ...
class Person
{
public string $firstName;
private array $children = [];
public function setChildren($children): void
{
$this->children = $children;
}
public function getChildren(): array
{
return $this->children;
}
}
$person = new Person();
$propertyAccessor->setValue($person, 'children[0]', new Person);
// equal to $person->getChildren()[0] = new Person()
$propertyAccessor->setValue($person, 'children[0].firstName', 'Wouter');
// equal to $person->getChildren()[0]->firstName = 'Wouter'
var_dump('Hello '.$propertyAccessor->getValue($person, 'children[0].firstName')); // 'Wouter'
// equal to $person->getChildren()[0]->firstName
启用其他功能
PropertyAccessor 可以配置为启用额外的功能。为此,你可以使用 PropertyAccessorBuilder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// ...
$propertyAccessorBuilder = PropertyAccess::createPropertyAccessorBuilder();
$propertyAccessorBuilder->enableMagicCall(); // enables magic __call
$propertyAccessorBuilder->enableMagicGet(); // enables magic __get
$propertyAccessorBuilder->enableMagicSet(); // enables magic __set
$propertyAccessorBuilder->enableMagicMethods(); // enables magic __get, __set and __call
$propertyAccessorBuilder->disableMagicCall(); // disables magic __call
$propertyAccessorBuilder->disableMagicGet(); // disables magic __get
$propertyAccessorBuilder->disableMagicSet(); // disables magic __set
$propertyAccessorBuilder->disableMagicMethods(); // disables magic __get, __set and __call
// checks if magic __call, __get or __set handling are enabled
$propertyAccessorBuilder->isMagicCallEnabled(); // true or false
$propertyAccessorBuilder->isMagicGetEnabled(); // true or false
$propertyAccessorBuilder->isMagicSetEnabled(); // true or false
// At the end get the configured property accessor
$propertyAccessor = $propertyAccessorBuilder->getPropertyAccessor();
// Or all in one
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
->enableMagicCall()
->getPropertyAccessor();
或者你可以直接将参数传递给构造函数(不推荐的方式)
1 2
// enable handling of magic __call, __set but not __get:
$propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL | PropertyAccessor::MAGIC_SET);