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

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

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

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

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

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

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

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

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

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

Начать->

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

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

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

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

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

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

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

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

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

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

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

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


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

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

Анонимные функции очень активно используются в ООП наряду с функциями обратного вызова (так называемые callback-функции). Ну что ж, давайте обратимся к примерам.

Для начала определим два класса.


class Product
{
    public $name;
    public $price;

    function __construct( $name, $price )
	{
        $this->name = $name;
        $this->price = $price;
    }
}

class ProcessSale
{
    private $callbacks;

    function registerCallback( $callback )
	{
        if ( ! is_callable( $callback ) )
		{
            throw new Exception( "callback not callable" );
        }

        $this->callbacks[] = $callback;
    }

    function sale( $product )
	{
        print "{$product->name}: processing <br>";

        foreach ( $this->callbacks as $callback )
		{
            call_user_func( $callback, $product );
        }
    }
}
?>

Данный код предназначен для запуска нескольких функций обратного вызова. В классе Product просто сохраняются значения свойств $name и $price. Для упрощения конструкции они объявлены открытыми, хотя в боевом проекте следует сделать их закрытыми (private) или защищенными (protected) и создать для этих свойств методы доступа.

В классе ProcessSale определены два метода. Методу registerCallback() передается обычная скалярная переменная безо всяких уточнений. После ее проверки, она добавляется в массив функций обратного вызова $callbacks. Процесс тестирования при этом выполняется с помощью встроенной функции is_callable().

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

Методу sale() передается объект типа Product. Метод выводит об этом информацию и затем в цикле выполняет перебор элементов массива $callback.

Каждый элемент вместе с объектом типа Product передается функции call_user_func(), которая, собственно, и вызывает код, написанный пользователем.

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

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

Представьте себе, что через какое-то время некто, дорабатывающий ваш класс ProcessSale захочет создать журнал продаж. Если у человека есть доступ к исходному коду класса, он может просто дописать код для фиксации фактов сделок непосредственно в метод sale(). Но это не всегда будет удачным решением.

Если этот пользователь (вносящий изменения) не является владельцем пакета, в котором определен класс ProcessSale, то все его исправления будут затерты, когда выйдет его обновленная версия. Но даже если с этим моментом все хорошо, все равно, добавлять в метод sale() дополнительные ветки кода, решающие некие "случайные" задачи, неразумно.

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

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

Давайте посмотрим на код функции обратного вызова, которая фиксирует все сделки в журнале продаж.


$logger = create_function('$product', 'print " Записываем ... ({$product->name})<br>";');

$processor = new ProcessSale();
$processor->registerCallback($logger);

$processor->sale(new Product("Туфли",6));
print "<br>";
$processor->sale (new Product ("Кофе",6));

Здесь для создания функции обратного вызова мы воспользовались функцией create_function(). Как видите, ей передаются два параметра. Сначала указывается список параметров функции, а затем - тело самой функции.

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

Вместо этого ссылка на вновь созданную функцию сохраняется в переменной, которую затем можно передать в качестве параметра другим функциям и методам. Как раз это мы и делаем в коде, приведенном выше: мы сохраняем ссылку на анонимную функцию в переменной $logger, которую затем передаем в качестве параметра методу ProcessSale::registerCallback().

В конце этого фрагмента кода мы создаем два объекта, описывающих товары, и передаем их по очереди методу sale(). О том, что произойдет дальше, вы уже, наверное, догадались: каждая сделка по продаже будет обработана (в нашем случае просто будет выведено сообщение о товаре), после чего произойдет вызов всех прописанных callback-функций.


Туфли: обрабатывается...
Записываем... (Туфли)

Кофе: обрабатывается
Записываем... (Кофе)

Давайте еще раз проанализируем пример кода с функцией create_function(). Она же выглядит просто кошмарно, заметили?))

При помещении исполняемого кода в строку всегда возникает головная боль. Для начала нам нужно выполнить экранирование всех символов "$" и "?", которые встречаются в теле скрипта. Более того, по мере роста тела функции (а почему-то это происходит всегда!), ее будет все труднее проанализировать и понять.

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


$logger2 = function($product)
{
	print " Записываем ({$product->name})<br>";
};

