Опасность копирования / вставки эхо-камер
Эхо-камеры как функция преднамеренного незнания
Эхо-камеры - частая ошибка разработчиков при изучении новых навыков или внедрении незнакомых систем. То, что начинается с простого вопроса, приводит к набору высоко оцененных знаний, полностью готовых для копирования в ваш проект - отличная вещь для продуктивности (если вы не против ошибиться).
Недавно я написал статью, в которой обсуждаются некоторые открытые риски безопасности, с которыми сталкиваются новые установки 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
потому что вам никогда не нужно создавать экземпляр самого родительского класса Singletonprivate
средство доступа для$instances
: полное устранение ранее существовавшей потребности вprotected
в случае подкласса переменных с одним экземпляром$instances
какarray
для хранения нескольких подклассов (ключ - это имя класса)protected function __construct()
, чтобы вы могли предоставить собственный конструктор (для инициализации данных, например, обработчик базы данных), если вам нужно$class
в качестве ключа для массива$instances
: в PHP7 он может быть заменен встроеннымstatic::class
__clone()
и__wakeup()
привязаны и непригодны для использования
Разрушение эхо-камеры
Иногда в таких случаях проявляются слабые стороны таких инструментов, как Stack Overflow и Google: главным образом, лучшие результаты часто являются наиболее устаревшими, а иногда содержат неполную и фактически неверную информацию.
Что мы можем сделать по этому поводу? Некоторые мысли:
- Свяжитесь с автором сообщения Stack Overflow, проголосовавшим за лучший (неполный) ответ, и попросите его пересмотреть: «http://meta.stackexchange.com/questions/93969/is-changing-the-accepted-answer-frowned -на"
- Используйте наши учетные записи Stack Overflow, чтобы увеличить количество голосов за более полезный и правильный ответ: http://stackoverflow.com/questions/203336/creating-the-singleton-design-pattern-in-php5#answer-15870364
- Сообщите о проблеме автору PHP: The Right Way для исправления из-за высокопоставленных результатов поиска, приводящих к неправильному ответу. Как отмечалось выше, для этого уже отправлена проблема с GitHub, которая осталась нереализованной.
- Предоставьте канонический пример в исходном коде, который должным образом документирован с четким вариантом использования.
Следовательно, нет простого ответа. Google и Stack Overflow на самом деле не созданы для принятия канонических, проверенных ответов.
Однако, если поисковик Google присутствует и слушает, у меня наверняка есть кое-какие мысли о том, как мы можем улучшить результаты для программистов!