Компонент PropertyAccess

Дата оновлення перекладу 2022-12-12

Компонент PropertyAccess

Компонент PropertyAccess надає функцію для читання та запису з/в обʼєкт або масив, використовуючи нотацію рядку.

Установка

1
$ composer require symfony/property-access

Note

Якщо ви встановлюєте цей компонент поза додатком 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
// ...
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->enableExceptionOnInvalidIndex()
    ->getPropertyAccessor();

$person = [
    'first_name' => 'Wouter',
];

// замість повернення null, тепер код викликає виключення типу
// Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
$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'

Читання з обʼєктів

Метод getValue() дуже великий, і ви можете побачити всі його функції при роботі з обʼєктами.

Доступ до публічних властивостей

Щоб зчитувати з властивостей, використайте нотацію "dot":

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'

Caution

Доступ до публічних властивостей - це остання опція, використовувана PropertyAccessor. Вона намагається отримати доступ до значення, використовуючи методи, описані нижче, до використання властивості напряму. Наприклад, якщо у вас є публічна властивість, яка має метод гетера, то вона використовуватиме гетер.

Використання гетерів

Метод getValue() також підтримує читання, використовуючи гетери. Цей метод буде створено, використовуючи загальні угоди про іменування для гетерів. Він перетворює імʼя властивості на camelCase (first_name стає FirstName) і додає до нього префікс get. Тому сам метод стає getFirstName():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ...
class Person
{
    private $firstName = 'Wouter';

    public function getFirstName()
    {
        return $this->firstName;
    }
}

$person = new Person();

var_dump($propertyAccessor->getValue($person, 'first_name')); // 'Wouter'

Використання хасерів/ісерів

На цьому все не завершується. Якщо гетер не знайдено, процес доступу шукатиме ісер або хасер. Цей метод створюється, використовуючи той же спосіб, що і гетери, що означає, що ви можете зробити щось на кшталт цього:

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 $author = true;
    private $children = [];

    public function isAuthor()
    {
        return $this->author;
    }

    public function hasChildren()
    {
        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

Доступ до неіcнуючого шляху властивості

За замовчуванням, якщо шлях властивості, переданий getValue() не існує, викликається NoSuchPropertyException. Ви можете змінити цю поведінку, використовуючи метод disableExceptionOnInvalidPropertyPath():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ...
class Person
{
    public $name;
}

$person = new Person();

$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->disableExceptionOnInvalidPropertyPath()
    ->getPropertyAccessor();

// замість виклику виключення, наступний код поверне null
$value = $propertyAccessor->getValue($person, 'birthday');

Магічний метод __get()

Метод getValue() може також використовувати магічний метод __get():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ...
class Person
{
    private $children = [
        'Wouter' => [...],
    ];

    public function __get($id)
    {
        return $this->children[$id];
    }
}

$person = new Person();

var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...]

Note

Підтримка методу __get() включена за замовчуванням. Див.
Включення інших функцій , якщо ви хочете її відключити.

Доступ до шляхів властивості 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, графік обʼєкта, типу comment.person.profile викличе виключення, якщо властивість $person - null. Рішенням буде відмітити всі властивості, які можуть бути null, операторор захистувід null (?):

1
2
3
4
5
6
7
// Цей код викличе виключення типу
// Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
var_dump($propertyAccessor->getValue($comment, 'person.firstname'));

// Якщо властивість, відмічена оператором захисту від null, є null, вираз більше не
// оцінюється, а null негайно повертається без виклику виключення
var_dump($propertyAccessor->getValue($comment, 'person?.firstname')); // null

6.2

Оператор захисту від null ? було представлено в Symfony 6.2.

Магічний метод __get()

Метод getValue() може також використовувати магічний метод __get():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ...
class Person
{
    private $children = [
        'Wouter' => [...],
    ];

    public function __get($id)
    {
        return $this->children[$id];
    }
}

$person = new Person();

var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...]

Note

Підтримка методу __get() включена за замовчуванням. Див. Включення інших функцій, якщо ви хочете її відключити.

