我有一个PHP类,在包含该文件之前需要一些预定义的全局变量:
文件:includes / Product.inc.php
if (class_exists('Product')) {
return;
}
// This class requires some predefined globals
if ( !isset($gLogger) || !isset($db) || !isset($glob) ) {
return;
}
class Product
{
...
}
以上内容包含在需要使用 require_once 的产品的其他PHP文件中。但是,任何想要使用Product的人都必须确保这些全局变量可用,至少这是个想法。
我最近在Product类的函数中调试了一个问题,因为$ gLogger为null。需要上面的Product.inc.php的代码没有费心去创建$ gLogger。所以问题是如果$ gLogger为空,这个类是如何被包含的?
我尝试调试代码(NetBeans中的xdebug),在Product.inc.php的开头放置一个断点来查找,每次遇到if(class_exists('Product'))子句时它都会简单介入并返回因此永远不会进行全局检查。那么第一次怎么包括它?
这是在MAMP(Apache / MySQL)下运行的PHP 5.1+。我没有定义任何自动加载器。
感谢您提供丰富的答案。我的信念是,当你 包含一个文件PHP从第一行开始逐行执行,所以 如果全局变量没有,它将不允许我包含该文件 定义。我将检查移动到构造函数中。基于 原始问题,我接受@deceze的回答
答案 0 :(得分:3)
文件在执行之前已解析。通过解析来“加载”类,但是在解析之后执行函数。通过将函数调用放在与该类相同的文件中,该类总是在该函数执行之前被解析并“加载”,因此它总是true
。
如果你总是使用require_once
包含文件(这很好),那么无论如何都没有必要进行检查。类定义不应有条件地依赖于某些全局变量。重新思考你在这里做的事情。
答案 1 :(得分:2)
我在这里看到一个主要问题:
// This class requires some predefined globals
这对您来说可能会让您感到惊讶,但我认为您实际想要做的是,如果是这种情况,您在定义类时不会检查它,而是在实例化它时。
当实例化一个类时,会调用它的构造函数。这似乎是我检查的最佳位置:
class Product
{
public function __construct() {
// This class requires some predefined globals
$this->needGlobal('gLogger', 'db', 'glob');
}
private function needGlobal() {
foreach (func_get_args() as $global) {
if (!isset($GLOBALS[$global])) {
throw new RuntimeException(sprintf('Global %s needed but not set.', $global));
}
}
}
...
}
当您实例化Product
时,它会自动检查是否满足前提条件:
$blueShoes = new Product();
如果不满足前提条件,这将不起作用,但如果符合,则无效。
但这只是部分解决你的问题。代码的真正问题是Product
需要全局变量才能工作。
相反,让产品只需要处理它需要的东西:
class Product
{
private $gLogger;
private $db;
private $glob;
public function __construct(LoggerInterface $gLogger, DbInterface $db, GlobInterface $glob) {
$this->gLogger = $gLogger;
$this->db = $db;
$this->glob = $glob;
}
...
}
用法:
$redShoes = new Product($gLogger, $db, $glob);
然后你不再需要关心Product
内的任何全局内容。
您评论过您希望逐步改进代码。你可以这样做,这是怎么做的。如上所述,上面的第二个变体是要走的路,但目前遗留代码与它不兼容。在任何情况下,如果Product
类是新代码,您应该使用依赖注入来编写它。这对于将遗留代码与新代码分开很重要。您不希望新代码吞噬旧版内容。这将使新代码遗留代码,因此您将无法逐步改进。您只需添加新的遗留代码。
因此,使用依赖注入来获取类定义。为了满足您的遗留需求,请编写另一个屏蔽此类的课程:
class ProductLegacy extends Product
{
public function __construct() {
// This class requires some predefined globals
list($gLogger, $db, $glob) = $this->needGlobal('gLogger', 'db', 'glob');
parent::__construct($gLogger, $db, $glob);
}
private function needGlobal() {
$variables = array();
foreach (func_get_args() as $global) {
if (!isset($GLOBALS[$global])) {
throw new RuntimeException(sprintf('Global %s needed but not set.', $global));
}
$variables[] = $GLOBALS[$global];
}
return $variables;
}
}
正如您所看到的,这个小小的存根以全新的方式汇集了全球的做事方式。您可以在新代码中使用Product
类,如果需要与旧代码接口,可以使用适用于类实例化的全局变量的ProductLegacy
类。
您还可以创建一个正在执行此操作的辅助函数,因此您可以将它用于不同的类。取决于您的需求。只需找到一个边框,您可以在旧代码和新代码之间划清界限。