PHP class_exists总是返回true

时间:2012-11-07 10:52:13

标签: php

我有一个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的回答

2 个答案:

答案 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类。

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