我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。
查看源代码

LightPHP v5: 闲聊PHP框架

展望LightPHP v4是半年前的事情了:http://jyprince.me/program/681.

LightPHP v4算是写出来了,但代码都没经过细致的测试,基本上一边用它写RootPanel一边完善,排Bug, 后来干脆把LightPHP的开发分支丢到RootPanel了。
刚刚统计了一下,不知不觉这么多代码了(不计空行):

  • cli工具集(PHP和部分参杂了PHP的配置文件模版) - 243行
  • 文档(.md) - 251行
  • LightPHP核心部分 - 1806行
  • LightPHP杂项文件 - 51行
  • 配置文件 - 75行
  • 控制器(Handler) - 693行
  • 核心文件 - 426行
  • 国际化(目前仅中文) - 507行
  • 数据模型(Model) - 338行
  • 独立的CSS - 186行
  • 独立的JS - 168行
  • 模版文件(参杂了PHP, CSS, JS的HTML文件) - 1479行
  • 杂项文件 - 15行
  • 引用的其他开源项目:jQuery, Bootstrap, 雅黑工作室的3款探针, 本人的taskinfo.php, Kill-IE6

列得我好有成就感~

总之,写了这么多代码,重构了那么多次,对于PHP的框架,我又有新的体会要说。


