跳到內容

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'

使用 Hasser/Isser

而且它甚至不止於此。如果找不到 getter,accessor 將會尋找 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');

存取可為 Null 的屬性路徑

考慮以下 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 是可為 Null 的,當 $person 屬性為 null 時,類似 comment.person.profile 的物件圖形將會觸發例外。解決方案是使用 nullsafe 運算子 (?) 標記所有可為 Null 的屬性。

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 組件 的 inflection 工具用於尋找屬性名稱的單數形式。

如果可用,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 授權條款授權。
目錄
    版本