Прекратите использование `global` в PHP

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

 $config = array(); $config['site_name'] = 'Site Name'; $config['base_path'] = '/home/docs/public_html/'; $config['libraries_path'] = $config['base_path'] . '/libraries'; //etc... 

Тогда у меня есть function.php , который также включен в почти каждую страницу, где мне нужно использовать global $config для доступа к ней – и это то, что я хотел бы избавиться!

Как мне получить доступ к $config в других частях моего кода без использования global ?

Может ли кто-нибудь объяснить, ПОЧЕМУ я не должен использовать global в моем примере? Некоторые говорят, что это плохой тон, другие говорят, что это не безопасно?

ИЗМЕНИТЬ 1:

Пример того, где и как я его использую:

 function conversion($Exec, $Param = array(), $Log = '') { global $config; $cmd = $config['phppath'] . ' ' . $config['base_path'] . '/' . $Exec; foreach ($Param as $s) { $cmd .= ' ' . $s; } } 

EDIT 2:

Поместить все это в класс, как было предложено Vilx , было бы круто, но в этом случае, как бы связать его со следующим циклом, который извлекает key конфигурации и value из базы данных.
Я упростил идею назначения $config массива, вот пример:

 $sql = "SELECT * from settings"; $rsc = $db->Execute($sql); if ( $rsc ) { while(!$rsc->EOF) { $field = $rsc->fields['setting_options']; $config[$field] = $rsc->fields['setting_values']; @$rsc->MoveNext(); } } 

ИЗМЕНИТЬ 3:

Кроме того, я должен получить доступ к другим vars из функций, которые установлены в config, и их немного, например: $db , $language и т. Д.

Если я положу их в класс, он действительно решит что-нибудь? Если я использую global что это действительно изменит?

EDIT 4:

Я читаю PHP глобальный в функциях, где Гордон объясняет очень красиво, почему вы не должны использовать global . Я согласен на все, но я не использую global в моем случае, чтобы переназначить переменные, что приведет, как он сказал, <-- WTF!! ,;)) Да, согласитесь, это безумие. Но если мне просто нужно получить доступ к базе данных из функции, просто используя global $db где проблема в этом случае? Как вы это делаете, не используя global ?

EDIT 5:

В одном и том же PHP-глобальном в функции deceze говорится: «Одна большая причина против глобального заключается в том, что это означает, что функция зависит от другой области действия. Это очень быстро станет беспорядочным».

Но я говорю здесь об основном «ИНИТЕ». Я в основном устанавливаю define но использую vars – ну, это неправильно по-технически. Но ваша функция не зависит ни от чего, а от имени одного var $db о котором вы могли бы помнить? Это действительно глобальная необходимость использовать $db , где здесь находится DEPENDENCY и как использовать ее в противном случае?

PS У меня просто была мысль, что мы столкнулись с конфликтом здесь двух разных умов, например: мой (еще не хорошо понимающий объектно-ориентированное программирование) и тех, кого можно назвать гуру (с моей нынешней точки зрения) в ООП – то, что для них очевидно для меня, порождает новые вопросы. Я думаю, именно поэтому этот вопрос задают снова и снова. Лично для меня это стало понятным, но все же есть вещи, которые нужно прояснить.

Solutions Collecting From Web of "Прекратите использование `global` в PHP"

Точка против global переменных заключается в том, что они очень сильно сочетают код. Вся ваша база кода зависит от: a) имени переменной $config и b) существования этой переменной. Если вы хотите переименовать переменную (по какой-либо причине), вы должны делать это везде на всей вашей кодовой базе. Вы также не можете использовать какой-либо фрагмент кода, который зависит от переменной независимо от нее.

Пример с global переменной:

 require 'SomeClass.php'; $class = new SomeClass; $class->doSomething(); 

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

 $config = array(...); require 'SomeClass.php'; $class = new SomeClass; $class->doSomething(); 

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

