轻量级OO框架
时间:2007-02-17 来源:PHP爱好者
开发这个框架的初衷是看到Java的Struts非常不错,希望在php中有一个类似的框架。后来在网上找到了php.mvc项目,完全是Structs的php实现,所以放弃了这个目标。但是php.mvc非常复杂,对于小项目来说实在是浪费。所以在最近做一个小项目时,就做了这个轻量级框架,封装了一些基本的东西。整个框架的初步构思来源于shukebeita兄的精华贴(http://bbs.chinaunix.net/forum/viewtopic.php?t=224412),我只是做了个实现品出来 :)
当然,由于只提供基本功能,所以很多细节内容还需要自己实现。
[b:12205a4c9c]1、框架的大体结构[/b:12205a4c9c]
整个框架其实只有两个class,其他一些class都是辅助工具,帮助实现一些具体的功能。
class Application
这个类提供了应用程序的入口、action映射、数据库初始化、模版引擎初始化等等功能。
class Page
提供了页面的入口,可以在此执行各种业务逻辑(本来应该独立出去,不过考虑到复杂性就放在Page类里面了)。
[b:12205a4c9c]2、class Application分析[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
// @author Liao Yu Lei <[email protected]>
// @version $Id$
require_once ('PEAR.php');
class Application extends PEAR
{
// private
var $_db = null;
// public
function Application() {
$this->PEAR();
// 设置PEAR的错误处理方式,这样可以减少很多错误判断代码
PEAR::setErrorHandling(PEAR_ERROR_DIE);
}
function _Application() {
if ($this->_db !== null) {
$this->_db->disconnect();
}
}
function getAction() {
$action = ($_GET['action']);
if (empty($action)) {
$action = CFG_DEFAULT_ACTION;
}
return $action;
}
function & initTemplate() {
require_once(APPROOT . '/smarty/Smarty.class.php');
$tpl = & new Smarty();
$tpl->template_dir = APPROOT . CFG_TEMPLATE_LIB;
$tpl->compile_dir = APPROOT . CFG_SMARTY_TEMPLATES_C;
$tpl->config_dir = APPROOT . CFG_SMARTY_CONFIGS;
$tpl->cache_dir = APPROOT . CFG_SMARTY_CACHE;
$tpl->cache = true;
return $tpl;
}
function & connectDatabase() {
require_once(APPROOT . '/pear/DB.php');
$dsn = CFG_DB_TYPE . '://' . CFG_DB_USER . ':' . CFG_DB_PASS . '@' .
CFG_DB_HOST . '/' . CFG_DB_NAME;
$db = & DB::connect($dsn);
if (DB::isError($db)) {
die ($db->toString());
}
if (!method_exists($db, 'quoteSmart')) {
// quoteSmart是PEAR::DB 1.6.0版本以后的新功能,用以替换quote函数
// Function available since: PEAR:DB Release 1.6.0
$db->disconnect();
die ('PEAR:DB 必须升级到 1.6.0 以上版本');
}
// 设置PEAR::DB的默认数据提取模式
$db->setFetchMode(DB_FETCHMODE_ASSOC);
$this->_db = & $db;
return $db;
}
function & getDb() {
return $this->_db;
}
function run() {
$action = strtolower($this->getAction());
$page_class_name = ucfirst($action) . 'Page';
$page_class_file = APPROOT . '/includes/class_' . $action . '.php';
@include_once($page_class_file);
if (!class_exists($page_class_name)) {
die ("需要的 class(${page_class_name}) 没有定义<BR>类定义文件 "" .
$page_class_path . "" 无法找到");
}
$page = & new $page_class_name($this);
$page->execute();
exit ();
}
}
?>
[/code:1:12205a4c9c]
Applcation类继承于PEAR(为了获得析构函数功能),然后在构造函数中修改了PEAR的默认错误处理方式。getAction()很简单,获得$_GET['action']的值,如果没有设置则使用默认值(我的程序里是Welcome)。initTemplate()初始化模版引擎(我用的是Smarty)。connectDatabase()初始化数据库联接,返回一个PEAR-DB对象的引用(需要一些常量来完成数据库连接,可以在inc_config.php中定义)。getDb()获得已经初始化的PEAR-DB对象。
Application类的入口就是run()。run()根据获得的action,载入对应的.php文件,并new一个对应class出来,然后执行这个class的execute()方法。
[b:12205a4c9c]3、Page类分析[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
// @author Liao Yu Lei <[email protected]>
// @version $Id$
class Page
{
// private
var $_app = null;
var $_action = null;
var $_SES_NAME = '_SES_POST_DATA_';
// public
function Page(& $app) {
$this->_app = & $app;
$this->_action = $this->_app->getAction();
session_start();
if (array_key_exists($this->$_SES_NAME, $_SESSION)) {
$array = $_SESSION[$this->$_SES_NAME];
unset($_SESSION[$this->$_SES_NAME]);
if (count($array) > 0) {
$_POST = & array_merge($_POST, $array);
}
}
}
function execute()
{
die ('只能够调用继承类的 execute 方法');
}
// protected
function _defaultTemplateVariables() {
return array();
}
function _redisplay() {
$this->_redirect('?action=' . $this->_action);
}
function _display($template_file, $title = 'Noname', & $values = array()) {
$tpl = $this->_app->initTemplate();
$default_variables = $this->_defaultTemplateVariables();
foreach($default_variables as $key => $value) {
$tpl->assign($key, $value);
}
foreach($values as $key => $value) {
$tpl->assign($key, $value);
}
$tpl->assign('title', $title);
$tpl->display($template_file);
}
function _redirect($url, & $params = array()) {
$_SESSION[$this->$_SES_NAME] = & $params;
header("Location: $url");
}
}
?>
[/code:1:12205a4c9c]
这个类本质上是封装一个action,但为了降低复杂度,我把页面的显示也做到了里面,对于小程序来说反倒更方便。Page类构造时需要传递Application对象的引用,并保存起来,方便Page以后使用。注意我在构造函数中还完成了一些特别的工作:
首先调用session_start()。我想大部分需要用户登录后操作的页面都需要首先调用session_start()吧,所以不如放在构造函数里。其次将$_SESSION[_SES_NAME]中的所有内容合并到$_POST中。这个工作是配合Page->_redirect()使用的,可以方便的在页面期间传递数据。
execute()这个函数还记得吗?是Application->run()调用的,是Page类的执行入口。Page类的execute()应该是个抽象函数,但因为php4还不支持,所以就写个die在里面。其实这些东东大部分都在shukebeita兄的精华贴(http://bbs.chinaunix.net/forum/viewtopic.php?t=224412)中提到过了。
_defaultTemplateVariables()是个非常有用的函数,不过Page里面的实现只是返回一个空array。而在Page继承类中就派上大用场了,具体说明请看稍后的_display()介绍。
_redisplay()这个函数用于重新显示当前页面,例如刷新什么的,不是很常用。
_display()函数是Page类中的重点之一,除了调用Application->initTemplate()初始化模版引擎外,还要调用$this->_defaultTemplateVariables()获得默认的模板变量。哈哈,现在明白_defaultTemplateVariables()干嘛了吧,只要在Page继承类中重载_defaultTemplateVariables()函数,并返回需要的array,就可以方便的显示一些公共信息。接下来就是将所有需要在模版中显示的数据都通过$tpl->assign()指定到相应的变量中。最后调用$tpl->display()显示模版内容。
_redirect()完成重定向工作,不过可以传递一些数据到下一个页面里面。这些数据会存放在$_SESSION[_SES_NAME]中,然后在新页面的构造函数中被合并到$_POST中。所以效果和通过post将数据提交到另一个页面差不多(本质上是不同的,而且用户按F5刷新新页面得到的结果也不同)。
整个框架就这么多内容,使用时首先需要在应用程序(网站)根目录放一个index.php文件,然后输入下面的内容:
[code:1:12205a4c9c]
require_once(APPROOT . '/framework/class_application.php');
$app = & new Application();
$app->run();
[/code:1:12205a4c9c]
这样不管是执行应用程序的什么功能,只要在网址后跟上“?action=xxxx”即可。假设我们的应用程序位于www.dualface.com,那么登录功能的url就应该是http://www.dualface.com/?action=login。
下面我就给出一个登录的示例:
首先需要一个class来封装用户数据和操作:
[b:12205a4c9c]class_member.php[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
class Member
{
var $_db = null;
var $_dbtable = null;
var $_uncol = null;
var $_pwcol = null;
var $_sesname = null;
function Member($db = null, $dbtable = 'members', $uncol = 'username',
$pwcol = 'password', $sesname = 'MEMBERINFO')
{
$this->_db = $db;
$this->_dbtable = $dbtable;
$this->_uncol = $uncol;
$this->_pwcol = $pwcol;
$this->_sesname = $sesname;
}
function _loadInfo($username)
{
$sql = 'SELECT * FROM ' . $this->_dbtable . ' WHERE ' .
$this->_db->quoteIdentifier($this->_uncol) . ' = ' .
$this->_db->quoteSmart($username);
$result = $this->_db->query($sql);
if (DB::isError($result))
{
die ($result->toString());
}
if ($result->numRows() == 0)
{
$result->free();
return array();
}
$data = $result->fetchRow(DB_FETCHMODE_ASSOC);
$result->free();
return $data;
}
function login($username, $password, $keys = array())
{
$result = $this->_loadInfo($username);
if (empty($result))
{
return 'username';
}
if (md5($password) != $result[$this->_pwcol])
{
return 'password';
}
$data = array();
foreach ($result as $key => $value)
{
$newkey = 'G_' . strtoupper($key);
$data[$newkey] = $value;
}
$data['member_login'] = true;
$_SESSION[$this->_sesname] = $data;
$this->info = $data;
return true;
}
function logout()
{
unset ($_SESSION[$this->_sesname]);
}
function checkLogin()
{
$data = $_SESSION[$this->_sesname];
return $data['member_login'] === true;
}
function getInfo()
{
if ($this->checkLogin())
{
return $_SESSION[$this->_sesname];
}
return array();
}
function getRawInfo()
{
if ($this->checkLogin())
{
$data = $_SESSION[$this->_sesname];
$data2 = array();
foreach ($data as $key => $value)
{
$newkey = strtolower(substr($key, 2));
$data2[$newkey] = $value;
}
return $data2;
}
return array();
}
function getAttribute($key)
{
if ($this->checkLogin())
{
return $_SESSION[$this->_sesname][$key];
}
return null;
}
function refresh()
{
if (!$this->checkLogin())
{
return false;
}
$data = $_SESSION[$this->_sesname];
$result = $this->_loadInfo($data['G_USERNAME']);
if (empty($result))
{
return false;
}
$data = array();
foreach ($result as $key => $value)
{
$newkey = 'G_' . strtoupper($key);
$data[$newkey] = $value;
}
$data['member_login'] = true;
$_SESSION[$this->_sesname] = $data;
return true;
}
}
?>
[/code:1:12205a4c9c]
这个类之所以有点复杂是因为我做的那个项目需要一些比较特别的功能,一般应用可以将这个类简化一下,这里我就懒得改了。
有了用户封装类以后,还准备了一个Page类的继承类,作为各个页面的基类。
[b:12205a4c9c]class_mypage.php[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
class MyPage extends Page
{
function _defaultTemplateVariables()
{
$data = array(
'CFG_CSS_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_CSS_LIB,
'CFG_IMAGE_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_IMAGE_LIB,
'CFG_SITE_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_SITE_LIB,
'CFG_ADMIN_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_ADMIN_LIB,
'CFG_script_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_script_LIB,
'CFG_ROLE_USER' => CFG_ROLE_USER,
'CFG_ROLE_ADMIN' => CFG_ROLE_ADMIN);
$member = new Member(null);
return array_merge($data, $member->getInfo());
}
}
?>
[/code:1:12205a4c9c]
这个MyPage类的唯一作用就是设置一些每个页面显示都需要的变量,其他页面只要继承这个类就可以了。
login页面:
[b:12205a4c9c]class_login.php[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
class LoginPage extends MyPage
{
function _login()
{
$db = $this->_app->connectDatabase();
$member = new Member($db);
$result = $member->login($_POST['username'], $_POST['password']);
$db->disconnect();
if ($result !== true)
{
$data[$result . '_error'] = true;
$data['username'] = $_POST['username'];
$this->_display(CFG_SITE_LIB . '/login.html', 'Log In', $data);
}
else
{
// 登录后显示欢迎页面
$this->redirect('?action=welcome');
}
}
function _displayLoginPage()
{
$member = new Member();
$member->logout();
$this->_display(CFG_SITE_LIB . '/login.html', 'Log in');
}
function execute()
{
$page_action = $_POST['page_action'];
switch ($page_action)
{
case 'login':
$this->_login();
break;
case '':
$this->_displayLoginPage();
break;
}
}
}
?>
[/code:1:12205a4c9c]
这个类里面execute()根据$_POST['page_action']来执行不同的函数。这样实际上等同于一个Page继承类可以完成多项工作。Page类在这一点上就和action映射类似了。这个技巧我在项目中大量使用,觉得非常方便。
class_login.php需要的html模版文件:
[b:12205a4c9c]login.html[/b:12205a4c9c]
[code:1:12205a4c9c]
{include file="$CFG_SITE_LIB/header.html"}
<DIV>
<DIV>
<H3>LOG IN TO YOUR ACCOUNT</H3>
<P> Log on here to view your profile. If you do not yet have an account,
you must first register for an account. </P>
</DIV>
<DIV>
<FORM name="form_login" id="form_login" method="post" action="?action=login">
<TABLE width="400" border="0" cellspacing="0" cellpadding="4">
<TR>
<TD>Username:</TD>
</TR>
<TR>
<TD {if $username_error}class="error"{/if}>
<INPUT name="username" type="text" id="username" style="width: 220px" maxlength="64" value="{$username}"/>
<P>{if $username_error}* 用户名没有找到。 <BR>{/if}</P>
</TD>
</TR>
<TR>
<TD>Password:</TD>
</TR>
<TR>
<TD {if $password_error}class="error"{/if}>
<INPUT name="password" type="password" id="password" maxlength="32" style="width: 220px">
<P>{if $password_error}* 错误的密码。 <BR>{/if}</P>
</TD>
</TR>
</TABLE>
<BR>
<INPUT type="submit" name="Submit" value="Log in">
<INPUT type="hidden" name="page_action" value="login">
</FORM>
</DIV>
</DIV>
{include file="$CFG_SITE_LIB/footer.html"}
[/code:1:12205a4c9c]
看到这个html文件中的<INPUT type="hidden" name="page_action" value="login">了吗?所以提交这个页面的表单(表单的action属性是"?action=login")时,虽然还是调用class_login.php,但却会执行LoginPage->_login()。
接下来是logout页面
[b:12205a4c9c]class_logout.php[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
class LogoutPage extends MyPage
{
function show()
{
$member = new Member();
$member->logout();
// 注销后显示退出页面
$this->redirect('?action=bye');
}
}
?>
[/code:1:12205a4c9c]
由于LogoutPage类根本没有显示模版,所以就不需要配套的html模板文件了。
这个框架虽然简单,但在我上一个项目的实际应用中,效果还是非常不错的。首先封装了Smarty模版引擎的使用,做到了代码和HTML页面的完全分离。其次OO结构可以方便的扩充功能(如果每一个功能都非常复杂,例如显示登录页面和处理登录,那么可以分别撰写class,并分配不同的action)。
我在这里把我实现这个框架贴出来,希望大家能够共同完善这个框架,做成一个稳定的东西,可以方便大家在项目中使用。
longnetpro 回复于:2004-03-13 03:39:24 基本思路与我的相同。但有些问题需要注意,主要考虑因素是效率。
1、变量作用域问题,大量数据复制明显会导致效率下降,即使用了引用。主要原因是包含页面时,所有变量的作用域都只在一个函数或是方法之中,而如果过多的用GLOBAL又不符合编程的原则。一般的办法是直接存贮在Application或是Page类中或用超级变量(对于超级变量如$_GET,最好是不要人为地修改,否则与GLOBAL无异),基本上可以做到全局调用。
2、模板问题。用SMARTY之类的模板会严重影响效率,不值得提倡。提倡嵌入式脚本,但要做到逻辑处理与显示分离(不是PHP代码与HTML分离)。其实这是利用PHP语言的先天优势,为什么要抛弃它呢?用模板预编译可以达到两个方面的综合平衡。
3、SESSION问题,不能过多依赖于SESSION。
4、数据库与模板初始化不宜放在Application或是Page中,而应该放到不同的任务中,因为每个任务的要求是不一样的。
还有其它一些考虑,如
5、容错性问题。
6、安全性问题。
以上两问题可能出现在文件或类找不到的情况下,或是有人伪造类。
象Application 或是 Page主要是两个接口类,而不应该实现多数细节,如果对不同的项目有某些具体的要求,不妨继承这两个类以适应不同的项目。如果作为通用框架,楼主提交的类中的某些功能还可简化。
另外,为了可扩展性。以上两个类只处理一个action是不够的,还应该可以处理语言参数,模板编号,任务名称(一个任务可以有多个action)。两个类的目的并非为了实现这些参数代表的内容,而是进行安全性检查,去掉非法的输入,并将不存在的内容置为默认值,这些值再往下传递下去,具体实现则应该由具体的业务逻辑与显示逻辑类来处理了。
在这个中间,业务逻辑没什么好谈的,因为很少涉及显示输出,因此只要能传入传出相应的值就可以了。对于显示逻辑,如果象楼主那样用Smaty一类推式的HTML模板的话,这个方式是很好的。但如果是用嵌入式脚本的方式,楼主的这个方法就有比较大的问题,也是主要是因为变量作用域的问题以及在类的方法中包含文件的问题。由于我是用的预编译嵌入式脚本,因此我并未继承任何Page类,而是在一个任务页面中直接调用include,为了保证业务逻辑与显示逻辑层次分明,用固定的接口类与模板ID打交道,在模板编译的时候就直接调用这个通用接口,以便两者能比较好的衔接。就是说,在模板中与程序中都用同一个接口,这样的话,模板只用关心它那一部分,程序就只用关心程序的部分了。因为是预编译,就能由程序比较好的控制,两者的接口与很容易结合。但就我本人的观点,应当避免使用Smarty或是类似的HTML模板(会给PHP解释器带来巨大的负担,因为是二次解析,其中第二次是人为的用PHP脚本来解析,效率大打折扣),而直接使用PHP本身的嵌入式脚本(虽然是一个缺点,但也是个优势,在使用过程中,这应该是个优势)。
shukebeita 回复于:2004-03-13 10:03:34 感动呀!比我的那一贴还长。多多联系,我加了你的msn。
I_Just_Shot_John_Lennon 回复于:2004-03-13 11:29:44 楼主写的不错
做沙发的点评的也不错
学习ing
dualface 回复于:2004-03-13 12:06:21 [b:37240ccd73]1、变量作用域问题,大量数据复制明显会导致效率下降,即使用了引用。主要原因是包含页面时,所有变量的作用域都只在一个函数或是方法之中,而如果过多的用GLOBAL又不符合编程的原则。一般的办法是直接存贮在Application或是Page类中或用超级变量(对于超级变量如$_GET,最好是不要人为地修改,否则与GLOBAL无异),基本上可以做到全局调用。 [/b:37240ccd73]
在上面的两个类中,变量传递较多的地方就是Page->_display()吧,如果很多数据要显示性能确实不如直接include快。不过你说的作用域问题我就不明白了,难道为了速度全部用GLOBAL变量?
[b:37240ccd73]2、模板问题。用SMARTY之类的模板会严重影响效率,不值得提倡。提倡嵌入式脚本,但要做到逻辑处理与显示分离(不是PHP代码与HTML分离)。其实这是利用PHP语言的先天优势,为什么要抛弃它呢?用模板预编译可以达到两个方面的综合平衡。[/b:37240ccd73]
说实话,我没有测试过Smarty的性能,但是Smarty是一种编译型的模版引擎,只有改变了模板文件以后第一次display的时候才会进行模版分析,以后都不会再进行分析了,这不就是你说的模版预编译吗?当然了,速度最快的方式还是准备好要显示的内容,然后include模版页面文件了,呵呵。
[b:37240ccd73]3、SESSION问题,不能过多依赖于SESSION。[/b:37240ccd73]
session就是解决页面间的数据共享问题的,干嘛不用啊?如果不用session,难道自己创建临时文件或者把数据放在数据库中?
[b:37240ccd73]4、数据库与模板初始化不宜放在Application或是Page中,而应该放到不同的任务中,因为每个任务的要求是不一样的。[/b:37240ccd73]
数据库初始化放在Application中,但只有在你调用connectDatabase的时候才会include PEAR-DB的文件并连接数据库。所以对于用不上数据库显示的页面来说完全没有影响。这和把数据库初始化代码放到一个单独的函数中是一个道理。同样模版引擎初始化也是一样的,只有在你调用Page->display的时候才会进行。
[b:37240ccd73]5、容错性问题。[/b:37240ccd73]
这个意见很正确,我确实没做什么容错性问题处理,绝大多数错误都是用die处理。要不就是在业务逻辑代码中自行处理错误,毕竟PHP没有异常这些错误处理工具。
[b:37240ccd73]6、安全性问题。以上两问题可能出现在文件或类找不到的情况下,或是有人伪造类。[/b:37240ccd73]
getAction我没有对不存在的action进行处理,而且也没有考虑url编码的问题,所以你说的问题是完全存在的。之所以不进行检查主要原因一是做的项目本身只是个演示项目,要求不高,其次我准备以后改进成action mapping方式,这样不存在的action直接就过滤掉了。
[b:37240ccd73]象Application 或是 Page主要是两个接口类,而不应该实现多数细节,如果对不同的项目有某些具体的要求,不妨继承这两个类以适应不同的项目。如果作为通用框架,楼主提交的类中的某些功能还可简化。 另外,为了可扩展性。以上两个类只处理一个action是不够的,还应该可以处理语言参数,模板编号,任务名称(一个任务可以有多个action)。两个类的目的并非为了实现这些参数代表的内容,而是进行安全性检查,去掉非法的输入,并将不存在的内容置为默认值,这些值再往下传递下去,具体实现则应该由具体的业务逻辑与显示逻辑类来处理了。 [/b:37240ccd73]
不错不错,获益良多啊!特别是任务名称这个东东,我就是在想一个业务模块中不同页面间怎么结合起来处理呢。不过你说的这两个类只提供接口,然后在继承类实现。这样当然不错了,但是小项目没这个必要吧,继承太多层次了。
[b:37240ccd73]在这个中间,业务逻辑没什么好谈的,因为很少涉及显示输出,因此只要能传入传出相应的值就可以了。对于显示逻辑,如果象楼主那样用Smaty一类推式的HTML模板的话,这个方式是很好的。但如果是用嵌入式脚本的方式,楼主的这个方法就有比较大的问题,也是主要是因为变量作用域的问题以及在类的方法中包含文件的问题。由于我是用的预编译嵌入式脚本,因此我并未继承任何Page类,而是在一个任务页面中直接调用include,为了保证业务逻辑与显示逻辑层次分明,用固定的接口类与模板ID打交道,在模板编译的时候就直接调用这个通用接口,以便两者能比较好的衔接。就是说,在模板中与程序中都用同一个接口,这样的话,模板只用关心它那一部分,程序就只用关心程序的部分了。因为是预编译,就能由程序比较好的控制,两者的接口与很容易结合。但就我本人的观点,应当避免使用Smarty或是类似的HTML模板(会给PHP解释器带来巨大的负担,因为是二次解析,其中第二次是人为的用PHP脚本来解析,效率大打折扣),而直接使用PHP本身的嵌入式脚本(虽然是一个缺点,但也是个优势,在使用过程中,这应该是个优势)。[/b:37240ccd73]
早期的模版引擎确实是每次显示时都回解析一次,但Smarty不是这样的。除了可以自动判断是否需要编译模版外,还可以缓存输出文件的html内容(特别适合显示新闻什么的)。从这段话看,兄弟说的预编译模版应该是自己实现的一种机制,不知道兄弟是否愿意公开出来大家学习一下啊。
我以前写的php程序虽然也用模版,但大概是这个样子的:
1、一个.php文件完成业务逻辑,并把所有需要输出的内容都放到$out数组中
2、需要显示的时候include模版文件
3、在模板文件里面用<?php echo $out['sss']; ?>来显示内容
这样做速度确实很快,不过由于有时需要在HTML中做比较复杂的显示,这就涉及到页面制作人员要么懂一点php知识,要么编程人员就要自己去改html模板文件,总是容易引发冲突。用模版库的最大好处就是页面制作人员只需要懂得怎么放置一些简单的标记就行了。
总的来说,这个框架还是很不完善的,所以我也在不断改进。同时这个框架的定位就是具有一定复杂程度的业务逻辑,相对来说性能要求不那么高的小型项目。如果是性能要求非常高的项目,我就会用最直接的include模版文件方式来实现。如果是中型的项目,那么我可能就会采用php.mvc这样的框架了。不过我认为php在大、中型项目上没什么优势,这些项目用java或者.net来完成要好得多。
longnetpro 回复于:2004-03-13 12:18:26 楼上的,我也加了你的MSN,多多交流。
dualface 回复于:2004-03-13 12:33:59 呵呵,是啊,大家多交流,一起提高 :)
swingcoder 回复于:2004-03-13 13:40:48 加精华
hongweig 回复于:2004-04-14 15:58:40 dualface 兄,我们都想到一块儿了。我也是看了那篇精华帖突然有感,
不过,我是用phplib tempalte模板来处理视图类。
有空多在msn交流哦。
odin_free 回复于:2004-04-16 13:17:53 好!
abin30 回复于:2004-04-17 18:32:43 看到有很多高手在这里,就过来了!
呵呵,希望大家交流的时候,都在这里,你们用MSN我们怎么办啊
当然拉,如果能吧你们涉及到技术方面的聊天记录贴出来,那是最好了。
先感谢了。!
tonera 回复于:2004-04-19 10:53:49 :D
呵呵,今天偷时间细细看了,不错,再改进一下贴出来更好.
我现在的思想与dualface原来的有一点不一样:
[quote:f3ae58648f]我以前写的php程序虽然也用模版,但大概是这个样子的:
1、一个.php文件完成业务逻辑,并把所有需要输出的内容都放到$out数组中
2、需要显示的时候include模版文件
3、在模板文件里面用<?php echo $out['sss']; ?>来显示内容 [/quote:f3ae58648f]
第一点,我不是把内容存到$out数组里再显示的,这样服务器很累,较慢.
我在输出的时候尽可能采用嵌入式脚本,业务逻辑可能只返回一个句柄,虽然很老土,但管用,速度快.呵呵.当然,工作量大了些,把文档做好还是很好的.
模版的二次解析真的不能用了,效率极低,个人网页倒还可以.
dualface 回复于:2004-04-20 12:23:18 确实,我也觉得那些二次模板没什么必要。
而且这次我做新项目的时候把整个框架都重写了,现在支持:
* 多个模块,每个模块可以有自己的action map
* 每个模块和每个action都可以进行权限检查
* 内置一个简单的模板引擎(直接include页面,引擎主要完成一些准备工作)
* 封装了FORM和URL(也就是$_POST和$_GET),包括FORM的验证
等这个项目做完,整理一下就发上来,欢迎大家来吐口水,哈哈。
lee3f 回复于:2005-06-28 15:59:26 来迟了,楼主,有新的代码和介绍吗?
imbiss 回复于:2005-06-29 18:33:59 [quote:0119f06c28="tonera"]
第一点,我不是把内容存到$out数组里再显示的,这样服务器很累,较慢.
我在输出的时候尽可能采用嵌入式脚本,业务逻辑可能只返回一个句柄,虽然很老土,但管用,速度快.呵呵.当然,工作量大了些,把文档做好还是很好的.
..........[/quote:0119f06c28]
这位和我这两天做的事差不多。我也把我的代码贴出来。和楼主的思路类似,但有所不同,可能我的效率会差一点。
主要区别
1.不依赖PEAR
2.逻辑层和表现层完全分开。他们之间用out数组传递,而没有使用引用。
3.使用PHPLIB的模板
代码如下
1.基类。
对于我的每个项目,我都喜欢建一个自己的基类,我自己的类全部从这里继承。
[code:1:0119f06c28]
class AAObject{
var $deubg = true;
var $debuginfo;
function setDebug($bool){
if($bool){
$this->debug = true;
}
else{
$this->debug = false;
}
}
function getDebug(){
return $this->debuginfo;
}
}
[/code:1:0119f06c28]
我的逻辑类的基类
[code:1:0119f06c28]
class logic extends AAObject{
var $output=array();
function getOutput(){
return $this->output;
}
}
[/code:1:0119f06c28]
我的视图类的基类
[code:1:0119f06c28]
class render extends AAObject{
var $output = array();//save output code array
//Constructor
function ibsRender(){
}
function getOutput(){
return $this->output;
}
}
[/code:1:0119f06c28]
后台管理员的逻辑类,针对用户的输入(从REQUEST来),完成逻辑事务。在这个例子里,主要示范完成,用户登录(action=login),登出(action=logout),登录成功(action=welcome)
完成后,把数据放到output里,传给视图类作处理.
[code:1:0119f06c28]
class action_logic extends logic{
/***************
output code list
-----+---------------+-------------+
CODE Beschreibung Parameter
-----+---------------+-------------+
1 Login original REQUEST / reson=1 for bad login/
2 Login Succesful NONE
3 Welcome NONE
4 Logout NONE
***************/
//Constructor
function action_logic($REQUEST){
$this->setDebug(false); //Set Debug ON/OFF
if($this->debug){ $this->debuginfo .="Debug: LogicModul. Request action is ".$REQUEST["action"]." ";}
global $CFG;
//Check Identitat using cookie before do anything
if($REQUEST["action"] == "checkLoginPass"){
//Do nothing
}
else{
if(!$_COOKIE["AA_admin"] || $_COOKIE["AA_admin"]!= md5($CFG['admin_pass']))
{ //Not Login(no cookie) or cookie password error, set it back to login page
if($this->debug){ $this->debuginfo .="You are not Login. Set page to Login Page";}
$REQUEST["action"] = "login";
}
}
switch ($REQUEST["action"]){
case "login":
$this->login($REQUEST);
break;
case "checkLoginPass":
$this->checkLoginPass($REQUEST);
break;
case "welcome":
$this->welcome($REQUEST);
break;
case "logout":
$this->logout($REQUEST);
break;
default:
$this->login($REQUEST);
break;
}
}
function destory(){
if($this->debug){
$this->debuginfo .= "output = ".var_dump($this->getOutput());
$this->debuginfo .= "</pre>";
print (htmlentities($this->debuginfo));
}
}
//--------private -----------
//Moudle show login logic
function login($REQUEST){
array_push( $this->output, array( "code" => 1,
"parm" => $REQUEST,));
return true;
}
//Moudle check Login and set cookie
function checkLoginPass($parm){
global $CFG;
if($this->debug)
{ $this->debuginfo .= "Checking Password&&CFG pass=".$CFG['admin_pass'].", user input=".$parm['admin_loginpass']."n";
}
if( md5($parm['admin_loginpass']) == md5($CFG['admin_pass']))
{ //Login sucesful
setcookie("AA_admin",md5($CFG['admin_pass']));
array_push ( $this->output, array( "code"=> 2,
"parm"=> ""));
}
else
{ //login failed
if($this->debug){ $this->debuginfo .= "Wrong passowrd. Login Failed!"; }
$parm["reason"] = 1;
$this->login($parm); //rediction to login
}
return true;
}
function welcome($REQUEST){
array_push( $this->output, array( "code" => 3,
"parm" => "",
));
return true;
}
function logout(){
setcookie("AA_admin","0X00000");
array_push($this->output, array( "code" => 4,
"parm" => "", ) );
return true;
}
}
[/code:1:0119f06c28]
后台管理员的视图类。负责处理所有的显示,重新定位等表现层的事务。
输出output保存模板所需的html代码。这里就是两个数组,
菜单代码 menu_code
内容代码 main_code
[code:1:0119f06c28]
class action_Render extends render{
var $main_code = "";
var $menu_code = "";
function action_Render($logic_output){
$this->setDebug(false);
if($this->debug){
$this->debuginfo = "class action_render, input:".var_dump($logic_output);
}
$this->setMenu();
for ($i=0; $i< sizeof($logic_output); $i++){
switch ($logic_output[$i]["code"]){
case "1":
$this->login($logic_output[$i]["parm"]);
break;
case "2":
$this->loginSucessul($logic_output[$i]["parm"]);
break;
case "3":
$this->welcome($logic_output[$i]["parm"]);
break;
case "4":
$this->logout($logic_output[$i]["parm"]);
break;
default:
break;
}
if($i>100){ print("Error! OUTPUT more than 100!"); exit();}
}
$this->setOutput();
}
function destory(){
if($this->debug){
$this->debuginfo .= "output = ".$this->getOutput();
var_dump($this->getOutput());
$this->debuginfo .= "";
//print (htmlentities($this->debuginfo));
print ($this->debuginfo);
}
}
//////////////// P R I V A T E /////////////////////
function setOutput(){
$this->output = array ( "menu" => $this->getMenu(),
"main" => $this->getMain(),
);
if($this->debug){
$this->debuginfo .= print_r($this->output,true);
}
}
function getMenu(){
return $this->menu_code;
}
function getMain(){
return $this->main_code;
}
function clearMain(){
$this->main_code ="";
}
function clearMenu(){
$this->menu_code = "";
}
//Set Administroar Menu
function setMenu(){
$this->menu_code = "<a href="?action=logout">Logout</a>";
return true;
}
//build login interface
function login($parm){
$tpl_login = "login.html"; //Template of Login Html file
//Text for bad login
if($parm["reason"] == 1){
$text = "<p class="error" style="color: red;">Falsche Password! Bitte noch mal versuchen.<br /> Bad Password! Please try again <br /> ÃÜÂ&&íÎó! Ç&ÖØÊÔ&&";
}
else{
$text = "Please login";
}
$t = new Template("templates");
$t->set_file(array("all" =>$tpl_login));
$t->set_var( array( "Login_titel" => "LOGIN",
"text" => $text,
"lang_pls_login" => "PASSWORD",
));
$t->parse("main", array("all"));
$this->main_code .= $t->get("main");
$this->clearMenu();
return true;
}
//for login sucessful
function loginSucessul($parm){
//header("Location: admin.php");
$this->main_code .= "Welcome.";
header("Location: index.php?action=welcome");
return true;
}
//welcome
function welcome($parm){
$tpl_file = "welcome.html";
$t = new Template("templates");
$t->set_file(array("all" =>$tpl_file));
$t->set_var( array( "welcome" =>"" ));
$t->parse("main", array("all"));
$this->main_code .= $t->get("main");
$this->setMenu();
return true;
}
function logout($parm){
$this->main_code = "<p>Sie sind abgemeldet.</p><a href=''>Klick hier um sich anzumelden.</a>";
$this->clearMenu();
return true;
}
}
[/code:1:0119f06c28]
最后具体使用的时候就设这样
[code:1:0119f06c28]
//-------逻辑层-----------
$logic = new action_logic($_REQUEST );
$output = $logic->getOutput();
$logic->destory();
//--------表现层----------
$render = new action_Render($output);
$output = $render->getOutput();
$render->destory();
//-------------------------------
//----------模板输出--------
$t = new Template("templates");
$t->set_file(array("All"=>"admin.html"));
$t->set_var(array( "Menu" => $output['menu'],
"Main" => $output['main'],
));
$t->parse("main", array("All"));
$t->p("main");
[/code:1:0119f06c28]
这些代码都是看了mvc思想后的一些实现.还比较幼稚,大家见笑了.
zj5562 回复于:2005-06-30 14:45:46 高人,文章写的不错
powerpolly 回复于:2005-07-01 10:18:32 我用PHPLIB写模板类,运行速度是快,但写的很累。在输出时要精确控制。这样在业务逻辑中有太多的表现逻辑。我一直有个想法:重写phplib模板基类,再构造一个它的继承类view,这样在业务逻辑中就不需关心内容怎么表现出来了,表现方式完全由模板文件控制。而不是由业务逻辑控制。
你们有何建议?前段时间看到一篇文章说PHP中不宜实现MVC我有点郁闷,欢迎楼主和广大PHP高手们一起深入探讨。共同进步。我不知道你们的MSN,你们加我好了,我的MSN:[email protected]。
powerpolly 回复于:2005-07-03 13:47:03 这么重要的问题居然没人顶?自己帮顶一下。高手们都来看看~~
怎么没人加我的MSN?楼主跑哪去了
php爱好者站 http://www.phpfans.net dreamweaver|flash|fireworks|photoshop.
当然,由于只提供基本功能,所以很多细节内容还需要自己实现。
[b:12205a4c9c]1、框架的大体结构[/b:12205a4c9c]
整个框架其实只有两个class,其他一些class都是辅助工具,帮助实现一些具体的功能。
class Application
这个类提供了应用程序的入口、action映射、数据库初始化、模版引擎初始化等等功能。
class Page
提供了页面的入口,可以在此执行各种业务逻辑(本来应该独立出去,不过考虑到复杂性就放在Page类里面了)。
[b:12205a4c9c]2、class Application分析[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
// @author Liao Yu Lei <[email protected]>
// @version $Id$
require_once ('PEAR.php');
class Application extends PEAR
{
// private
var $_db = null;
// public
function Application() {
$this->PEAR();
// 设置PEAR的错误处理方式,这样可以减少很多错误判断代码
PEAR::setErrorHandling(PEAR_ERROR_DIE);
}
function _Application() {
if ($this->_db !== null) {
$this->_db->disconnect();
}
}
function getAction() {
$action = ($_GET['action']);
if (empty($action)) {
$action = CFG_DEFAULT_ACTION;
}
return $action;
}
function & initTemplate() {
require_once(APPROOT . '/smarty/Smarty.class.php');
$tpl = & new Smarty();
$tpl->template_dir = APPROOT . CFG_TEMPLATE_LIB;
$tpl->compile_dir = APPROOT . CFG_SMARTY_TEMPLATES_C;
$tpl->config_dir = APPROOT . CFG_SMARTY_CONFIGS;
$tpl->cache_dir = APPROOT . CFG_SMARTY_CACHE;
$tpl->cache = true;
return $tpl;
}
function & connectDatabase() {
require_once(APPROOT . '/pear/DB.php');
$dsn = CFG_DB_TYPE . '://' . CFG_DB_USER . ':' . CFG_DB_PASS . '@' .
CFG_DB_HOST . '/' . CFG_DB_NAME;
$db = & DB::connect($dsn);
if (DB::isError($db)) {
die ($db->toString());
}
if (!method_exists($db, 'quoteSmart')) {
// quoteSmart是PEAR::DB 1.6.0版本以后的新功能,用以替换quote函数
// Function available since: PEAR:DB Release 1.6.0
$db->disconnect();
die ('PEAR:DB 必须升级到 1.6.0 以上版本');
}
// 设置PEAR::DB的默认数据提取模式
$db->setFetchMode(DB_FETCHMODE_ASSOC);
$this->_db = & $db;
return $db;
}
function & getDb() {
return $this->_db;
}
function run() {
$action = strtolower($this->getAction());
$page_class_name = ucfirst($action) . 'Page';
$page_class_file = APPROOT . '/includes/class_' . $action . '.php';
@include_once($page_class_file);
if (!class_exists($page_class_name)) {
die ("需要的 class(${page_class_name}) 没有定义<BR>类定义文件 "" .
$page_class_path . "" 无法找到");
}
$page = & new $page_class_name($this);
$page->execute();
exit ();
}
}
?>
[/code:1:12205a4c9c]
Applcation类继承于PEAR(为了获得析构函数功能),然后在构造函数中修改了PEAR的默认错误处理方式。getAction()很简单,获得$_GET['action']的值,如果没有设置则使用默认值(我的程序里是Welcome)。initTemplate()初始化模版引擎(我用的是Smarty)。connectDatabase()初始化数据库联接,返回一个PEAR-DB对象的引用(需要一些常量来完成数据库连接,可以在inc_config.php中定义)。getDb()获得已经初始化的PEAR-DB对象。
Application类的入口就是run()。run()根据获得的action,载入对应的.php文件,并new一个对应class出来,然后执行这个class的execute()方法。
[b:12205a4c9c]3、Page类分析[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
// @author Liao Yu Lei <[email protected]>
// @version $Id$
class Page
{
// private
var $_app = null;
var $_action = null;
var $_SES_NAME = '_SES_POST_DATA_';
// public
function Page(& $app) {
$this->_app = & $app;
$this->_action = $this->_app->getAction();
session_start();
if (array_key_exists($this->$_SES_NAME, $_SESSION)) {
$array = $_SESSION[$this->$_SES_NAME];
unset($_SESSION[$this->$_SES_NAME]);
if (count($array) > 0) {
$_POST = & array_merge($_POST, $array);
}
}
}
function execute()
{
die ('只能够调用继承类的 execute 方法');
}
// protected
function _defaultTemplateVariables() {
return array();
}
function _redisplay() {
$this->_redirect('?action=' . $this->_action);
}
function _display($template_file, $title = 'Noname', & $values = array()) {
$tpl = $this->_app->initTemplate();
$default_variables = $this->_defaultTemplateVariables();
foreach($default_variables as $key => $value) {
$tpl->assign($key, $value);
}
foreach($values as $key => $value) {
$tpl->assign($key, $value);
}
$tpl->assign('title', $title);
$tpl->display($template_file);
}
function _redirect($url, & $params = array()) {
$_SESSION[$this->$_SES_NAME] = & $params;
header("Location: $url");
}
}
?>
[/code:1:12205a4c9c]
这个类本质上是封装一个action,但为了降低复杂度,我把页面的显示也做到了里面,对于小程序来说反倒更方便。Page类构造时需要传递Application对象的引用,并保存起来,方便Page以后使用。注意我在构造函数中还完成了一些特别的工作:
首先调用session_start()。我想大部分需要用户登录后操作的页面都需要首先调用session_start()吧,所以不如放在构造函数里。其次将$_SESSION[_SES_NAME]中的所有内容合并到$_POST中。这个工作是配合Page->_redirect()使用的,可以方便的在页面期间传递数据。
execute()这个函数还记得吗?是Application->run()调用的,是Page类的执行入口。Page类的execute()应该是个抽象函数,但因为php4还不支持,所以就写个die在里面。其实这些东东大部分都在shukebeita兄的精华贴(http://bbs.chinaunix.net/forum/viewtopic.php?t=224412)中提到过了。
_defaultTemplateVariables()是个非常有用的函数,不过Page里面的实现只是返回一个空array。而在Page继承类中就派上大用场了,具体说明请看稍后的_display()介绍。
_redisplay()这个函数用于重新显示当前页面,例如刷新什么的,不是很常用。
_display()函数是Page类中的重点之一,除了调用Application->initTemplate()初始化模版引擎外,还要调用$this->_defaultTemplateVariables()获得默认的模板变量。哈哈,现在明白_defaultTemplateVariables()干嘛了吧,只要在Page继承类中重载_defaultTemplateVariables()函数,并返回需要的array,就可以方便的显示一些公共信息。接下来就是将所有需要在模版中显示的数据都通过$tpl->assign()指定到相应的变量中。最后调用$tpl->display()显示模版内容。
_redirect()完成重定向工作,不过可以传递一些数据到下一个页面里面。这些数据会存放在$_SESSION[_SES_NAME]中,然后在新页面的构造函数中被合并到$_POST中。所以效果和通过post将数据提交到另一个页面差不多(本质上是不同的,而且用户按F5刷新新页面得到的结果也不同)。
整个框架就这么多内容,使用时首先需要在应用程序(网站)根目录放一个index.php文件,然后输入下面的内容:
[code:1:12205a4c9c]
require_once(APPROOT . '/framework/class_application.php');
$app = & new Application();
$app->run();
[/code:1:12205a4c9c]
这样不管是执行应用程序的什么功能,只要在网址后跟上“?action=xxxx”即可。假设我们的应用程序位于www.dualface.com,那么登录功能的url就应该是http://www.dualface.com/?action=login。
下面我就给出一个登录的示例:
首先需要一个class来封装用户数据和操作:
[b:12205a4c9c]class_member.php[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
class Member
{
var $_db = null;
var $_dbtable = null;
var $_uncol = null;
var $_pwcol = null;
var $_sesname = null;
function Member($db = null, $dbtable = 'members', $uncol = 'username',
$pwcol = 'password', $sesname = 'MEMBERINFO')
{
$this->_db = $db;
$this->_dbtable = $dbtable;
$this->_uncol = $uncol;
$this->_pwcol = $pwcol;
$this->_sesname = $sesname;
}
function _loadInfo($username)
{
$sql = 'SELECT * FROM ' . $this->_dbtable . ' WHERE ' .
$this->_db->quoteIdentifier($this->_uncol) . ' = ' .
$this->_db->quoteSmart($username);
$result = $this->_db->query($sql);
if (DB::isError($result))
{
die ($result->toString());
}
if ($result->numRows() == 0)
{
$result->free();
return array();
}
$data = $result->fetchRow(DB_FETCHMODE_ASSOC);
$result->free();
return $data;
}
function login($username, $password, $keys = array())
{
$result = $this->_loadInfo($username);
if (empty($result))
{
return 'username';
}
if (md5($password) != $result[$this->_pwcol])
{
return 'password';
}
$data = array();
foreach ($result as $key => $value)
{
$newkey = 'G_' . strtoupper($key);
$data[$newkey] = $value;
}
$data['member_login'] = true;
$_SESSION[$this->_sesname] = $data;
$this->info = $data;
return true;
}
function logout()
{
unset ($_SESSION[$this->_sesname]);
}
function checkLogin()
{
$data = $_SESSION[$this->_sesname];
return $data['member_login'] === true;
}
function getInfo()
{
if ($this->checkLogin())
{
return $_SESSION[$this->_sesname];
}
return array();
}
function getRawInfo()
{
if ($this->checkLogin())
{
$data = $_SESSION[$this->_sesname];
$data2 = array();
foreach ($data as $key => $value)
{
$newkey = strtolower(substr($key, 2));
$data2[$newkey] = $value;
}
return $data2;
}
return array();
}
function getAttribute($key)
{
if ($this->checkLogin())
{
return $_SESSION[$this->_sesname][$key];
}
return null;
}
function refresh()
{
if (!$this->checkLogin())
{
return false;
}
$data = $_SESSION[$this->_sesname];
$result = $this->_loadInfo($data['G_USERNAME']);
if (empty($result))
{
return false;
}
$data = array();
foreach ($result as $key => $value)
{
$newkey = 'G_' . strtoupper($key);
$data[$newkey] = $value;
}
$data['member_login'] = true;
$_SESSION[$this->_sesname] = $data;
return true;
}
}
?>
[/code:1:12205a4c9c]
这个类之所以有点复杂是因为我做的那个项目需要一些比较特别的功能,一般应用可以将这个类简化一下,这里我就懒得改了。
有了用户封装类以后,还准备了一个Page类的继承类,作为各个页面的基类。
[b:12205a4c9c]class_mypage.php[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
class MyPage extends Page
{
function _defaultTemplateVariables()
{
$data = array(
'CFG_CSS_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_CSS_LIB,
'CFG_IMAGE_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_IMAGE_LIB,
'CFG_SITE_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_SITE_LIB,
'CFG_ADMIN_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_ADMIN_LIB,
'CFG_script_LIB' => APPROOT . CFG_TEMPLATE_LIB . CFG_script_LIB,
'CFG_ROLE_USER' => CFG_ROLE_USER,
'CFG_ROLE_ADMIN' => CFG_ROLE_ADMIN);
$member = new Member(null);
return array_merge($data, $member->getInfo());
}
}
?>
[/code:1:12205a4c9c]
这个MyPage类的唯一作用就是设置一些每个页面显示都需要的变量,其他页面只要继承这个类就可以了。
login页面:
[b:12205a4c9c]class_login.php[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
class LoginPage extends MyPage
{
function _login()
{
$db = $this->_app->connectDatabase();
$member = new Member($db);
$result = $member->login($_POST['username'], $_POST['password']);
$db->disconnect();
if ($result !== true)
{
$data[$result . '_error'] = true;
$data['username'] = $_POST['username'];
$this->_display(CFG_SITE_LIB . '/login.html', 'Log In', $data);
}
else
{
// 登录后显示欢迎页面
$this->redirect('?action=welcome');
}
}
function _displayLoginPage()
{
$member = new Member();
$member->logout();
$this->_display(CFG_SITE_LIB . '/login.html', 'Log in');
}
function execute()
{
$page_action = $_POST['page_action'];
switch ($page_action)
{
case 'login':
$this->_login();
break;
case '':
$this->_displayLoginPage();
break;
}
}
}
?>
[/code:1:12205a4c9c]
这个类里面execute()根据$_POST['page_action']来执行不同的函数。这样实际上等同于一个Page继承类可以完成多项工作。Page类在这一点上就和action映射类似了。这个技巧我在项目中大量使用,觉得非常方便。
class_login.php需要的html模版文件:
[b:12205a4c9c]login.html[/b:12205a4c9c]
[code:1:12205a4c9c]
{include file="$CFG_SITE_LIB/header.html"}
<DIV>
<DIV>
<H3>LOG IN TO YOUR ACCOUNT</H3>
<P> Log on here to view your profile. If you do not yet have an account,
you must first register for an account. </P>
</DIV>
<DIV>
<FORM name="form_login" id="form_login" method="post" action="?action=login">
<TABLE width="400" border="0" cellspacing="0" cellpadding="4">
<TR>
<TD>Username:</TD>
</TR>
<TR>
<TD {if $username_error}class="error"{/if}>
<INPUT name="username" type="text" id="username" style="width: 220px" maxlength="64" value="{$username}"/>
<P>{if $username_error}* 用户名没有找到。 <BR>{/if}</P>
</TD>
</TR>
<TR>
<TD>Password:</TD>
</TR>
<TR>
<TD {if $password_error}class="error"{/if}>
<INPUT name="password" type="password" id="password" maxlength="32" style="width: 220px">
<P>{if $password_error}* 错误的密码。 <BR>{/if}</P>
</TD>
</TR>
</TABLE>
<BR>
<INPUT type="submit" name="Submit" value="Log in">
<INPUT type="hidden" name="page_action" value="login">
</FORM>
</DIV>
</DIV>
{include file="$CFG_SITE_LIB/footer.html"}
[/code:1:12205a4c9c]
看到这个html文件中的<INPUT type="hidden" name="page_action" value="login">了吗?所以提交这个页面的表单(表单的action属性是"?action=login")时,虽然还是调用class_login.php,但却会执行LoginPage->_login()。
接下来是logout页面
[b:12205a4c9c]class_logout.php[/b:12205a4c9c]
[code:1:12205a4c9c]
<?php
class LogoutPage extends MyPage
{
function show()
{
$member = new Member();
$member->logout();
// 注销后显示退出页面
$this->redirect('?action=bye');
}
}
?>
[/code:1:12205a4c9c]
由于LogoutPage类根本没有显示模版,所以就不需要配套的html模板文件了。
这个框架虽然简单,但在我上一个项目的实际应用中,效果还是非常不错的。首先封装了Smarty模版引擎的使用,做到了代码和HTML页面的完全分离。其次OO结构可以方便的扩充功能(如果每一个功能都非常复杂,例如显示登录页面和处理登录,那么可以分别撰写class,并分配不同的action)。
我在这里把我实现这个框架贴出来,希望大家能够共同完善这个框架,做成一个稳定的东西,可以方便大家在项目中使用。
longnetpro 回复于:2004-03-13 03:39:24 基本思路与我的相同。但有些问题需要注意,主要考虑因素是效率。
1、变量作用域问题,大量数据复制明显会导致效率下降,即使用了引用。主要原因是包含页面时,所有变量的作用域都只在一个函数或是方法之中,而如果过多的用GLOBAL又不符合编程的原则。一般的办法是直接存贮在Application或是Page类中或用超级变量(对于超级变量如$_GET,最好是不要人为地修改,否则与GLOBAL无异),基本上可以做到全局调用。
2、模板问题。用SMARTY之类的模板会严重影响效率,不值得提倡。提倡嵌入式脚本,但要做到逻辑处理与显示分离(不是PHP代码与HTML分离)。其实这是利用PHP语言的先天优势,为什么要抛弃它呢?用模板预编译可以达到两个方面的综合平衡。
3、SESSION问题,不能过多依赖于SESSION。
4、数据库与模板初始化不宜放在Application或是Page中,而应该放到不同的任务中,因为每个任务的要求是不一样的。
还有其它一些考虑,如
5、容错性问题。
6、安全性问题。
以上两问题可能出现在文件或类找不到的情况下,或是有人伪造类。
象Application 或是 Page主要是两个接口类,而不应该实现多数细节,如果对不同的项目有某些具体的要求,不妨继承这两个类以适应不同的项目。如果作为通用框架,楼主提交的类中的某些功能还可简化。
另外,为了可扩展性。以上两个类只处理一个action是不够的,还应该可以处理语言参数,模板编号,任务名称(一个任务可以有多个action)。两个类的目的并非为了实现这些参数代表的内容,而是进行安全性检查,去掉非法的输入,并将不存在的内容置为默认值,这些值再往下传递下去,具体实现则应该由具体的业务逻辑与显示逻辑类来处理了。
在这个中间,业务逻辑没什么好谈的,因为很少涉及显示输出,因此只要能传入传出相应的值就可以了。对于显示逻辑,如果象楼主那样用Smaty一类推式的HTML模板的话,这个方式是很好的。但如果是用嵌入式脚本的方式,楼主的这个方法就有比较大的问题,也是主要是因为变量作用域的问题以及在类的方法中包含文件的问题。由于我是用的预编译嵌入式脚本,因此我并未继承任何Page类,而是在一个任务页面中直接调用include,为了保证业务逻辑与显示逻辑层次分明,用固定的接口类与模板ID打交道,在模板编译的时候就直接调用这个通用接口,以便两者能比较好的衔接。就是说,在模板中与程序中都用同一个接口,这样的话,模板只用关心它那一部分,程序就只用关心程序的部分了。因为是预编译,就能由程序比较好的控制,两者的接口与很容易结合。但就我本人的观点,应当避免使用Smarty或是类似的HTML模板(会给PHP解释器带来巨大的负担,因为是二次解析,其中第二次是人为的用PHP脚本来解析,效率大打折扣),而直接使用PHP本身的嵌入式脚本(虽然是一个缺点,但也是个优势,在使用过程中,这应该是个优势)。
shukebeita 回复于:2004-03-13 10:03:34 感动呀!比我的那一贴还长。多多联系,我加了你的msn。
I_Just_Shot_John_Lennon 回复于:2004-03-13 11:29:44 楼主写的不错
做沙发的点评的也不错
学习ing
dualface 回复于:2004-03-13 12:06:21 [b:37240ccd73]1、变量作用域问题,大量数据复制明显会导致效率下降,即使用了引用。主要原因是包含页面时,所有变量的作用域都只在一个函数或是方法之中,而如果过多的用GLOBAL又不符合编程的原则。一般的办法是直接存贮在Application或是Page类中或用超级变量(对于超级变量如$_GET,最好是不要人为地修改,否则与GLOBAL无异),基本上可以做到全局调用。 [/b:37240ccd73]
在上面的两个类中,变量传递较多的地方就是Page->_display()吧,如果很多数据要显示性能确实不如直接include快。不过你说的作用域问题我就不明白了,难道为了速度全部用GLOBAL变量?
[b:37240ccd73]2、模板问题。用SMARTY之类的模板会严重影响效率,不值得提倡。提倡嵌入式脚本,但要做到逻辑处理与显示分离(不是PHP代码与HTML分离)。其实这是利用PHP语言的先天优势,为什么要抛弃它呢?用模板预编译可以达到两个方面的综合平衡。[/b:37240ccd73]
说实话,我没有测试过Smarty的性能,但是Smarty是一种编译型的模版引擎,只有改变了模板文件以后第一次display的时候才会进行模版分析,以后都不会再进行分析了,这不就是你说的模版预编译吗?当然了,速度最快的方式还是准备好要显示的内容,然后include模版页面文件了,呵呵。
[b:37240ccd73]3、SESSION问题,不能过多依赖于SESSION。[/b:37240ccd73]
session就是解决页面间的数据共享问题的,干嘛不用啊?如果不用session,难道自己创建临时文件或者把数据放在数据库中?
[b:37240ccd73]4、数据库与模板初始化不宜放在Application或是Page中,而应该放到不同的任务中,因为每个任务的要求是不一样的。[/b:37240ccd73]
数据库初始化放在Application中,但只有在你调用connectDatabase的时候才会include PEAR-DB的文件并连接数据库。所以对于用不上数据库显示的页面来说完全没有影响。这和把数据库初始化代码放到一个单独的函数中是一个道理。同样模版引擎初始化也是一样的,只有在你调用Page->display的时候才会进行。
[b:37240ccd73]5、容错性问题。[/b:37240ccd73]
这个意见很正确,我确实没做什么容错性问题处理,绝大多数错误都是用die处理。要不就是在业务逻辑代码中自行处理错误,毕竟PHP没有异常这些错误处理工具。
[b:37240ccd73]6、安全性问题。以上两问题可能出现在文件或类找不到的情况下,或是有人伪造类。[/b:37240ccd73]
getAction我没有对不存在的action进行处理,而且也没有考虑url编码的问题,所以你说的问题是完全存在的。之所以不进行检查主要原因一是做的项目本身只是个演示项目,要求不高,其次我准备以后改进成action mapping方式,这样不存在的action直接就过滤掉了。
[b:37240ccd73]象Application 或是 Page主要是两个接口类,而不应该实现多数细节,如果对不同的项目有某些具体的要求,不妨继承这两个类以适应不同的项目。如果作为通用框架,楼主提交的类中的某些功能还可简化。 另外,为了可扩展性。以上两个类只处理一个action是不够的,还应该可以处理语言参数,模板编号,任务名称(一个任务可以有多个action)。两个类的目的并非为了实现这些参数代表的内容,而是进行安全性检查,去掉非法的输入,并将不存在的内容置为默认值,这些值再往下传递下去,具体实现则应该由具体的业务逻辑与显示逻辑类来处理了。 [/b:37240ccd73]
不错不错,获益良多啊!特别是任务名称这个东东,我就是在想一个业务模块中不同页面间怎么结合起来处理呢。不过你说的这两个类只提供接口,然后在继承类实现。这样当然不错了,但是小项目没这个必要吧,继承太多层次了。
[b:37240ccd73]在这个中间,业务逻辑没什么好谈的,因为很少涉及显示输出,因此只要能传入传出相应的值就可以了。对于显示逻辑,如果象楼主那样用Smaty一类推式的HTML模板的话,这个方式是很好的。但如果是用嵌入式脚本的方式,楼主的这个方法就有比较大的问题,也是主要是因为变量作用域的问题以及在类的方法中包含文件的问题。由于我是用的预编译嵌入式脚本,因此我并未继承任何Page类,而是在一个任务页面中直接调用include,为了保证业务逻辑与显示逻辑层次分明,用固定的接口类与模板ID打交道,在模板编译的时候就直接调用这个通用接口,以便两者能比较好的衔接。就是说,在模板中与程序中都用同一个接口,这样的话,模板只用关心它那一部分,程序就只用关心程序的部分了。因为是预编译,就能由程序比较好的控制,两者的接口与很容易结合。但就我本人的观点,应当避免使用Smarty或是类似的HTML模板(会给PHP解释器带来巨大的负担,因为是二次解析,其中第二次是人为的用PHP脚本来解析,效率大打折扣),而直接使用PHP本身的嵌入式脚本(虽然是一个缺点,但也是个优势,在使用过程中,这应该是个优势)。[/b:37240ccd73]
早期的模版引擎确实是每次显示时都回解析一次,但Smarty不是这样的。除了可以自动判断是否需要编译模版外,还可以缓存输出文件的html内容(特别适合显示新闻什么的)。从这段话看,兄弟说的预编译模版应该是自己实现的一种机制,不知道兄弟是否愿意公开出来大家学习一下啊。
我以前写的php程序虽然也用模版,但大概是这个样子的:
1、一个.php文件完成业务逻辑,并把所有需要输出的内容都放到$out数组中
2、需要显示的时候include模版文件
3、在模板文件里面用<?php echo $out['sss']; ?>来显示内容
这样做速度确实很快,不过由于有时需要在HTML中做比较复杂的显示,这就涉及到页面制作人员要么懂一点php知识,要么编程人员就要自己去改html模板文件,总是容易引发冲突。用模版库的最大好处就是页面制作人员只需要懂得怎么放置一些简单的标记就行了。
总的来说,这个框架还是很不完善的,所以我也在不断改进。同时这个框架的定位就是具有一定复杂程度的业务逻辑,相对来说性能要求不那么高的小型项目。如果是性能要求非常高的项目,我就会用最直接的include模版文件方式来实现。如果是中型的项目,那么我可能就会采用php.mvc这样的框架了。不过我认为php在大、中型项目上没什么优势,这些项目用java或者.net来完成要好得多。
longnetpro 回复于:2004-03-13 12:18:26 楼上的,我也加了你的MSN,多多交流。
dualface 回复于:2004-03-13 12:33:59 呵呵,是啊,大家多交流,一起提高 :)
swingcoder 回复于:2004-03-13 13:40:48 加精华
hongweig 回复于:2004-04-14 15:58:40 dualface 兄,我们都想到一块儿了。我也是看了那篇精华帖突然有感,
不过,我是用phplib tempalte模板来处理视图类。
有空多在msn交流哦。
odin_free 回复于:2004-04-16 13:17:53 好!
abin30 回复于:2004-04-17 18:32:43 看到有很多高手在这里,就过来了!
呵呵,希望大家交流的时候,都在这里,你们用MSN我们怎么办啊
当然拉,如果能吧你们涉及到技术方面的聊天记录贴出来,那是最好了。
先感谢了。!
tonera 回复于:2004-04-19 10:53:49 :D
呵呵,今天偷时间细细看了,不错,再改进一下贴出来更好.
我现在的思想与dualface原来的有一点不一样:
[quote:f3ae58648f]我以前写的php程序虽然也用模版,但大概是这个样子的:
1、一个.php文件完成业务逻辑,并把所有需要输出的内容都放到$out数组中
2、需要显示的时候include模版文件
3、在模板文件里面用<?php echo $out['sss']; ?>来显示内容 [/quote:f3ae58648f]
第一点,我不是把内容存到$out数组里再显示的,这样服务器很累,较慢.
我在输出的时候尽可能采用嵌入式脚本,业务逻辑可能只返回一个句柄,虽然很老土,但管用,速度快.呵呵.当然,工作量大了些,把文档做好还是很好的.
模版的二次解析真的不能用了,效率极低,个人网页倒还可以.
dualface 回复于:2004-04-20 12:23:18 确实,我也觉得那些二次模板没什么必要。
而且这次我做新项目的时候把整个框架都重写了,现在支持:
* 多个模块,每个模块可以有自己的action map
* 每个模块和每个action都可以进行权限检查
* 内置一个简单的模板引擎(直接include页面,引擎主要完成一些准备工作)
* 封装了FORM和URL(也就是$_POST和$_GET),包括FORM的验证
等这个项目做完,整理一下就发上来,欢迎大家来吐口水,哈哈。
lee3f 回复于:2005-06-28 15:59:26 来迟了,楼主,有新的代码和介绍吗?
imbiss 回复于:2005-06-29 18:33:59 [quote:0119f06c28="tonera"]
第一点,我不是把内容存到$out数组里再显示的,这样服务器很累,较慢.
我在输出的时候尽可能采用嵌入式脚本,业务逻辑可能只返回一个句柄,虽然很老土,但管用,速度快.呵呵.当然,工作量大了些,把文档做好还是很好的.
..........[/quote:0119f06c28]
这位和我这两天做的事差不多。我也把我的代码贴出来。和楼主的思路类似,但有所不同,可能我的效率会差一点。
主要区别
1.不依赖PEAR
2.逻辑层和表现层完全分开。他们之间用out数组传递,而没有使用引用。
3.使用PHPLIB的模板
代码如下
1.基类。
对于我的每个项目,我都喜欢建一个自己的基类,我自己的类全部从这里继承。
[code:1:0119f06c28]
class AAObject{
var $deubg = true;
var $debuginfo;
function setDebug($bool){
if($bool){
$this->debug = true;
}
else{
$this->debug = false;
}
}
function getDebug(){
return $this->debuginfo;
}
}
[/code:1:0119f06c28]
我的逻辑类的基类
[code:1:0119f06c28]
class logic extends AAObject{
var $output=array();
function getOutput(){
return $this->output;
}
}
[/code:1:0119f06c28]
我的视图类的基类
[code:1:0119f06c28]
class render extends AAObject{
var $output = array();//save output code array
//Constructor
function ibsRender(){
}
function getOutput(){
return $this->output;
}
}
[/code:1:0119f06c28]
后台管理员的逻辑类,针对用户的输入(从REQUEST来),完成逻辑事务。在这个例子里,主要示范完成,用户登录(action=login),登出(action=logout),登录成功(action=welcome)
完成后,把数据放到output里,传给视图类作处理.
[code:1:0119f06c28]
class action_logic extends logic{
/***************
output code list
-----+---------------+-------------+
CODE Beschreibung Parameter
-----+---------------+-------------+
1 Login original REQUEST / reson=1 for bad login/
2 Login Succesful NONE
3 Welcome NONE
4 Logout NONE
***************/
//Constructor
function action_logic($REQUEST){
$this->setDebug(false); //Set Debug ON/OFF
if($this->debug){ $this->debuginfo .="Debug: LogicModul. Request action is ".$REQUEST["action"]." ";}
global $CFG;
//Check Identitat using cookie before do anything
if($REQUEST["action"] == "checkLoginPass"){
//Do nothing
}
else{
if(!$_COOKIE["AA_admin"] || $_COOKIE["AA_admin"]!= md5($CFG['admin_pass']))
{ //Not Login(no cookie) or cookie password error, set it back to login page
if($this->debug){ $this->debuginfo .="You are not Login. Set page to Login Page";}
$REQUEST["action"] = "login";
}
}
switch ($REQUEST["action"]){
case "login":
$this->login($REQUEST);
break;
case "checkLoginPass":
$this->checkLoginPass($REQUEST);
break;
case "welcome":
$this->welcome($REQUEST);
break;
case "logout":
$this->logout($REQUEST);
break;
default:
$this->login($REQUEST);
break;
}
}
function destory(){
if($this->debug){
$this->debuginfo .= "output = ".var_dump($this->getOutput());
$this->debuginfo .= "</pre>";
print (htmlentities($this->debuginfo));
}
}
//--------private -----------
//Moudle show login logic
function login($REQUEST){
array_push( $this->output, array( "code" => 1,
"parm" => $REQUEST,));
return true;
}
//Moudle check Login and set cookie
function checkLoginPass($parm){
global $CFG;
if($this->debug)
{ $this->debuginfo .= "Checking Password&&CFG pass=".$CFG['admin_pass'].", user input=".$parm['admin_loginpass']."n";
}
if( md5($parm['admin_loginpass']) == md5($CFG['admin_pass']))
{ //Login sucesful
setcookie("AA_admin",md5($CFG['admin_pass']));
array_push ( $this->output, array( "code"=> 2,
"parm"=> ""));
}
else
{ //login failed
if($this->debug){ $this->debuginfo .= "Wrong passowrd. Login Failed!"; }
$parm["reason"] = 1;
$this->login($parm); //rediction to login
}
return true;
}
function welcome($REQUEST){
array_push( $this->output, array( "code" => 3,
"parm" => "",
));
return true;
}
function logout(){
setcookie("AA_admin","0X00000");
array_push($this->output, array( "code" => 4,
"parm" => "", ) );
return true;
}
}
[/code:1:0119f06c28]
后台管理员的视图类。负责处理所有的显示,重新定位等表现层的事务。
输出output保存模板所需的html代码。这里就是两个数组,
菜单代码 menu_code
内容代码 main_code
[code:1:0119f06c28]
class action_Render extends render{
var $main_code = "";
var $menu_code = "";
function action_Render($logic_output){
$this->setDebug(false);
if($this->debug){
$this->debuginfo = "class action_render, input:".var_dump($logic_output);
}
$this->setMenu();
for ($i=0; $i< sizeof($logic_output); $i++){
switch ($logic_output[$i]["code"]){
case "1":
$this->login($logic_output[$i]["parm"]);
break;
case "2":
$this->loginSucessul($logic_output[$i]["parm"]);
break;
case "3":
$this->welcome($logic_output[$i]["parm"]);
break;
case "4":
$this->logout($logic_output[$i]["parm"]);
break;
default:
break;
}
if($i>100){ print("Error! OUTPUT more than 100!"); exit();}
}
$this->setOutput();
}
function destory(){
if($this->debug){
$this->debuginfo .= "output = ".$this->getOutput();
var_dump($this->getOutput());
$this->debuginfo .= "";
//print (htmlentities($this->debuginfo));
print ($this->debuginfo);
}
}
//////////////// P R I V A T E /////////////////////
function setOutput(){
$this->output = array ( "menu" => $this->getMenu(),
"main" => $this->getMain(),
);
if($this->debug){
$this->debuginfo .= print_r($this->output,true);
}
}
function getMenu(){
return $this->menu_code;
}
function getMain(){
return $this->main_code;
}
function clearMain(){
$this->main_code ="";
}
function clearMenu(){
$this->menu_code = "";
}
//Set Administroar Menu
function setMenu(){
$this->menu_code = "<a href="?action=logout">Logout</a>";
return true;
}
//build login interface
function login($parm){
$tpl_login = "login.html"; //Template of Login Html file
//Text for bad login
if($parm["reason"] == 1){
$text = "<p class="error" style="color: red;">Falsche Password! Bitte noch mal versuchen.<br /> Bad Password! Please try again <br /> ÃÜÂ&&íÎó! Ç&ÖØÊÔ&&";
}
else{
$text = "Please login";
}
$t = new Template("templates");
$t->set_file(array("all" =>$tpl_login));
$t->set_var( array( "Login_titel" => "LOGIN",
"text" => $text,
"lang_pls_login" => "PASSWORD",
));
$t->parse("main", array("all"));
$this->main_code .= $t->get("main");
$this->clearMenu();
return true;
}
//for login sucessful
function loginSucessul($parm){
//header("Location: admin.php");
$this->main_code .= "Welcome.";
header("Location: index.php?action=welcome");
return true;
}
//welcome
function welcome($parm){
$tpl_file = "welcome.html";
$t = new Template("templates");
$t->set_file(array("all" =>$tpl_file));
$t->set_var( array( "welcome" =>"" ));
$t->parse("main", array("all"));
$this->main_code .= $t->get("main");
$this->setMenu();
return true;
}
function logout($parm){
$this->main_code = "<p>Sie sind abgemeldet.</p><a href=''>Klick hier um sich anzumelden.</a>";
$this->clearMenu();
return true;
}
}
[/code:1:0119f06c28]
最后具体使用的时候就设这样
[code:1:0119f06c28]
//-------逻辑层-----------
$logic = new action_logic($_REQUEST );
$output = $logic->getOutput();
$logic->destory();
//--------表现层----------
$render = new action_Render($output);
$output = $render->getOutput();
$render->destory();
//-------------------------------
//----------模板输出--------
$t = new Template("templates");
$t->set_file(array("All"=>"admin.html"));
$t->set_var(array( "Menu" => $output['menu'],
"Main" => $output['main'],
));
$t->parse("main", array("All"));
$t->p("main");
[/code:1:0119f06c28]
这些代码都是看了mvc思想后的一些实现.还比较幼稚,大家见笑了.
zj5562 回复于:2005-06-30 14:45:46 高人,文章写的不错
powerpolly 回复于:2005-07-01 10:18:32 我用PHPLIB写模板类,运行速度是快,但写的很累。在输出时要精确控制。这样在业务逻辑中有太多的表现逻辑。我一直有个想法:重写phplib模板基类,再构造一个它的继承类view,这样在业务逻辑中就不需关心内容怎么表现出来了,表现方式完全由模板文件控制。而不是由业务逻辑控制。
你们有何建议?前段时间看到一篇文章说PHP中不宜实现MVC我有点郁闷,欢迎楼主和广大PHP高手们一起深入探讨。共同进步。我不知道你们的MSN,你们加我好了,我的MSN:[email protected]。
powerpolly 回复于:2005-07-03 13:47:03 这么重要的问题居然没人顶?自己帮顶一下。高手们都来看看~~
怎么没人加我的MSN?楼主跑哪去了
php爱好者站 http://www.phpfans.net dreamweaver|flash|fireworks|photoshop.
相关阅读 更多 +