FunPHP#5: access to private and protected

FunPHP#5: access to private and protected

https://medium.com/@frontman/php-access-to-private-and-protected-b1028b974169


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 4 (если не ошибаюсь). По сути это к вопросу как формируются имена защищенных свойств. Мы можем получить значения так:

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


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

\0ClassName\0property

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

\0*\0property

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

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["\0*\0$p"];
         is_null($r) or $r = @$this->o["\0{$this->c}\0$p"];
         return $r;
      }
   };
}

$pm = new PublicMorozov();

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

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

$obj = (object) [
   "\0stdClass\0foo" => 1,
   "\0*\0bar" => 2,
];

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

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

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


Report Page