Опасность копирования / вставки эхо-камер

Эхо-камеры как функция преднамеренного незнания

Эхо-камеры - частая ошибка разработчиков при изучении новых навыков или внедрении незнакомых систем. То, что начинается с простого вопроса, приводит к набору высоко оцененных знаний, полностью готовых для копирования в ваш проект - отличная вещь для продуктивности (если вы не против ошибиться).

Недавно я написал статью, в которой обсуждаются некоторые открытые риски безопасности, с которыми сталкиваются новые установки Apache HTTP Server. Одна вещь, которую я намеренно упустил, - это осуждение использования ETags, которые вы можете найти во множестве других недавних руководств по обеспечению безопасности Apache. Почему?

Потому что ETags не были проблемой для Apache с версий 1.3.22 по 1.3.27 и затрагивали только BSD. Основная проблема с утечкой Inode была решена путем фундаментального изменения способа реализации алгоритма много лет назад.

И все же многие современные руководства по-прежнему рекомендуют отключать ETags или удалять Inode из метода расчета. Не потому, что они правы, а потому, что этот меметический кусок ETags плохие вошел в спиралевидную эхо-камеру группового мышления по вопросам безопасности.

Давайте выберем PHP (это так просто)

Имея в виду этот пример эхо-камер, давайте проследим путь разработчика, решившего реализовать шаблон Singleton в PHP.

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

Возможно, мы думаем, как неопытный разработчик PHP, или, может быть, даже опытные системные программисты, которым нужно быстро создать веб-проект.

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

Имея это в виду, давайте Google:

Потрясающие! Множество ссылок, конечно, правильный ответ где-то здесь.

PHP-синглтон: правильный путь?

Давайте посмотрим на главный результат: Шаблоны проектирования - PHP: правильный путь

Сразу же нам представлена ​​очень чистая реализация, включая несколько красиво отформатированных комментариев PHPDoc.

Прокручивая страницу автора, мы находим отличное описание реализации класса Singleton, включая обоснование увязки __clone(), __wakeup() и __construct().

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

Вот быстрая реализация, скопированная / вставленная из приведенного выше примера, без комментариев для удобства чтения.

class Singleton
{
    private static $instance;
    public static function getInstance()
    {
        if (null === static::$instance) {
            static::$instance = new static();
        }
        return static::$instance;
    }
    protected function __construct() {}
    protected function __clone() {}
    protected function __wakeup() {}
}

Теперь мне просто нужны дочерние классы для моих обработчиков базы данных и конфигурации:

class Config extends Singleton
{
    public $config;
    public function setConfig($json)
    {
        $this->config = json_decode($json);
    }
}
class Database extends Singleton
{
    public $handler;
    protected function __construct()
    {
        $config = Config::getInstance();
        $this->handler = new PDO($config->dsn, 
                                 $config->user,
                                 $config->pass);
    }
}
/* Some dirty tests */
// Fill the config with some JSON
$config = Config::getInstance();
$config->setConfig(
'{"dsn":"mysql:host=localhost;dbname=test","user":"test","pass":"test"}'
);
// Init the db connection (which depends on the Config singleton)
$db = Database::getInstance();
// Query a made up table
$sth = $db->handler->prepare("SELECT * FROM testtable");
$sth->execute();

Примечание: все вышеперечисленные классы находятся в одном файле для этой демонстрации, включая само определение класса Singleton.

OK! А теперь давайте просто запустим php test.php, и мы увидим, что ... ах.

PHP Fatal error: Cannot access property Config::$instance in /home/ec2-user/test/test.php on line 8

Строка 8?

Компилятор очень бесполезно говорит о Config::$instance, но сама ошибка содержится в определении класса Singleton, а не в определении класса Config! Ошибку не так-то легко интерпретировать для младших разработчиков.

Конечно, оказывается, что для статической переменной $instance установлено значение private, несмотря на то, что будущий пример кода от автора явно демонстрирует ее способность наследоваться - так много для размышлений о дочерних элементах.

Изменение его на protected решает эту начальную проблему.

В целом отличный пример оптимизированного для SEO контента, но не лучший пример правильных, полезных знаний программирования.

