GPF开发记录

GPF 是 G PHP Framework 的缩写,其中“G”表示我的名字,可以是gevolution90,也可以是g0。
GPF是一个非常简陋的PHP"框架",出于个人想法而开发.
项目托管在github上:<https://github.com/nameG0/gpf>

在最开始学习PHP时,那时大约是2009年,从网上知道"PHP框架"这种概念,比如像ThinkPHP,FleaPHP.
当时内心不愿意使用现成的框架,不愿意使用别人的代码,心中想的是"我自己也可以写一个出来".
这可以算是"自己写框架"最初的起源吧.当然,"自己写框架"这件事后来也就不了了知了.

后来做了三年phpcms2008这种cms网站的维护工作,就更加没有使用PHP框架了,直到现在,其实也没有使用过一个PHP框架.
但是"自己写框架"的想法却是一直都在,自己也搞不清楚是为什么.

最初的想法是想做一个不比ThinkPHP,FleaPHP这些框架差的,甚至比它们更强的框架出来.因为这些框架的源代码都是可以从官网上下载的,我只需要照着抄,并在我自己想修改的地方修改,理论上就可以实现自己的想法.
不过一直都没有实际行动,到后来,意识到这种想法如果真的去做,其实是一种无谓的重复把别人已实现好的东西重新实现一次而已.
而且,自己一个人不会比别人一个团队做得好.

这之后,便很少去想自己去重新实现现成框架中的功能,比如像数据库访问层,文件缓存函数等.
但还是不愿意使用现成的框架,感觉框架那一套规则,像MVC,像数据库访问接口,跟自己一直以来接触到的(像phpcms2008)很不一样,
如果使用了框架,就好像觉得没有了"自己"那样.并且心中不知为什么的觉得框架那一套其实并不好.

对自己感觉的成因进行猜测,注意到自己一直以来写代码都倾向于把一些功能包装成一些函数或类,以备下次遇到相关需求时,可以直接调用.
猜想自己所追求的这种"重用"的效果,并不认为现成的框架可以做得到吧.
但是,自己写代码,"重用"的效果其实也并不好,基本上都是无用功.
或者是没有"下一次"了,或者是"下一次"来时发现要重用以前的代码,还不如重新写一篇来得快.

既然自己写的代码也达不"重用"的效果,不如尝试去调用现成的代码吧,比如现成的框架.
但只想简单地调用自己需要的功能,而不需要因为要调用其中一个功能而不得不使用框架那一套东西.
于是,开始一个尝试:怎样能抛开一个系统那一套自己不想使用的规则,只是调用自己感觉趣那一部份代码呢?

这便是写GPF最初的目的:通过一个GPF,可以整合不同的系统,比如在phpcms2008中调用thinkphp的代码,在thinkphp的项目中也可以调用phpcms2008的代码.可以把不同系统中自己需要的功能组织起来.

在结合了维护phpcms2008这种cms的一些经验后,gpf的做法是确定一种代码规则,只规定必须的规定,让不同的系统,不同的代码互相隔离,而又可以互相调用.
当时的规则是这样的:
一个项目的代码主要分成两大类:
* 一种是模块,类似于项目本身的代码.
* 一种是外部代码,类似于第三方库.
比如一个项目是基于phpcms2008的,那phpcms2008的代码就按模块的规则来组织,如果想使用thinkphp,那thinkphp的代码就作为外部代码处理.

模块代码放在 0module 目录中,每个模块的各种文件使用一个目录存放,比如 A 模块的路径为:
0module/A/
一个模块的所有相关文件都全部放在自己的模块目录内,比如模块用到的模板,图片文件,css文件等.但不包括数据缓存这类"数据"类的文件.

外部代码放在 0lib 目录中,每一个第三方库各种文件使用一个目录存放,跟模块的规则相似.比如 thinkphp 的路径为:
0lib/thinkphp/

通过把相关的代码放在一个目录下,而不是分散到不同的目录下(常见到各种框架的目录结构,控制器放在controller目录,模型又放在model目录等,十分不利于模块化),
便有可能达到这个项目的某个module或lib可以直接通过简单的文件夹复制在另一个相同结构的项目中直接使用.