Поэтому вместо создания неявных, невидимых зависимостей, введите все зависимости:

 require 'SomeClass.php'; $arbitraryConfigVariableName = array(...); $class = new SomeClass($arbitraryConfigVariableName); $class->doSomething(); 

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


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


Попытка объединить ваш вопрос в один пример:

 require_once 'Database.php'; require_once 'ConfigManager.php'; require_once 'Log.php'; require_once 'Foo.php'; // establishes a database connection $db = new Database('localhost', 'user', 'pass'); // loads the configuration from the database, // the dependency on the database is explicit without `global` $configManager = new ConfigManager; $config = $configManager->loadConfigurationFromDatabase($db); // creates a new logger which logs to the database, // note that it reuses the same $db as earlier $log = new Log($db); // creates a new Foo instance with explicit configuration passed, // which was loaded from the database (or anywhere else) earlier $foo = new Foo($config); // executes the conversion function, which has access to the configuration // passed at instantiation time, and also the logger which we created earlier $foo->conversion('foo', array('bar', 'baz'), $log); 

Я оставлю для реализации отдельных классов в качестве упражнения для читателя. Когда вы пытаетесь их реализовать, вы заметите, что они очень просты и понятны для реализации и не требуют единого global . Каждая функция и класс получают все необходимые данные, переданные в виде аргументов функции. Также должно быть очевидно, что вышеуказанные компоненты могут быть подключены друг к другу в любой другой комбинации или что зависимости могут быть легко заменены другими. Например, конфигурация вообще не требуется из базы данных, или регистратор может регистрироваться в файле вместо базы данных без Foo::conversion , чтобы знать об этом.


Пример реализации для ConfigManager :

 class ConfigManager { public function loadConfigurationFromDatabase(Database $db) { $result = $db->query('SELECT ...'); $config = array(); while ($row = $result->fetchRow()) { $config[$row['name']] = $row['value']; } return $config; } } 

Это очень простая часть кода, которая даже не делает много. Вы можете спросить, почему вы хотите, чтобы это было как объектно-ориентированный код. Дело в том, что это делает использование этого кода чрезвычайно гибким, поскольку он отлично изолирует его от всего остального. Вы даете одно соединение с базой данных, вы получаете один массив с определенным синтаксисом. Вход → Выход. Четкие швы, четкие интерфейсы, минимальные, четко определенные обязанности. Вы можете сделать то же самое с простой функцией.

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

 $config = $configManager->loadConfigurationFromDatabase($db); 

Вы можете заменить $configManager здесь для любого другого объекта, который также имеет метод loadConfigurationFromDatabase . Это «утка». Вам все равно, что именно $configManager , если у него есть метод loadConfigurationFromDatabase . Если он ходит, как утка и шарлатанцы, как утка, это утка. Вернее, если он имеет метод loadConfigurationFromDatabase и возвращает loadConfigurationFromDatabase конфигурационный массив, это своего рода ConfigManager. Вы отделили свой код от одной конкретной переменной $config , от одной конкретной функции loadConfigurationFromDatabase и даже от одного конкретного ConfigManager . Все части могут быть изменены и заменены, заменены и загружены динамически из любого места, потому что код не зависит от какой-либо одной конкретной части.

Метод loadConfigurationFromDatabase сам по себе также не зависит от какого-либо конкретного подключения к базе данных, если он может вызывать query и извлекать результаты. Объект $db , передаваемый в него, может быть полностью фальшивым и читать свои данные из XML-файла или где-либо еще, если его интерфейс все еще ведет себя одинаково.

Я решил это с помощью класса:

 class Config { public static $SiteName = 'My Cool Site'; } function SomeFunction { echo 'Welcome to ' , Config::$SiteName; } 

Предложение fcortes использовать константы также является хорошим. Я хотел бы только предложить указать всем константам префикс, например CFG_SITE_NAME , чтобы избежать случайных конфликтов имен с другими константами.

Для вашего случая я бы создал единственный файл constants.php с определениями (если ваша цель заключается в том, что эти «переменные» никогда не изменяются во время выполнения):

 define('SITE_NAME','site name'); define('BASE_PATH','/home/docs/public_html/'); ... 

