PHP能够进行魔术实例化吗?

5
给定一个类class RandomName extends CommonAppBase {},有没有办法自动创建任何扩展CommonAppBase的类的实例,而不必显式使用new
通常每个PHP文件只有一个类定义。在所有文件的末尾添加new RandomName()是我想要消除的事情。扩展类没有构造函数;只调用了CommonAppBase的构造函数。CommonAppBase->__construct()启动应用程序的其余部分执行。
奇怪的问题,但如果有人知道解决方法就太好了。
编辑
进一步说明下面的评论。进行实例化的代码不会在类文件中。类文件将仅仅是这样,我想让一些其他代码include('random.class.php')并且实例化任何扩展CommonAppBase的类。
对于任何不确定我要什么的人,my hackish answer可以做到我想要的,但不是最理智的方式。
感谢您的提前帮助,Aiden
(顺便说一句,我的PHP版本是5.3.2)请在回答中注明版本限制。
答案如下:
可以通过php.ini或Apache将以下内容附加到文件中,以自动启动特定父类的类。
首先(感谢dnagirl)
$ca = get_declared_classes();
foreach($ca as $c){
    if(is_subclass_of($c, 'MyBaseClass')){
        $inst = new $c();
    }
}

并且(接受的答案,作为最接近的答案)

auto_loader();
function auto_loader() {
    // Get classes with parent MyBaseClass
    $classes = array_filter(get_declared_classes(), function($class){
        return get_parent_class($class) === 'MyBaseClass';
    });
    // Instantiate the first one
    if (isset($classes[0])) {
        $inst = new $classes[0];
    }
}

3
我不这么认为 - 你总是需要使用 new。但是首先你需要这个是为了什么?在类文件内进行实例化并不是一个很好的做法。 - Pekka
@Pekka 一个auto_append_file附加文件将只在特定环境下实例化。对于测试来说,测试代码会包含该类并手动实例化。 - Aiden Bell
1
静态类方法不需要使用“new”来实例化。 - powtac
你可以通过在“注册表”中注册所有对象来实现它... - powtac
@powtac,听起来比添加“new”和几个“if”要费更多的力气。 - Aiden Bell
10个回答

2

我可能误解了你的问题,但如果你想在整个应用程序中使用类的单个实例(例如数据库类),我建议你使用工厂模式。然后,每当你需要这个类的时候,你可以像这样做...

$obj = &MyFactory::getClass('mysql_database_class');

我肯定会排除在类文件结尾实例化你的类。如果你试图遵循标准的面向对象编程原则,那么你应该尽量避免这样做,因为它可能会在代码其他地方引起冲突。


2
自PHP4以来,通过引用返回对象并没有为性能带来任何好处,并且在5.3中被弃用。 - ryeguy
难道不是将“new”OBJ的返回值通过引用赋值已经被弃用了吗?如果我在帖子中表达不清楚,敬请谅解。将对象存储在静态变量中,然后在工厂调用时检查您的静态变量是否已经实例化。如果是,则从静态变量中返回对象,如果没有则开始一个新的对象。 - Jarrod Nettles

2

我不确定下面的内容是否完全符合您的要求,但这是一个想法。代码应该相当容易理解。

auto_loader();
function auto_loader() {
    // Get classes with parent MyBaseClass
    $classes = array_filter(get_declared_classes(), function($class){
        return get_parent_class($class) === 'MyBaseClass';
    });
    // Instantiate the first one
    if (isset($classes[0])) {
        $inst = new $classes[0];
    }
}

注意:这些函数早在PHP 4的生命周期中就已经存在,但与array_filter一起使用的匿名函数语法仅在PHP 5.3.0中引入。

+1并接受。我错过了你的答案,但我和dnagirl用不同的方式得出了同样的结论。你的答案最接近且完整。 - Aiden Bell

1

我认为你总是需要使用new

如果你想要加载一个类文件并实例化其中的任何类,我认为唯一真正可靠的方法是在包含之前和之后使用get_defined_classes()。这两个列表之间的任何差异(array_diff())都将被实例化。我在过去的项目中做过这件事,但出于对优雅性和性能的考虑,我今天不会再这样做了。

我认为最好的方法是非常严格地命名。因此,当你加载random.class.php时,你定义了一个约定,即在其中有一个名为random的类,并自动实例化它:

$classname = "random";

require "$classname.class.php";
$$classname = new $classname();  // Produces an object instance `$random`

+1 个实用的建议。这是我的初步想法。我理解你所说的降低优雅度的观点。你提出的命名方式是我的下一个选择。 - Aiden Bell

1

这个做你想要的吗?

class CommonAppBase {}
class RandomName extends CommonAppBase {}
$klass = 'RandomName';
$instance = new $klass();

执行 new 操作的代码并不知道 RandomName 的值,只知道它扩展了 CommonAppBase - Aiden Bell
它如何知道它扩展了CommonAppBase? - Zak

1
假设您有一个__autoload函数,那么new的问题是什么?
但是,如果想要更间接的实例化方法,可以将new隐藏在静态方法中。因此,如果您的基类具有这两个方法:
static function make(array $args=NULL){
  $class=static::who(); //note: static, NOT self
  $obj=new $class($args);

  //some test for whether or not $obj is acceptable
  return ($test) ? $obj : false;  
}

abstract static function who(){
  return __CLASS__;
}

然后您的 RandomClass 对象可以尝试以这种方式进行实例化:

