0

我有一个 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
{
   ...
}

以上内容包含在其他需要使用 Product 使用require_once的 PHP 文件中。然而,任何想要使用 Product 的人都必须确保这些全局变量可用,至少是这样的想法。

我最近在 Product 类中的一个函数中调试了一个问题,该问题是由于 $gLogger 为空而引起的。需要上述 Product.inc.php 的代码无需费心创建 $gLogger。所以问题是如果 $gLogger 为空,这个类是如何包含的?

我尝试调试代码(NetBeans 中的 xdebug),在 Product.inc.php 的开头放置一个断点来查找,每次遇到 if (class_exists('Product')) 子句时,它都会简单地介入并return 因此永远不会进入全局检查。那么它是如何第一次包含在内的呢?

这是在 MAMP (Apache/MySQL) 下运行的 PHP 5.1+。我没有定义任何自动装载机。

感谢您提供信息丰富的答案。我的信念是,当您包含一个文件时,PHP 从第一行开始逐行执行它,因此如果未定义全局变量,它将不允许我包含该文件。我会将检查移到构造函数中。基于原始问题,我接受@deceze 的回答

4

2 回答 2

4

文件在执行之前被解析。类通过解析“加载”,但函数在解析后执行。通过将函数调用与类放在同一个文件中,类总是在该函数执行之前被解析和“加载”,因此它总是true.

如果您总是使用require_once(这很好)包含文件,那么无论如何检查都没有意义。类定义不应有条件地依赖于某些全局变量。重新考虑你在这里做什么。

于 2012-11-07T10:58:43.753 回答
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));
            }
        }
    }

    ...
}

当您实例化 aProduct时,它会自动检查是否满足先决条件:

$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与全局变量一起使用的类进行类实例化。

您还可以创建一个辅助函数来执行此操作,以便您可以将它用于不同的类。取决于你的需要。只需找到一个可以在旧代码和新代码之间划清界限的边界。

于 2012-11-07T11:06:41.413 回答