在框架开发,模块化开发等场合,我们可能有一种需求,那就是在PHP运行时动态实例化对象。

什么是动态实例化对象呢?我们先来看一下PHP有一种变量函数(可变函数)的概念,例如如下代码:

function foo() {
    echo 'This is the foo function';
}
$bar = 'foo';
$bar();

运行上述代码将会输出“This is the foo function”。具体请参考PHP手册:可变函数。当然,如果需要动态调用的话,那么就使用call_user_func或call_user_func_array函数。这两个函数的用法不是本文的重点,不懂的同学请查阅其它资料。回到本文的话题上:什么是动态实例化对象?本人认为动态实例化对象,即是:需要实例化的对象是在程序运行时(run-time)动态决定(由变量决定)需要实例化什么样的对象,而不是直接写死在代码里。

通过上述例子我们已经知道了如何在运行时动态调用一个函数了,在现在面向对向如此流行的今天,在一些代码中,我们需要去动态去实例化一个类,该怎么做呢?

情况一:类的构造函数没有参数或参数的个数是确定的

如果类的构造函数没有参数或者我们要实例化的类根本没有构造函数,似乎简单一点,可以照上面的例子改一个嘛,嗯,照葫芦画瓢,谁不会:

代码示例:(构造函数没有参数)

class FOO {
    private $a, $b;
    public function __construct() {
        $this->a = 1;
        $this->b = 2;
    }
    public function test() {
        echo 'This is the method test of class FOO<br />';
        echo '$this->a=', $this->a, ', $this->b=', $this->b;
    }
}
$bar = 'FOO';
$foo = new $bar();
$foo->test();

运行一下,就看到了输出了如下结果:

//This is the method test of class FOO
//$this->a=1, $this->b=2

嗯,我们要传参的话,那么就这样吧:

class FOO {
    private $a, $b;
    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }
    public function test() {
        echo 'This is the method test of class FOO<br />';
        echo '$this->a=', $this->a, ', $this->b=', $this->b;
    }
}
$bar = 'FOO';
$foo = new $bar('test', 5);
$foo->test();

一样可以得到类似的结果:

//This is the method test of class FOO
//$this->a=test, $this->b=5

很理想嘛。

情况二:类的构造函数的参数个数不确定

这种情况就要麻烦很多了,但是如果要写得比较通用的话,就不得不考虑这种情况了。例如,我们有如下两个类

class FOO {
    public function test() {
        echo 'This is the method test of class FOO';
    }
}
class BAR {
    private $a, $b;
    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }
    public function test() {
        echo 'This is the method test of class BAR<br />';
        echo '$this->a=', $this->a, ', $this->b=', $this->b;
    }
}

我们想要一个通用的方式来实例化这两个类。我们注意到FOO类是没有写构造函数的,或者是可以认为FOO类的构造函数的参数个数为零;而BAR类的构造函数却有参数。还好,PHP5已经足够强大了,引入了反射的概念,具体请参考PHP手册:反射,虽然手册上也没有什么可参考的:)。还好,命名写得不错,从类名和方法名上面已经可以看出大概的端倪,不需要太多的文字。

那么好吧,就让我们用PHP5的反射来着手这个事情:

(还在用PHP4的同学请不要走开,如果你拥有一个没有反射的PHP版本或者是你为了兼容也好还是不想升级也好,反正不想用反射的,下面有解决方案)

$class = new ReflectionClass('FOO');
$foo = $class->newInstance(); //或者是$foo = $class->newInstanceArgs();
$foo->test();

看到什么了没有?接着来:

$class = new ReflectionClass('BAR');
$bar = $class->newInstanceArgs(array(55, 65));
$bar->test();

OK,似乎可以了,那么就整理一下吧,来个通用函数,我们想这么设计,此函数的第一个函数是要实例化的类名,从第二个参数开始就是要实例化类的构造函数的参数,有几个就写几个上去,没有就不写。要想实现参数个数可变的函数,我们有两种方法:

第一种是类似于:

function foo($arg1, $arg2 = 123, $arg3 = 'test', $arg4 = null, ....... ) {
    //some code;
}

的办法,这种方法有两个缺点,第一个是你如果需要传100个参数难道就写100个参数吗?第二个是你还要在程序里判断哪个参数是不是null,或是其它默认值。(题外话:这种写法的参数默认值必须放在最后,你不能将没有默认值的参数插在有默认值的中间或前面,否则你调用的时候也必须显式地写上有默认值参数的值)

另一种实现参数数量可变的方法是在函数里用PHP的内置函数func_get_args(猛击这里看手册)取得传给函数的参数。类似的函数有func_get_num和func_get_arg,算了,我懒,你们自己找手册看吧。