Магічний метод __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 $children = [
        'wouter' => [...],
    ];

    public function __call($name, $args)
    {
        $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();

// включає магічний метод PHP __call()
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->enableMagicCall()
    ->getPropertyAccessor();

var_dump($propertyAccessor->getValue($person, 'wouter')); // [...]

Caution

Функція __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'
// або
// var_dump($person['first_name']); // 'Wouter'

Запис в обʼєкти

Метод setValue() має такі ж функції, як метод getValue(). Ви можете використати сетери, магічний метод __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 $firstName;
    private $lastName;
    private $children = [];

    public function setLastName($name)
    {
        $this->lastName = $name;
    }

    public function getLastName()
    {
        return $this->lastName;
    }

    public function getChildren()
    {
        return $this->children;
    }

    public function __set($property, $value)
    {
        $this->$property = $value;
    }
}

$person = new Person();

$propertyAccessor->setValue($person, 'firstName', 'Wouter');
$propertyAccessor->setValue($person, 'lastName', 'de Jong'); // викликається setLastName
$propertyAccessor->setValue($person, 'children', [new Person()]); // викликається __set 

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 $children = [];

    public function __call($name, $args)
    {
        $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();

// Включити magic __call
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->enableMagicCall()
    ->getPropertyAccessor();

$propertyAccessor->setValue($person, 'wouter', [...]);

var_dump($person->getWouter()); // [...]

Note

Підтримка методу __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 $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 використовується, щоб знайти однину імені властивості.

Якщо вони доступні, то методи додавання та видалення мають пріоритет перед методом сетера.

Використання нестандартних методів додавання/видалення

Іноді методи додавання та видалення не використовують стандартний префікс 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
// ...
class PeopleList
{
    // ...

    public function joinPeople(string $people): void
    {
        $this->peoples[] = $people;
    }

    public function leavePeople(string $people): void
    {
        foreach ($this->peoples as $id => $item) {
            if ($people === $item) {
                unset($this->peoples[$id]);
                break;
            }
        }
    }
}

use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyAccess\PropertyAccessor;

$list = new PeopleList();
$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, 'peoples', ['kevin', 'wouter']);

var_dump($person->getPeoples()); // ['kevin', 'wouter']

Замість виклику add<SingularOfThePropertyName>() і remove<SingularOfThePropertyName>(), компонент PropertyAccess викличе методи join<SingularOfThePropertyName>() і leave<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 $firstName;
    private $children = [];

    public function setChildren($children)
    {
        $this->children = $children;
    }

    public function getChildren()
    {
        return $this->children;
    }
}

$person = new Person();

$propertyAccessor->setValue($person, 'children[0]', new Person);
// дорівнює $person->getChildren()[0] = new Person()

$propertyAccessor->setValue($person, 'children[0].firstName', 'Wouter');
// дорівнює $person->getChildren()[0]->firstName = 'Wouter'

var_dump('Hello '.$propertyAccessor->getValue($person, 'children[0].firstName')); // 'Wouter'
// дорівнює $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(); // включає магічний __call
$propertyAccessorBuilder->enableMagicGet(); // включає магічний __get
$propertyAccessorBuilder->enableMagicSet(); // включає магічний __set
$propertyAccessorBuilder->enableMagicMethods(); // включає магічні __get, __set та __call

$propertyAccessorBuilder->disableMagicCall(); // відключає магічний __call
$propertyAccessorBuilder->disableMagicGet(); // відключає магічний __get
$propertyAccessorBuilder->disableMagicSet(); // відключає магічний __set
$propertyAccessorBuilder->disableMagicMethods(); // відключає магічні __get, __set та __call

// перевіряє, чи включена обробка магічних __call, __get або __set
$propertyAccessorBuilder->isMagicCallEnabled(); // true або false
$propertyAccessorBuilder->isMagicGetEnabled(); // true або false
$propertyAccessorBuilder->isMagicSetEnabled(); // true або false

// Наприкінці, отримати сконфігурований аксесор властивості
$propertyAccessor = $propertyAccessorBuilder->getPropertyAccessor();

// Або все одразу
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->enableMagicCall()
    ->getPropertyAccessor();

Або ж ви можете передати параметри напряму у конструктор (не рекомендовано):

1
2
// включити обробку магічних magic __call, __set але не __get:
$propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL | PropertyAccessor::MAGIC_SET);