1. 全局变量。可以正常工作。全局变量不受喜爱,这就是我不使用它的原因。
其实全局变量并不只是不受喜爱,有它们被讨厌的原因。如果你没有遇到全局变量带来的问题,那就没关系了。你不需要重构你的代码。
2. 在我的config.php文件中定义一个常量。
这其实就像是一个全局变量,但是用另一个名字。你也不需要使用$
,并且在函数开头使用global
。WordPress就是这样为它们的配置做的,我认为这比使用全局变量更糟糕。这会让引入seams变得更加复杂。而且你无法将对象赋值给常量。
3. 在函数中包含config文件。
我认为这是一种额外的负担。你将代码库分割成了更小的部分,但收益微乎其微。这里的“全局变量”在其中会变成你所包含的文件的名称。
考虑到你的三个想法和我的评论,我想说:除非你遇到了某些全局变量的实际问题,否则你可以继续使用它们。全局变量可以作为你的服务定位器(配置、数据库)工作。其他方法会做更多事情来完成同样的任务。
如果你遇到了问题(例如,你可能想要开发测试驱动),我建议你逐步对每部分进行测试,然后学习如何避免使用全局变量。
依赖注入
由于在评论中已经明确表明您正在寻找依赖注入,如果您无法编辑函数参数定义,则可以-如果您使用对象-通过构造函数或使用所谓的setter方法注入依赖项。在下面的示例代码中,我将演示如何同时使用这两种方法,仅供演示目的,您可能已经猜到,同时使用两种方法是没有用的:
假设我们希望注入的依赖关系是配置数组。我们将其称为config
,并将变量命名为$config
。由于它是一个数组,因此我们可以将其类型提示为array
。首先在一个包含文件中定义配置,也可以使用parse_ini_file
,如果您喜欢ini文件格式,我认为这甚至更快。
config.php
:
<?php
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 method):
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
,这没有意义)。
我希望这些示例有所帮助,并且表明在这里不需要太多的代码就可以获得更大的灵活性并且减少代码中的全局变量。但是必须清楚,服务定位器会为您的代码引入全局状态。好处仅在于, 更容易将具体变量名称与其解耦并提供更大的灵活性。也许您能够将代码中使用的对象分成某些组,其中仅有一些需要通过服务定位器可用,并且可以使依赖定位器的代码保持简短。
$foo = some_function($connectionObject);
- tereško