Не пытайтесь быть грубым

Прежде чем идти дальше, я не выбираю специально проект «PHP: The Right Way» или самого автора: вывод здесь состоит в том, что такая простая ошибка из авторитетных источников усиливается эхом. камера, когда другие, часто имея в виду, копируют / вставляют эти отрывки как евангельскую истину, вместо того, чтобы пробовать их сами.

Если этот конкретный пример не был универсальным классом Singleton, предназначенным для наследования (например, класс User, созданный как Singleton, а не расширяющий класс Singleton), то использование of private было бы совершенно верно.

С другой стороны, беглый поиск в Google (постфактум) показывает, что я не единственный, кто когда-либо замечал это. Есть подробный отчет об ошибке (№ 585) за 2015 год, который все еще ждет решения на GitHub для PHP The Right Way's issues with Singletons.

Но подождите, это еще не все!

Давайте познакомимся с основным ответом на этот же вопрос Stack Overflow и не забудем о «расширяемом» классе Singleton.



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

Отличный момент. Если мы используем одну переменную для хранения экземпляра Singleton, то второй подкласс будет указывать на тот же ресурс, что и первый подкласс.

Чтобы доказать это, давайте проверим наш предыдущий код:

PHP Notice: Undefined property: Config:$handler in /home/ec2-user/test/test.php on line 48

PHP Fatal error: Call to a member function prepare() on a non-object in /home/ec2-user/test/test.php on line 48

Конечно, строка 48 на самом деле $sth = $db->handler->prepare("SELECT * FROM testtable");, поэтому мы определенно используем тот же ресурс для Database подкласса, что и Config.

Синглтоны с несколькими подклассами

Решение обманчиво простое: используйте массив для экземпляров классов (по одному экземпляру на класс).

Вот версия PHP5.3, отвечающая нашим требованиям:

abstract class Singleton
{
    private static $instances = array();
    protected function __construct() {}
    public static function getInstance()
    {
        $class = get_called_class();
        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new static();
        }
        return self::$instances[$class];
    }
    private function __clone() {}
    private function __wakeup() {}
} 

Краткая разбивка:

  • abstract потому что вам никогда не нужно создавать экземпляр самого родительского класса Singleton
  • private средство доступа для$instances: полное устранение ранее существовавшей потребности в protected в случае подкласса переменных с одним экземпляром
  • $instances как array для хранения нескольких подклассов (ключ - это имя класса)
  • protected function __construct(), чтобы вы могли предоставить собственный конструктор (для инициализации данных, например, обработчик базы данных), если вам нужно
  • $class в качестве ключа для массива $instances: в PHP7 он может быть заменен встроенным static::class
  • __clone() и __wakeup() привязаны и непригодны для использования

Разрушение эхо-камеры

Иногда в таких случаях проявляются слабые стороны таких инструментов, как Stack Overflow и Google: главным образом, лучшие результаты часто являются наиболее устаревшими, а иногда содержат неполную и фактически неверную информацию.

Что мы можем сделать по этому поводу? Некоторые мысли:

  1. Свяжитесь с автором сообщения Stack Overflow, проголосовавшим за лучший (неполный) ответ, и попросите его пересмотреть: «http://meta.stackexchange.com/questions/93969/is-changing-the-accepted-answer-frowned -на"
  2. Используйте наши учетные записи Stack Overflow, чтобы увеличить количество голосов за более полезный и правильный ответ: http://stackoverflow.com/questions/203336/creating-the-singleton-design-pattern-in-php5#answer-15870364
  3. Сообщите о проблеме автору PHP: The Right Way для исправления из-за высокопоставленных результатов поиска, приводящих к неправильному ответу. Как отмечалось выше, для этого уже отправлена ​​проблема с GitHub, которая осталась нереализованной.
  4. Предоставьте канонический пример в исходном коде, который должным образом документирован с четким вариантом использования.

Следовательно, нет простого ответа. Google и Stack Overflow на самом деле не созданы для принятия канонических, проверенных ответов.

Однако, если поисковик Google присутствует и слушает, у меня наверняка есть кое-какие мысли о том, как мы можем улучшить результаты для программистов!