Магический метод get в ООП на PHP

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

Если реализовать метод __get в каком-нибудь классе, то все обращения к несуществующим или скрытым свойствам будут обрабатываться этим методом.

При этом PHP автоматически будет передавать имя запрошенного свойства в первый параметр этого метода, а возвращаемое этим методом значение будет воспринято как значение свойства, к которому произошло обращение.

Скорее всего пока не очень понятно, как это работает, поэтому давайте посмотрим на практическом примере. Пусть у нас есть вот такой класс Test с приватным и публичным свойствами:

<?php class Test { public $prop1 = 1; // публичное свойство private $prop2 = 2; // приватное свойство } ?>

Давайте добавим в наш класс магический метод __get, который для начала будет просто возвращать имя свойства, к которому произошло обращение:

<?php class Test { public $prop1 = 1; private $prop2 = 2; public function __get($property) { return $property; // просто вернем имя свойства } } ?>

Давайте проверим работу созданного магического метода. Обратимся к трем типам свойств: к публичному свойству, к приватному и к несуществующему:

<?php $test = new Test; // Обращаемся к публичному свойству: echo $test->prop1; // выведет 1 - то есть значение свойства // Обращаемся к приватному свойству: echo $test->prop2; // выведет 'prop2' - имя свойства // Обращаемся к несуществующему свойству: echo $test->prop3; // выведет 'prop3' - имя свойства ?>

Как вы видите, наш магический метод реагирует на обращение к приватным и несуществующим свойствам, но игнорирует обращение к публичным — они работают так, как и работали раньше.

Применение: свойства только для чтения

Пусть теперь в нашем классе все свойства приватные:

<?php class Test { private $prop1 = 1; private $prop2 = 2; } ?>

Давайте сделаем так, чтобы эти свойства во внешнем мире были доступны только для чтения. Ранее мы такое уже делали, создавая геттеры для каждого свойства и не создавая сеттеры.

Давайте теперь для решения этой задачи воспользуемся магическим методом __get. Будем возвращать в нем значение запрошенного свойства. Как это сделать: имя запрошенного свойства попадает в параметр метода __get, в нашем случае $property.

Это значит, что мы можем прочитать свойство, имя которого хранится в переменной, вот так:

$this->$property (имя свойства будет переменной, то есть с долларом вначале, мы это проходили в предыдущих уроках).

Давайте сделаем описанный метод __get:

<?php class Test { private $prop1 = 1; private $prop2 = 2; public function __get($property) { return $this->$property; } } ?>

Воспользуемся им для чтения свойств:

<?php $test = new Test; echo $test->prop1; // выведет 1 echo $test->prop2; // выведет 2 ?>

Попытка записать что-то в свойство приведет к ошибке:

<?php $test = new Test; $test->prop1 = 2; // выдаст ошибку ?>

Это именно то, что нам нужно: свойство можно прочитывать, но нельзя записывать.

Попытка прочитать несуществующее свойство выдаст ошибку:

<?php $test = new Test; echo $test->prop3; // выдаст ошибку ?>

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

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

Пусть дан вот такой класс User, свойства которого доступны только для чтения с помощью геттеров:

<?php class User { private $name; private $age; public function __construct($name, $age) { $this->name = $name; $this->age = $age; } public function getName() { return $this->name; } public function getAge() { return $this->age; } } ?>

Переделайте код этого класса так, чтобы вместо геттеров использовался магический метод

__get.

Несуществующее свойство

В примере выше мы применяли магию метода __get для отлавливания обращения к приватным свойствам. На самом деле этот метод также может быть полезен для отлавливания обращений к несуществующим свойствам.

Посмотрим на практическом примере. Пусть у нас есть класс User с фамилией, именем и отчеством, являющимися публичными свойствами:

<?php class User { public $surname; public $name; public $patronymic; } $user = new User; $user->surname = 'Иванов'; $user->name = 'Иван'; $user->patronymic = 'Иванович'; ?>

Давайте сделаем так, чтобы объект класса вел себя так, будто у него также есть свойство fullname, выводящее ФИО юзера:

<?php $user = new User; $user->surname = 'Иванов'; $user->name = 'Иван'; $user->patronymic = 'Иванович'; // Выведет 'Иванов Иван Иванович': echo $user->fullname; // это пока не работает, является нашей целью ?>

Используем для этого наш магический метод __get:

<?php class User { public $surname; public $name; public $patronymic; // Используем метод-перехватчик: public function __get($property) { // Если идет обращение к свойству fullname: if ($property == 'fullname') { return $this->surname . ' ' . $this->name . ' ' . $this->patronymic; } } } ?>

Проверим:

<?php $user = new User; $user->surname = 'Иванов'; $user->name = 'Иван'; $user->patronymic = 'Иванович'; echo $user->fullname; // выведет 'Иванов Иван Иванович' ?>

Получается, что с помощью __get мы создали в классе виртуальное свойство: в классе его нет, но прочитать его можно.

Кстати, записать в такое свойство будет нельзя, так как в реальности его не существует в нашем классе. То есть это свойство только для чтения.

Сделайте класс Date с публичными свойствами year, month и day.

С помощью магии сделайте свойство weekDay, которое будет возвращать день недели, соответствующий дате.

несоответствие }

В приведенном выше фрагменте метод SuperFoo::process

имеет несовпадающую подпись с его родительским Foo . Это явное нарушение принципа подстановки Лискова (LSP), но PHP выдает только предупреждение:

 Предупреждение: объявление SuperFoo::process(array $items): массив должен быть совместим с Foo::process(stdClass $item): массив в ... в строке ... 

В PHP 8 такие несоответствия подписи приводят к фатальной ошибке .

 Неустранимая ошибка: Объявление SuperFoo::process(array $items): массив должен быть совместим с Foo::process(stdClass $item): array in ... on line ... 

Обратите внимание, что эта фатальная ошибка возникает только в том случае, если она нарушает LSP. Изменение типа сигнатур метода разрешено, если оно соответствует LSP.

До версии PHP 8 PHP не применял проверку подписи при расширении типаж-метода. В PHP 8.0 и более поздних версиях методы abtract с несовпадающими сигнатурами завершатся фатальной ошибкой.

 черта Фу {
    абстрактная публичная функция inParams (stdClass $ item): массив;
    абстрактная публичная функция inReturn (stdClass $ item): int;
}
класс SuperFoo{
    использовать Фу;
    публичная функция inParams (массив $items): массив {}
    // ^^^^^ Несоответствие
    публичная функция inReturn (stdClass $ item): int {}
    // ^^^ Несоответствие
} 

В PHP 8 фрагмент выше приведет к фатальной ошибке:

 Фатальная ошибка: Объявление SuperFoo::inParams(array $items): массив должен быть совместим с Foo::inParams(stdClass $item): массив в . .. в сети ... 

Обратите внимание, что эта фатальная ошибка возникает только в том случае, если она нарушает LSP. Изменение типа сигнатур метода разрешено, если оно соответствует LSP.

Это изменение, наконец, позволит PHP 8 активно применять проверки подписи для всех шаблонов экстентов/реализаций:

В следующей таблице показано, как версии PHP до 8.0 принудительно применяли несоответствие подписи и как это меняется в PHP 8

PHP <8 PHP >=8
класс реализует интерфейс : параметры метода Фатальная ошибка Фатальная ошибка
класс реализует интерфейс : возвращаемый тип Фатальная ошибка Фатальная ошибка
класс расширяет абстрактный метод: параметры метода Фатальная ошибка Фатальная ошибка
класс расширяет абстрактный метод: возвращаемый тип Фатальная ошибка Фатальная ошибка
класс расширяет класс : параметры метода Предупреждение Фатальная ошибка
класс расширяет класс : Тип возврата метода Фатальная ошибка Фатальная ошибка
Черта Использование и расширение: Параметры метода нет нет
Черта Использование и расширение: Тип возврата метода нет нет
Черта Использование и реализация: Аннотация Параметры метода нет Фатальная ошибка
Черта использовать и реализовать: абстрактный Тип возврата метода нет Фатальная ошибка

Влияние на обратную совместимость

Если у вас были классы, расширяющие родительский класс с несовпадающими сигнатурами, теперь они завершатся с фатальной ошибкой. До версии PHP 8 эти ошибки вызывали предупреждение.

Если ваш код PHP 7 не вызывает таких предупреждений, вы сможете без проблем обновиться до PHP 8. Исправление кода для PHP 8 также устранит предупреждение в PHP 7.

abtract несовпадение методов типажа не выдавало никаких предупреждений в любых версиях PHP до PHP 8, и в PHP они завершатся с фатальной ошибкой. Как указано в обсуждении на Reddit, это изменение может привести к неожиданным ошибкам, которые в настоящее время не обнаруживаются никакими инструментами анализа кода, поскольку это технически разрешено.

Symfony, например, использовал этот шаблон, который нужно было исправить.


Фатальные ошибки метода класса

Обсуждение RFC Реализация

абстрактный типичный метод фатальные ошибки

Реализация обсуждения RFC

Введите подсказку в параметрах функций PHP и возвращаемых значениях, свойствах и константах | mlocati

Function parameters and return types:

4499 9 9 9 9 9 7 ( (9 7 (9 (7,2

array

function foo(array $bar) PHP 5. 1+ example
function foo(array $bar = ноль) PHP 5.1+ example
function foo(?array $bar) PHP 7.1+ example
function foo(): array PHP 7.0+ example
Функция Foo ():? Array PHP 7,1+ Пример

BOOL

Функция FOO (BOOL $ BAR)
FUNCAT0061
Функция FOO (Bool $ Bar = NULL) PHP 7,0+ Пример
FUNFOU Функция Foo (): Bool Php 7,0+ Пример
Функция Foo ():? Bool PHP 7,1994444444444445659565956595659565956595659556566666664.0054 function foo(callable $bar) PHP 5.4+ example
function foo(callable $bar = null) PHP 5. 4+ example
function foo(? Callable $ Bar) PHP 7,1+ Пример
Функция FOO (): Callable PHP 7,05 Пример
9007.0054 PHP 7.1+ example

float

function foo(float $bar) PHP 7.0+ example
function foo(float $bar = null) PHP 7,0+ Пример
Функция Foo (? Float $ Bar) PHP 7,1+ Пример
Функция FOO (): Float 9008
Funct0061 example
function foo(): ?float PHP 7.1+ example

int

function foo(int $bar) PHP 7. 0+ Пример
Функция Foo (int $ bar = null) PHP 7,0+ Пример
Функция Foo (? Int $ Bar) Php 7.1154 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 9001 0061
function foo(): int PHP 7.0+ example
function foo(): ?int PHP 7.1+ example

iterable

Функция FOO (итерабильный $ Bar) PHP 7,1+ Пример
Функция Foo (Итерабильный $ Bar = NULL) PHP 7.1 Пример) PHP 7.1. Пример) 9.1. 0044 function foo(?iterable $bar) PHP 7.1+ example
function foo(): iterable PHP 7.1+ example
function foo(): ?iterable PHP 7.1+ example

object

function foo(object $bar) PHP 7.2+ example
Функция Foo (Object $ bar = null) PHP 7,2+ Пример
Функция Foo (Object $ Bar) PHP 7,2 Пример
. : object PHP 7.2+ example
function foo(): ?object PHP 7. 2+ example

self

function foo(self $bar ) PHP 5,0+ Пример
Функция Foo (Self $ Bar = NULL) PHP 5,19 Пример
Функция Foo Foo Foo) 9008 44444444444 Function foo (? + example
function foo(): self PHP 7.0+ example
function foo(): ?self PHP 7.1+ example

Строка

Функция Foo (String $ Bar) PHP 7,0+ Пример
FUNCTION FOOD (String $ BAR = NULL) 9008

1944444 FUNCTION FOOD (String $ BAR = NULL) 9008

1
449.00007 FUNCAT
function foo(?string $bar) PHP 7. 1+ example
function foo(): string PHP 7.0+ example
function foo(): ?строка PHP 7.1+ example

class names

function foo(ClassName $bar) PHP 5.0+ example
function foo(ClassName $bar = NULL) PHP 5.1+ Пример
Функция FOO (? ClassName $ Bar) PHP 7.1 Пример
9007 Функция Foo Foo ():).0008 PHP 7.0+ example
function foo(): ?ClassName PHP 7.1+ example

void

function foo(): void PHP 7.1+ пример

логическое, двойное, целое, ресурсное, статическое

Никогда не признавался допустимым для подсказки типа пример 1 9208480848 пример 3

Константы класса

видимость

const NAME = '. ..'; PHP 5.0+ пример
private const NAME = '...'; PHP 7.1+ пример
protected const NAME = '...'; PHP 7.1+ пример
public const NAME = '...'; PHP 7.1+ пример

типы

const string NAME = '...'; Никогда Пример

Класс.

PHP 4.3+ пример private $name = '...'; PHP 5.0+ пример protected $name = '...'; PHP 5.0+ пример public $name = '...'; PHP 5.0+ пример

типы

общедоступная строка $name = '.