1。全球人。奇迹般有效。 Globals 很讨厌,因此我不想使用它。
嗯,不只是讨厌全局变量。他们被讨厌是有原因的。如果您还没有遇到全局变量导致的问题,那很好。您无需重构代码。
2。在我的 config.php 文件中定义一个常量。
这实际上就像一个全局变量,但有另一个名称。您也可以省略$,并在函数的开头使用global。 Wordpress 为他们的配置做了这个,我想说这比使用全局变量更糟糕。这使得引入接缝变得更加复杂。您也不能将对象分配给常量。
3。在函数中包含配置文件。
我认为这是开销。您对代码库进行分段并没有太大收获。此处的“全局”将成为您包含的文件的名称 btw..
考虑到您和我的 cmets 对他们的这三个想法,我会说:除非您遇到一些全局变量的实际问题,否则您可以坚持使用它们。然后全局作为您的服务定位器(配置、数据库)。其他人做更多的事情来创造同样的东西。
如果您遇到问题(例如,您可能想开发测试驱动),我建议您先将一个部分放在另一个待测部分,然后学习如何避免全局变量。
依赖注入
在 cmets 内部,很明显您正在寻找依赖项注入,如果您无法编辑函数参数定义,您可以 - 如果您使用对象 - 通过构造函数或使用所谓的 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 Service 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'];
...
正如示例已经显示的那样,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,这很有意义)。
我希望这些示例对您有所帮助,并表明无需太多代码即可在此处获得更大的灵活性并从您的代码中剔除全局变量。但是您应该清楚,服务定位器将全局状态引入您的代码。好处只是,更容易将它与具体的变量名分离,并提供更多的灵活性。也许您可以将您在代码中使用的对象划分为特定组,其中只有一些需要通过服务定位器变得可用,并且您可以根据定位器保持代码较小。