PHP Globals的安全替代品(良好编码惯例)

时间:2011-09-03 03:33:54

标签: php performance security

多年来,我在我的应用程序中使用global $var,$var2,...,$varn方法。我已将它们用于两个主要实现:

获取已设置的类(例如数据库连接),并将信息传递给显示到页面的函数。

示例:

$output['header']['log_out'] = "Log Out";
function showPage(){
     global $db, $output;
     $db = ( isset( $db ) ) ? $db : new Database();
     $output['header']['title'] = $db->getConfig( 'siteTitle' );
     require( 'myHTMLPage.html' );
     exit();
}

然而,这样做会产生性能和安全后果。

我可以使用哪种替代做法来维护我的功能,但改善设计,性能和/或安全性?

这是我曾经问过的第一个问题,所以如果你需要澄清,请发表评论!

6 个答案:

答案 0 :(得分:14)

  

1。全局。奇迹般有效。因为我不想使用Globals,所以我很讨厌Globals。

好吧,全局变量不仅仅是讨厌。他们讨厌是有原因的。如果你没有在全局问题中运行到目前为止,那很好。您无需重构代码。

  

2。在我的config.php文件中定义一个常量。

这实际上就像一个全球性的,但有另一个名字。您也可以省略$并在函数开头使用global。 Wordpress为他们的配置做了这个,我说这比使用全局变量更糟糕。这使得引入接缝变得更加复杂。此外,您无法将对象分配给常量。

  

3。在函数中包含配置文件。

我认为这是开销。您需要对代码库进行细分,以获得更多收益。 "全球"这里将成为你包含的文件的名称..


考虑到你和我对他们的这三个想法我会说:除非你遇到一些全局变量的实际问题,否则你可以坚持下去。 Global然后作为您的服务定位器(配置,数据库)工作。其他人做更多的事情来创造同样的东西。

如果你遇到问题(例如你可能想要开发测试驱动),我建议你先从一个部分接下来测试,然后学习如何避免全局变量。

依赖注入

作为内部注释,很明显您正在寻找依赖注入,如果您无法编辑函数参数定义,您可以 - 如果您使用对象 - 通过构造函数或使用所谓的 setter方法。在下面的示例代码中,我将执行这两个操作,仅用于演示目的,因为您可能已经猜到了,一次使用它们没有用处:

让我们说配置数组是我们想要注入的依赖项。我们将其称为config并将变量命名为$config。由于它是一个数组,我们可以将其提示为array。首先,如果您更喜欢ini文件格式,也可以使用parse_ini_file来定义包含文件中的配置。我认为它更快。

config.php

<?php
/**
 * configuration file
 */
return array(
    'db_user' => 'root',
    'db_pass' => '',
);

然后,您可以在应用程序内部使用该文件:

$config = require('/path/to/config.php');

因此可以在代码中的某个位置轻松转换为数组变量。到目前为止,没有任何壮观的事情与依赖注入完全无关。让我们看一个需要在这里配置的示例数据库类,它需要有用户名和密码,否则它无法连接让我们说:

class DBLayer
{
    private $config;

    public function __construct(array $config)
    {
        $this->setConfig($config);
    }

    public function setConfig(array $config)
    {
        $this->config = $config;
    }

