Причиной этого исследования является моя попытка оптимизацировать "hydrator" для работы с большими объемами данных без накладных расходов на инициализацию.
PHP 5.4 выручай!
PHP 5.4 дал нам новое API для замыканий и метод Closure#bind()
.
Closure#bind()
в принципе позволяет получить экземпляр замыкания с областью видимости данного класса, или объекта. Изящно! Вот так можно добавить API к существующим объектам!
Давайте же нарушим инкапсуляцию объектов в соответствии с нашими потребностями.
Методы доступа к приватным свойствам уже объяснялись в документации, но я все равно приведу пример.
Украдем приватное свойство Kitchen#yummy
:
<?php class Kitchen { private $yummy = 'cake'; }
Определим замыкание для получения этого поля:
<?php $sweetsThief = function (Kitchen $kitchen) { return $kitchen->yummy; }
А теперь украдем yummy
из экземпляра Kitchen
:
<?php $kitchen = new Kitchen(); var_dump($sweetsThief($kitchen));
К сожалению, мы получим фатальную ошибку в $sweetsThief
:
Fatal error: Cannot access private property Kitchen::$yummy in [...] on line [...]
Сделаем нашего вора умнее Closure#bind()
:
<?php $kitchen = new Kitchen(); // Closure::bind() на самом деле создает новое замыкание $sweetsThief = Closure::bind($sweetsThief, null, $kitchen); var_dump($sweetsThief($kitchen));
Closure::bind vs Reflection: быстродействие
Я сделала простой бенчмарк для 100000 итераций инициализации:
<?php for ($i = 0; $i < 100000; $i += 1) { $sweetsThief = Closure::bind(function (Kitchen $kitchen) { return $kitchen->yummy; }, null, 'Kitchen'); } <?php for ($i = 0; $i < 100000; $i += 1) { $sweetsThief = new ReflectionProperty('Kitchen', 'yummy'); $sweetsThief->setAccessible(true); }
На только что скомпилированном PHP 5.5 (Ubuntu 13.04 amd64 box), первый тест занял 0.325 секунд, а второй 0.658.
Рефлексия здесь гораздо медленнее.(На 49%)
Но это совсем не интересно, так как никому не потребуется проходить 100000 раз этап инициализации, по крайней мере мне точно. Что на самом деле интересно — так это доступ к приватным свойствам. Я протестировала и это тоже:
<?php $kitchen = new Kitchen(); $sweetsThief = Closure::bind(function (Kitchen $kitchen) { return $kitchen->yummy; }, null, 'Kitchen'); for ($i = 0; $i < 100000; $i += 1) { $sweetsThief($kitchen); } <?php $kitchen = new Kitchen(); $sweetsThief = new ReflectionProperty('Kitchen', 'yummy'); $sweetsThief->setAccessible(true); for ($i = 0; $i < 100000; $i += 1) { $sweetsThief->getValue($kitchen); }
Первый тест занял ~ 0.110 секунд, а второй ~ 0.199!
Это гораздо быстрее рефлексии, впечатляет!(На 55%)
Доступ к приватным свойствам по ссылкам
Есть еще одно преимущество, используя замыкания вместо рефлексии мы можем работать с свойствами объекта по ссылкам!
<?php $sweetsThief = Closure::bind(function & (Kitchen $kitchen) { return $kitchen->yummy; }, null, $kitchen); $cake = & $sweetsThief($kitchen); $cake = 'lie'; var_dump('the cake is a ' . $sweetsThief($kitchen));
Универсальный метод доступа
Учитывая выше сказанное, мы можем написать простую обертку для получения любого свойства любого объекта:
<?php $reader = function & ($object, $property) { $value = & Closure::bind(function & () use ($property) { return $this->$property; }, $object, $object)->__invoke(); return $value; }; $kitchen = new Kitchen(); $cake = & $reader($kitchen, 'cake'); $cake = 'sorry, I ate it!'; var_dump($kitchen);
Рабочий пример.
У нас есть доступ к любому свойству, в любом месте, и даже по ссылке. Ура! Мы нарушили правила еще раз!
Заключение
В очередной раз PHP показал себя с хорошей и плохой стороны одновременно. Это ужасный язык, с ужасным синтаксисом, но он позволяет нам обходить любые языковые ограничения радуя нас новой функциональностью с каждым релизом.
Я не буду использовать эту технику сама, но если мне потребуется получить приватное свойство объекта, да еще и по ссылке, я сделаю это именно так.
Дисклеймер: используйте данную возможность с осторожностью!
ссылка на оригинал статьи http://habrahabr.ru/post/186718/
Добавить комментарий