简介

即使开发一个新的大型PHP程序,你也不可避免的要使用到全局数据,因为有些数据是需要用到你的代码的不同部分的。一些常见的全局数据有:程序设定类、数据库连接类、用户资料等等。有很多方法能够使这些数据成为全局数据,其中最常用的就是使用“global”关键字申明,稍后在文章中我们会具体的讲解到。
   使用“global”关键字来申明全局数据的唯一缺点就是它事实上是一种非常差的编程方式,而且经常在其后导致程序中出现更大的问题,因为全局数据把你代码中原本单独的代码段都联系在一起了,这样的后果就是如果你改变其中的某一部分代码,可能就会导致其他部分出错。所以如果你的代码中有很多全局的变量,那么你的整个程序必然是难以维护的。
   本文将展示如何通过不同的技术或者设计模式来防止这种全局变量问题。当然,首先让我们看看如何使用“global”关键字来进行全局数据以及它是如何工作的。

使用全局变量和“global”关键字

PHP默认定义了一些“超级全局(Superglobals)”变量,这些变量自动全局化,而且能够在程序的任何地方中调用,比如$_GET和$_REQUEST等等。它们通常都来自数据或者其他外部数据,使用这些变量通常是不会产生问题的,因为他们基本上是不可写的。
但是你可以使用你自己的全局变量。使用关键字“global”你就可以把全局数据导入到一个函数的局部范围内。如果你不明白“变量使用范围”,请你自己参考PHP手册上的相关说明。

下面是一个使用“global”关键字的演示例子:

