- PVSM.RU - https://www.pvsm.ru -
Несколько недель назад я работала над проблемой [1] в ProxyManager [2]. Проблема была проста: ReflectionClass [3] и ReflectionProperty [3] очень, очень, и ооочень медленные!
Причиной этого исследования является моя попытка оптимизацировать "hydrator [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));
К сожалению, мы получим [5] фатальную ошибку в $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));
Удача [6]!
Я сделала простой бенчмарк для 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);
Рабочий пример [7].
У нас есть доступ к любому свойству, в любом месте, и даже по ссылке. Ура! Мы нарушили правила еще раз!
В очередной раз PHP показал себя с хорошей и плохой стороны одновременно. Это ужасный язык, с ужасным синтаксисом, но он позволяет нам обходить любые языковые ограничения радуя нас новой функциональностью с каждым релизом.
Я не буду использовать эту технику сама, но если мне потребуется получить приватное свойство объекта, да еще и по ссылке, я сделаю это именно так.
Дисклеймер: используйте данную возможность с осторожностью!
Автор: hell0w0rd
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/38751
Ссылки в тексте:
[1] проблемой: https://github.com/Ocramius/ProxyManager/issues/62
[2] ProxyManager: https://github.com/Ocramius/ProxyManager
[3] ReflectionClass: http://php.net/manual/en/class.reflectionproperty.php
[4] hydrator: http://framework.zend.com/manual/2.2/en/modules/zend.stdlib.hydrator.html
[5] мы получим: http://3v4l.org/ET06l
[6] Удача: http://3v4l.org/2E2mr
[7] Рабочий пример: http://3v4l.org/JE0eX
[8] Источник: http://habrahabr.ru/post/186718/
Нажмите здесь для печати.