2012-11-07 49 views
0

我有一个PHP类,它需要一些预先定义的全局包含文件之前:PHP class_exists总是返回true

文件:包括/ 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_once。任何想要使用产品的人都必须确保这些全局变量可用,至少这是这个想法。

我最近调试了一个产品类中由于$ gLogger为null而导致的问题。需要上述Product.inc.php的代码并没有打算创建$ gLogger。所以问题是,如果$ gLogger为null,这个类是如何包含的?

我试着调试代码(NetBeans中的xdebug),在Product.inc.php的开头放置了一个断点,以便每次找到if(class_exists('Product'))子句时它都会简单地介入并返回,从而永远不会进入全局检查。那么它是如何第一次被包括在内的呢?

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

感谢您提供丰富的答案。我的信念是,当你 包括文件PHP开始从一个行执行它一行行,所以 它不会让我包括文件,如果全局没有 定义。我将把支票移到构造函数中。基于该 原来的问题,我接受@deceze

+3

这个类是在那里声明的,所以它存在 – Esailija

+2

顺便说一句,你可以使用'!isset($ gLogger,$ db,$ glob)'。 – deceze

+0

@deceze,很棒的提示! – iMoses

回答

3

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

如果你总是包括使用require_once(好的)的文件,有没有点在检查反正。类定义不应该有条件地依赖于某些全局变量。重新思考你在这里做什么。

+0

谢谢,那么如何获得想要的行为,除非已定义全局变量,否则Product不能包含(require_onced)? – Ali

+1

重新思考你在这里做什么。如果有的话,让***实例化***取决于全局变量,但不取决于***定义。更好的是,不要依赖全局变量。将这些作为参数传递给类,并让其他代码检查是否要实例化类。 – deceze

+0

你真的打算实现什么?你是否真的需要将类定义保存在代码之外,还是只想限制实例的创建,直到满足您的验证? – iMoses

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

您也可以创建一个辅助函数来执行此操作,以便您可以将它用于不同的类。取决于你的需求。只需找到一个边框,您可以在旧代码和新代码之间绘制清晰的线条。

+0

感谢您的详细解决方案。我知道产品需要全局变量很糟糕,但是有很多类,他们都需要全局变量。因此,将它们传递给每个类构造函数会使整个代码更难以阅读。 $ product =新产品($ id)看起来像一个更清洁的API? – Ali

+0

你应该在这里分开你的需求。如果'Product'和许多* *类似的类共享一个共同的基础,你可以让它们共享一个基类。此外,您可以为这些处理正确初始化的工厂对象创建一个工厂对象:'$ factory = new Factory($ gLogger,$ db,$ glob);''然后'$ redShoes = $ factory-> make($ id) ;'。这是干净的API,更少的全局变量,更简化的代码=更少的错误。 – hakre