Паблик Морозов на собеседовании

PHP protected & private property hacker

На собеседованиях каких вопросов только не встретишь. Матерые волки, собеседуя php-гуру, могут спрашивать разные нетривиальные вещи. Одна из таких вещей: паттерн “Паблик Морозов”.

Паблик Морозов — антипаттерн, позволяющий получить доступ к закрытым полям класса.

Встречаются как-то два программиста. Один — тимлид, другой php-гуру, оба с опытом дохреналет (писали на PHP, когда его еще не было). Начинается беседа, аки битва двух богатырей. Один задает вопросы коварнее другого. Другой парирует и отбивается, как ниндзя.

PHP ninja job interview

Вопрос: бла бла, ООП… Бла бла бла, инкапсуляция, бла бла бла… А можно ли получить значение из private поля экземпляра класса?

Конечно можно, для этого есть Reflection API. Допустим есть класс:

class PublicMorozov {
public $woo = 1;
private $foo = 2;
protected $bar = 3;
public function foo() { return $this->foo; }
}

Чтобы считать значения из инкапсулированных свойств (и даже изменить) мы можем использовать такой код:

$class = new ReflectionClass("PublicMorozov");
$property = $class->getProperty("foo");
$property->setAccessible(true);

$pm = new PublicMorozov();
var_dump( $property->getValue($pm) );
$property->setValue($pm, 456);

var_dump( $pm->foo() );

— Да, все верно, мы можем читать и писать в защищенные свойства через Reflection API. А еще способы знаешь?

— Ммм, ну можно это переписать так:

$pm = new PublicMorozov();

$property = new ReflectionProperty("PublicMorozov", "foo");
$property->setAccessible(true);

var_dump( $property->getValue($pm) );
$property->setValue($pm, 456);

var_dump( $pm->foo() );

— Да это же то же самое, ну немного короче. А что если… А если без использования Reflection API, м?

— Хм, ну есть несколько хаков…

— Не стенсяйся, показыавай!

— Ну мы можем написать кложуру, забиндить ее контекст на экземпляр класса и внутри вызвать значение, после чего вернуть его (значение):

$pm = new PublicMorozov();

$foo = Closure::bind(
function(PublicMorozov $pm){return $pm->foo;},null,$pm)($pm);
var_dump($foo);

— Нормалды, нормалды… Но я бы сократил запись:

$foo = Closure::bind(
function(){return $this->foo;},$pm,'PublicMorozov')($pm)
;
// или даже так
$foo = Closure::bind(function(){return $this->foo;},$pm,$pm)($pm);

Эту же запись можно еще укоротить:

$foo = ((function(){return $this->foo;})->bindTo($pm,$pm))();

— Ух ты! Да, прикольно.

— А поменять значение можем? Опять же без Reflection API.

— В принципе, да. Почему бы и нет. Мы можем вернуть не значение, а ссылку на свойство:

$foo = & Closure::bind(
function & (PublicMorozov $pm){return $pm->foo;},null,$pm)($pm)
;

$foo = 456;

var_dump($foo); // 456
var_dump( $pm->foo() ); // 456

— Круто! Все верно. И опять же я бы сделал это короче:

$foo = &((function & (){return $this->foo;})->bindTo($pm,$pm))();

А еще таким образом можно сделать простую универсальную отмычку для манкипатчинга, используя твой метод:

function & crackprop(object $obj, string $prop) {
return ( Closure::bind
(
function & () use ($prop) { return $this->$prop; }
, $obj, $obj
)
)();
}

$foo = &crackprop($pm, 'foo');
$foo = 456;

var_dump($foo);
var_dump($pm->foo());

Или, опять же, короче, как бы я написал:

function & crackprop(object $obj, string $prop) {
return (
(function & () use ($prop) { return $this->$prop; })
->bindTo($obj, $obj))()
;
}

— Все что выше было проделано для private свойств сработает для protected?

— Да, все то же самое можно проделать и с protected.

— Напоследок по этой теме, чисто по фану, можешь еще назвать способ получить доступ к защищенным свойствам?

— Да, есть еще один способ, я не стал его озвучивать так как это, вроде бы, уже давно всем известный хак, работавший еще с давних версий PHP. По сути это к вопросу как формируются имена защищенных свойств. Мы можем получить значения так:

function _protected(object $obj, string $prop) {
return ((array) $obj)["*$prop"];
}
function _private(object $obj, string $prop) {
return ((array) $obj)["".get_class($obj)."$prop"];
}
$foo = _private($pm, 'foo');
$bar = _protected($pm, 'bar');

Имя приватного свойства формируется как:

ClassNameproperty

а protected имеет формат:

*property

Раз заговорили про фан, то мы можем написать универсальную функцию на этом механизме с удобным способом через чейнинг:

function crack(object $obj) {
return new class ($obj) {
public function __construct(object $obj) {
$this->o = (array) $obj;
$this->c = get_class($obj);
}
public function __get(string $p) {
$r = @$this->o["*$p"];
is_null($r) or $r = @$this->o["{$this->c}$p"];
return $r;
}
};
}

$pm = new PublicMorozov();

var_dump( crack($pm)->foo ); // 2
var_dump( crack($pm)->bar ); // 3

— Но производительность такой красоты сильно проседает, естественно. Кстати, по такой же схеме можно создать экземпляр класса stdClass с приватными и протектекд полями:

$obj = (object) [
"stdClassfoo" => 1,
"*bar" => 2,
];

var_dump($obj);
object(stdClass)#1 (2) {
["foo":"stdClass":private]=>
int(1)
["bar":protected]=>
int(2)
}

— Вообще вы задаете такие интересные вопросы… А чем мне придется тут заниматься?

— Оу, ну у нас все хорошо, новые технологии, модный стек. Просто есть немного легаси кода, который нужно сапортить и, иногда, патчить. Но, судя по твоим ответам, у тебя все получится, даже не сомневайся. Мы делаем тебе оффер 🙂