Для людей, которые читают 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/