Статьи

Закрытие привязки объектов в PHP 5.4

Закрытия (лямбды) были очень популярной и полезной языковой функцией с тех пор, как они дебютировали в JavaScript. Теперь Java 8 планирует включить их, а в PHP 5.4 будет привязка объектов Closure. Кристоф Хохштрассер — автор следующего поста в блоге, в котором объясняется, как добавить эту функцию в PHP и как ее использовать.

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

Source: http://christophh.net/2011/10/26/closure-object-binding-in-php-54/