Безопасные альтернативы PHP Globals (хорошая практика кодирования)

В течение многих лет я использовал global $var,$var2,...,$varn для методов в моем приложении. Я использовал их для двух основных реализаций:

Получение уже заданного класса (например, соединение с БД) и передача информации в функции, отображаемые на странице.

Пример:

 $output['header']['log_out'] = "Log Out"; function showPage(){ global $db, $output; $db = ( isset( $db ) ) ? $db : new Database(); $output['header']['title'] = $db->getConfig( 'siteTitle' ); require( 'myHTMLPage.html' ); exit(); } 

Тем не менее, есть последствия для производительности и безопасности для этого.

Какую альтернативную практику я могу использовать, чтобы сохранить мои функциональные возможности, но улучшить дизайн, производительность и / или безопасность?

Это первый вопрос, который я когда-либо задавал в SO, поэтому, если вам нужны пояснения, прокомментируйте!

Solutions Collecting From Web of "Безопасные альтернативы PHP Globals (хорошая практика кодирования)"

Альтернативой называется инъекция зависимостей . В двух словах это означает, что вы передаете данные, которые функция / класс / объект требует в качестве параметров.

 function showPage(Database $db, array &$output) { ... } $output['header']['log_out'] = "Log Out"; $db = new Database; showPage($db, $output); 

Это лучше по ряду причин:

  • localizing / encapsulating / namespacing (тело функции больше не имеет никаких неявных зависимостей для внешнего мира, и наоборот, теперь вы можете переписать любую часть, не переписывая другую, если вызов функции не изменяется)
  • позволяет проводить единичное тестирование, поскольку вы можете тестировать функции отдельно, не требуя установки определенного внешнего мира
  • ясно, что функция будет делать с вашим кодом, просто взглянув на подпись

1. Глобал. Работает как шарм. Глобалы ненавидят, поэтому мои мысли не использовать его.

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

2. Определите константу в файле config.php.

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

3. Включите конфигурационный файл в функцию.

Я бы подумал, что это накладные расходы. Вы сегментируете кодовую базу для небольшого выигрыша. «Глобальный» здесь станет названием файла, который вы вложили в битву.


Взяв эти три мысли о вас и мои комментарии к ним, я бы сказал: если вы не столкнетесь с актуальными проблемами с некоторыми глобальными переменными, вы можете придерживаться их. Затем Global работает как ваш локатор сервисов (конфигурация, база данных). Другие делают гораздо больше, чтобы создать то же самое.

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

Внедрение зависимости

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

Предположим, что массив конфигурации – это зависимость, которую мы хотели бы ввести. Назовем его config и назовите переменную $config . Поскольку это массив, мы можем набрать его как array . в первую очередь, определите конфигурацию в файле include, возможно, вы также можете использовать parse_ini_file если вы предпочитаете формат ini-файла. Я думаю, что это еще быстрее.

config.php :

 <?php /** * configuration file */ return array( 'db_user' => 'root', 'db_pass' => '', ); 

