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

Sha*_*neC 10 php security performance

多年来,我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();
}
Run Code Online (Sandbox Code Playgroud)

但是,这样做会产生性能和安全后果.

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

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

hak*_*kre 15

1.全球.奇迹般有效.因为我不想使用它,所以全球都很讨厌.

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

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

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

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

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


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

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

依赖注入

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

假设配置数组是我们想要注入的依赖项.让我们调用它config并命名变量$config.因为它是一个数组,我们可以将其类型提示为array.首先,parse_ini_file如果您更喜欢ini文件格式,也可以使用包含文件中的配置.我认为它更快.

config.php:

<?php
/**
 * configuration file
 */
return array(
    'db_user' => 'root',
    'db_pass' => '',
);
Run Code Online (Sandbox Code Playgroud)

然后可以在您的应用程序中需要该文件,您希望:

$config = require('/path/to/config.php');
Run Code Online (Sandbox Code Playgroud)

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

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

        ...
}
Run Code Online (Sandbox Code Playgroud)

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

public function __construct(array $config)
Run Code Online (Sandbox Code Playgroud)

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

第二个例子是有一个公共setter方法:

public function setConfig(array $config)
Run Code Online (Sandbox Code Playgroud)

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

服务定位器

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

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

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

第一个例子:假设函数oneICanNotChange不是对象的一部分,而只是全局命名空间中的一个简单函数.我们无法注入config依赖.这是ServicesService Locator对象的来源:

$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'];

    ...
Run Code Online (Sandbox Code Playgroud)

如示例所示,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]);
   }
   ...
}
Run Code Online (Sandbox Code Playgroud)

这个示例性存根只需要工厂脚本,如果它还没有完成,否则将返回脚本返回值,如数组,对象甚至字符串(但没有NULL意义).

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


dec*_*eze 8

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

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


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

showPage($db, $output);
Run Code Online (Sandbox Code Playgroud)

出于以下几个原因,这样做更好:

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

  • 我认为这可以追溯到正确构建应用程序的更大问题.如果你需要继续传递更多的资源,这意味着这些函数的功能会发生变化(否则它们不需要资源,对吗?),这意味着你的函数可能具有错误定义的职责.假设存在合法需求,您可以传递包含所有所需资源的通用对象或数组(如果您需要传递多个参数,则建议无论如何).MVC控制器对象方法也可以提供帮助. (4认同)

You*_*nse 7

但是,这样做会产生性能和安全后果.

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

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

在更清晰的代码问题上,如果我在名为showPage的函数中看到数据库连接,我会感到害怕.