解析在PHP中使用全局变量的几种方法


Posted in PHP onJune 24, 2013

简介
即使开发一个新的大型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);
$registry->set ('settings', $settings);
$registry->set ('user', $user);
function test() {
    $registry =& Registry::getInstance();
    $db =& $registry->get('db');
    $settings =& $registry->get('settings');
    $user =& $registry->get('user');
    // Do something with the objects
}
?>

正如你看到的,我们不需要把私有的东西都传递到一个函数,也不需要使用“global”关键字。所以注册器模式是这个问题的理想解决方案,而且它非常的灵活。

请求封装器
虽然我们的注册器已经使“global”关键字完全多余了,在我们的代码中还是存在一种类型的全局变量:超级全局变量,比如变量$_POST,$_GET。虽然这些变量都非常标准,而且在你使用中也不会出什么问题,但是在某些情况下,你可能同样需要使用注册器来封装它们。
一个简单的解决方法就是写一个类来提供获取这些变量的接口。这通常被称为“请求封装器”,下面是一个简单的例子:

<?php
Class Request {
    var $_request = array();
    function Request() {
        // Get request variables
        $this->_request = $_REQUEST;
    }
    function get($name) {
        return $this->_request[$name];
    }
}
?>

上面的例子是一个简单的演示,当然在请求封装器(request wrapper)里面你还可以做很多其他的事情(比如:自动过滤数据,提供默认值等等)。
下面的代码演示了如何调用一个请求封装器:
<?php
$request = new Request;
// Register object
$registry =& Registry::getInstance();
$registry->set ('request', &$request);
test();
function test() {
    $registry =& Registry::getInstance();
    $request =& $registry->get ('request');
    // Print the 'name' querystring, normally it'd be $_GET['name']
    echo htmlentities($request->get('name'));
}
?>

正如你看到的,现在我们不再依靠任何全局变量了,而且我们完全让这些函数远离了全局变量。

结论
在本文中,我们演示了如何从根本上移除代码中的全局变量,而相应的用合适的函数和变量来替代。注册模式是我最喜欢的设计模式之一,因为它是非常的灵活,而且它能够防止你的代码变得一塌糊涂。
另外,我推荐使用函数参数而不是单件模式来传递注册器对象。虽然使用单件更加轻松,但是它可能会在以后出现一些问题,而且使用函数参数来传递也更加容易被人理解。
PHP 相关文章推荐
用Socket发送电子邮件
Oct 09 PHP
php expects parameter 1 to be resource, array given 错误
Mar 23 PHP
Zend的MVC机制使用分析(一)
May 02 PHP
解密ThinkPHP3.1.2版本之模板继承
Jun 19 PHP
Zend Framework教程之Loader以及PluginLoader用法详解
Mar 09 PHP
PHP使用curl制作简易百度搜索
Nov 03 PHP
PHP创建单例后台进程的方法示例
May 23 PHP
Laravel 5.5 的自定义验证对象/类示例代码详解
Aug 29 PHP
PHP实现APP微信支付的实例讲解
Feb 10 PHP
PHP设计模式之抽象工厂模式实例分析
Mar 25 PHP
PHP利用pdo_odbc实现连接数据库示例【基于ThinkPHP5.1搭建的项目】
May 13 PHP
php源码的使用方法讲解
Sep 26 PHP
探讨:array2xml和xml2array以及xml与array的互相转化
Jun 24 #PHP
解析Ubuntu下crontab命令的用法
Jun 24 #PHP
关于crontab的使用详解
Jun 24 #PHP
解析PHPExcel使用的常用说明以及把PHPExcel整合进CI框架的介绍
Jun 24 #PHP
关于Zend Studio 配色方案插件的介绍
Jun 24 #PHP
解析argc argv在php中的应用
Jun 24 #PHP
解析func_num_args与func_get_args函数的使用
Jun 24 #PHP
You might like
简单PHP上传图片、删除图片实现代码
2010/05/12 PHP
phpMyAdmin 链接表的附加功能尚未激活问题的解决方法(已测)
2012/03/27 PHP
PHP设计模式之调解者模式的深入解析
2013/06/13 PHP
php实现微信公众平台账号自定义菜单类
2015/10/11 PHP
基于PHP实现数据分页显示功能
2016/05/26 PHP
jQuery图片预加载 等比缩放实现代码
2011/10/04 Javascript
JS对img标签进行优化使用onerror显示默认图像
2014/04/24 Javascript
JavaScript实现给按钮加上双重动作的方法
2015/08/14 Javascript
js实现简单计算器
2015/11/22 Javascript
15个值得开发人员关注的jQuery开发技巧和心得总结【经典收藏】
2016/05/25 Javascript
webpack入门+react环境配置
2017/02/08 Javascript
JS实现数组去重复值的方法示例
2017/02/18 Javascript
jQuery操作DOM_动力节点Java学院整理
2017/07/04 jQuery
使用vue与jquery实时监听用户输入状态的操作代码
2017/09/19 jQuery
VeeValidate在vue项目里表单校验应用案例
2018/05/09 Javascript
javascript实现考勤日历功能
2018/11/29 Javascript
JavaScript之解构赋值的理解
2019/01/30 Javascript
layui问题之模拟table表格中的选中按钮选中事件的方法
2019/09/20 Javascript
Vue记住滚动条和实现下拉加载的完美方法
2020/07/31 Javascript
[49:27]2018DOTA2亚洲邀请赛 4.4 淘汰赛 TNC vs VG 第一场
2018/04/05 DOTA
[46:12]完美世界DOTA2联赛循环赛 DM vs Matador BO2第一场 11.04
2020/11/04 DOTA
Python装饰器基础详解
2016/03/09 Python
Python实现字典去除重复的方法示例
2017/07/31 Python
python实现音乐下载的统计
2018/06/20 Python
flask-socketio实现WebSocket的方法
2018/07/31 Python
django Serializer序列化使用方法详解
2018/10/16 Python
opencv实现简单人脸识别
2021/02/19 Python
使用virtualenv创建Python环境及PyQT5环境配置的方法
2019/09/10 Python
Python scrapy增量爬取实例及实现过程解析
2019/12/24 Python
中国一家专注拼团的社交购物网站:拼多多
2018/06/13 全球购物
财务出纳员岗位职责
2013/11/26 职场文书
内业资料员岗位职责
2014/01/04 职场文书
个人能力自我鉴赏
2014/01/25 职场文书
超市创业计划书
2014/04/24 职场文书
村主任群众路线个人对照检查材料
2014/09/26 职场文书
住房公积金贷款工资证明
2015/06/12 职场文书