正在设计和编写中的LightPHP v5应该说和v4相比变化不算太大,但是根据语义化版本控制(http://semver.org/)的精神,还是要把它命名为v5.

我有提到,我打算认真写一本PHP的书,暂定《PHP进阶——一个论坛系统的实现》。以我这几年折腾的经验,来谈一谈如何利用好PHP的特征,写出优雅的代码,跳过PHP基础,以一个论坛系统的实现为例,这其中当然也隐含了一个PHP框架的实现。

部分草稿可以看下面,不过最后成品肯定会有很大变化的:

这个所谓的“以一个论坛系统的实现为例”, 我打算给它起名 LightTalk. 除此之外我还有另一个论坛系统的项目:JyBBS.
这两个论坛唯的区别恐怕就是LightTalk是随书的示例,是一个完整的论坛,其部分代码也来自LightPHP, 具有宽松的版权,书写好后不再维护。
而JyBBS是基于LightPHP的论坛系统,版权略严格,我打算一直维护。

真是乱呢…


  • 定位

在v4以及之前的版本中,LightPHP虽定位为PHP库,但仍包含了一部分前端模版(v3), 以及Bootstrap, jQuery等东西(v4).
v5中将删去他们,LightPHP将成为一个纯粹的PHP库。

// LightPHP v5的结构

lp-class/  --  LightPHP的核心类
lp-config.php  --  配置文件
lp-load.php  --  用于载入LightPHP
...  --  其他杂项文件,如URL重写规则等
  • 数据库, Model

LightPHP最初就是从一个MySQL封装开始的(模仿自Abreto), 后来数据库封装也一直是LightPHP的核心部分, 在v3中,我力图实现无关SQL的CRUD.
v4中我开始模仿MongoDB的链式调用语法,并且打算实现数据库无关,但我好像失败了。

数据库的差异毕竟是存在的,不可能实现真正的无关,Model不需要处理所有的数据库查询,只要提供最简单的CRUD就够了,复杂的理应交给SQL或具体的数据库API.
Model是我学习了最长时间的概念,现在我可以说我真正地理解了。

另外v5中我用PDO代替了C Style MySQL API, 毕竟是大势所趋。

// 抽象的Model基类,以下是经过精简的成员函数列表
abstract class lpPDOModel implements ArrayAccess
{
    static public function select($if = [], $config = []);
    static public function find($if = [], $config = []);
    static public function count($if = [], $config = []);
    static public function insert($data);
    static public function update($if, $data);
    static public function delete($if);
    static public function install();
}

// 通过继承来“实例化”一个Model, 代码有精简
class rpUserModel extends lpPDOModel
{
    static protected $metaData = null;

    // 具体Model特有的常量成员
    const NO = "no";
    const STD = "std";
    const EXT = "ext";
    const FREE = "free";

    // 为lpPDOModel提供元信息,有了这些信息,lpPDOModel不仅可以检查错误,还可以自动完成数据表的构建
    static protected function metaData()
    {
        if(!self::$metaData) {
            self::$metaData = [
                "db" => f("lpDBDrive"),
                "table" => "user",
                "engine" => "MyISAM",
                "charset" => "utf8",
                self::PRIMARY => "id"
            ];

            self::$metaData["struct"] = [
                "id" => ["type" => self::AI],
                "uname" => ["type" => self::VARCHAR, "length" => 256],
                "passwd" => ["type" => self::TEXT],

                // 支持 JSON 格式哦!

                "settings" => ["type" => self::JSON],
            ];
        }

        return self::$metaData;
    }

    // Model特有的成员函数
    public function isAllowToPanel()
    {
        if($this["type"] != self::NO && !$this->isAdmin())
            return true;
        return false;
    }
}

// 查询
$passwd = rpUserModel::find(["uname" => "jybox"])["passwd"];
// 插入,可以直接插入数组哦,lpPDOModel会将其 json_encode 后储存为字符串,读取时反之
rpUserModel::insert(["uname" => "jybox", "settings" => ["key1" => "value1"]]);
  • 模版

模版也是LightPHP的早期功能之一,我没有选择Smarty之类的专有语言,我直接用PHP编写模版,毕竟PHP本身也算是一个十分方便的模版语言。
在v4中,对模版的解析是用eval, 这不仅存在安全问题,而且显得非常不优雅。知道前一阵,我才发现,我要找到的东西其实就近在眼前!其实只要include就好…
v5中模版类的代码相比于v4有了不少精简,变得更加“一般化”了。

v5中删去了Bootstrap, jQuery等库,我意识到了静态文件至少逻辑上应该位于单独的服务器,不应该和LightPHP混在一起。

一个模版就是一个普通的PHP文件,参数来自 $this:

Hello <?= $this["name"];?>

显示模版:

lpTemplate::outputFile("/path/to/template.php", ["name" => "Jybox"]);
  • 缓存(键/值对储存)

我是从Asp.Net过来的,很喜欢Application类提供的全局键值对储存,但PHP是并发模型的,没有全局的管理器,语言本身没办法实现这种功能,于是我先后调研了使用文件, MySQL, Memcache, APC来实现。
在之前的版本中,一直都有这个功能(虽然Bug成堆), 但v5现在却没有,原因是还没来得及写,我愈发觉得这个功能可能并没有那么重要,大概要等到站点规模大到需要缓存的时候才会用到吧。

  • 登录状态验证

在v4中,我用接口的方式实现了两套验证组件,分别是经典验证和会话式验证,应该说虽然没实现当初设想的全部功能,但它们工作的还不错。

  • 配置文件管理

在v3和v4中LightPHP都没有用于管理配置文件的类,只需定义一个数组,然后在程序启动的时候include一下就好了。
但是当RootPanel需要4个配置文件(虽然后来精简到了3个), 我发现我需要这么一个玩意了。

编写这么一个单独的类我也是为了去掉全局变量,因为在函数中使用全局变量,还要很麻烦地用gloabl声明一下。

  • 调试

v5中我引入了一个自己编写的错误处理器,可以在脚本出错时打印一些有用的调试信息。
同时我还正在编写一个有用的调试日志系统。

关于错误报告可以参见这篇文章:http://jyprince.me/program/1079

  • 构造器

构造器的概念来自于设计模式中的所谓单例模式,在一个PHP脚本中,很多对象需要使用多次,每次都构造新的对象是很浪费的,但如果不使用全局变量又很难实现代码的多个部分共享一个对象。
构造器以“注册——获取”的功能解决了这个问题,在程序开始时,程序向构造器注册用于构造各种对象的函数,但只有当程序需要获取这个对象时,这个函数才会真正地被执行,而且构造一次后,再反复获取,获取到的都是第一次构造的对象的引用。
使用构造器统一构造对象,也省去了接口,LightPHP的类型检查本来就不严格。

// 注册一个构造器
lpFactory::register("lpLocale", function() {
    $path = rpROOT . "/locale";
    return new lpLocale($path, lpLocale::judegeLanguage($path, c("DefaultLanguage")));
});

// 获取一个对象
$rpL = lpFactory::get("lpLocale");

目前该构造器存在一个问题就是无法获得IDE的类型提示,不过目前部分IDE正在着手开发针对于这类构造器的类型提示API.

  • 国际化

v5首次加入了国际化支持,目前使用的是一种非常简单的,基于数组的专有翻译文件格式,我正在设法改进。

  • 路由和分发器

路由在v4的基础上仅做了一丁点优化。

例如请求URL是 /user/show/jybox
那么默认将会创建user类的一个实例, 以jybox为参数, 调用它的show函数。
当然,这个过程中会对类名做一些修饰,这些都是可控的,你可以编写自己的分发器。

  • 工具

v5包含了更多工具组件:分页,锁,Smtp等。

  • 插件

在v4设计之处我就设法加入插件系统,但我查阅了一些项目的设计之后发现,除了在代码中加入大量的hook, 似乎没啥有效的办法了,不喜欢这种简单粗暴的方式。

  • 短函数名

之前看ThinkPHP, 看它用了很多一个字母的短函数名,感觉很不优雅,但现在为了折中代码长度,我也不得不定义了四个短函数名。

c() - 配置文件选项
d() - 数据库连接
f() - 构造器
l() - 本地化翻译

LightPHP到底是个啥定位呢,除了README中(https://github.com/jybox/LightPHP/blob/master/README.md)写的那些精神上的东西。

LightPHP是我逐渐摸索出来的,后期参考了一些其他框架,抽取了他们最核心,最基本的功能。
换句话说,我用20%的代码,实现了他们80%的功能,至于另外比较难实现的20%, 我干脆不要了~

我对LightPHP的定位是一个库,并非框架,这一点可以体现在LightPHP对全局命名空间几乎没有污染,LightPHP的各个部分也比较松散,可以单独使用。
LightPHP也从未规定应用的文件如何组织,没对应用的代码做什么假设。

撰写评论

如希望撰写评论,请发邮件至 jysperm@gmail.com 并注明文章标题,我会挑选对读者有价值的评论附加到文章末尾。

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2024-04-08.