PHP 2821 ~ 7 мин.

PHP 8.4: Property hooks

PHP 8.4: Property hooks

Итак, получение свойства класса или установка его значения является обычной задачей в объектно-ориентированном программировании. В PHP есть несколько способов сделать это. Давайте сначала обсудим их.

Возьмем, к примеру, следующий класс.

class User
{
    private string $name;
}

Как вы можете заметить, у нас в классе есть приватное свойство $name. Теперь мы можем определить геттеры и сеттеры для чтения и записи значения свойства соответственно, что до PHP 7.4 можно было считать идиоматическим:

class User
{
    private string $name;

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }
}

$user = new User();
$user->setName('Sergey');

echo $user->getName(); // Sergey

Это обычный традиционный подход, и люди используют его уже давно.

Начиная с PHP 8 мы можем сократить код еще больше, используя объявление свойства в конструкторе:

class User
{
    public function __construct(public string $name) {}
}

$user = new User('Sergey');

echo $user->name; // Sergey

Это довольно аккуратный подход, и он немного более краток при использовании методов получения и установки. Это намного приятнее, но за это приходится платить: если позже мы захотим добавить дополнительное поведение (например, проверку или предварительную обработку), то сделать это будет негде.

Мы также можем использовать магические методы __get и __set для достижения того же результата. Но это очень многословно, подвержено ошибкам и не подходит для инструментов статического анализа, таких как, например PHPStan.

В PHP 8.4 этот ключевой аспект будет улучшен за счет введения хуков свойств.

Propertry Hooks

Согласно этому RFC, хуки свойств - это новая функция PHP 8.4, которая позволит вам определять собственную логику для доступа к свойствам и их изменения. Это может быть полезно для различных случаев использования, таких как мутация, ведение логов, проверка или кэширование.

По сути, Propertry Hooks позволяют вам определять дополнительное поведение свойств класса, используя в основном два хука: get и set. И это будет индивидуально для определенных свойств.

Приведенный ниже дизайн и синтаксис больше всего похож на Kotlin, хотя он также опирается на влияние C# и Swift. Python и JavaScript имеют схожие функции благодаря разному синтаксису, хотя этот синтаксис и непригоден для PHP. Ruby рассматривает свойства и методы почти одинаково, поэтому эта функциональность достигается как побочный эффект. Короче говоря, "средства доступа к свойствам" - очень распространенная функция в основных популярных языках программирования.

Что интересно, авторы просят не пугаться длинному тексту RFC, а предлагают отнестись к этому как глубоко проработанному материалу, отвечающему на все вопросы) Мы конечно пробежимся по самому главному, но если вы хотите узнать детали разработки, то можете почитать оригинальный RFC.

Хук set

Вот как мы можем написать set для $name свойства в предыдущем примере:

class User
{
    public string $name {
        set (string $value) {
            // валидация имени
            if (mb_strlen(value) < 4)) {
                throw new InvalidArgumentException(
                    'Invalid Name'
                );
            }
            
            // Установка значения
            $this->name = $value;
        }
    }
}

Как вы можете заметить, хуки заключаются в фигурные скобки, которые идут сразу после имени свойства. Затем мы можем определить хуки внутри этого блока кода.

Тело хука set - это тело метода произвольной сложности, принимающее один аргумент. Если указано, оно должно включать как тип, так и имя параметра. Здесь мы можем проверить или изменить значение свойства до его установки.

Таким образом, когда свойству присвоено значение $name, будет вызван хук set, и значение будет проверено перед его установкой.

$user = new User();
$user->name = 'Ser'; // Должно вывести исключение InvalidArgumentException

$user = new User();
$user->name = 'Sergey';

echo $user->name; // Sergey

Существует также сокращенный синтаксис для определения хука set с помощью оператора =>, похожий на замыкания:

class User
{
    public string $name {
        set => strtoupper($value);
    }
}

Здесь предполагается, что $value это значение свойства, и для него будет вызвана функция strtoupper.

Хук get

Хук get позволяет вам определить собственную логику для доступа к свойствам. Это может быть полезно для свойств, которые необходимо изменить, прежде чем они будут возвращены пользователю.

Например, если в классе User есть два свойства $name и $lastName, мы можем определить get для свойства $fullName следующим образом:

class User
{
    public function __construct(
        public string $name, public string $lastName
    ) {}

    public string $fullName {
        get {
            return $this->name . " " . $this->lastName;
        }
    }
}

$user = new User('Sergey', 'Mukhin');

echo $user->fullName; // Sergey Mukhin    

Как вы можете заметить, хук get не принимает никаких аргументов. Он просто возвращает значение свойства. Как и обычный геттер.

Таким образом, при доступе к значению $fullName будет вызван хук get, и значение будет возвращено на основе логики, определенной в хуке.

Так же существует сокращенный синтаксис для хука get с помощью оператора =>

class User
{
    public function __construct(
        public string $name, 
        public string $lastName
    ) {}

    public string $fullName {
        get => string $this->name . " " . $this->lastName;
    }
}

Это эквивалентно предыдущему примеру.

Использование хуков с интерфейсами

Чтобы устранить необходимость в геттерах и сеттерах, интерфейс должен иметь возможность указать, какие свойства он включает и таким образом в довесок этот RFC также добавляет возможность для интерфейсов асимметрично объявлять публичные свойства. Класс реализации может предоставить свойство через обычное свойство или хуки. Любого способа из них достаточно для удовлетворения интерфейса.

Рассмотрим сначала за пример код указанный в RFC:

