php单一入口模式可谓是现在一种比较流行的大型web应用开发模式,比如当前比较流行的一些php开发框架,zend,thinkphp,qeephp,还有cakephp 等他们都是采用的单一入口模式的。本文将就什么是单一入口模式,单一入口模式有哪些优点以缺点做一下研究。
什么是单一入口?
在解释什么是单一入口之前,先说说与之对应的多入口。多入口即通过访问不同的 php 文件运行对应的功能。比如刚开始学习 php 的时候,我们做一个项目通常都会如下这样做:
index.php - 网站首页
list.php?page=5 - 内容列表页
info.php?id=12 - 内容详细页
login.php - 用户登录页
对于这个项目来说,这其实就是一个多入口。
那么单一入口的应用程序就是说用一个文件处理所有的HTTP请求,例如不管是内容列表页,用户登录页还是内容详细页,都是通过从浏览器访问 index.php 文件来进行处理的,这里这个 index.php 文件就是这个应用程序的单一入口。
php 是如何实现单一入口的呢?
很简单,一般单一入口程序都是在访问index.php时附带一个特定的参数。例如:index.php?action=list 就可以定义为访问内容列表页,而 index.php?action=info 则可以定义为访问内容详细页等,具体实现代码如下:
1 | $action = $_GET [ 'action' ]== '' ? 'index' : $_GET [ 'action' ]; //从url中取出action参数,如果没有提供action参数,就设置一个默认的'index'作为参数 |
2 | include ( 'files/' . $action . '.php' ); //根据$action参数调用不同的代码文件,从而满足单一入口实现对应的不同的功能 |
以上这个就实现了一个最简单的单一入口模式程序,当然真正的单一入口模式会比这个要复杂很多。但只要懂得如何合理组织各个功能的处理代码并遵循一定的步骤,也可以轻松的解决掉这个难题,下面就一个后台的例子来做一下说明:
比如我们现在要做一个新闻管理的后台。那么首先,对于应用程序的功能要做出一个合理的分解。例如后台的新闻栏目可能包含“添加新闻”、“编辑新闻”、“删除新闻”等多个功能。这时我们就可以将这一组逻辑上关联的功能组合到一个功能模块中,称为“新闻管理”模块。
按照上面的方法整理完应用程序的功能,我们就会得到多个功能模块,而每个模块又是由多个功能组成(实际上,即便不是单一入口应用程序,功能的整理也是必须的步骤)。
整理完功能后,我们就需要确定如何存放各个功能的代码。这里我推荐两种方式:
1、每个功能模块一个子目录,目录里的每一个文件就是一个功能的实现代码。
这种方式的好处是每个功能的代码都互相隔离,非常便于多人协作。缺点是每个功能之间共享代码和数据不那么方便。例如新闻管理模块中的所有功能都需要一个“取出新闻栏目记录”的功能,那么采用这种多个独立文件的组织方式,“取出新闻栏目记录”就只能写在另一个文件中,然后由需要该功能的文件include 进去。
2、每个模块一个文件,模块中的每个功能写成一个函数或者一个类方法。
好处不用多说了,非常便于共享代码和数据。缺点就是如果几个人同时改,容易发生冲突。不过借助版本控制软件和差异比较合并工具,冲突还是很容易解决的。
单一入口应用程序对应多入口有哪些优势呢?
单一入口应用程序的所有http请求都是通过index.php接收并转发到功能代码中去的,所以在index.php里面就能完成许多实际工作(所有页面都需要做的且都一样的工作)。比如进行集中的安全性检查,访问统计等等,如果不是单一入口,那么开发者就必须记得在每一个文件的开始加上安全性检查代码,当然,你也许会说,多入口的安全性检查可以写到另一个文件中,然后include一下就可以了。但实际针对一个相对较大型一点的应用项目,在几十个文件中保持头部的几个include都一致可不是一件让人省心的事。
与安全性检查类似。在入口里,我们还可以对url参数和post进行必要的检查和特殊字符过滤、记录日志、访问统计等等各种可以集中处理的任务。这样就可以看出,由于这些工作都被集中到了index.php来完成,可以减轻我们维护其他功能代码的难度。
单一入口应用程序的缺点?
任何事情都有两面性,单一入口应用程序也不例外。由于所有http请求都是访问 index.php ,所以程序的 url 看起来不那么美观,特别是对搜索引擎来说不太友好。比如下面这个 url:
http://www.phpernote.com/index.php?controller=posts&action=index
我们知道这种URl不太方便记忆,而且搜索引擎不认它是一个正常的 URL,当然是相比下面这种 URl 来说的:
http://www.phpernote.com/index.php/posts/index/
不过这个也不是什么大问题,可以采用url重写、PATHINFO等方式就可以轻松解决这个问题。
OK,单一入口模式就写这么多了,当然要想深刻理解单一模式,最好的办法还是自己尝试着用单一入口模式写一个小应用出来深刻体会一下。
2013年1月22日 19:35:25
大概过程:所有URL请求->重定向到index.php->加载一个类A->分析URL得到参数信息和将要调用的另一个类B->加载这个类B,并将参数信息传递给该类->执行->结束.
入口文件(通过.htaccess文件中的rewrite功能把所有请求都转向这个文件):
test.php <?php include './a.php'; //存放处理URL获得参数信息的祖先类A $objA = new A(); $objA->run(); exit;
主要入口处理类(相当于zend framework的front,用于处理和分发请求):
a.php <?php class A { public function __construct() { var_dump('A 构造函数'); } public function run() { include './b.php'; $obj = new B(); $obj->action(); } public function __destruct() { var_dump('A 析构函数'); } }
功能类,处理分发来的请求(超级类将URL分解获得信息,动态new此class,并将信息交给此class处理)
b.php <?php class B { public function action() { var_dump('B action函数'); } }
如果类B没有继承类A,执行test.php入口文件的代码结果很容易知道:
string 'A 构造函数' (length=14) string 'B action函数' (length=14) string 'A 析构函数' (length=14)
如果类B继承了主要入口请求处理类A:
b.php (继承了A) <?php class B extends A { public function action() { var_dump('B action函数'); } }
那么结果是这样的:
string 'A 构造函数' (length=14) string 'A 构造函数' (length=14) string 'B action函数' (length=14) string 'A 析构函数' (length=14) string 'A 析构函数' (length=14)
结论1:如果B继承了A,当在A中创建B的对象那么会再次调用A的构造函数,也只调用A的构造函数
结论2:如果要想在B中使用A中的数据(往往是对入口URL处理得到的结果),就必须在A的构造函数中定义这些值
如果在类B中写了B的构造函数会怎样?:
<?php class B extends A { public function B() { var_dump('B 构造函数'); } public function action() { var_dump('B action函数'); } }
那么结果是这样的:
string 'A 构造函数' (length=14) string 'B 构造函数' (length=14) string 'B action函数' (length=14) string 'A 析构函数' (length=14) string 'A 析构函数' (length=14)
对,和你想的一样,A的构造函数没有执行,因为子类的构造函数覆盖了父类的构造函数
知道这个结果没有什么可喜可贺的,重要的是:
结论3:不能写子类的构造函数,否则你别想从父类哪里得到任何动态信息
结论4:最好将类名和类成员函数名字区分开,避免将类名和方法名写的一样(别笑,这样说不是废话,看下边代码),像zf做的那样,类名后边加上'Controller'后缀,方法名后边加上'Action'后缀
我想大部分人都这样干过 <?php class index { public function index() { } }
如果这样套到单一入口框架中就不行了.
别急,还有:
结论5:因为每次执行子类B都会执行两次A的构造函数,因此最好将类A设计成单例模式
ok,木有了.
2013年1月22日 19:35:25
大概过程:所有URL请求->重定向到index.php->加载一个类A->分析URL得到参数信息和将要调用的另一个类B->加载这个类B,并将参数信息传递给该类->执行->结束.
入口文件(通过.htaccess文件中的rewrite功能把所有请求都转向这个文件):
test.php <?php include './a.php'; //存放处理URL获得参数信息的祖先类A $objA = new A(); $objA->run(); exit;
主要入口处理类(相当于zend framework的front,用于处理和分发请求):
a.php <?php class A { public function __construct() { var_dump('A 构造函数'); } public function run() { include './b.php'; $obj = new B(); $obj->action(); } public function __destruct() { var_dump('A 析构函数'); } }
功能类,处理分发来的请求(超级类将URL分解获得信息,动态new此class,并将信息交给此class处理)
b.php <?php class B { public function action() { var_dump('B action函数'); } }
如果类B没有继承类A,执行test.php入口文件的代码结果很容易知道:
string 'A 构造函数' (length=14) string 'B action函数' (length=14) string 'A 析构函数' (length=14)
如果类B继承了主要入口请求处理类A:
b.php (继承了A) <?php class B extends A { public function action() { var_dump('B action函数'); } }
那么结果是这样的:
string 'A 构造函数' (length=14) string 'A 构造函数' (length=14) string 'B action函数' (length=14) string 'A 析构函数' (length=14) string 'A 析构函数' (length=14)
结论1:如果B继承了A,当在A中创建B的对象那么会再次调用A的构造函数,也只调用A的构造函数
结论2:如果要想在B中使用A中的数据(往往是对入口URL处理得到的结果),就必须在A的构造函数中定义这些值
如果在类B中写了B的构造函数会怎样?:
<?php class B extends A { public function B() { var_dump('B 构造函数'); } public function action() { var_dump('B action函数'); } }
那么结果是这样的:
string 'A 构造函数' (length=14) string 'B 构造函数' (length=14) string 'B action函数' (length=14) string 'A 析构函数' (length=14) string 'A 析构函数' (length=14)
对,和你想的一样,A的构造函数没有执行,因为子类的构造函数覆盖了父类的构造函数
知道这个结果没有什么可喜可贺的,重要的是:
结论3:不能写子类的构造函数,否则你别想从父类哪里得到任何动态信息
结论4:最好将类名和类成员函数名字区分开,避免将类名和方法名写的一样(别笑,这样说不是废话,看下边代码),像zf做的那样,类名后边加上'Controller'后缀,方法名后边加上'Action'后缀
我想大部分人都这样干过 <?php class index { public function index() { } }
如果这样套到单一入口框架中就不行了.
别急,还有:
结论5:因为每次执行子类B都会执行两次A的构造函数,因此最好将类A设计成单例模式
ok,木有了.