停止在 PHP 中使用 `global` [英] Stop using `global` in PHP

查看:17
本文介绍了停止在 PHP 中使用 `global`的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

$config = array();$config['site_name'] = '站点名称';$config['base_path'] = '/home/docs/public_html/';$config['libraries_path'] = $config['base_path'] .'/图书馆';//等等...

然后我有 function.php,它也包含在几乎每个页面中,我必须使用 global $config 来访问它 - 和 这个是我想摆脱的!

如何在不使用 global 的情况下访问代码的其他部分中的 $config?

谁能解释一下,为什么我不应该在我的例子中使用 global ?有人说这是一种不好的语气,有人说它不安全?

编辑 1:

我在哪里以及如何使用它的示例:

函数转换($Exec, $Param = array(), $Log = '') {全局 $config;$cmd = $config['phppath'] .' ' .$config['base_path'] .'/' .$执行;foreach ($Param 作为 $s){$cmd .= ' ' .$s;}}

编辑 2:

按照 Vilx 的建议,将所有这些都放在课堂上会很酷,但在这种情况下,如何我将它与以下循环联系起来,该循环从数据库中提取配置 keyvalue.
我过于简化了分配$config数组的想法,这里是一个例子:

$sql = "SELECT * from settings";$rsc = $db->Execute($sql);如果($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 全局函数 其中 Gordon 以非常好的方式解释了为什么不应该使用 global.我同意一切,但我不使用 global 在我的情况下重新分配变量,这将导致,就像他说的,<-- WTF!!,;)) 是的,同意,这很疯狂.但是,如果我只需要使用 global $db 从函数访问数据库,在这种情况下问题出在哪里?如果不使用 global,你如何做到这一点?

编辑 5:

在相同的 PHP 全局函数中 deceze 说:"反对 global 的一个重要原因是这意味着该函数依赖于另一个作用域.这会很快变得混乱."

但我在这里谈论的是基本的INIT".我基本上设置了 define 但使用 vars - 这在技术上是错误的.但是你的函数不依赖于任何东西——而是你可以记住的一个 var $db 的名称?使用$db确实是全球需要,这里的DEPENDENCY在哪里以及如何使用它?

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

解决方案

反对 global 变量的一点是它们非常紧密地耦合代码.您的整个代码库取决于 a) 变量 name $config 和 b) 该变量的存在.如果您想重命名变量(无论出于何种原因),您必须在整个代码库中处处这样做.您也不能再使用任何依赖于变量的独立于变量的代码.

global 变量示例:

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

在上面几行的任何地方你都可能会得到一个错误,因为SomeClass.php 中的类或一些代码隐含地依赖于一个全局变量$config.尽管只是看班级,但没有任何迹象表明这一点.要解决这个问题,你必须这样做:

$config = array(...);需要'SomeClass.php';$class = 新 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';//建立数据库连接$db = new Database('localhost', 'user', 'pass');//从数据库加载配置,//对数据库的依赖是显式的,没有 `global`$configManager = 新的配置管理器;$config = $configManager->loadConfigurationFromDatabase($db);//创建一个记录到数据库的新记录器,//请注意,它重用了与之前相同的 $db$log = 新日志($db);//通过显式配置创建一个新的 Foo 实例,//之前从数据库(或其他任何地方)加载的$foo = new Foo($config);//执行转换函数,可以访问配置//在实例化时传递,还有我们之前创建的记录器$foo->conversion('foo', array('bar', 'baz'), $log);

我将把各个类的实现留给读者作为练习.当您尝试实现它们时,您会注意到它们实现起来非常容易和清晰,并且不需要单个 global.每个函数和类都获得以函数参数形式传递的所有必要数据.很明显,上述组件可以以任何其他组合插入在一起,或者依赖项可以很容易地替换为其他组件.例如,配置根本不需要来自数据库,或者记录器可以记录到文件而不是数据库,而Foo::conversion 不必知道这些.><小时>

ConfigManager 的示例实现:

class ConfigManager {公共函数 loadConfigurationFromDatabase(Database $db) {$result = $db->query('SELECT ...');$config = array();while ($row = $result->fetchRow()) {$config[$row['name']] = $row['value'];}返回 $config;}}

这是一段非常简单的代码,甚至没有做太多事情.您可能会问为什么要将其作为面向对象的代码.关键是这使得使用此代码非常灵活,因为它将它与其他任何东西完美地隔离开来.你输入一个数据库连接,你会得到一个具有特定语法的数组.输入 → 输出.清晰的接缝、清晰的接口、最小的、明确定义的职责.你可以用一个简单的函数来做同样的事情.

对象具有的额外优势是它甚至进一步将调用 loadConfigurationFromDatabase 的代码与该函数的任何特定实现分离.如果你只是使用一个全局的function loadConfigurationFromDatabase(),你基本上又会遇到同样的问题:当你尝试调用它时需要定义该函数,如果你想替换它会存在命名冲突它与别的东西.通过使用对象,代码的关键部分移到这里:

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

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

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

I have a config.php that is included to each page. In config I create an array that looks something like:

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

Then I have function.php, that is also included to almost each page, where I have to use global $config to get access to it - and this is what I would like to get rid of!

How do I access $config in the other parts of my code without using global?

Could anyone explain, WHY I shouldn't use global in my example? Some say it's a bad tone, others say it's not secure?

