Reg.ru: домены и хостинг

Крупнейший регистратор и хостинг-провайдер в России.

Более 2 миллионов доменных имен на обслуживании.

Продвижение, почта для домена, решения для бизнеса.

Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

Перейти на сайт->

Бесплатный Курс "Практика HTML5 и CSS3"

Освойте бесплатно пошаговый видеокурс

по основам адаптивной верстки

на HTML5 и CSS3 с полного нуля.

Начать->

Фреймворк Bootstrap: быстрая адаптивная вёрстка

Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.

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

Верстайте на заказ и получайте деньги.

Получить в подарок->

Бесплатный курс "Сайт на WordPress"

Хотите освоить CMS WordPress?

Получите уроки по дизайну и верстке сайта на WordPress.

Научитесь работать с темами и нарезать макет.

Бесплатный видеокурс по рисованию дизайна сайта, его верстке и установке на CMS WordPress!

Получить в подарок->

*Наведите курсор мыши для приостановки прокрутки.


Магические методы в PHP или методы-перехватчики (сеттеры, геттеры и др.)

Вся "магия" данных методов сводится к тому, что они могут перехватывать (отсюда их второе название - методы-перехватчики) сообщения, посланные неопределенным (по сути - несуществующим) методам и свойствам.

Официально такое деяние называется "перегрузкой", но, поскольку в других языках программирования (например, в Java и С++) этот термин обозначает нечто совершенно другое, будем называть это "перехватом". В контексте данной статьи это будет точнее.

В PHP есть ряд встроенных методов-перехватчиков. Их вызов производится неявно, подобно тому, как это происходит с конструктором класса __construct(). Для их вызова должны выполниться определенные условия, а именно:

Метод __get($property) вызывается при обращении к неопределенному свойству.

Метод __set($property, $value) вызывается, когда неопределенному свойству присваивается значение.

Метод __isset($property) вызывается, когда функция isset() вызывается для неопределенного свойства.

Метод __unset($property) вызывается, когда функция unset() вызывается для неопределенного свойства.

Метод __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 44;
    }
}

Когда клиентский код пытается получить доступ к неопределенному свойству, вызывается метод __get(), в котором перед именем преданного ему свойства добавляется строка "get".

Затем полученная строка, содержащая новое имя метода (в нашем случае - getName), передается функции method_exists(), проверяющей факт существования метода. Кроме того, данной функции мы должны передать и ссылку на текущий объект ($this), что мы и делаем в первом параметре.

Если метод существует, мы вызываем его и передаем возвращенное им значение клиентскому коду. Поэтому если клиентский код запрашивает свойство $name


$p = new Person();
print $p->name;

то метод getName() вызывается неявно и на экран выведется следующая строка:


Петр

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

Метод __isset() работает по аналогичному принципу.

Он вызывается после того, как в клиентском коде вызывается функция 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( $name )
    {
        $this->_name = $name;
        if ( ! is_null( $name ) )
	{
            $this->_name = strtoupper($this->_name);
        }
    }

    function setAge( $age )
    {
        $this->_age = $age;
    }
}

В этом примере мы с вами работаем с методами-установщиками (сеттерами), а не с методами-получателями (геттерами).

Если пользователь попытается присвоить значение неопределенному свойству, то методу __set() будет передано имя этого свойства и присваиваемое ему значение.

В методе __set() проверяется, существует ли указанный метод, и, если да, то он вызывается. В результате мы можем отфильтровать присваиваемое свойству значение.

Итак, если мы создаем объект типа Person, а затем пытаемся установить свойство Person::$name, то вызывается метод __set(), потому что в этом классе не определено свойство $name.

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

В данном примере мы создаем новое имя метода, добавив перед именем свойства строку "set".

В итоге интерпретатор находит метод setName() и запускает его должным образом - преобразует входящую строку в символы верхнего регистра и сохраняет ее в реальном свойстве.


$p = new Person();
$p->name = "Петр"; // реальному свойству _$name присваивается строка "ПЕТР"

Метод __unset() является зеркальным отражением метода __set(). Он вызывается в случае, когда функции unset() передается имя неопределенного свойства. Имя этого свойства и передается методу __unset().