interface I
{
    // Класс, реализующий интерфейс ДОЛЖЕН иметь публичное свойство,
    // но независимо от того, является ли оно публичным или нет - ограничений нет.
    public string $readable { get; }
 
    // Класс реализации ДОЛЖЕН иметь публичное свойство для записи,
    // но независимо от того, доступно ли оно публичному чтению или нет - ограничений нет.
    public string $writeable { set; }
 
    // Класс реализации ДОЛЖЕН иметь свойство, которое является публичным и
    // доступно для чтения и записи.
    public string $both { get; set; }
}

Теперь возьмем что-нибудь из жизни и покороче:

interface User
{
    // Объекты, реализующие этот интерфейс, ДОЛЖНЫ иметь возможность
    // получить свойство $fullName. Этого можно добиться обычным
    // свойством или свойство с хуком get.
    public string $fullName { get; }
}
 
class Customer implements User
{
    // Хук get необязателен, если он не указан,
    // свойство будет доступно для чтения даже без хука get.
    public function __construct(public string $fullName) {}
}

как видите, свойство $fullName прекрасно читается и без хука get. Но если мы определим хук get для свойства, оно будет доступно для чтения именно с помощью хука get.

Интерфейсы связаны только с публичным доступом, поэтому наличие закрытых свойств не зависит от интерфейса и не может удовлетворить интерфейс. Это та же самая связь, что и для методов. Ключевое слово public свойства необходимо для обеспечения согласованности синтаксиса (или его редко используемый псевдоним var).

Следует отметить, что свойство интерфейса, которое требует только get может удовлетворяться общедоступным readonly свойством, поскольку ограничения readonly применяются только при записи. Однако свойство интерфейса, которое требует set несовместимо с readonly свойством, поскольку публичная запись будет запрещена.

Магическая константа свойства

В хуке свойства специальная константа __PROPERTY__ определяется автоматически. Его значением будет установлено имя свойства. Это в основном полезно для повторения самоссылающегося кода, пример:

class User
{
    private array $cache = [];

    // ...

    public string $fullName { get => $this->cache[__PROPERTY__] ??= $this->name . " " . $this->lastName; }
}

Взаимодействие с Trait'ами

Свойства в трейтах могут объявлять хуки, как и любое другое свойство. Однако, как и в случае с обычными свойствами, здесь нет механизма разрешения конфликтов, который имеется в методах. Если трейт и класс, в котором он используется, объявляют одно и то же свойство с помощью хуков, выдается ошибка. Ожидается, что это очень редкий крайний случай, и поэтому никаких дополнительных механизмов разрешения не требуется.

Работа с рефлексией

Появилось новое глобальное перечисление PropertyHookType. Он имеет поддержку строки, что позволяет при необходимости легко "преобразовать" примитивные значения:

enum PropertyHookType: string
{
    case Get = 'get';
    case Set = 'set';
}

На что следует обратить внимание

Есть вещи, которые следует учитывать при использовании хуков свойств.

  • Хуки доступны только в свойствах объекта. Таким образом, статические свойства не могут иметь хуков.

  • Хуки свойств переопределяют любое поведение свойства при чтении или записи.

  • Хуки свойств имеют доступ ко всем публичным, приватным или защищенным методам объекта, а также к любым публичным, приватным или защищенным свойствам, включая свойства, которые могут иметь свои собственные хуки свойств.

  • Установка ссылок на хуки свойств не допускается, поскольку любая попытка изменения значения по ссылке приведет к обходу хука set, если он определен.

  • Дочерний класс может определять или переопределять индивидуальные хуки свойства путем переопределения свойства и только тех хуков, которые он хочет переопределить. Тип и видимость свойства объекта независимо регулируются собственными правилами. Таким образом, каждый хук переопределяет родительские реализации независимо друг от друга.

В заключение

Хуки свойств - это мощная функция, позволяющая настраивать поведение свойств более понятным, кратким и гибким способом, чем другие подходы. Они особенно полезны, когда вы хотите добавить пользовательскую логику к свойствам, которые читаются или записываются объектом.

В RFC упоминается, что, хотя в настоящее время существует два хука свойств, в будущем есть возможность добавить больше, что сделает хуки свойств еще более мощными.

Можно только поблагодарить Илью Товило и Ларри Гарфилда, в очередной раз создавших столь прекрасный функционал!

Что думаешь?

Категории
  • PHP 68
  • Заметки 18
  • Безопасность 4
  • Флуд 2
  • Nginx 2
  • ИТ новости 2
  • Видео 1
  • Docker 1
  • Roadmap 1
  • Архитектура 0

Хочешь поддержать сайт?

Делаем из мухи слона

sergeymukhin.com

персональный блог о веб-разработке от Сергея Мухина. Блог был основан в 2018 году, и собирался уделять основное внимание последним тенденциям, учебным пособиям, а также советам и рекомендациям, позволяющим начинающим девелоперам встать быстрее на правильную дорогу веб разработки, но что-то пошло не так 😃

Релизы PHP 8.4

Дата Релиз
4 Июля 2024 Альфа 1
18 Июля 2024 Альфа 2
1 Августа 2024 Альфа 3
13 Августа 2024 Feature freeze
15 Августа 2024 Бета 1
29 Августа 2024 Бета 2
12 Сентября 2024 Бета 3
26 Сентября 2024 RC 1
10 Октября 2024 RC 2
24 Октября 2024 RC 3
7 Ноября 2024 RC 4
21 Ноября 2024 GA

Что нового?