PHP类_exists始终返回true

PHP类_exists始终返回true,php,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 { ... }

我有一个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
{
   ...
}
上述内容包含在需要使用产品的其他PHP文件中,这些文件使用require\u once。然而,任何想要使用该产品的人都必须确保这些globals是可用的,至少这是一个想法

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

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

这是在MAMP(Apache/MySQL)下运行的PHP5.1+。我没有定义任何自动加载器

谢谢各位提供的信息丰富的答案。我相信当你 包含一个文件PHP从第一行开始逐行执行它,所以 如果全局文件不存在,它将不允许我包含该文件 定义我将把检查移到构造函数中。基于 原始问题,我接受@deceze的回答

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


如果您总是使用
require\u一次包含文件(这很好),那么该检查就没有意义了。类定义不应该有条件地依赖于某些全局变量。重新考虑一下你在这里做什么。

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


如果您总是使用
require\u一次包含文件(这很好),那么该检查就没有意义了。类定义不应该有条件地依赖于某些全局变量。重新思考你在这里做什么。

我看到了一个主要问题:

// 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));
            }
        }
    }

    ...
}
实例化
产品时,它会自动检查是否满足先决条件:

$blueShoes = new 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);
这样,您就不必再关心
产品
中的任何全局内容了


您评论说您希望逐步改进代码。你可以这样做,这里是如何做到的。如前所述,上面的第二个变体是可行的,但目前遗留代码与之不兼容。在任何情况下,如果
产品
类是新代码,则应该使用依赖项注入来编写它。这对于将旧代码与新代码分开很重要。你不想让遗留的东西被你的新代码吞没。这将使新代码成为遗留代码,因此您将无法逐步改进。您只需添加新的遗留代码

因此,以依赖项注入的类定义为例。为满足您的遗留需求,编写第二个类来屏蔽:

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
类,该类与全局变量一起用于类实例化


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

我在这里看到一个主要问题:

// 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));
            }
        }
    }

    ...
}
实例化
产品时,它会自动检查是否满足先决条件:

$blueShoes = new 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);
这样,您就不必再关心
产品
中的任何全局内容了


您评论说您希望逐步改进代码。你可以这样做,这里是如何做到的。如前所述,上面的第二个变体是可行的,但目前遗留代码与之不兼容。在任何情况下,如果
产品
类是新代码,则应该使用依赖项注入来编写它。这对于将旧代码与新代码分开很重要。你不想让遗留的东西被你的新代码吞没。这将使新代码成为遗留代码,因此您将无法升级