php框架
piaoling 2011-12-23 10:53:27
最近整理做过的小东西,发现为学php曾短暂搞过个php框架。后面忙别的就没下文了,估计接下来也没什么精力搞,想想还是放出来晒晒吧,主要是思路的交流。
项目目录结构采用playframework的类似结构。
既然宣称它是个MVC框架,基本的东西要有:
路由,参数绑定,参数验证,重定向,模版机制,自定义标签,ORM,插件结构。实现了这几个的基本东西,一个MVC框架就成形,接下来做的就是完善各部份功能与性能的改善。
一个个说这版提供了什么功能:
路由:
路由的配置其目的是url到action的映射。我这里直接用pcre来做这个映射,示例:
001 |
<?php |
002 |
003 |
Router::root( 'GET' , 'application.home' ); |
004 |
005 |
Router::route( 'GET' , '/docs' , "docs.index" ); |
006 |
Router::route( 'GET' , '/downloads' , "downloads.index" ); |
007 |
008 |
//将形如/admin/user/list/1这样的url映射到controllers/admin/user.php的list方法,1为参数 |
009 |
Router::route( '*' , '/admin/(?P<ct>.+)/(?P<ac>.+)/(?P<id>d+)' , 'admin/{ct}.{ac}' ); |
010 |
011 |
Router::route( '*' , '/admin/(?P<ct>.+)/(?P<ac>.+)' , 'admin/{ct}.{ac}' ); |
012 |
013 |
Router::route( '*' , '/(?P<ct>.+)/(?P<ac>.+)/(?P<id>d+)' , '{ct}.{ac}' ); |
014 |
//映射所有的url |
015 |
Router::route( '*' , '/(?P<ct>.+)/(?P<ac>.+)' , '{ct}.{ac}' ); |
016 |
|
017 |
这里面有几个说明的地方 |
018 |
第一个参数,声明接受的http方法。 |
019 |
第二个参数声明url的正则表达式: |
020 |
分组必须用()包含,因为匹配是分组捕获的。ct是表示控制器,ac表示action方法 |
021 |
这两的字面量是写死的。 |
022 |
会有需要带参数的url声明,如/blog/2011/11/list这样的url需要映射到控制器方法,可这么声明: |
023 |
Router::route( 'GET' , '/blog/(?P<year>d+)/(?P<month>d+)' , 'blog.list' ); |
024 |
第三个参数声明控制器规则 |
025 |
匹配是按照声明的顺序,逐个查找来匹配的。 |
026 |
027 |
?> |
028 |
029 |
控制器: |
030 |
PM的控制器不需要继承任何类,唯一的要求是放到controllers里面。示例: |
031 |
controllers/news.php |
032 |
033 |
<?php |
034 |
035 |
PM::_include( "core" , "model/Db" ); |
036 |
037 |
class news { |
038 |
039 |
public static function all() { |
040 |
041 |
$db = Db::get(); |
042 |
$results = $db ->find( "select * from news order by id desc" , array ())->fetchAsMap(10); |
043 |
MVC::putTArg( "objects" , $results ); |
044 |
MVC::render(); |
045 |
|
046 |
} |
047 |
048 |
public static function show() { |
049 |
050 |
$db = Db::get(); |
051 |
$object = $db ->find( "select * from news where id=" . $_GET [ 'id' ])->fetchSigAsObject(); |
052 |
MVC::putTArg( "object" , $object ); |
053 |
MVC::render(); |
054 |
|
055 |
} |
056 |
057 |
} |
058 |
059 |
?> |
060 |
061 |
062 |
几点说明: |
063 |
action方法都是静态方法,由于php的运行机制,不需要关心同步问题。 |
064 |
支持flash范围的变量。MVC::flash( 'success' , '成功' ); |
065 |
简单的参数绑定:MVC::bind( $object , $_POST ); |
066 |
参数验证:MVC::validate( array ( 'required' => $_POST [ 'username' ])); |
067 |
会去找pm/mvc/validate/下面的required.php文件: |
068 |
class PM_MVC_Validator_required { |
069 |
070 |
public $msg = '不能为空' ; |
071 |
public function validate( $value ) { |
072 |
if ( $value === '' || is_null ( $value || !isset( $value ))) |
073 |
return false; |
074 |
else |
075 |
return true; |
076 |
} |
077 |
function __get( $name ) { |
078 |
return $this -> $name ; |
079 |
} |
080 |
function __set( $name , $value ) { |
081 |
$this -> $name = $value ; |
082 |
} |
083 |
} |
084 |
自定义验证增照这个模版方法改就好了。 |
085 |
086 |
渲染模版: |
087 |
MVC::render();默认会去找views目录下与action方法同名的模版文件。 |
088 |
也支持渲染不同名的文件。MVC::render( 'list.html' ); |
089 |
重定向: |
090 |
有两种,如果是跨域重定向,直接写上url,如:MVC::redirect( 'http://www.oschina.net' ); |
091 |
其它形式会根据action表达式来解析: |
092 |
MVC::redirect( 'show' ),会根据Router里声明的路由,反向解析出最匹配的url后重定向。 |
093 |
094 |
控制器还实现了简单的拦截机制: |
095 |
如果在控制方法中声明了 __before方法,则该控制器方法调用前都会被__before拦截到,如: |
096 |
<?php |
097 |
098 |
public class admin { |
099 |
public static function __before() { |
100 |
//assert logged |
101 |
$username = $_SESSION [ 'username' ]; |
102 |
if (! $username ) |
103 |
MVC::redirect( "errors.forbidden" ); |
104 |
MVC:: $layout = '/layouts/admin.html' ; //指定布局文件为admin.html。 |
105 |
} |
106 |
} |
107 |
?> |
108 |
109 |
拦截机制还支持继承结构: |
110 |
public class user extends admin{},这个控制器下的方法将会首先被父类admin的__before |
111 |
拦截。 |
001 |
视图模版: |
002 |
003 |
模版是基于宏哥推荐的vem那个于2007年就停止更新的模版引擎。 |
004 |
除了支持基本功能外,还做了一些扩展:模版布局,自定义标签等。 |
005 |
006 |
先看看布局文件: |
007 |
views/layouts/main.html |
008 |
<!DOCTYPE> |
009 |
<html lang= 'zh-CN' xml:lang= 'zh-CN' xmlns= 'http://www.w3.org/1999/xhtml' > |
010 |
011 |
<head> |
012 |
<meta http-equiv= "Content-Type" content= "text/html; charset=UTF-8" /> |
013 |
<title>PM</title> |
014 |
<link rel= "shortcut icon" href= "{site}/favicon.ico" /> |
015 |
<link rel= "stylesheet" type= "text/css" href= "{site}/static/css/screen.css" media= "screen" /> |
016 |
<link rel= "stylesheet" type= "text/css" href= "{site}/static/css/pm.css" media= "screen" /> |
017 |
</head> |
018 |
<body> |
019 |
020 |
<div class = "container" > |
021 |
<!-- top --> |
022 |
<div class = "span-24 top" > |
023 |
{ include :/widgets/top.html} |
024 |
</div> |
025 |
<!-- center --> |
026 |
<div class = "span-24 center" > |
027 |
<!-- left --> |
028 |
<div style= "width:270px;float:left;" > |
029 |
{ include :/widgets/left.html} |
030 |
</div> |
031 |
<!-- right --> |
032 |
<div style= "float:left;margin-left:5px;width:675px;" > |
033 |
{__doLayout()} |
034 |
</div> |
035 |
</div> |
036 |
|
037 |
<hr /> |
038 |
<div class = "span-24" |
039 |
style= "text-align: center; font-size: 12px; padding-bottom: 10px;" > |
040 |
{ include :/widgets/footer.html} |
041 |
</div> |
042 |
043 |
</div> |
044 |
|
045 |
</body> |
046 |
</html> |
047 |
可以看到有个{__doLayout()},普通模版内容会嵌入到该声明中,这样就实现了模版布局。 |
048 |
049 |
普通视图文件: |
050 |
051 |
views/news/list.html |
052 |
053 |
//循环 |
054 |
{ foreach objects,object} |
055 |
<li>{object.title}</li> |
056 |
{ end } |
057 |
058 |
//@ 后面跟action表达式,编译的时候会根据Router里的声明解析出url |
059 |
<a href= "{@:news.list}" >新闻列表</a> // /news/list |
060 |
<form action= "{@:save}" ></form> // /news/save |
061 |
062 |
自定义标签: |
063 |
只要将文件放到views/tags下,就成为一个标签了,如: |
064 |
views/tags/select.html //自定义html <select>标签 |
065 |
066 |
<select value= "{_param['value']}" name= "{_param['name']}" > |
067 |
068 |
{ foreach : $this ->data->_param[ 'items' ],item} |
069 |
{ if :item.id == _param[ 'value' ]} |
070 |
<option value= "{item.id}" selected= "selected" > |
071 |
{item.name} |
072 |
</option> |
073 |
{ else } |
074 |
<option value= "{item.id}" > |
075 |
{item.name} |
076 |
</option> |
077 |
{ end } |
078 |
{ end } |
079 |
|
080 |
</select> |
081 |
引用标签: |
082 |
{tag:select<-( 'items' =>@@items)}{/tag} |
083 |
可以向标签内传参数 |
084 |
'<-' 后面根的就是需要传入的参数,其形式就是数组的形式,不过省略了 array 声明。 |
085 |
传入参数有个引用的范围问题: |
086 |
@@表示引用模版全局变量,也就是由MVC::putTArg()设置的变量 |
087 |
@表示模版局部变量,如循环内的变量 |
088 |
$表示引用php范围变量。 |
089 |
(好像很复杂啊??) |
090 |
标签内要引用传入的参数的话,是:{_params[ 'varName' ]} |
091 |
支持嵌套标签。 |
092 |
---- 还实现了什么功能,我要看自己写的代码,因为蛮久没弄了,:)。 |
093 |
094 |
MVC相关的东西,基本完事。 |
095 |
096 |
ORM |
097 |
orm做得比较的粗糙,因为好像搞个复杂的orm也没有什么必要,还是看看我的思路吧。 |
098 |
将所有的Model放到app/models目录下。 |
099 |
100 |
配置db相关参数 |
101 |
文件位置: |
102 |
conf/app.php |
103 |
$AppConf = array ( |
104 |
'tmpDir' => 'tmp' , |
105 |
'viewsDir' => 'app/views/' , |
106 |
'controllersDir' => 'app/controllers/' , |
107 |
'frontController' => '/index.php' , |
108 |
'web.root' => '/pmorg' , |
109 |
'db.host' => 'localhost' , |
110 |
'db.username' => 'root' , |
111 |
'db.password' => '123456' , |
112 |
'db.name' => 'test' |
113 |
); |
114 |
115 |
看到,以db开头的就是数据源相关的配置。 |
116 |
看看一个模型的示例: |
117 |
118 |
PM::_include( 'core' , 'model/PM_Model' ); |
119 |
120 |
class news_model extends PM_Model{ |
121 |
122 |
public $id ; |
123 |
public $title = '' ; |
124 |
public $text = '' ; |
125 |
public $dateCreated ; |
126 |
public $dateUpdated ; |
127 |
|
128 |
//参数验证 |
129 |
static $constraints = array ( |
130 |
|
131 |
'id' => array ( 'required' => true) |
132 |
|
133 |
); |
134 |
135 |
//数据库映射相关 (只是思路而已) |
136 |
static $mapping = array ( |
137 |
'table' => 'news' , |
138 |
'id' => array ( 'column' => 'id' , 'type' => 'number' , 'sqlType' => '' , 'vendor' => '' , 'lazy' => 'false' ) |
139 |
); |
140 |
141 |
} |
142 |
143 |
只是这么个思路,参数验证是实现了,数据库映射相关只实现部分哦。 |
144 |
145 |
控制器代码: |
146 |
147 |
public static function save(){ |
148 |
var $model = new news_model(); |
149 |
MVC::bind( $model , $_POST ); |
150 |
$model .save(); |
151 |
if (MVC::hasValidErrors()){ |
152 |
MVC::flash( 'errors' ,MVC::validErrors()); |
153 |
redirect(); |
154 |
} |
155 |
else |
156 |
echo '保存成功' '; |
157 |
} |
158 |
159 |
?> |
插件结构只有几个思路,暂时没精力去实现了,以上介绍的可能不包含全部功能,提到的功能也可能没实现(98%还是实现了的)。
这个框架是为熟悉php目的,边看php文档,边写的,没有看其他开源实现,错漏应该不少了。
目的就是晒晒自己的小东西,与分享一下思路,你硬要说,没做好就别放出来,那我也懒得理了。
发表评论(评论将通过邮件发给作者):