那么,用这个函数似乎要方便很多,我们根据想象的函数参数的排列,代码应该是这个样子的:

function newInstance() {
    $arguments = func_get_args();
    $className = array_shift($arguments);
    $class = new ReflectionClass($className);
    return $class->newInstanceArgs($arguments);
}

OK,让我们来看一下效果:

$foo = newInstance('FOO');
$foo->test();
//输出结果:
//This is the method test of class FOO

$bar = newInstance('BAR', 3, 5);
$bar->test();
//输出结果:
//This is the method test of class BAR
//$this->a=3, $this->b=5

短短四行代码,效果相当完美啊。那么,如果应用到类里面,我们可以利用这种思想,直接写成魔术方法,可以让我们的类更酷哦!

class INSTANCE {
    function __call($className, $arguments) {
        $class = new ReflectionClass($className);
        return $class->newInstanceArgs($arguments);
    }
}
$inst = new INSTANCE();
$foo = $inst->foo();
$foo->test();
//输出结果:
//This is the method test of class FOO

$bar = $inst->bar('arg1', 'arg2');
$bar->test();
//输出结果:
//This is the method test of class BAR
//$this->a=3, $this->b=5

咔咔,爽吧。

接下来讨论一下不使用反射类的情况。例如PHP4中就没有反射,而一些老项目就是运行在PHP4上面的。或者是要保证项目对未知环境的兼容性,Whatever,来关心一下怎么动态传参吧。PHP中动态传参的函数只有一个:call_user_func_array(轻击此处查看手册)。这是一个动态调用函数的函数,作用是可以将函数的参数以数组的形式传递给要调用的函数。好吧,我自己也被自己绕晕了,直接来看实例:

function foo($a, $b) {
    echo '$a=', $a, '<br />';
    echo '$b=', $b;
}
call_user_func_array('foo', array(1, 'string'));
//本例输出结果:
//$a=1
//$b=string

那么,要实现用这种方法来动态实例化对象并传参,呃……,只有曲线救国了,我们得先写一个函数,让这个函数来实例化对象,而这个函数的参数就原原本本地传给要实例化对象的类的构造函数就好了。打住!那这个函数得有几个参数啊?怎么实现传递不同个数的参数呢?嘿嘿,我一声冷笑,你忘了PHP里提供一个创建匿名函数的函数吗?(又开始绕起来了……)create_function(手册在此),照着手册里面的例子直接画就可以了,我也懒得打字了,直接看下面的代码,注释我写清楚点大家都明白了:

function newInst() {
    //取得所有参数
    $arguments = func_get_args();

    //弹出第一个参数,这是类名,剩下的都是要传给实例化类的构造函数的参数了
    $className = array_shift($arguments);

    //给所有的参数键值加个前缀
    $keys = array_keys($arguments);
    array_walk($keys, create_function('&$value, $key, $prefix', '$value = $prefix . $value;'), '$arg_');

    //动态构造实例化类的函数,主要是动态构造参数的个数
    $paramStr = implode(', ',$keys);
    $newClass=create_function($paramStr, "return new {$className}({$paramStr});");

    //实例化对象并返回
    return call_user_func_array($newClass, $arguments);
}

好了,至于效果是什么,就麻烦各位看官自己动动手,运行一下看看,是不是自己期望的结果。

如果改写成类里面的魔术方法,哼哼,威力嘛,你懂的!然后呢,我还是懒了,如果要写成魔术方法的话,相信这么easy的事情你很轻松就办到了。就当一个作业吧。另,本文的代码都是本人运行过的,但是,写文章的时候没有使用复制/粘贴功能,所以,你最好是也不要复制粘贴。如果从本文中的代码copy下来运行出错的话,还烦请各位自己debug一下,编程不就是要自己写么。

本来这个话题到这里就应该可以结束了,但是想到我在想到这个办法之前用的方法(好绕……),本着重在交流的态度,一起放出来。我例两个关键函数吧:extracteval。只是我个人觉得用eval函数比较山寨,能不用最好不用。于是又想出了上面的办法,哈哈,编程最重要的是思想,不是吗?好,又是一道作业题了,大家可以试试用这两个函数(当然也会用到别的函数)来实现带不定数量的参数动态实例化对象的函数。

转自:http://blog.unlink.link/php/php_runtime_instance_class_and_pass_parameters.html

http://blog.phpdr.net/