以下为引用的内容:
<?php 
$my_var = 'Hello World'; 
test_global(); 
function test_global() { 
    // Now in local scope 
    // the $my_var variable doesn't exist 
    // Produces error: "Undefined variable: my_var" 
    echo $my_var; 
    // Now let's important the variable 
    global $my_var; 
    // Works: 
    echo $my_var; 

?>

正如你在上面的例子中看到的一样,“global”关键字是用来导入全局变量的。看起来它工作的很好,而且很简单,那么为什么我们还要担心使用“global”关键字来定义全局数据呢?
下面是三个很好的理由:
1、  代码重用几乎是不可能的。
如果一个函数依赖于全局变量,那么想在不同的环境中使用这个函数几乎是不可能的。另外一个问题就是你不能提取出这个函数,然后在其他的代码中使用。
2、  调试并解决问题是非常困难的。
跟踪一个全局变量比跟踪一个非全局变量困难的多。一个全局变量可能会在一些不明显的包含文件中被重新定义,即使你有一个非常好的程序编辑器(或者IDE)来帮助你,你也得花了几个小时才能发现这个问题所在。
3、  理解这些代码将是非常难的事情。
你很难弄清楚一个全局变量是从哪里来得,它是用来做什么的。在开发的过程中,你可能会知道知道每一个全局变量,但大概一年之后,你可能会忘记其中至少一般的全局变量,这个时候你会为自己使用那么多全局变量而懊悔不已。
那么如果我们不使用全局变量,我们该使用什么呢?下面让我们看看一些解决方案。

使用函数参数

停止使用全局变量的一种方法就是简单的把变量作为函数的参数传递过去,如同下面所示:

以下为引用的内容:

<?php 
$var = 'Hello World'; 
test ($var); 
function test($var) { 
    echo $var; 

?>

如果你仅仅只需要传递一个全局变量,那么这是一种非常优秀甚至可以说是杰出的解决方案,但是如果你要传递很多个值,那该怎么办呢?
比如说,假如我们要使用一个数据库类,一个程序设置类和一个用户类。在我们代码中,这三个类在所有组件中都要用到,所以必须传递给每一个组件。如果我们使用函数参数的方法,我们不得不这样:

以下为引用的内容:
<?php 
$db = new DBConnection; 
$settings = new Settings_XML; 
$user = new User; 
test($db, $settings, $user); 
function test(&$db, &$settings, &$user) { 
    // Do something 

?>

显然,这是不值得的,而且一旦我们有新的对象需要加入,我们不得不为每一个函数增加多一个函数参数。因此我们需要用采用另外一种方式来解决。

使用单件(Singletons)

解决函数参数问题的一种方法就是采用单件(Singletons)来代替函数参数。单件是一类特殊的对象,它们只能实例化一次,而且含有一个静态方法来返回对象的接口。下面的例子演示了如何构建一个简单的单件:

以下为引用的内容:
<?php 
// Get instance of DBConnection 
$db =& DBConnection::getInstance(); 
// Set user property on object 
$db->user = 'sa'; 
// Set second variable (which points to the same instance) 
$second =& DBConnection::getInstance(); 
// Should print 'sa' 
echo $second->user; 
Class DBConnection { 
    var $user; 
    function &getInstance() { 
        static $me; 
        if (is_object($me) == true) { 
            return $me; 
        } 
        $me = new DBConnection; 
        return $me; 
    } 
    function connect() { 
        // TODO 
    } 
    function query() { 
        // TODO 
    } 

?>

上面例子中最重要的部分是函数getInstance()。这个函数通过使用一个静态变量$me来返回这个类的实例,从而确保了只有一个DBConnection类的实例。
使用单件的好处就是我们不需要明确的传递一个对象,而是简单的使用getInstance()方法来获取到这个对象,就好像下面这样:

以下为引用的内容:
<?php 
function test() { 
    $db = DBConnection::getInstance(); 
    // Do something with the object 

?>

然而使用单件也存在一系列的不足。首先,如果我们如何在一个类需要全局化多个对象呢?因为我们使用单件,所以这个不可能的(正如它的名字是单件一样)。另外一个问题,单件不能使用个体测试来测试的,而且这也是完全不可能的,除非你引入所有的堆栈,而这显然是你不想看到的。这也是为什么单件不是我们理想中的解决方法的主要原因。

注册模式

让一些对象能够被我们代码中所有的组件使用到(译者注:全局化对象或者数据)的最好的方法就是使用一个中央容器对象,用它来包含我们所有的对象。通常这种容器对象被人们称为一个注册器。它非常的灵活而且也非常的简单。一个简单的注册器对象就如下所示:

以下为引用的内容:

<?php 
Class Registry { 
    var $_objects = array(); 
    function set($name, &$object) { 
        $this->_objects[$name] =& $object; 
    } 
    function &get($name) { 
        return $this->_objects[$name]; 
    } 

?>

使用注册器对象的第一步就是使用方法set()来注册一个对象:

以下为引用的内容:
<?php 
$db = new DBConnection; 
$settings = new Settings_XML; 
$user = new User; 
// Register objects 
$registry =& new Registry; 
$registry->set ('db', $db); 
$registry->set ('settings', $settings); 
$registry->set ('user', $user); 
?>

现在我们的寄存器对象容纳了我们所有的对象,我们指需要把这个注册器对象传递给一个函数(而不是分别传递三个对象)。看下面的例子:

以下为引用的内容:
<?php 
function test(&$registry) { 
    $db =& $registry->get('db'); 
    $settings =& $registry->get('settings'); 
    $user =& $registry->get('user'); 
    // Do something with the objects 

?>

注册器相比其他的方法来说,它的一个很大的改进就是当我们需要在我们的代码中新增加一个对象的时候,我们不再需要改变所有的东西(译者注:指程序中所有用到全局对象的代码),我们只需要在注册器里面新注册一个对象,然后它(译者注:新注册的对象)就立即可以在所有的组件中调用

为了更加容易的使用注册器,我们把它的调用改成单件模式(译者注:不使用前面提到的函数传递)。因为在我们的程序中只需要使用一个注册器,所以单件模式使非常适合这种任务的。在注册器类里面增加一个新的方法,如下所示:

以下为引用的内容:

<? 
function &getInstance() { 
    static $me; 
    if (is_object($me) == true) { 
        return $me; 
    } 
    $me = new Registry; 
    return $me; 

?>

这样它就可以作为一个单件来使用,比如:

以下为引用的内容:
<?php 
$db = new DBConnection; 
$settings = new Settings_XML; 
$user = new User; 
// Register objects 
$registry =& Registry::getInstance(); 
$registry->set ('db', $db);

(转) 在PHP中使用全局变量的更多相关文章

  1. C语言中定义全局变量

    (1)在C语言的头文件中定义变量出现的问题 最好不要傻嘻嘻的在头文件里定义什么东西.比如全局变量: /*xx头文件*/ #ifndef  _XX_头文件.H #define  _XX_头文件.H in ...

  2. js中的全局变量和静态变量的使用, js 的调试?- 如果js出错, js引擎 就会停止, 这会 导致 后面的 html中 refer 该函数时, 会报错 函数为定义!!

    效果里面的函数, 如show, hide,slideDown等, 这些都叫 "效果"函数, 但是里面可以包含动画, 也可以 不包含动画. 动画,是指 元素 的内容 是 逐渐 显示/ ...

  3. c语言头文件中定义全局变量的问题

    c语言头文件中定义全局变量的问题 (转http://www.cnblogs.com/Sorean/) 先说一下,全局变量只能定义在 函数里面,任意函数,其他函数在使用的时候用extern声明.千万不要 ...

  4. 在android.app.Application中定义全局变量

    在Android应用中使用全局变量,除了public的静态变量,还有更优雅的方式是使用android.app.Application. 启动Application时,系统会创建一个PID,即进程ID, ...

  5. Yii Framework2.0开发教程(4)在yii中定义全局变量

    在yii中定义全局变量最好的地方是入口脚本处.也就是web目录中的index.php文件 比如我们在defined('YII_ENV') or define('YII_ENV', 'dev');后写上 ...

  6. python中的全局变量和局部变量

    python中,对于变量作用域的规定有些不一样. 在诸如C/C++.java等编程语言中,默认在函数的内部是能够直接訪问在函数外定义的全局变量的,可是这一点在python中就会有问题.以下是一个样例. ...

  7. java中的全局变量如何实现?ThreadLocal~

    全局变量就是不管你在哪里,都能够直接引用的变量,还不用担心各种问题.每个语言都有自己的全局变量,我想! 一般地,面向过程的语言当中,可能就是一个声明在最前面的变量,后面的代码直接引用,就成了全局变量! ...

  8. python线程中的全局变量与局部变量

    在python多线程开发中,全局变量是多个线程共享的数据,局部变量是各自线程的,非共享的. 如下几种写法都是可以的: 第一种:将列表当成参数传递给线程 from threading import Th ...

  9. Loadrunner 运行场景-场景中的全局变量与关联结果参数

    运行场景-场景中的全局变量与关联结果参数   by:授客 QQ:1033553122 A.   全局变量 实验1: globals.h #ifndef _GLOBALS_H #define _GLOB ...

  10. python中的全局变量和局部变量(转)

    python中,对于变量作用域的规定有些不一样. 在诸如C/C++.java等编程语言中,默认在函数的内部是能够直接訪问在函数外定义的全局变量的,可是这一点在python中就会有问题.以下是一个样例. ...

随机推荐

  1. UVALive - 6436、HYSBZ - 2435 (dfs)

    这两道题都是用简单dfs解的,主要是熟悉回溯过程就能做,据说用bfs也能做 道路修建(HYSBZ - 2435) 在 W 星球上有n 个国家.为了各自国家的经济发展,他们决定在各个国家 之间建设双向道 ...

  2. 转 rman 恢复报错

    ###sample 1   原因是新库起了FRA 区,FRA 区的旧文件属于之前的归档日志文件产生,这样会导致无法识别的问题. 解决办法,清空FRA或者恢复时候不启用FRA. RMAN RESTORE ...

  3. (转)搭建企业内部yum仓库(centos6+centos7+epel源)

    搭建企业内部yum仓库(centos6+centos7+epel源) 原文:https://www.cnblogs.com/nulige/p/6081192.html https://www.linu ...

  4. Log4J2 配置文件模板及代码说明

    Log4j是Apache的著名项目,随着Java应用的越来越广泛,对日志性能等方面的要求也越来越高.Log4j的升级版本Log4j2在前些年发布.Log4J2的优点和好处有很多,可以自行搜索查阅相关文 ...

  5. Mysql远程连接配置

    Mysql远程连接配置 环境:unbuntu 16.04 最新版本的Mysql在远程连接的配置上与老版本有了一些出入,照原先的配置已经不行了,所以在这里记录一下遇到的所有新问题. 配置远程连接的步骤如 ...

  6. 以cmd命令行方式执行php文件时,传递参数

    1. php自带的两个参数$argc, $argv: 1.1. $argv : (后面的v是variables的意思),传递进来的参数会以数组的方式保持在这个变量里 1.2. $argc : (后面的 ...

  7. php过滤数组空值

    如果我们想过滤数组里面的空值,例如null,,false,' '等等,可以使用php自带的一个函数,使用起来非常方便简洁: //测试数据 $data = array( '0' => '测试内容1 ...

  8. render函数的使用

    render函数的几种使用方法最近使用element-ui的tree组件时,需要在组件树的右边自定义一些图标,就想到了用render函数去渲染. <el-tree class="p-t ...

  9. Java入门系列-13-String 和 StringBuffer

    这篇文章带你学会字符串的日常操作 String类 字符串在日常生活中无处不在,所以掌握字符串的使用至关重要. 使用 String 对象存储字符串,String 类位于 java.lang 包中,jav ...

  10. telnet 命令使用方法详解

    参考自:这里 什么是telnet? 简单来说,可以把telnet当作一种通信协议.但对于入侵者来说,telnet只是一种远程登陆的工具.一旦入侵者与远程主机建立了telnet链接,入侵者便可以使用目标 ...