С полученной информацией можно делать все, что угодно. В приведенном ниже примере значение NULL передается найденному результирующему методу тем же самым способом, который мы использовали при рассмотрении метода __set().


function __unset( $property )
{
    $method = "set{$property}";

    if ( method_exists( $this, $method ) )
    {
        $this->$method( null );
    }
}

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

Значение, возвращаемое методом __call() передается клиенту так, будто оно было возвращено вызванным несуществующим методом.

Метод __call() может использоваться для так называемого делегирования.

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

В случае наследования взаимосвязь между родительским и дочерним классами фиксирована. Поэтому возможность изменить объект-получатель во время выполнения программы означает, что делегирование является более гибким, чем наследование.

Чтобы лучше это понять, давайте посмотрим на пример кода. Рассмотрим простой класс, предназначенный для форматирования информации, полученной от класса Person.


class PersonWriter
{
    function writeName( Person $p )
    {
        print $p->getName()."\n";
    }

    function writeAge( Person $p )
    {
        print $p->getAge()."\n";
    }
}

Конечно мы могли бы создать подкласс от этого класса, чтобы выводить данные о классе Person различными способами. Ниже представлена реализация класса Person, в которой используется и объект PersonWriter, и метод __call().


class Person
{
    private $writer;

    function __construct( PersonWriter $writer )
    {
        $this->writer = $writer;
    }

    function __call( $method, $args )
    {
        if ( method_exists( $this->writer, $method ) )
	{
            return $this->writer->$method( $this );
        }
    }

    function getName()  { return "Петр"; }
    function getAge() { return 44; }
}

Как мы видим, здесь конструктору класса Person в качестве аргумента передается объект типа PersonWriter, который сохраняется в переменной свойства.

В методе __call() используется значение аргумента $method и проверяется наличие метода с таким же именем в объекте PersonWriter, ссылка на который была сохранена в конструкторе.

Если такой метод найден, его вызов делегируется объекту PersonWriter. При этом методу передается ссылка на текущий экземпляр объекта типа Person, которая хранится в псевдопеременной $this.

Поэтому если произойдет вызов метода, который не существует в классе Person, например:


$person= new Person( new PersonWriter() );
$person->writeName();

то будет вызван метод __call(). В нем определяется, что в объекте типа PersonWriter существует метод с именем writeName(), который и вызывается. Это позволяет избежать вывода делегированного метода вручную, как показано ниже:


function writeName()
{
    $this->writer->writeName($this);
}

Таким образом класс Person благодаря "магии" метода __call() получил два новых метода класса PersonWriter.

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

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

Причина в том, что логика, лежащая в основе взаимодействия между делегирующим классом и целевым объектом, может быть непонятной, ведь она скрыта в таких методах, как __call(), а не явно задана отношениями наследования или уточнениями типа аргументов методов.

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

На этом пока все. Увидимся в следующей статье под названием "Деструктор и копирование объектов с помощью метода __clone()".

Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!


Смотрите также:

PHP: Получение информации об объекте или классе, методах, свойствах и наследовании

PHP: Получение информации об объекте или классе, методах, свойствах и наследовании

CodeIgniter: жив или мертв?

CodeIgniter: жив или мертв?

Функции обратного вызова, анонимные функции и механизм замыканий

Функции обратного вызова, анонимные функции и механизм замыканий

Применение функции к каждому элементу массива

Применение функции к каждому элементу массива

Слияние массивов. Преобразование массива в строку

Слияние массивов. Преобразование массива в строку

Деструктор и копирование объектов с помощью метода __clone()

Деструктор и копирование объектов с помощью метода __clone()

Эволюция веб-разработчика или Почему фреймворк - это хорошо?

Эволюция веб-разработчика или Почему фреймворк - это хорошо?

PHP: Удаление элементов массива

PHP: Удаление элементов массива

Ключевое слово final (завершенные классы и методы в PHP)

Ключевое слово final (завершенные классы и методы в PHP)

50 классных сервисов, программ и сайтов для веб-разработчиков

50 классных сервисов, программ и сайтов для веб-разработчиков

Наверх