Обход Singleton с помощью Reflection Api

199
08 июля 2017, 10:43

Нашел способ обхода стандартной реализации паттена Singletion с помощью php Reflection Api.

Стандартная реализация выглядит примерно так:

class Singleton {
    static private $instance = null;
    private function __construct() { /* ... @return Singleton */ }  // Защищаем от создания через new Singleton
    private function __clone() { /* ... @return Singleton */ }  // Защищаем от создания через клонирование
    private function __wakeup() { /* ... @return Singleton */ }  // Защищаем от создания через unserialize
    static public function getInstance() {
        return 
        self::$instance===null
            ? self::$instance = new static()//new self()
            : self::$instance;
    }
}

 Используя ReflectionClass мы можем создать экземпляр объекта нужного нам класса, в том числе в обход метода __construct()

$ref = new ReflectionClass('className');
$third = $ref->newInstanceWithoutConstructor();

 Вот полный пример, в котором первые 2 объекта Singleton создаются стандартным способом, а третий через Reflection

class Singleton {
    static private $instance = null;
    private function __construct() { /* ... @return Singleton */ }  // Защищаем от создания через new Singleton
    private function __clone() { /* ... @return Singleton */ }  // Защищаем от создания через клонирование
    private function __wakeup() { /* ... @return Singleton */ }  // Защищаем от создания через unserialize
    private $value = "Изначальное значение свойства";
    static public function getInstance() {
        return 
        self::$instance===null
            ? self::$instance = new static()//new self()
            : self::$instance;
    }
    /**
     * обновить значение приватного свойства
     * @param [type] $v [description]
     */
    public function setValue($v) {
        $this->value = $v;
    }
    /**
     * получить значение приватного свойства
     * @return [type] [description]
     */
    public function getValue() {
        return $this->value;
    }
}
$first = Singleton::getInstance();
$first->setValue('Обновленное значение свойства');
var_dump('$first->getValue: ' . $first->getValue());
$second = Singleton::getInstance();
var_dump('$sceond->getValue: ' . $second->getValue());
var_dump('$first===$second: '.(($first===$second)? 'true':'false'));
$ref = new ReflectionClass('Singleton');
$third = $ref->newInstanceWithoutConstructor();
var_dump('$third->getValue: ' . $third->getValue());
var_dump('$third instanceof Singleton: ' . (($third instanceof Singleton)? 'true':'false'));
var_dump('$third===$second: '.(($third===$second)? 'true':'false'));

В итоге мы видим, что третий объект является инстансом класса Singleton, но обладает другими свойствами.

$ php reflection.php 
"$first->getValue: Обновленное значение свойства"
"$sceond->getValue: Обновленное значение свойства"
"$first===$second: true"
"$third->getValue: Изначальное значение свойства"
"$third instanceof Singleton: true"
"$third===$second: false"

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

Answer 1

Паттерны проектирования существуют для облегчения разработки и сопровождения кода, а не для того, чтобы "запретить и не пущать".

Если в какой-то момент разработчик пал столь низко, что решил использовать в рабочем коде рефлексию, то это в большинстве случаев (не рассматриваем особые случаи, типа тестов, фреймворков и т.д.) говорит о больших проблемах. А уж если с помощью рефлексии стали "ломать" синглтон, то это повод очень всерьез задуматься, нужен ли он там.

Что касается мифического "внешнего вмешательства". Использовать рефлексию может только а) человек, который знает, что это такое, б) имеющий доступ к вашему коду и с) если ему действительно понадобилось использовать рефлексию. Как вы заметили, условие б) в 99.99% случаев позволяет обойтись вообще без рефлексии. А остальные 0.01% случаев выпадают на условие с), например для написания экзотических тестов

PS Отвечая на вопрос. Reflection для того и создавался, чтобы можно было ковыряться в кишках объектов и собирать любых франкенштейнов. Не напишите вы синглтон, который будет защищен от рефлексии.

READ ALSO
Проверка на соответствие имени и пароля

Проверка на соответствие имени и пароля

Есть проверка соответствия имени и пароля на phpПринцип такой: если имя есть в файле(хранение имён и паролей в json файле) то проверить соответствует...

225
Работа с клавиатурой в Win Api

Работа с клавиатурой в Win Api

Как через Win Api проверить состояние клавиши: зажата, нажата, отпущена? События использовать для этой цели не могуТолько функции

297