$classname= 'RandomClass';
$someargs=array(1,2,3);

if(!$newobj= $classname::make($someargs)) die('cannot make new object');

我事先不知道 'RandomClass' 的值。 - Aiden Bell
@Aiden Bell:那么你什么时候确定你的“RandomClass”呢?是谁发送请求来创建对象,为什么它不能发送类的名称? - dnagirl
一个脚本被简单地告知要包含一个文件。除了其他帖子中提到的命名约定之外...加载脚本不知道文件中类的名称,但必须实例化它。我不想在类文件本身中实例化它。 - Aiden Bell

1

我的尝试,勉强做到了我想要的效果

function auto_loader()
{
    $file = $_SERVER['SCRIPT_FILENAME']; // Some class file
    $cont = file_get_contents($file);
    $tokens = token_get_all($cont);
    for($i=0; $i<sizeof($tokens); $i++) {
        // Token = $token[$i][0]
        // Lexeme is $token[$i][1]
        if($tokens[$i][0]==T_EXTENDS && $tokens[$i+2][1]=="MyBaseClass"){
            // Get the lexeme of the class
            $class = $tokens[$i-2][1];
            break;
        }
    }
    $inst = new $class();
}
auto_loader();

这是通过Apache或其他方式自动添加的。看起来可以工作,但没有考虑到extendsBaseAppClass之间没有/变化的空格($i-2)。

作为一个hack,它似乎可以工作,再加上一些代码并将其链接到cache,它可能会成为一个竞争者。稍微过度设计了一点。

因此,当请求http://www.foo.com/some_class_file.php时,以上内容将被Apache/php.ini附加,并且可以实例化some_class_file.php中的类并开始执行,而不管类名如何。这是因为在我的情况下,URL与类没有关系。它可以是一个实例化的类MyDogBenjiClass

尝试2,感谢dnagirl 以下内容会像上面一样自动附加以启动应用程序。

$ca = get_declared_classes();
foreach($ca as $c){
    if(is_subclass_of($c, 'MyBaseClass')){
        $inst = new $c();
    }
}

1
看起来不错,但每次请求都在数十个文件上运行分词器...?我的意思是,当PHP解析文件时,它必须这样做,但仍然不太对。我更喜欢使用命名约定。 - Pekka
嗯,现在我大概明白你想要什么了!如果你尝试在每个“include”之后使用“get_declared_classes()”,并取数组中的最后一个条目来获取你的$classname,那会怎样呢?你可以使用is_subclass_of('MyBaseClass)检查父类关系,然后使用new $classname实例化。 - dnagirl
@Pekka - 我也觉得不太对劲。我希望我的问题能引出一些更有效的方法。@ndagirl - 我认为那种方法会起作用...(而且更有效率),我会试一下。 - Aiden Bell
@dnagirl - 我在这个答案的结尾基于你的方法提供了一个解决方案。 - Aiden Bell

1

call_user_func_array(array($className,'__construct'),$args);


+1 你明白我的意思,但是:call_user_func_array()要求参数1必须是一个有效的回调函数,不能静态地调用BaseApplication::__construct()这个非静态方法。 - Aiden Bell
1
call_user_func_array() 无法调用构造函数。会抛出一个“期望参数1为有效回调,非静态方法MyClass::__construct()不能被静态调用”的错误。详情请参见http://blog.esfex.com/How-to-call-a-PHP-constructor-like-call_user_func_array.html。 - dnagirl
@Aiden Bell:没错,事实上我知道它行不通的唯一原因是因为我上周试过了。 - dnagirl

0

您是否考虑过使用反射动态实例化类?

$cls = new ReflectionClass('ClassName');
$obj = $cls->newInstance();

你也可以检索其父类


我不知道在脚本执行的那个时刻'ClassName'。我只知道基类。 - Aiden Bell
你应该知道你的类文件在哪里,或者至少知道在哪里查找它们。使用有意义的目录/文件命名应该让你将文件名映射到类名。 - nuqqsa

0

作为极客实验,您可以使用APD函数override_function来覆盖include函数。

在新函数中,您可以对包含的文件进行标记化处理(以获取类定义),并在找到类时进行实例化。


我在考虑这个,以及其他一些事情,比如使用正则表达式将“ClassFoo extends Base”部分筛选出来。如果我可以缓存它,可能不会太糟糕。但有点锁定哦 ;) - Aiden Bell
谢谢Pekka - 我应该在我的回答中放上那个链接,那正是我想说的! - Adam Hopkinson
@ Pekka - 请看我使用 token_get_all() 的尝试性答案,谢谢! - Aiden Bell

0
  1. 将每个URL映射到index.php
  2. $_SERVER['REQUEST_URI']中找出文件(或者您认为合适的方式)
  3. 使用变量类名来初始化($main = new $file()

MVC框架通常会以更结构化的方式执行此操作:

  1. 通过mod_rewrite重定向请求到index.php,加载并运行某种调度程序
  2. 调度程序解析URL:http://example.com/foo/bar/baz转换为$controller = 'foo',$action = 'bar',$param = 'baz'
  3. 调度程序包括controllers/foo.php
  4. 然后实例化它:$controllerInstance = new $controller()
  5. 然后调用操作并将参数传递给它:call_user_func_array(array($controllerInstance, 'do'.ucfirst($action)), array($param))

URL路由的良好描述。但这不是我想要的 ;) - Aiden Bell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接