Дата обновления перевода 2022-01-03

Компонент PropertyAccess (доступ к свойству)

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

Установка

1
$ composer require symfony/property-access

Note

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

Использование

Входная точка этого компонента - это фабрика createPropertyAccessor(). Это фабрика создаст новый экземпляр класса PropertyAccessor с конфигурацией по умолчанию:

use Symfony\Component\PropertyAccess\PropertyAccess;

$propertyAccessor = PropertyAccess::createPropertyAccessor();

Чтение из массивов

Вы можете прочитать массив с помощью метода getValue(). Это делается, используя нотации индекса, которые используются в PHP:

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

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

Как вы можете увидеть, метод вернёт null, если индекс не существует. Но вы можете изменить это поведение с помощью метода enableExceptionOnInvalidIndex():

// ...
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->enableExceptionOnInvalidIndex()
    ->getPropertyAccessor();

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

// вместо возвращения null, теперь код вызывает исключение типа
// Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
$value = $propertyAccessor->getValue($person, '[age]');

Вы также можете использовать многомерные массивы:

// ...
$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”:

// ...
$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():

// ...
class Person
{
    private $firstName = 'Wouter';

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

$person = new Person();

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

Использование хассеров / иссеров

На этом всё не останавливается. Если геттер не найден, процесс доступа будет искать иссер или хассер. Этот метод создается, используя тот же способ что и геттеры, что означает, что вы можете сделать что-то вроде этого:

// ...
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

Доступ к несуществующему пути свойства

По умолчанию, если путь свойства, переданный getValue() не существует, вызывается NoSuchPropertyException. Вы можете изенить это поведение, используя метод disableExceptionOnInvalidPropertyPath():

// ...
class Person
{
    public $name;
}

$person = new Person();

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

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

Волшебный метод __get()

Метод getValue() может также использовать волшебный метод __get():

// ...
class Person
{
    private $children = [
        'Wouter' => [...],
    ];

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

$person = new Person();

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

New in version 5.2: Волшебный метод __get() может быть отключен, начиная с Symfony 5.2. См. Включение других функций.

Волшебный метод __call()

Наконец, getValue() может использовать волшебный метод __call(), но вам нужно включить эту функцию, используя PropertyAccessorBuilder:

// ...
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():

// ...
$person = [];

$propertyAccessor->setValue($person, '[first_name]', 'Wouter');

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

Запись в объекты

Метод setValue() имеет такие же функции, как метод getValue(). Вы можете использовать сеттеры, волшебный метод __set() или свойства для установки значений:

// ...
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 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(), чтобы устанавливать значения, но вам нужно включить функцию, см. Включение других функций:

// ...
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();

// Enable magic __call
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->enableMagicCall()
    ->getPropertyAccessor();

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

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

New in version 5.2: Волшебный метод __set() может быть отключен, начиная с Symfony 5.2. См. Включение других функций.

Запись в массив свойств

Класс PropertyAccessor позволяет обновлять содержание массивов, содержащихся в свойствах, через методы добавления и удаления (adder и remover):

// ...
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. Компонент Инфлектор используется, чтобы найти сингуляр имени свйоства.

Если они доступны, то методы добавления и удаления имеют приоритет перед методом сеттера.

Использование нестандартный методов добвления/удаления (adder/remover)

Инодга методы добавления и удаления не используют стандартный префикс add или remove, Как в этом примере:

// ...
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() instead:

$person = new Person();

if ($propertyAccessor->isReadable($person, 'firstName')) {
    // ...
}

То же самое возможно для setValue(): Вызовите метод isWritable(), чтобы узнать, можно ли обновить путь свойства:

$person = new Person();

if ($propertyAccessor->isWritable($person, 'firstName')) {
    // ...
}

Смешение объектов и массивов

Вы можете также смешивать объекты и массивы:

// ...
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:

// ...
$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 or false
$propertyAccessorBuilder->isMagicGetEnabled(); // true or false
$propertyAccessorBuilder->isMagicSetEnabled(); // true or false

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

// Или все сразу
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->enableMagicCall()
    ->getPropertyAccessor();

Или вы можете передать параметры напрямую в конструктор (не рекомендуется):

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

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.