Объектно-ориентированное программирование в PHP. Методы-перехватчики
В PHP предусмотрены встроенные методы-перехватчики, их еще называют «магические методы», которые позволяют перехватывать сообщения посланные неопределенным методам или свойствам. Как и в случае метода __construct(), вызов этих методов происходит неявно, когда выполняются соответствующие условия, а названия этих методов начинаются с двух символов подчеркивания.
Что же это за методы такие?
- __get($property) - вызывается при обращении к неопределенному свойству
- __set($property,$value) - вызывается, когда неопределенному свойству присваивается значение
- __unset($property) - вызывается, когда функция unset() вызывается для неопределенного свойства
- __isset($property) - вызывается, когда функция isset() вызывается для неопределенного свойства
- __call($method,$arg array) - вызывается при обращении к неопределенному методу
Методы __get() и __set() предназначены для работы со свойствами, которые не были объявлены в классе или его родителе.
Метод __get () вызывается, когда клиентский код пытается прочитать не объявленное свойство. Он вызывается автоматически с одним строковым аргументом, содержащим имя свойства, к которому клиентский код пытается получить доступ.
Все, что вернет метод __get(), будет отослано обратно клиенту, как будто искомое свойство существует с этим значением. Рассмотрим короткий пример.
Class Person{ function __get($property){ $method = 'get'.$ property; if(method_exists($this,$method)){ return $this->$method(); } } function getname(){ return 'Иван'; } function getage(){ return '26'; } }
Когда клиентский код пытается получить доступ к неопределенному свойству, вызывается метод __get(), в котором перед именем переданного ему свойства добавляется строка «get». Затем полученная строка, содержащая новое имя метода, передается функции method_exists(). Этой функции передается также ссылка на текущий объект, для которого проверяется существование метода. Если метод существует, мы вызываем его и передаем возвращенное им значение клиентскому коду. Поэтому если клиентский код запрашивает свойство $name:
$p = new Person(); echo $p->name;
то метод getname() вызывается неявно и выводится строка «Иван». Если же метод не существует, то ничего не происходит. Свойству, к которому пользователь пытается обратиться, присваивается значение NULL.
Метод __isset() работает аналогично методу __get(). Он вызывается после того, как клиентский код вызывает функция isset () для неопределенного свойства. Рассмотрим пример расширения класса Person.
function __isset($property){ $method = 'get'.$property; return method_exists($this,$method); }
А теперь предусмотрительный пользователь может проверить свойство, прежде чем работать с ним.
if ( isset ( $p->name ) } { print $p->name; }
Метод __set() вызывается, когда клиентский код пытается присвоить значение неопределенному свойству. При этом передается два аргумента: имя свойства и значение, которое клиентский код пытается присвоить. Затем вы можете решить, как работать с этими аргументами. Давайте продолжим расширение класса Person.
Class Person{ private $_name; private $_age; function __set($property, $value){ $method = 'set'.$property; if(method_exists($this, $method)){ return $this->$method($value); } } function setname($value){ $this->_name = $value; if(!is_null($value)){ $this->_name = strtoupper($this->_name); } } function setage($value){ $this->_age = $value; } }
В этом примере мы работаем с методами-установщиками (setter), а не с методами-получателями (getter). Если пользователь попытается присвоить значение неопределенному свойству, то методу __set() будет передано имя этого свойства и присваиваемое ему значение. В методе __set() проверяется, существует ли указанный метод, и если да, то он вызывается. В результате мы можем отфильтровать присваиваемое свойству значение.
Итак, если мы создаем объект типа Person, а затем пытаемся установить свойство Person:: $name, то вызывается метод __set(), потому что в этом классе не определено свойство $name. Методу передается две строки, содержащие имя свойства и значение, которое мы хотим установить. И уже от нас зависит, что мы сделаем с этой информацией. В данном примере мы создаем новое имя метода, добавив перед именем свойства строку «set». Программа находит метод setname() и запускает его должным образом. Он преобразует входящую строку в символы верхнего регистра и сохраняет ее в реальном свойстве.
setLocale(LC_ALL, «ru_RU.CP1251″); $р = new Person(); $p->name = "Иван"; //Реальному свойству $_name присваивается строка "Иван"
Как и можно было ожидать, метод __unset () является зеркальным отражением метода __set (). Он вызывается в случае, когда функции unset () передается имя неопределенного свойства. Имя этого свойства и передается методу __unset ().
С полученной информацией можно делать все что угодно. В приведенном ниже примере значение null передается найденному результирующему методу тем же самым способом, который мы использовали при рассмотрении метода __set().
function __unset($property, $value){ $method = 'set'.$property; if(method_exists($this, $method)){ return $this->$method(NULL); } }
Метод __call(), вероятно, — самый полезный из всех методов-перехватчиков.
Он вызывается, когда клиентский код обращается к неопределенно методу. При этом методу __call () передается имя несуществующего метода и массив, в котором содержатся все аргументы, переданные клиентом. Значение, возвращаемое методом __call (), передается клиенту так, как будто оно было возвращено вызванным несуществующим методом.
Метод __call () может использоваться для делегирования. Делегирование — это механизм, посредством которого один объект может вызвать метод другого объекта. Это чем-то напоминает наследование, когда дочерний класс вызывает метод, реализованный в родительском классе. В случае наследования взаимосвязь между родительским и дочерним классами фиксирована. Поэтому возможность изменить объект-получатель во время выполнения программы означает, что делегирование является более гибким, чем наследование. Чтобы лучше это понять, давайте проиллюстрируем все на примере. Рассмотрим простой класс, предназначенный для форматирования информации, полученной от класса Person,
Class PersonWriter{ function writeName(Person $p){ echo $p->getName()."\n"; } function writeAge(Person $p){ echo $p->getAge()."\n"; } }
Конечно, мы можем создать подкласс от этого класса, чтобы выводить данные о классе Person различными способами. Вот реализация класса Person, в которой используются и объект PersonWriter, и метод __call ().
class Person { private $writer; function __ construct( PersonWriter $writer ) { $this->writer = $writer; } function __ call( $methodname, $args ) { if ( method_exists( $this->writer, $methodname ) ) { return $this->writer->$methodname( $this ); } } function getName { return "Иван"; } function getAge { return 44; } }
Здесь конструктору класса Person в качестве аргумента передается объект типа PersonWriter, который сохраняется в переменной свойства. В методе __call() используется значение аргумента $methodname и проверяется наличие метода с таким же именем в объекте PersonWriter, ссылка на который была сохранена в конструкторе. Если такой метод найден, его вызов делегируется объекту PersonWriter. При этом методу передается ссылка на текущий экземпляр объекта типа Person, которая хранится в псевдопеременной $this. Поэтому если клиент вызовет несуществующий в классе Person метод
$person = new Person( new PersonWriter() ); $person->writeName();
то будет вызван метод __call (). В нем определяется, что в объекте типа PersonWriter существует метод с именем writeName, который и вызывается. Это позволяет избежать вызова делегированного метода вручную, как показано ниже.
function writeName() { $this->writer->writeName( $this ); }
Таким образом класс Person, как по волшебству, получил два новых метода класса PersonWriter. Хотя автоматическое делегирование избавляет вас от рутинной работы по однотипному кодированию вызовов методов, сам код становится труден для понимания. И если в вашей программе активно используется делегирование, то для внешнего мира создается динамический интерфейс, который не поддается рефлексии (исследованию аспектов класса во время выполнения программы) и не всегда с первого взгляда понятен программисту клиентского кода. Причина в том, что логика, лежащая в основе взаимодействия между делегирующим классом и целевым объектом, может быть непонятной. Ведь она скрыта в таких методах, как __call (), а не явно задана отношениями наследования или уточнениями типа аргументов методов. Методы-перехватчики имеют свою область применения, но использовать их следует с осторожностью. И в классах, которые применяют эти методы, факт такого применения необходимо очень четко и ясно документировать.
Таким образом в этой статье Вы изучили основные методы-перехватчики в ООП на PHP и узнали, как их можно использовать на практике
-
Комментарии (1)
- Сайт