Подтвердить что ты не робот

Прекратите использование `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 в моем примере? Некоторые говорят, что это плохой тон, другие говорят, что это не безопасно?

EDIT 1:

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

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

ИЗМЕНИТЬ 2:

Вводя все это в класс, как было предложено Vilx, было бы круто, но в этом случае, как бы связать его со следующим циклом, который извлекает config 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 глобальный в функциях, где Gordon объясняет очень хороший способ, почему вы должны 't использовать global. Я согласен на все, но я не использую global в моем случае для переназначения переменных, что приведет, как он сказал, <-- WTF!!,;)) Да, согласитесь, это безумие. Но если мне просто нужно получить доступ к базе данных из функции, просто используя global $db, где проблема в этом случае? Как вы это делаете в противном случае, не используя global?

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

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

Но я говорю здесь об основном "INIT". Я в основном устанавливаю define, но использую vars - ну, это неправильно по техническим причинам. Но ваша функция не зависит ни от чего, а от имени одного var $db, о котором вы могли бы помнить? Это действительно глобальная необходимость использовать $db, где здесь находится ЗАВИСИМОСТЬ и как использовать ее в противном случае?

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

4b9b3361

Ответ 1

Точка против переменных 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 нужны или не нужны, и когда это необходимо, трудно воссоздать правильную среду для правильной работы. Это также создает конфликты, если у вас уже есть переменная $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 и возвращает верный конфигурационный массив, это своего рода ConfigManager. Вы отделили свой код от одной конкретной переменной $config, от одной конкретной функции loadConfigurationFromDatabase и даже от одного конкретного ConfigManager. Все части могут быть изменены и заменены, заменены и загружены динамически из любого места, потому что код не зависит от какой-либо одной другой части.

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

Ответ 2

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

class Config
{
    public static $SiteName = 'My Cool Site';
}

function SomeFunction
{
    echo 'Welcome to ' , Config::$SiteName;
}

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

Ответ 3

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

define('SITE_NAME','site name');

define('BASE_PATH','/home/docs/public_html/');

...

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

include_once('constants.php');

Ответ 4

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

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

У вас есть два способа:

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

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

Ответ 5

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

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');
}

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

ИЗМЕНИТЬ 2:

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

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

Ответ 6

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
?>