Этот файл может потребоваться только в вашем приложении, где бы вы ни захотели:

 $config = require('/path/to/config.php'); 

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

 class DBLayer { private $config; public function __construct(array $config) { $this->setConfig($config); } public function setConfig(array $config) { $this->config = $config; } public function oneICanNotChange($paramFixed1, $paramFixed2) { $user = $this->config['db_user']; $password = $this->config['db_pass']; $dsn = 'mysql:dbname=testdb;host=127.0.0.1'; try { $dbh = new PDO($dsn, $user, $password); } catch (PDOException $e) { throw new DBLayerException('Connection failed: ' . $e->getMessage()); } ... } 

Этот пример немного грубо, но он имеет два примера инъекции зависимостей. Сначала через конструктор:

 public function __construct(array $config) 

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

Второй пример – использовать общедоступный метод настройки :

 public function setConfig(array $config) 

Это позволяет позже добавить зависимость, но некоторым методам может потребоваться проверить, доступны ли вещи, прежде чем выполнять свою работу. Например, если вы могли бы создать объект DBLayer без предоставления конфигурации, метод oneICanNotChange можно было бы вызывать без того, чтобы этот объект имел конфигурацию и должен был иметь дело с этим (что не показано в этом примере).

Сервисный локатор

Поскольку вам нужно, вероятно, интегрировать код «на лету», и вы хотите, чтобы ваш новый код подвергался тестированию с помощью инъекции зависимостей, и все, что облегчает нашу жизнь, вам, возможно, нужно будет сопоставить его с вашим старым / устаревшим кодом. Я считаю, что эта часть жесткая. Зависимость от инъекции по своему усмотрению довольно проста, но ставить это вместе со старым кодом не так прямо.

Я могу предложить здесь, что вы создаете одну глобальную переменную, которая является так называемым локатором сервисов . Он содержит центральную точку для извлечения объектов (или даже массивов, подобных вашей $config ). Его можно использовать тогда, а контракт – это одноименное имя. Поэтому, чтобы удалить глобальные переменные, мы используем глобальную переменную. Звучит немного контрпродуктивно, и даже если ваш новый код слишком сильно его использует. Тем не менее, вам нужен инструмент для объединения старых и новых. Итак, вот самая обнаженная реализация локатора PHP-сервиса, которую я мог себе представить.

Он состоит из одного объекта Services который предлагает все ваши услуги, например, config сверху. Потому что, когда начинается PHP-скрипт, мы пока не знаем, нужна ли вообще услуга (например, мы не можем запускать какой-либо запрос к базе данных, поэтому нам не нужно создавать экземпляр базы данных), он также предлагает некоторую ленивую функцию инициализации. Это делается с помощью заводских скриптов, которые являются только файлами PHP, которые настраивают службу и возвращают ее.

Первый пример: предположим, что функция oneICanNotChange не была бы частью объекта, а просто простой функцией в глобальном пространстве имен. Мы бы не смогли ввести config зависимость. Здесь находится объект Locator Service Service Locator:

 $services = new Services(array( 'config' => '/path/to/config.php', )); ... function oneICanNotChange($paramFixed1, $paramFixed2) { global $services; $user = $services['config']['db_user']; $password = $services['config']['db_pass']; ... 

Как уже показано в примере, объект Services отображает строку 'config' на путь файла PHP, который определяет массив $config : /path/to/config.php . Он использует интерфейс ArrayAccess чем для того, чтобы открыть эту службу внутри функции oneICanNotChange .

Я предлагаю интерфейс ArrayAccess здесь, потому что он хорошо определен и показывает, что здесь у нас есть динамический характер. С другой стороны, это позволяет нам ленивую инициализацию:

 class Services implements ArrayAccess { private $config; private $services; public function __construct(array $config) { $this->config = $config; } ... public function offsetGet($name) { return @$this->services[$name] ? : $this->services[$name] = require($this->config[$name]); } ... } 

Этот примерный заглушка просто требует заводских скриптов, если он еще не сделал этого, в противном случае вернет возвращаемое значение сценариев, например массив, объект или даже строку (но не NULL которая имеет смысл).

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

Тем не менее, есть последствия для производительности и безопасности для этого.

Чтобы сказать вам правду, нет никакой производительности и последствий для безопасности. Использование globals – это вопрос более чистого кода, и не более того. (Ну, ладно, до тех пор, пока вы не пропустите переменные размером в десятки мегабайт)

Итак, сначала вы должны думать, будут ли альтернативы делать для вас более чистый код, или нет.

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

Один из вариантов, который некоторые люди могут недооценивать, – создать одноэлементный объект, ответственный за сохранение состояния приложения. Когда вы хотите получить доступ к некоторому общему «глобальному» объекту, вы можете сделать такой вызов, как: State::get()->db->query(); или $db = State::get()->db; ,

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

РЕДАКТИРОВАТЬ:

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

 class State { private static $instance; private $_db; public function getDB() { if(!isset($this->_db)){ // or call your database initialization code or set this in some sort of // initialization method for your whole application $this->_db = new Database(); } return $this->_db; } public function getOutput() { // do your output stuff here similar to the db } private function __construct() { } public static function get() { if (!isset(self::$instance)) { $className = __CLASS__; self::$instance = new State; } return self::$instance; } public function __clone() { trigger_error('Clone is not allowed.', E_USER_ERROR); } public function __wakeup() { trigger_error('Unserializing is not allowed.', E_USER_ERROR); } } 

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

 function showPage(){ $output = State::get()->getOutput(); $output['header']['title'] = State::get()->getDB()->getConfig( 'siteTitle' ); require( 'myHTMLPage.html' ); exit(); } 

Альтернативой использованию объекта singleton является передача объекта состояния в различные функции, это позволяет вам иметь альтернативные «состояния», если ваше приложение становится сложным, и вам нужно будет только обходить один объект состояния.

 function showPage($state){ $output = $state->getOutput(); $output['header']['title'] = $state->getDB()->getConfig( 'siteTitle' ); require( 'myHTMLPage.html' ); exit(); } $state = new State; // you'll have to remove all the singleton code in my example. showPage($state); 
 function showPage(&$output, $db = null){ $db = is_null( $db ) ? new Database() : $db; $output['header']['title'] = $db->getConfig( 'siteTitle' ); require( 'myHTMLPage.html' ); exit(); } 

а также

 $output['header']['log_out'] = "Log Out"; showPage($output); $db =new Database(); showPage($output,$db); 

Начните разработку кода в ООП, затем вы можете передать конфигурацию конструктору. Вы также можете инкапсулировать все свои функции в класс.

 <?php class functions{ function __construct($config){ $this->config = $config; } function a(){ //$this->config is available in all these functions/methods } function b(){ $doseSomething = $this->config['someKey']; } ... } $config = array( 'someKey'=>'somevalue' ); $functions = new functions($config); $result = $functions->a(); ?> 

Или, если вы не можете реорганизовать сценарий, пройдите через конфигурационный массив и определите константы.

 foreach($config as $key=>$value){ define($key,$value); }