$processor = new ProcessSale();
$processor->registerCallback($logger2);

$processor->sale(new Product("Туфли",6));
print "<br>";
$processor->sale (new Product ("Кофе",6));

Как видите, отличие здесь заключается в способе создания переменной, ссылающейся на анонимную функцию. Очевидно, что этот вариант кода не только понятнее, но и проще. Мы просто указываем в операторе присвоения ключевое слово function и не задаем имя функции.

При этом обратите внимание, что поскольку в операторе присваивания используется встроенная функция, в конце блока нужно обязательно поместить точку с запятой.

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


class Mailer
{
	function doMail ($product)
	{
		print " Упаковываем ({$product->name})<br>";
	}
}

$processor = new ProcessSale();
$processor->registerCallback(array (new Mailer(), "doMail"));

$processor->sale(new Product("Туфли",6));
print "<br>";
$processor->sale (new Product ("Кофе",6));

В коде выше мы создаем новый класс Mailer, содержащий метод doMail(). Данному методы мы передаем объект типа Product, о котором метод выводит сообщение.

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

Помните, что в методе registerCallback() с помощью функции is_callable() выполняется проверка типа передаваемого аргумента? Отлично. Что интересно, эта функция достаточно интеллектуальна, чтобы распознать массивы подобного вида. Поэтому при указании функции обратного вызова в виде массива, в первом элементе такого массива должен находиться объект, содержащий вызываемый метод, а имя этого метода помещается в виде строки во второй элемент массива.

Таким образом, мы успешно проходим проверку типа аргумента, а наш код при отработке даст следующий результат:


Туфли: обрабатывается
Упаковываем (Туфли)

Кофе: обрабатывается
Упаковываем (Кофе)

Разумеется, что анонимную функцию можно вернуть из метода, как показано ниже.


class Totalizer
{
	static function warnAmount()
	{
		return function ($product)
		{
			if ($product->price > 5)
			{
				print " покупается дорогой товар: {$product->price}<br>";
			}
		};
	}
}

$processor = new ProcessSale();
$processor->registerCallback(Totalizer::warnAmount());

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

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

Предположим, мы хотим, чтобы метод warnAmount() выполнял следующее:

1. Чтобы ему можно было передавать пороговое значение стоимости товаров.
2. Чтобы он подсчитывал стоимость (т.е. сумму цен) проданного товара.
3. И, когда стоимость товара превысит некий порог, программа должна выполнить некое действие (для упрощения конструкции в нашем случае это не более, чем вывод сообщения)

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


class Totalizer
{
	static function warnAmount($amt)
	{
		$count = 0;

		return function ($product) use ($amt, &$count)
		{
			$count += $product->price;
			print " сумма: $count<br>";

			if ($count > $amt)
			{
				print " Продано товаров на сумму: {$count}<br>";
			}
		};
	}
}

$processor = new ProcessSale();
$processor->registerCallback(Totalizer::warnAmount(8));

$processor->sale(new Product("Туфли",6));
print "<br>";
$processor->sale (new Product ("Кофе",6));


В директиве use анонимной функции, которая возвращается методом Totalizer::warnAmount(), указаны две переменные. Первая из них - $amt, которая является аргументом, переданным методу warnAmount().

Вторая - замкнутая переменная $count. Она объявлена в теле метода warnAmount(), и начальное ее значение равно нулю. Обратите внимание на то, что перед именем переменой $count в директиве use указан символ амперсанда - "&".

Это значит, что данная переменная будет передаваться в анонимную функцию по ссылке, а не по значению. Дело в том, что в теле анонимной функции мы добавим к ней цену товар и затем сравним новую сумму со значением переменной $amt. Если будет достигнуто пороговое значение, выводится соответствующее сообщение, как показано ниже.


Туфли: обрабатывается
Упаковываем (Туфли)

Кофе: обрабатывается
Упаковываем (Кофе)

Продано товаров на сумму: 12

В этом примере было показано, что значение переменной $count сохраняется между вызовами функции обратного вызова. Обе переменые и $count, и $amt, остаются связанными с этой функцией, поскольку они указаны в контексте ее объявления и перечислены в директиве use.

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Наверх