46

我有一个config.php包含在每个页面中的内容。在配置中,我创建了一个看起来像这样的数组:

$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;
    }
}

编辑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:

此外,我必须从 config 中设置的函数访问其他vars函数,并且它们很少,例如:$db$language

如果我把它们放在课堂上,它真的能解决任何问题吗?如果我使用global它真正改变了什么?

编辑4:

我在函数中阅读了 PHP 全局,戈登以非常好的方式解释了为什么你不应该使用global. 我同意一切,但在我的情况下我不使用global重新分配变量,这将导致,就像他说的那样<-- WTF!!,;)) 是的,同意,这太疯狂了。但是,如果我只需要通过使用global $db这种情况下的问题来从函数访问数据库?否则,您如何在不使用的情况下执行此操作global

编辑 5:

在同一个 PHP global in functions deceze说:“反对 global 的一个重要原因是它意味着该函数依赖于另一个范围。这将很快变得混乱。”

但我在这里谈论的是基本的'INIT'。我基本上设置define但使用vars- 这在技术上是错误的。但是您的功能不依赖于任何东西 - 而是$db您可以记住的一个 var 的名称?这真的是全球需要使用$db的,这里的依赖关系在哪里以及如何使用它?

PS我只是有一个想法,我们在这里面临两种不同思想的冲突,例如:的(但不太了解面向对象的编程)和那些在OOP中可以被称为大师的人(从我目前的角度来看) - 对我来说看起来很明显的事情会引发新的问题。我想这就是为什么这个问题被一遍又一遍地问到的原因。就我个人而言,它毕竟变得更加清晰,但仍有一些事情需要澄清。

4

6 回答 6

55

反对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与该函数的任何特定实现分离。如果您只是使用 global function loadConfigurationFromDatabase(),那么您基本上又遇到了同样的问题:当您尝试调用它时需要定义该函数,如果您想用其他东西替换它,则会出现命名冲突。通过使用一个对象,代码的关键部分移到了这里:

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

$configManager您可以在此处替换任何其他也具有方法的对象loadConfigurationFromDatabase。那就是“鸭子打字”。你不在乎到底$configManager是什么,只要它有一个方法loadConfigurationFromDatabase。如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那它就是鸭子。或者更确切地说,如果它有一个loadConfigurationFromDatabase方法并返回一个有效的配置数组,它就是某种 ConfigManager。您已经将您的代码与一个特定的变量$config、一个特定的loadConfigurationFromDatabase函数甚至一个特定的函数分离ConfigManager。所有部分都可以从任何地方动态更改、换出、替换和加载,因为代码不依赖于任何一个特定的其他部分。

loadConfigurationFromDatabase方法本身也不依赖于任何一个特定的数据库连接,只要它可以调用query它并获取结果。传递给它的$db对象可能完全是假的,而是从 XML 文件或其他任何地方读取其数据,只要它的接口仍然表现相同。

于 2012-09-16T10:52:39.500 回答
9

我已经用一堂课解决了这个问题:

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

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

fcortes 使用常量的建议也是一个很好的建议。我只想建议给所有常量一个前缀,比如CFG_SITE_NAME,以避免意外的名称与其他常量发生冲突。

于 2012-09-16T10:09:50.010 回答
6

对于您的情况,我将创建一个constants.php带有定义的唯一文件(如果您的目的是这些“变量”永远不会在执行时更改):

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

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

...

将其包含constants.php在您需要它的所有文件中:

include_once('constants.php');
于 2012-09-16T10:03:58.157 回答
3

在面向对象方法和过程方法之间(更一般地说,在声明式和命令式方法之间)有很多讨论,每种方法都有其优点和缺点。

我使用的是单例(全局的 OOP 版本)的“配置”类。它对我很有用,直到我发现需要在一个应用程序中一起使用几个以前开发的解决方案 - 因为所有配置都是全局的并且由同一个类(在你的情况下是相同的变量名)引用它们冲突并且我有每次我从其他子应用程序调用代码时切换到正确的配置。

你有两种方法:

a) 以您习惯和熟悉的方式设计您的应用程序(这会更好,因为您已经有这方面的经验,并且您可以预测开发需要多长时间以及可能会或可能不会出现哪些问题);在你陷入当前方法的限制之后,重构以避免全局变量;

b) 看看它在 OOP 框架中是如何完成的(至少看三个或四个,即 Cake、CodeIgniter、Zend、Symfony、Flow3),然后借用一些东西,或者切换到使用框架(或者也许你会更确定你会这样做一切正常)。

于 2012-09-16T14:23:28.183 回答
1

我创建了一个简单的小班:

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 ).

编辑:现在语法应该是有效的。

您可以轻松地修改此类,如下所示:

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 必须在某种程度上是全局的。将某些内容放在全局范围内时,您需要争论这对于其他不打算查看此信息的类是否可能是危险信息...默认配置?我认为在全局范围内是安全的,然后您只需要易于修改和自定义的东西。

如果您认为它是危险信息,那么除了它所针对的类之外的类之外的类应该无法访问该信息,那么您可能需要签入Dependency injection。通过依赖注入,一个类将在其构造函数中获取一个对象,将其私有地放置在一个变量中以供使用。这个对象可以是来自配置类的对象,然后你需要一个包装类首先创建配置对象,然后是注入配置的模板对象。这是在更复杂的设计模式中经常看到的设计,例如领域驱动设计。

于 2012-09-16T12:25:15.283 回答
0

配置文件

<?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
?>
于 2015-07-16T12:21:50.407 回答