EDIT 1:

Example of where and how I use it:

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

EDIT 2:

Putting all of this in the class, as suggested by Vilx, would be cool but in this case, how would I tie it with the following loop that is extracting config key and value from database.
I oversimplified the idea of assigning $config array, here is an example:

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

EDIT 3:

Besides, I have to access other vars from functions that are set in config and it's few of them, e.g.:$db, $language and etc.

If I put them in the class will it really solve anything? If I use global what does it really change?

EDIT 4:

I read PHP global in functions where Gordon explains in the very nice way why you shouldn't use global. I agree on everything but I don't use global in my case to reassign the variables, which will result in, like he said, <-- WTF!!, ;)) yeah agree, it's crazy. But if I just need to access database from a function just by using global $db where is the problem in this case? How do you do this otherwise, without using global?

EDIT 5:

In the same PHP global in functions deceze says: "The one big reason against global is that it means the function is dependent on another scope. This will get messy very quickly."

But I'm talking here about basic 'INIT'. I basically set define but use vars - well that is wrong in technical way. But your function is not depended on anything - but the name of one var $db that you could keep in mind? It's really global need to use $db, where is the DEPENDENCY here and how to use it otherwise?

P.S. I just had a thought, that we're facing the conflict here of two different minds, e.g.: mine (yet NOT well understanding object-oriented programming) and those who could be called gurus (from my current point of view) in OOP - what looks obvious for them for me arises new questions. I think that's why this question is being asked over and over again. Personally for me it's gotten more clear after all but still there are things to clarify.

解决方案

The point against global variables is that they couple code very tightly. Your entire codebase is dependent on a) the variable name $config and b) the existence of that variable. If you want to rename the variable (for whatever reason), you have to do so everywhere throughout your codebase. You can also not use any piece of code that depends on the variable independently of it anymore.

Example with global variable:

require 'SomeClass.php';

$class = new SomeClass;
$class->doSomething();

Anywhere in the above lines you may get an error because the class or some code in SomeClass.php implicitly depends on a global variable $config. There's no indication of this whatsoever though just looking at the class. To solve this, you have to do this:

$config = array(...);

require 'SomeClass.php';

$class = new SomeClass;
$class->doSomething();

This code may still fail somewhere if you do not set the correct keys inside $config. Since it's not obvious what parts of the config array SomeClass needs or doesn't need and when it needs them, it's hard to recreate the correct environment for it to run correctly. It also creates conflicts if you happened to already have a variable $config used for something else wherever you want to use SomeClass.

So instead of creating implicit, invisible dependencies, inject all dependencies:

require 'SomeClass.php';

$arbitraryConfigVariableName = array(...);

$class = new SomeClass($arbitraryConfigVariableName);
$class->doSomething();

By passing the config array explicitly as a parameter, all the above problems are solved. It's as simple as handing the required information around inside your app. It also makes the structure and flow of the application and what talks to what much clearer. To get to this state if your application is currently a big ball of mud may take some restructuring.


The bigger your codebase gets, the more you have to decouple the individual parts from each other. If every part is dependent on every other part in your codebase, you simply cannot test, use or reuse any part of it individually. That simply devolves into chaos. To separate parts from each other, code them as classes or functions which take all their required data as parameters. That creates clean seams (interfaces) between different parts of your code.


Trying to tie your question together into one example:

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

I'll leave to implementation of the individual classes up as an exercise for the reader. When you try to implement them, you'll notice that they're very easy and clear to implement and do not require a single global. Every function and class gets all its necessary data passed in the form of function arguments. It should also be obvious that the above components can be plugged together in any other combination or that dependencies can easily be substituted for others. For example, the configuration does not need to come from the database at all, or the logger can log to a file instead of the database without Foo::conversion having to know about any of this.


Example implementation for 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;
    }

}

It's a very simple piece of code that doesn't even do much. You may ask why you'd want this as object oriented code. The point is that this makes using this code extremely flexible, since it isolates it perfectly from everything else. You give one database connection in, you get one array with a certain syntax back. Input → Output. Clear seams, clear interfaces, minimal, well defined responsibilities. You can do the same with a simple function.

The extra advantage an object has is that it even further decouples the code that calls loadConfigurationFromDatabase from any particular implementation of that function. If you'd just use a global function loadConfigurationFromDatabase(), you basically have the same problem again: that function needs to be defined when you try to call it and there are naming conflicts if you want to replace it with something else. By using an object, the critical part of the code moves here:

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

You can substitute $configManager here for any other object that also has a method loadConfigurationFromDatabase. That's "duck typing". You don't care what exactly $configManager is, as long as it has a method loadConfigurationFromDatabase. If it walks like a duck and quacks like a duck, it is a duck. Or rather, if it has a loadConfigurationFromDatabase method and gives back a valid config array, it's some sort of ConfigManager. You have decoupled your code from one particular variable $config, from one particular loadConfigurationFromDatabase function and even from one particular ConfigManager. All parts can be changed and swapped out and replaced and loaded dynamically from anywhere, because the code does not depend on any one particular other piece.

The loadConfigurationFromDatabase method itself also does not depend on any one particular database connection, as long as it can call query on it and fetch results. The $db object being passed into it could be entirely fake and read its data from an XML file or anywhere else instead, as long as its interface still behaves the same.

这篇关于停止在 PHP 中使用 `global`的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