跳到内容

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 类允许通过 adderremover 方法更新存储在属性中的数组内容

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 用于查找属性名称的单数形式。

如果可用,adderremover 方法优先于 setter 方法。

使用非标准的 adder/remover 方法

有时,adder 和 remover 方法不使用标准的 addremove 前缀,例如在这个例子中

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);
包括代码示例在内的本作品根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本