2013-09-15
玩php玩到一定时间一般会去了解一下php框架,我是很久前接触了几个框架——其实只是看了看CI的使用方法(也没用它写过东西),其他的框架也就是知道名字而已。某天,我终于受不了总是去了解这些现成的有文档的东西,于是决心去写一个——这就是起因,很简单。
这篇文章分为两部分:
第一部分所描述的仍然是传统的php开发模式(体现在url上)。
第二部分以第一部分为基础,描述的是一个完整支持RESTful URL的MVC框架,见下一篇文章。
程序流程
如上图所示: 1、用户访问web app中某个页面; 2、该页面对应的内容是业务逻辑,业务逻辑中需要使用类库进行逻辑判断、数据库操作、缓存操作,如果用户的请求在缓存中,那么进入3,否则进4; 3、将缓存内容响应给用户; 4、业务逻辑将必须的数据传递给视图,之后将视图化后的内容响应给用户。
下面是该框架的基本结构:
tinierPHP
├── Config
│ ├── Config.php
│ ├── Loader.php
│ └── .htaccess
├── Corelib
│ ├── Medoo.php
│ ├── Rcache.php
│ ├── Render.php
│ └── .htaccess
├── Template
│ ├── header.php
│ ├── body.php
│ ├── footer.php
│ └── .htaccess
├── Userlib
│ └── .htaccess
├── 404.php
└── index.php
目录Config、Corelib、Template、Userlib是默认的。其中Config中Config.php是app的配置文件,Loader.php用来装载类(装载Corelib和Userlib中的类);Corelib目录包含的是作者定义的常用类,Userlib包含的是网站开发人员常用的类(需要自己添加)。Template保存的是视图文件。index.php的内容是开发人员控制的,在这儿主要是我用来测试。
安全起见,添加了.htaccess文件,相应的,web服务器应该开启rewrite功能。文件内容有两种选择: 选择1:
Deny from all
选择2:
RewriteEngine on
RewriteRule ^(.*)$ ../404.php/$1 [L]
第二种方法是向客户端提供上一级目录中404.php的内容,用户体验更好一些。
配置你的web app
有两个地方需要配置:
1、Config/Config.php 配置变量$_site_url
,用来指明用户访问web app的基本url,例如$_site_url = 'http://127.0.0.1/tinierPHP/'
,这对于引用css、js、图片等特别有用。
2、Config/Loader.php 配置变量$_root_dir
,指明web app所在目录在文件系统中的绝对路径。该变量在加载类的时候需要用到。
导入类库
类库被区分为核心类库(框架作者为框架添加的类库)、用户类库(开发人员的类库)。下面是导入自定义类库的示例: 我们在Userlib/下创建文件Test.php,其中定义Test类(类名和文件名必须相同):
<?php
class Test {
/*
* 这是一个测试
*/
private $developer_name = 'letian';
public function __construct() {}
public function get_developer_name() {
return $this->developer_name;
}
}
?>
之后我们在web app根目录下创建文件1.php,内容如下:
<?php
include_once './Config/Loader.php';
$test = Loader::get_instance()->load_user_class('Test'); //加载类
echo $test->get_developer_name();
?>
创建类库的原则:
类名和文件名必须相同; 构造函数必须有public属性,且不含参数; 每个类致力于解决一个方面的问题,不建议类之间互相调用。
数据持久化
在目录Corelib/中的Medoo.php提供了与数据库之间的交互。Medoo是一个轻量级的基于PDO的php数据库框架,具体使用请见其官方网站:http://medoo.in/。
不过这里对Medoo进行了些许的修改:将原有的构造函数改为set_options($options)函数,重写了一个不含参数的构造函数,具体请看源码。
使用模板
视图的实现依赖于模板文件,令人欣慰的是,php本身就是一个完美的模板实现。
首先应该说一下php中的流程控制。我们在流程控制中一般使用花括号{}来包含语句块,例如:
if(1===1){ echo "两者相等";}
不过也有一种替代语法,即:
if(1===1):
echo "两者相等";
endif;
我们比较一下下面这两种写法:
<?php if (1===1){ ?>
<h2>两者相等</h2>
<?php } ?>
<?php if (1===1): ?>
<h2>两者相等</h2>
<?php endif; ?>
个人是觉得第二种方法更加直白一些,特别是在模板中使用流程控制的时候。相应的,还有endwhile、endfor、endforeach、endswitch。
如何使用模板:
在web app根目录下创建文件2.php,内容如下:
<?php
include_once './Config/Loader.php';
$render = Loader::get_instance()->load_core_class('Render');
$data1 = ["title" => "测试模板"];
$data2 = [
"title"=>"这是一个测试",
"lists"=>[1=>"一二三",2=>"四五六"]
];
$render->render("2/header",$data1);
$render->render("2/body",$data2);
$render->render("2/footer");
$render->show();
?>
在Template/目录下创建目录2,在目录2中添加文件header.php、body.php、footer.php。 header.php内容如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><?=$title;?></title>
</head>
<body>
body.php内容如下:
<h1><?=$title?></h1>
<?php foreach ($lists as $list): ?>
<h2><?=$list?></h2>
<?php endforeach;?>
footer.php内容如下:
</body>
</html>
访问2.php我们会看到:
这是如何实现的?
首先应该说一下extract函数,w3school这样说明该函数:
PHP extract() 函数从数组中把变量导入到当前的符号表中。 对于数组中的每个元素,键名用于变量名,键值用于变量值。 第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract() 函数如何对待这样的冲突。 本函数返回成功设置的变量数目。作为参数的数组中的key应该符合变量命名规范,例如extract(['this->aa' => 'aa', '1' =?'bb'])的返回值就为0。
下面是Render类中render方法:
public function render($template_path, $data = null)
{
$this->_template_path = Loader::get_instance()->root_dir() .'Template/' . $template_path .'.php';
if(is_array($data)) {
extract($data);
}
ob_start();
echo eval('?>'.preg_replace("/;*\s*\?>/", "; ?>",
str_replace('<?=', '<?php echo ', file_get_contents($this->_template_path))));
$this->_buffer = ob_get_contents();
@ob_end_clean();
$this->_content = $this->_content . $this->_buffer;
}
由于在业务逻辑中已经将Loader类include进来,所以我们可以获得模板文件的绝对路径$this->_template_path;
之后extract将$data分解用以向视图文件传递数据;eval方法用来使得模板文件生效;模板文件生成的视图内容追加到变量$this->_buffer
中。很明显,在我们的模板文件中不应该对$this->_buffer
进行操作。这里ob_start()
、 ob_get_contents()
、ob_end_clean()
用于控制缓冲区,具体请参考PHP文档。
render方法也为模板实现了一个简单的短标记语法(无论PHP本身有没有开启短标记),例如<?=$title;?>相当于<? echo $title;?>。 另,render方法的实现参考了CodeIgniter。
缓存
将需要缓存的内容持久化,在再次访问时候如果该缓存并未过期,则将该缓存的内容响应给用户。Corelib/Rcache.php实现了一个使用单个redis server作为缓存的类,该类适用于对视图内容进行缓存,我们将用户请求的url作为key,视图内容作为value。
该类没有测试过。
然后
该项目命名为tinierPHP,源码放在了https://github.com/letiantian/tinierPHP。