动态加载模块简介
动态
可以理解成运行时按需加载代码(不是运行时编译)
加载模块
加载模块这种形式可以用于对已有系统的扩展(在不改动系统核心代码的情况下)。
比如,你做了一个社交网站,想要为你的网站增加新的功能,如要要去重新改动原来的代码,一是麻烦,而是可能引起新的问题。
不过如果你实现了模块话加载,那很大程度上就只需要写一个新的模块就行了,而不用去改动原来的代码。
现在也有很多这种模式的系统,比如discuz,thinksns,opensns等等…
实现代码
这里先给出完整代码再作分析
<?php
/**
* Created by PhpStorm.
* User: 奔跑的MT
* Date: 17-1-21
* Time: 下午2:11
*/
class Person
{
public $name;
function __construct($name)
{
$this->name = $name;
}
}
interface Module
{
function execute();
}
class FtpModule implements Module
{
function setHost($host)
{
print "FtpModule::setHost(): $host\n";
}
function setUser($user)
{
print "FtpModule::setUser(): $user\n";
}
function execute()
{
print "execute: FtpModule\n";
}
}
class PersonModule implements Module
{
function setPerson(Person $person)
{
print "PersonModule::setPerson(): ($person->name)\n";
}
function execute()
{
print "execute: PersonModule\n";
}
}
class ModuleRunner
{
private $configData = array(
"PersonModule" => array('person' => 'bob'),
"FtpModule" => array('host' => 'baidu.com',
'user' => 'anon'
),
);
private $modules = array();
function init()
{
$interface = new ReflectionClass('Module');
foreach ($this->configData as $modulename => $params) {
$module_class = new ReflectionClass($modulename);
if (!$module_class->isSubclassOf($interface)) {
throw new Exception("unknown module type: $modulename");
}
$module = $module_class->newInstance();
foreach ($module_class->getMethods() as $method) {
$this->handleMethod($module, $method, $params);
}
array_push($this->modules, $module);
}
}
function handleMethod(Module $module, ReflectionMethod $method, $params)
{
$name = $method->getName();
$args = $method->getParameters();
if (count($args) != 1 || substr($name, 0, 3) != "set")
return false;
$property = strtolower(substr($name, 3));
if (!isset($params[$property])) {
return false;
}
$arg_class = $args[0]->getClass();
if (empty($arg_class))
$method->invoke($module, $params[$property]);
else
$method->invoke($module, $arg_class->newInstance($params[$property]));
}
/* 测试一下 */
function execute()
{
foreach ($this->modules as $module) {
call_user_func_array(array($module, 'execute'), array());
}
}
}
$runner = new ModuleRunner();
$runner->init();
$runner->execute();
代码分析
Person类
就相当于是后面PersonModule的模型类,在这里是可有可无的
Module接口
用来规范所以模块的。
里面定义了一个execute函数,目的是统一模块入口。FtpModule类
Ftp模块类,在这里只是为了演示,没有特别意义
这里的setter函数,即setHost和setUser也是模块规范之一,后面会解释
execute函数便是Ftp模块的入口PersonModule类
Person模块类
具体和FtpModule类是差不多的,就不重复叙述了核心:ModuleRunner类
ModuleRunner类在这里相当于模块的驱动,所有模块都在这个类里面完成初始化,以便其他地方使用
$configData数组
保存模块信息和配置,也可以用其他形式来保存,比如json或者xml
$modules数组
保存模块实例的数组
init函数
首先通过反射API来获取Module接口,目的是为了判断模块类有没有实现Module接口,也是规范模块的手段之一
然后循环获取configData数组里面的模块配置:模块名以及相应的属性和参数。
之后判断模块类是否实现了Module接口(isSubclassOf函数),如果没有实现则抛出异常,
如果实现了,便获取模块类的一个实例
然后通过handleMethod函数处理模块类中的setter函数来设置模块类的属性
最后把模块类实例加入$modules数组。
至此,init函数就完成了。handleMethod函数
handleMethod函数的参数是模块类实例,模块类中函数的实例,函数的参数
首先获取函数的名字和需要的参数
再判断函数是否合法,即是否只需要一个参数或者函数是setxxx函数
然后通过$property = ….来获取相应的属性名,接着判断这个属性是否设置
这些检查都通过后便可以开始调用setxx函数来设置模型的属性了
不过还有一个要注意的,如果setxx函数的参数是一个对象,那么我们肯定要先实例化相应的类
所以最后的一个判断便是干这个工作的
完结
至此,上述代码的运行流程便分析完了,最后的一个execute函数只是我用来测试的一个函数
我的返回结果是:
PersonModule::setPerson(): (bob)
FtpModule::setHost(): baidu.com
FtpModule::setUser(): anon
execute: PersonModule
execute: FtpModule
当然,实现模块化的方式有很多,这里是利用了PHP提供的反射API