给自己写一个php框架(1)


#PHP


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



( 本文完 )