...

понедельник, 15 июля 2013 г.

[Перевод] Получаем доступ к приватным свойствам объектов в PHP без рефлексии

Несколько недель назад я работала над проблемой в ProxyManager. Проблема была проста: ReflectionClass и ReflectionProperty очень, очень, и ооочень медленные!

Причиной этого исследования является моя попытка оптимизацировать "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 показал себя с хорошей и плохой стороны одновременно. Это ужасный язык, с ужасным синтаксисом, но он позволяет нам обходить любые языковые ограничения радуя нас новой функциональностью с каждым релизом.

Я не буду использовать эту технику сама, но если мне потребуется получить приватное свойство объекта, да еще и по ссылке, я сделаю это именно так.

Дисклеймер: используйте данную возможность с осторожностью!


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

Отправить комментарий