这个时候的GPF,强制要求使用常量定义0module, 0lib 两个目录的路径,并提供这两个目录内的文件加载函数(即实现include语句的作用).
因为模块总是免不了要互相调用,比如留言模块会使用会员模块的数据那样.
为了解决模块间通讯的问题,gpf同时规定了一个模块内一些基本的目录:
0init 用于存放模块初始化用到的代码,比如一个模块要定义一些常量之类的,都放在此文件夹中.
0hook 提供给模块一个全局的钩子功能,尝试做到不同模块间的关联代码使用hook实现,令一个模块可以像一个插件那样.
0callback 提供模块扩展其它模块功能的尝试.同样希望功能的扩展可以像插件那样.
后来还添加了其它的目录:
0c 用于存放"控制器"代码
0m 用于存放"模型"代码
0v 用于存放"view"代码

关于"模型",可以参见[php 数据库访问类(模型 Model) v1.1](http://blog.csdn.net/gevolution90/article/details/8241450).
关于hook,可以参见[php ghook 全局钩子功能 v0](http://blog.csdn.net/gevolution90/article/details/8241414)
关于callback,可以参见[phpcms2008 模块扩展形式](http://blog.csdn.net/gevolution90/article/details/7706134)

在这期间获得一条比较有用的经验:相关的代码放在一起是很好的.
比如一个模块的相关文件放在模块目录内,而不是分散在各个目录中.对数据库的操作代码都放在模型中,而不是把SQL语句分散在控制器各处.
像一个ajax功能,实现ajax的js部份实际上是程序员写的,所以请求ajax的js和处理ajax请求的php代码也应该放在同一个文件中,而且是紧紧相连在一起.
最近看到一篇文章,感觉可以用文中"无缝"这个词表达上述感觉:在写一个需要ajax的功能时,能感觉到代码是结合在一起的,不会需要使用不同的语言而在二个甚至多个文件之间切换来切换去地编写代码.
[自动化的高效团队开发环境](http://tchen.me/posts/2013-04-25-engineering-environment-for-smart-team.html)
>无缝是很重要的体验,如果分离意味着在两个系统显示地频繁切换,那还不如不分离;

这个时候GPF的使用方法便是,在当前的系统上硬加上GPF的规则,并慢慢把系统原有的规则修改为GPF的规则.
这也是为什么 0lib 中有一个"0"的原因,是为了尽可能不与任何系统本身的规则重复,一个系统很可能本身就有一个lib目录,但很少会有一个0lib目录.

但是,在实际使用GPF过程中,GPF并没有想像中神奇,我自己的想像中,通过GPF,可能不用怎样修改代码,就可以简单地整合不同的系统.
而且我也并没有真的去在一个现成系统中整合一个框架入去使用.

当时的GPF源代码可以从Github中看到:
<https://github.com/nameG0/gpf_initial>
里面还包括了一些其它"模块"的代码.但到现在基本都已经年久失修了.

后来,无意中看到这篇文章,对GPF造成了打击:
[关于不要重复造轮子的二三事](http://avnpc.com/pages/howto-find-best-wheel-for-programming)
通过这篇文章,我才知道原来有"PHP-FIG"这个东西.PHP-FIG就是一套规范,在一定程度上规定了项目目录结构:
[PHP开源项目使用Travis CI进行持续集成](http://avnpc.com/pages/php-open-source-project-plus-travis-ci)

这让我意识到自己在很大程度上其实是在闭门造车.像三年前那样,只是三年前是重复实现出别人已实现出来的功能,现在是重复搞一些别人已经制定好的规范.

在这期间也意识到另一个问题:在开发过程中,总是会有一些复杂性在那里的,这些复杂性并不是重用一些代码,定一些规范就可以解决的.这个情况最常见于维护现成系统的情况.
比如说,现成系统的一个功能现在是这样,但需要改成那样,要完成这个改动需要修改数据表,需要修改页面的JS逻辑,需要修改数据相关的处理代码.
这种情况复杂性就来了.或许是因为本身的代码,或许是因为需要修改的需求,也或许是PHP语言层面的问题(我没有用过其它语言),但复杂性它就是在那里,没有办法通过对旧代码的重用或规范来解决.

有时候甚至要对数据表加多一层关系.比如原本两个数据表是直接关联的,现在要加多一个数据表在它们中间.由 A-B 变成 A-C-B. 这种情况下可能就更复杂了.

反思自己GPF中的东西,慢慢发现有些东西是多余的,有些东西则可以用更简单的方法代替.
像功能,规范这些东西,与其自己去制定,还不如直接拿现成的来用.
并且有上面所说的"复杂性"的存在,实现出来的需求本身也很容易过时,那些基本实现之外的努力就更容易过时了(可以用"过度设计"这个概念来帮助理解这个"基本实现之外的努力").

另外,如果开发工作是维护一套现成的系统,那么就连"PHP-FIG"这样的规范就也不用谈了.只能按着现有系统的那一套走.

引用下面的文章尝试表达一下我想说的观点:
[C 的回归](http://blog.codingnow.com/2007/09/c_vs_cplusplus.html)
>“——低效的抽象编程模型,可能在两年之后你会注意到有些抽象效果不怎么样,但是所有代码已经依赖于围绕它设计的‘漂亮’对象模型了,如果不重写应用程序,就无法改正。”这一段依旧是 Linus 语,

>我隐隐担心的是,这么庞大的代码,它的设计不可能是永远正确的。两年之后,他们的设计肯定依旧正确,再两年还是的。但是我几乎敢肯定,放之更长远的时间来看,绝对会在某些设计领域发现其不是最佳的选择。

[重构代码的7个阶段](http://coolshell.cn/articles/5201.html)
>对于很多维护性质的项目,我犯过的错误让我成了一个实实在在的保守派,我几乎不敢动,那怕看到代码很不合口味。

[为什么重写代码?](http://jianshu.io/p/GzvCbt)
>可是,重写代码真的比读懂代码容易吗?

[抵制代码重写](http://www.aqee.net/fight-the-rewrite/)
>读代码比写代码要难

我自己也尝试表达一下:
我做的"开发"实际上是要"完成工作",但工作成果总是很容易过时的.
如果成果未过时,估计也不会去看那部份代码.
如果成果过时,那么相关的代码也会一起过时.

在这种情况下,如果代码以一种"排他"的结构来组织,那么就会整个结构中的所有代码一起过时.
反而是那种零零散散的代码可能会更持久一点.
一个可能的例子是:把实现某个功能的代码分成若干个函数,那个功能需要修改时,这些函数其实都已经没用了.

这个时候,我开始关注"完成工作"这件事.
如果重用一些代码可以帮助我更快更好地完成工作,那我应该可以通过重用这些代码更快更好地完成工作.
如果按照一些规范可以帮助我更快更好地完成工作,那我应该可以通过这些规范更快更好地完成工作.
因为我不知道到底会是什么样的代码可以帮助我完成工作,所以我所使用的方法不能是"排他"的.
不能因为第三方库不符合PHP-FIG就无法使用这个第三方库,又或是需要先对这个第三方库动个大手术让其符合PHP-FIG才能使用.
如果我想使用ThinkPHP框架中的memcache访问接口,我最好可以直接调用,而不需要先花时间把memcache访问接口从ThinkPHP中抽出来.

我猜,这种七拼八凑的代码很可能会更持久一点.

于是开始修改GPF,往帮助完成工作的方向上修改.
主要是减少功能和约束,**而不是**增加.

现在,GPF有一个单次包含函数,相当于require_once语句的功能.
有一个用于加载声明类源代码的函数,并可以顺带单次实例化代码中所定义的类,函数参数使用源代码文件绝对路径和完整类名,不带任何约束.
有一个专用于实例化类似于数据库连接,memcache连接的"工厂"函数.基于配置文件,绝对灵活.
一个用于把一些函数放在脚本结束时执行的注册函数.
一个方便记录调试用信息的简单日志函数,能自动在页面底部输出所有信息.
一个用于辅助模块间隔离的持久挂hook功能.
一套GET,POST数据获取函数,带有一定的安全过滤功能.
一个文件,文件夹复制函数,且通过比较文件的更新时间,跳过相同的文件.
一个简单的控制器调度器,方便把代码组织成单一入口.
一种debug模式,方便跟踪,调试现成系统(在不使用像IDE单步跟踪这类高级货的情况下).
上面这些是在实际开发中使用过并感觉确实有作用的功能,另外还有一些想尝试但未在实际开发中验证有用的功能.

主要代码文件gpf.inc.php,1500行.debug模式功能代码文件,400行.工作源代码文件加起来大小为56K.
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页