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
類別允許透過 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 組件 的 inflection 工具用於尋找屬性名稱的單數形式。
如果可用,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);