PHP动态实例化对象并向构造函数传递参数的更多相关文章

  1. 2016/3/21 面向对象: ①定义类 ②实例化对象 ③$this关键字 ④构造函数 ⑤析构函数 ⑥封装 ⑦继承

    一:定义类   二:实例化对象 //定义类 class Ren { var $name; var $sex; var $age; function Say() { echo "{$this- ...

  2. JavaScript学习总结(三、函数声明和表达式、this、闭包和引用、arguments对象、函数间传递参数)

    一.函数声明和表达式 函数声明: function test() {}; test();    //运行正常 function test() {}; 函数表达式: var test = functio ...

  3. js用for循环为对象添加事件并传递参数

    var objArr = getObjArr(id);   for(var i=0; i<objArr.length; i++){    var param=objArr.param    ad ...

  4. 利用java.lang.reflect.Constructor动态实例化对象

         }                    }                    }                    }  }              Student t = co ...

  5. C#通过重载构造函数传递参数、实现两个窗体下的方法的互相调用

    直接切入主题 有时候同一个项目下我们可能会使用多个窗体,窗体间方法互相调用也不可避免,好了,使用无参无返回值的方法,开始上图 1.新建一个winform项目Form1,并再添加一个窗体Form2:拖入 ...

  6. JS中面向对象的,对象理解、构造函数、原型、原型链

    6.1 理解对象 6.1.1 对象属性类型 ECMS属性有两种类型:数据属性和访问器属性 1 数据属性 [[configurable]] 表示能否通过Delete 删除属性从而从新定义属性,能否修改属 ...

  7. 声明对象的方式/构造函数/原型/this指向

      函数的发展历程(声明函数的方式):     1.通过Object构造函数或字面量的方式创建单个对象 var obj = new Object; obj.name="新华"; o ...

  8. JavaScript OOP(三):prototype原型对象(即构造函数的prototype属性)

    通过构造函数生成的实例化对象,无法共享属性或方法(即每个实例化对象上都有构造函数中的属性和方法):造成了一定的资源浪费 function Obj(name,age){ this.name=name; ...

  9. javascript基础知识--什么是构造函数?什么是实例化对象?

    前言--讲在前面 我想有很多以前很少接触后台编程语言的初学者朋友跟我一样,对javascript里面一系列的“名词”搞的一头雾水.好像大概知道讲的是什么,但其实理解的还是不清楚:我想,学习任何一种知识 ...

随机推荐

  1. FileSaver.js ////////////////////zzzzzzzzzzzzzz

    FileSaver.js 实现浏览器端文件保存的 JavaScript 库 查看次数: 758 下载次数: 89 更新时间: 2015-06-05 发布时间: 2015-06-05 收藏 插件信息金币 ...

  2. mysql数据库的备份与恢复

    假定我们的目标数据库是 test, 表是 user.假定mysql的用户名和密码均为 root. 备份与恢复所用的程序分别是mysql软件包提供的 mysqldump 命令和 mysql 命令.思想很 ...

  3. NSURLSessionTask使用dispatch_semaphore 完成同步机制

    在NSURLSessionTask发起网路请求时,一般是异步操作,如果需要进行同步等待的话,可采用dispatch_semaphore_t信号量基于计数器的一种多线程同步机制.但是在多个线程访问共有资 ...

  4. MI卡UID

    卡号是根据第0扇区第0块的UID,高位和低位互换后转10进制后出的数字.一般读卡器都会在左边补0补足10位.

  5. >Python下使用subprocess中文乱码的解决方案

    # -*- coding: CP936 -*- import subprocess cmd="cmd.exe" begin=101 end=110 while begin<e ...

  6. 忘记mysql root密码的解决方法

    一. MySQL密码的恢复方法一 有可能你的系统没有 safe_mysqld 程序(比如我现在用的 ubuntu操作系统, apt-get安装的mysql) , 下面方法可以恢复 1. 停止mysql ...

  7. linux下TCP/IP及内核参数优化调优(转)

    Linux下TCP/IP及内核参数优化有多种方式,参数配置得当可以大大提高系统的性能,也可以根据特定场景进行专门的优化,如TIME_WAIT过高,DDOS攻击等等. 如下配置是写在sysctl.con ...

  8. FTPClient使用中的问题--获取当前工作目录为null

    使用org.apache.commons.net.ftp.FTPClient 来做ftp的上传下载功能 FTPClient ftp = new FTPClient();ftp.connect(doc. ...

  9. treeview所有节点递归解法(转+说明)或者说递归的实际应用

    public void PrintTreeViewNode(TreeNodeCollection node) { foreach (TreeNode n in node) { Response.Wri ...

  10. webstorm--破解

    2016.2.2 版本的破解方式: 安装以后,打开软件会弹出一个对话框:选择"license server" 输入:http://114.215.133.70:41017 2016 ...