Включите этот файл constants.php во все файлы, где он вам понадобится:

 include_once('constants.php'); 

Существует большое обсуждение объектно-ориентированных и процедурных подходов (и в более общем плане, между декларативными и императивными), и каждый подход имеет свои преимущества и недостатки.

Я использовал класс «Config», который был Singleton (глобальная версия OOP). Это работало хорошо для меня, пока я не обнаружил необходимость использовать несколько ранее разработанных решений вместе в одном приложении – поскольку все конфиги были глобальными и назывались одним и тем же классом (одно и то же имя переменной, в вашем случае), они конфликтуют, и я для переключения на соответствующую конфигурацию каждый раз, когда я вызываю код из другого под-приложения.

У вас есть два пути:

а) либо разрабатывайте свое приложение так, как вы привыкли, и вы знакомы с ним (это будет лучше, потому что у вас уже есть опыт в нем, и вы можете предсказать, сколько времени потребуется на разработку и какие проблемы могут возникнуть или не возникнуть); и после того, как вы столкнетесь с ограничениями своего нынешнего подхода, рефакторинг, чтобы избежать глобальных перемен;

б) посмотрите, как это делается в рамках ООП (см., по крайней мере, три или четыре, то есть Cake, CodeIgniter, Zend, Symfony, Flow3) и либо заимствовать что-то, либо переключиться на использование фреймворка (или, может быть, вы будете уверены, что Все в порядке).

Я создал легкий небольшой класс:

 class Config { private static $config = array(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } } Config::set( 'my_config', 'the value' ); echo 'the config value is: ' . Config::get('my_config'); 

это можно легко реорганизовать, чтобы иметь функцию isSet( $key ) или, возможно, setAll( $array ) .

EDIT: теперь синтаксис должен быть действительным.

вы можете легко изменить этот класс следующим образом:

 class Config { private static $config = array(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } public static function setAll( array $array ) { self::$config = $array; } public static function isKeySet( $key ) { return isset( self::$config[ $key ] ); } } Config::setAll( array( 'key' => 'value', 'key2' => array( 'value', 'can be an', 'array' ) ) ); Config::set( 'my_config', 'the value' ); if( Config::isKeySet( 'my_config' ) ) { echo 'the config value is: ' . Config::get('my_config'); } 

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

EDIT 2:

Это почти то же самое, что и использование глобального, с той разницей, что вам не нужно указывать, что вы хотите использовать его в начале каждой функции. Если вы хотите использовать Configs глобально, то Configs должны быть, в некотором роде глобальными. Когда вы размещаете что-то в глобальной области, вам нужно спорить, может ли это быть опасной информацией для другого класса, не предназначенного для просмотра этой информации … конфигурации по умолчанию? Я думаю, что это безопасно иметь в глобальном масштабе, а затем вам просто нужно что-то, что легко изменить и настроить.

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

config.php

 <?php class config { private static $config = array(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { if( config::isKeySet( $key ) ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } } public static function setAll( array $array ) { self::$config = $array; } public static function isKeySet( $key ) { return isset( self::$config[ $key ] ); } } // set valuable values config::setAll( array( 'key' => 'value', 'key2' => array( 'value', 'can be an', 'array' ), 'database' => array( "username" => "root", "password" => "root") ) ); config::set( 'my_config', 'the value' ); ?> 

config.usage.php

 <?php require_once 'config.php'; $database_credentials = config::get('database'); echo 'the config value for username is ' . $database_credentials['username']; echo '<br> the config value for password is ' . $database_credentials['password']; function additionalFunctionality($database_credentials) { echo '<br> the config value for password is ' . $database_credentials['password']; } ?> 

config.usage.too.php

 <?php require_once 'config.php'; // put this first require_once 'config.usage.php'; // include some functionality from another file $database_credentials = Config::get('database'); echo 'the config value for username is ' . $database_credentials['username']; additionalFunctionality($database_credentials); // great ?>