    public function oneICanNotChange($paramFixed1, $paramFixed2)
    {
        $user = $this->config['db_user'];
        $password = $this->config['db_pass'];
        $dsn = 'mysql:dbname=testdb;host=127.0.0.1';
        try {
            $dbh = new PDO($dsn, $user, $password);
        } catch (PDOException $e) {
            throw new DBLayerException('Connection failed: ' . $e->getMessage());
        }

        ...
}

这个例子有点粗糙,但它有两个依赖注入的例子。首先通过构造函数:

public function __construct(array $config)

这个很常见,所有类需要做的依赖都是在创建时注入的。这也确保了当调用该对象的任何其他方法时,该对象将处于可预先确定的状态 - 这对系统来说有点重要。

第二个示例是使用公共 setter方法

public function setConfig(array $config)

这允许稍后添加依赖项,但某些方法可能需要在完成工作之前检查可用的内容。例如。如果您可以在不提供配置的情况下创建DBLayer对象,则可以在没有该对象进行配置的情况下调用oneICanNotChange方法,并且必须处理该对象(在此示例中未显示)。

服务定位器

由于您需要动态集成代码,并希望您的新代码能够通过依赖注入进行测试以及所有使我们的生活变得更轻松的内容,您可能需要将它与您的古代一起使用/遗留代码。我认为这部分很难。对它自己的依赖注入非常简单,但将它与旧代码放在一起并不是那么直接。

我在这里建议的是你创建一个全局变量,即所谓的服务定位器。它包含一个中心点,用于从中获取对象(甚至是像$config这样的数组)。然后可以使用它,合同就是单个变量名。因此,要删除全局变量,我们使用全局变量。听起来有点适得其反,甚至如果你的新代码也使用它太多了。但是,您需要一些工具来将新旧结合在一起。所以这是迄今为止我能想象的最基本的PHP服务定位器实现。

它由一个提供所有服务的Services对象组成,例如上面的config。因为当PHP脚本启动时,我们还不知道是否需要一个服务(例如我们可能不运行任何数据库查询,因此我们不需要实例化数据库),它提供了一些惰性初始化功能,如好。这是通过使用工厂脚本来完成的,这些脚本只是设置服务并返回服务的PHP文件。

第一个例子:假设函数oneICanNotChange不是对象的一部分,而只是全局命名空间中的一个简单函数。我们无法注入config依赖项。这是Services服务定位器对象的来源:

$services = new Services(array(
    'config' => '/path/to/config.php',
));

...

function oneICanNotChange($paramFixed1, $paramFixed2)
{
    global $services;
    $user = $services['config']['db_user'];
    $password = $services['config']['db_pass'];

    ...

如示例所示,Services对象将字符串'config'映射到定义$config数组的PHP文件的路径:/path/to/config.php。它使用ArrayAccess接口,而不是在oneICanNotChange函数中公开该服务。

我建议使用ArrayAccess界面,因为它定义得很好,它表明我们在这里有一些动态角色。另一方面,它允许我们进行延迟初始化:

class Services implements ArrayAccess
{
    private $config;
    private $services;

    public function __construct(array $config)
    {
        $this->config = $config;
    }
    ...
    public function offsetGet($name)
    {
        return @$this->services[$name] ?
            : $this->services[$name] = require($this->config[$name]);
   }
   ...
}

这个示例性存根只需要工厂脚本,如果它还没有到目前为止,否则将返回脚本返回值,如数组,对象甚至字符串(但不是NULL,这是有意义的)。

我希望这些示例很有用,并且表明在这里获得更多灵活性并从代码中冲出全局变量不需要太多代码。但是您应该清楚,服务定位器将全局状态引入您的代码。好处是,更容易将其与具体的变量名解除,并提供更多的灵活性。也许您能够将您在代码中使用的对象划分为某些组,其中只有一些需要通过服务定位器提供,并且您可以保持代码较小,这取决于定位器。

答案 1 :(得分:8)

替代方案称为依赖注入。简而言之,它意味着您将函数/类/对象所需的数据作为参数传递。

function showPage(Database $db, array &$output) {
    ...
}


$output['header']['log_out'] = "Log Out";
$db = new Database;

showPage($db, $output);

由于多种原因,这样做更好:

  • 本地化/封装/命名空间功能(函数体不再具有对外界的隐式依赖性,反之亦然,只要函数调用没有改变,您现在可以重写任一部分而无需重写另一部分)< / LI>
  • 允许单元测试,因为您可以独立测试功能而无需设置特定的外部世界
  • 只需查看签名
  • ,就可以清楚地了解函数的功能

答案 2 :(得分:7)

  

然而,这样做会产生性能和安全后果。

说实话,没有任何表现或安全后果。使用全局变量是一个更清晰的代码问题,仅此而已。 (好吧,好吧,只要你没有传递数十兆字节的变量)

所以,你必须首先思考,替代方案是否会为你制作更清晰的代码。

在更清晰的代码方面,如果我在名为showPage的函数中看到数据库连接,我会感到害怕。

答案 3 :(得分:2)

有些人可能不赞成的一个选择是创建一个负责保存应用程序状态的singleton对象。当您想要访问某些共享的“全局”对象时,您可以拨打电话,如:State::get()->db->query();$db = State::get()->db;

我认为这种方法是一种合理的方法,因为它可以节省必须遍布整个地方的一堆物体。

编辑:

使用此方法有助于简化应用程序的组织和可读性。例如,您的状态类可以调用正确的方法来初始化数据库对象,并将其初始化与showPage函数分离。

class State {
    private static $instance;
    private $_db;

    public function getDB() {
        if(!isset($this->_db)){ 
            // or call your database initialization code or set this in some sort of
            // initialization method for your whole application
            $this->_db = new Database();
        }
        return $this->_db;
    }

    public function getOutput() {
        // do your output stuff here similar to the db
    }

    private function __construct() { }

    public static function get() {
        if (!isset(self::$instance)) {
            $className = __CLASS__;
            self::$instance = new State;
        }
        return self::$instance;
    }

    public function __clone() {
        trigger_error('Clone is not allowed.', E_USER_ERROR);
    }

    public function __wakeup() {
        trigger_error('Unserializing is not allowed.', E_USER_ERROR);
    }
}

你的节目页面功能可能是这样的:

function showPage(){
     $output = State::get()->getOutput();
     $output['header']['title'] = State::get()->getDB()->getConfig( 'siteTitle' );
     require( 'myHTMLPage.html' );
     exit();
}

使用单例对象的另一种方法是将状态对象传递给各种函数,如果应用程序变得复杂并且只需要传递一个状态对象,则可以使用其他“状态”。

function showPage($state){
     $output = $state->getOutput();
     $output['header']['title'] = $state->getDB()->getConfig( 'siteTitle' );
     require( 'myHTMLPage.html' );
     exit();
}

$state = new State; // you'll have to remove all the singleton code in my example.
showPage($state);

答案 4 :(得分:0)

function showPage(&$output, $db = null){
     $db = is_null( $db )  ? new Database() : $db;
     $output['header']['title'] = $db->getConfig( 'siteTitle' );
     require( 'myHTMLPage.html' );
     exit();
}

$output['header']['log_out'] = "Log Out";
showPage($output);

 $db =new Database();
showPage($output,$db);

答案 5 :(得分:0)

开始在OOP中设计代码,然后可以将config传递给构造函数。您还可以将所有函数封装到类中。

<?php 
class functions{
    function __construct($config){
        $this->config = $config;
    }

    function a(){
        //$this->config is available in all these functions/methods
    }

    function b(){
        $doseSomething = $this->config['someKey'];
    }

    ...

}


$config = array(
'someKey'=>'somevalue'
);

$functions = new functions($config);

$result = $functions->a();
?>

或者,如果您无法重构脚本,请遍历配置数组并定义常量。

foreach($config as $key=>$value){
    define($key,$value);
}