在PHP中停止使用`global`

Ili*_*sev 44 php

我有一个config.php包含在每个页面.在配置中我创建一个类似于下列的数组:

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

然后我有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;
                }
 }
Run Code Online (Sandbox Code Playgroud)

编辑2:

按照Vilx的建议,将所有这些放在类中会很酷但在这种情况下,我如何将它与从提取配置keyvalue数据库的以下循环联系起来.
我过分简化了分配$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();
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑3:

此外,我必须vars从配置中设置的功能中访问其他功能,而且它们很少,例如:$db,$language等等.

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

编辑4:

在函数中阅读了PHP全局,其中Gordon以非常好的方式解释了为什么你不应该使用它global.我同意一切,但我不会global在我的情况下重新分配变量,这将导致,就像他说的那样<-- WTF!!;))是的,同意,这很疯狂.但是,如果我只是需要通过使用函数来访问数据库,global $db在这种情况下问题出在哪里?如果不使用,你怎么做global呢?

编辑5:

在函数deceze的同一个PHP全局中说:"反对全局的一个重要原因是它意味着函数依赖于另一个范围.这将很快变得混乱."

但我在这里谈的是基本的'INIT'.我基本上设置define但使用vars- 这在技术方面是错误的.但是你的功能不依赖于任何东西 - 但是$db你可以记住一个变量的名称?这真的是全球需要使用$db,这里的依赖性在哪里以及如何使用它?

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

dec*_*eze 51

反对global变量的要点是他们非常紧密地结合代码.您的整个代码库依赖于a)变量名称 $config和b)该变量的存在.如果要重命名变量(无论出于何种原因),则必须整个代码库中的任何位置执行此操作.您也不能再使用依赖于变量的任何代码.

global变量的示例:

require 'SomeClass.php';

$class = new SomeClass;
$class->doSomething();
Run Code Online (Sandbox Code Playgroud)

上述行中的任何位置都可能会出现错误,因为类或某些代码SomeClass.php隐式依赖于全局变量$config.只是看着课堂,没有任何迹象表明这一点.要解决这个问题,你必须这样做:

$config = array(...);

require 'SomeClass.php';

$class = new SomeClass;
$class->doSomething();
Run Code Online (Sandbox Code Playgroud)

如果您未在内部设置正确的密钥,此代码可能仍会失败$config.由于配置阵列的哪些部分SomeClass需要或不需要以及何时需要它们并不明显,因此很难为正确运行重新创建正确的环境.如果你碰巧已经有一个变量$config用于其他任何你想要使用的东西,它也会产生冲突SomeClass.

因此,不是创建隐式的,不可见的依赖项,而是注入所有依赖项:

require 'SomeClass.php';

$arbitraryConfigVariableName = array(...);

$class = new SomeClass($arbitraryConfigVariableName);
$class->doSomething();
Run Code Online (Sandbox Code Playgroud)

通过明确地将配置数组作为参数传递,解决了上述所有问题.这就像在应用程序内部处理所需信息一样简单.它还使应用程序的结构和流程与更清晰的内容进行了对话.要达到这种状态,如果你的申请目前是泥泞的大球,可能会进行一些重组.


代码库越大,就越需要各个部分相互分离.如果每个部分都依赖于代码库中的每个其他部分,那么您根本无法单独测试,使用或重用它的任何部分.这简直就是陷入了混乱.要将部件彼此分开,请将它们编码为将所有必需数据作为参数的类或函数.这样可以在代码的不同部分之间创建干净的接缝(接口).


试着将你的问题结合在一起作为一个例子:

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);
Run Code Online (Sandbox Code Playgroud)

我将把各个班级的实施作为读者的练习.当您尝试实现它们时,您会注意到它们非常容易实现,并且不需要单个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;
    }

}
Run Code Online (Sandbox Code Playgroud)

这是一段非常简单的代码甚至没有做太多.您可能会问为什么要将此作为面向对象的代码.关键在于,这使得使用此代码非常灵活,因为它将其与其他所有代码完美隔离.你给一个数据库连接,你得到一个具有特定语法的数组.输入→输出.清晰的接缝,清晰的界面,最小的,明确的职责.您可以使用简单的功能执行相同的操作.

对象的额外优势在于它甚至可以进一步解耦loadConfigurationFromDatabase从该函数的任何特定实现调用的代码.如果你只是使用全局function loadConfigurationFromDatabase(),你基本上会再次遇到同样的问题:当你试图调用它时需要定义该函数,如果你想用其他东西替换它就有命名冲突.通过使用对象,代码的关键部分在此处移动:

$config = $configManager->loadConfigurationFromDatabase($db);
Run Code Online (Sandbox Code Playgroud)

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

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

  • 谢谢!最后我了解依赖注入!:)现在找出它的黑暗面,当_not_使用它.:) (4认同)
  • 这是一个很好的解释,我敢打赌这对其他程序员来说绝对有用.如果我可以两次投票给你的答案,我会!感谢您的时间!:-) (3认同)
  • @Ilia如果通过*"只调用`Config :: $ SiteName`*你的意思是你想要使用静态类属性:***不要***.它与`global`变量相同.每次使用时依赖于`Config :: $ SiteName`的代码,你必须确保你的类加载了正确的配置.它与`global $ config`没什么不同.你想使用*函数参数*.这是唯一的将变量分隔成独立范围的方法. (2认同)

Vil*_*lx- 9

我用课程解决了这个问题:

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

function SomeFunction
{
    echo 'Welcome to ' , Config::$SiteName;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 类常量/静态值与全局变量基本相同.他们没有解决任何问题. (9认同)
  • @IliaRostovtsev - 我认为没有任何根本问题.这只是一个偏好问题.我喜欢这种方式,因为我不必一直编写`global`行.此外 - 你要求另一种选择,这就是我给的. (3认同)
  • 那么这并不比使用全局更好:你仍然与Config类紧密结合. (3认同)
  • @IliaRostovtsev - 呃,什么?PHP中的类与数组(您一直在使用它)几乎相同,除了语法有点不同. (2认同)

fco*_*tes 6

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

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

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

...
Run Code Online (Sandbox Code Playgroud)

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

include_once('constants.php');
Run Code Online (Sandbox Code Playgroud)

  • 常量与`global`变量相同,它们不解决任何问题. (3认同)