Для людей, которые читают PHP-файл NEWS, это не удивительно, но для всех, кто этого не делает, возможно, одна из самых больших функций PHP 5.4: поддержка Closure Object вернулась . Для меня это то, что я пропустил больше всего, когда в PHP 5.3 были представлены Closures. Так что я очень рад, что наконец-то здесь (или вернулся).
Я расскажу вам о каменистой дороге, которую имела поддержка связывания объектов замыкания, и покажу вам несколько простых примеров ее использования.
Скалистая дорога
Поддержка Object Binding для Closures была очень сложной, пока она наконец не приземлилась. Я помню, что он присутствовал в некоторых альфа-версиях PHP 5.3, но когда вышел 5.3, он исчез.
После некоторого поиска я обнаружил, что он был исключен из релиза, потому что поведение привязки объектов было не определено. Слава богу, этот RFC появился и убрал проблемы для реализации.
Это было грустно пропущено. Замыкания — отличный способ позволить другим предоставлять свое поведение для аспектов вашей библиотеки. Например, пусть Пользователь предоставит обработчик запроса в виде Closure, как это используется многими Microframeworks. Но без привязки объекта вы не можете дать контексту замыкание. Например, как вы получаете доступ к экземпляру приложения вашего Microframework из закрытия? Да, вы можете использовать ключевое слово use, чтобы импортировать его из переменной в замыкание:
<?php use Silex\Application; $app = new Application; $app->get('/', function() use ($app) { $request = $app['request']; // do something with the request object });
Да, это работает, но если у вас более одного обработчика запросов, вам придется импортировать переменную $ app каждый раз, когда вы определяете замыкание . Это отстой.
Теперь с привязкой объекта Closure Silex Framework может связывать все замыкания, передаваемые как обработчики запросов, с экземпляром приложения. Это может быть достигнуто с помощью метода bindTo замыкания. Возьмите этот пример:
<?php $closure = function() { echo $this->foo; }; $context = new \StdClass; $context->foo = "Hello World"; // rebinds the closure to the $context object $boundClosure = $closure->bindTo($context); $boundClosure(); // outputs "Hello World"
Метод bindTo возвращает новый экземпляр замыкания, в котором $ this привязывается к переданному объекту.
Если бы Silex реализовал это, обработчик запроса мог бы быть переписан следующим образом:
<?php $app->get('/', function() { $request = $this['request']; // do something with the request object });
Это намного лучше. Больше не нужно использовать приложение $ каждый раз, когда мы хотим прикрепить обработчик запроса.
Простой вариант использования
Мне пришло в голову, что это было бы идеально для шаблонизаторов. Например, Zend_View должен изолировать назначенные свойства шаблона от своих собственных свойств, создав подклассы и пометив их как частные. Теперь с помощью привязки объекта Closure это можно сделать, просто включив шаблонный скрипт в замыкание!
I’ve written a small prototype which is available as this Gist. This implements a quite small templating class, which takes a context object and binds it to the template as $this, via closure binding.
The template is able to access the context’s properties and methods then as its own properties and methods — but none of the template engine’s properties and methods are exposed to the template.
Consider the following example:
// heisdead.phtml He is dead <?= $this->name ?>. // test.php <?php $template = new \CHH\Template(__DIR__.'/heisdead.phtml'); $context = (object) array( 'name' => 'Jim' ); echo $template->render($context); // Outputs "He is dead Jim."
Though, for now this doesn’t buy us anything. What’s with Helper Methods?
Helper Methods could then be provided by a default context implementation. The user then would assign additional properties to this context.
Take this example of a (rather inflexible) context with a helper method:
<?php namespace CHH; class DefaultContext { function formatDate($date, $format = 'Y/m/d H:i') { $dateTime = new \DateTime($date); return $dateTime->format($format); } }
Our previous example could then be written as:
// heisdead.phtml He is dead <?= $this->name ?>. He died at <?= $this->formatDate($this->dateTimeOfDeath); ?>. // test.php ... $context = new \CHH\DefaultContext; $context->name = 'Jim'; $context->dateTimeOfDeath = '2011-10-26 12:00:00'; echo $template->render($context); // Outputs "He is dead Jim. He died at 2011/10/26 12:00.
This default context, could then also have some kind of plugin loader to allow extending the context with additional helper methods (think Zend_Loader_PluginBroker of the upcoming ZF2).
Conclusion
Closure object support makes injecting behaviour into classes much more elegant — as they can give you a access to the classes’ properties. It can also simplify templating engines alot, by putting the template inside the closure.
Further Reading
- PHP Manual, Anonymous Functions
- PHP Manual, The Closure Class
- PHP Wiki/RFC, RFC: Closures Object Extension
Source: http://christophh.net/2011/10/26/closure-object-binding-in-php-54/