climbPHP思路


climbPHP是一个基于CodeIginter的框架,下面记录的是我开发的整个思路。

【初衷】

我终于决定好好写一篇东西来总结一下climbPHP,之前总是觉得没开发完,还不是时候。现在觉得可能永远都是处于没开发完的阶段。

最早写climbPHP时的动因很简单,就是想把前端常用的事件类和模块间通过事件类来通信这种模式模式移植到后端来。说白了就是一个全局的观察者模式。

写着写着我发现自己的思路得到了新的启发,主要有两点:第一,将系统的任何操作都看作是对外界刺激的反射的话(看《失控》学到的东西),使用事件的方式比起抵用接口来说更容易理解,也更容易表述业务逻辑。第二,引导模块使用事件来通信能它们能更加松耦合。于是,首先有了下面这个设计:

对事件的绑定和触发很简单,像下面这种形式:

第一行是绑定到某个函数,第二行是绑定到某个对象的方法。

实现了简单的绑定和触发后,我又做了一点小改进来支持命名空间,像下面这样:

命名空间使用的是后缀式,上图中,namespace1是从属于namespace2的。直接触发.namespace2的话,下面从属的事件的都会被触发。想要触发namespace1的话,一定要全命名空间才行。

完成命名空间后,对事件类的开发一度就停止了。直到后来在让框架自动支持restful形式的接口时,忽然需要用到带变量的事件名,于是,它又增加了像下面这样的绑定和触发形式:

【进化】

到这里,因为个人原因,climbPHP整个开发进入一个停滞期。在这期间,我重新审视了自己对系统以及对人的理解。

人的任何复杂行为都可以看做是无数的简单发射的叠加,比如说我想喝水了,我不用去想怎么样握住杯子,怎么样送到嘴边,这一切似乎是在没有知觉的情况下就自动完成了。这里其实有个指令自动分解的工程:高级指令“我想喝水”,自动分解成了“伸手拿杯子”,再分解成“动手指握杯子”,“碰到杯子后握紧”,最后“送回嘴边”。这里的对每一个指令的响应其实都是我们在成长过程中就已经建立好的反射,包括指令间的联系。所以最后我只需要想我要喝水,一切就完成了。

如果也按照这个思路来设计系统,是不是会很有意思?把软件系统看做一个人,那么对URL的访问(对web程序来说)就是对他的指令。需要对这个指令负责的模块收到指令后会进行相应的操作,也可能触发更低级的事件,让更低级的模块去完成响应操作。

想到了这里,我发现目前的事件类已经能基本上实现的这样的设计了,但是还有以下几个问题:

1.对于人来说,我在喝水的时候也还是可以自主地控制手指的,我可以只用三个手指端杯子。那系统中怎么实现?

2.对于同一事件的响应,往往是有一个顺序的,怎么来表示顺序?

3.事件的触发是需要一个返回值来表述各个模块对该事件的相应结果的,有时候后续操作还依赖于这个结果,怎么来设计这个结果?

第一个问题,其实是需要一种机制能在事件触发的过程中得到更细粒度的操作权。为此,我又在事件的触发上增加了以下两种方式:

1.定向触发事件,如果你知道某个具体操作(例子中是Test->action_2,这个操作如果是一个函数,就直接写函数名,如果是一个对象的方法,就按例子中的写)绑定到了某个事件上(例子中是test/test)。那你可以只对这个操作触发事件,相当于直接执行这个操作。相比起直接调用它,有以下几个好处:1.你不需要获得Test这个实例;2.返回结果仍然是统一的事件返回结果(后面会讲);3.它仍然在事件链中,可以使用事件类提供的调试函数来查看(后面会讲)。

2.抑制事件链中的某一事件。事件链的意思是,在对某一个事件的响应过程中又触发了子事件,那么这两个就会形成一个事件链。在这个链中,如果某一步的触发使用"!{}"这种形式,那么后续子事件里面如果还碰到触发花括号里里面的名字的事件,就会忽略掉。

第二个问题,为了支持响应的顺序,我为事件的绑定增加了以下的方式:

bind方法的第三个参数用来表示当前绑定的顺序,这个参数支持“first”,“last”两中字符串,和索引为“before”或者“after”的数组,数组里面值是操作名字,和上面提到的一样。

第三个问题,事件的触发的返回结果统一为数组。数组中的每一项就是每一个绑定了该事件的操作返回结果,这个结果被自动包装成了一个eventRes类实例。climbPHP提供了一些函数来简化对这个结果的操作。

例如 ·res_bool()· , 将结果集转化一个bool值,遵循的是和逻辑操作符相同的规则,也就是所有绑定到改事件的操作只有要一个返回false。那么使用res_bool整个结果就是false。

还有 ·res_link()·,可以将子事件返回的结果和当前结果link起来,这样上层的事件就能够获得子事件的结果了。

开发到这里,我认为在经过实际项目使用之前事件这一块应该不会再改动了。

【打包】

对于模块的整体管理,一开始设计的思路就是使用一个全局的管理器单例来控制。这个管理器主要负责三件事:1.根据配置文件加载模块,并自动加载相关的依赖。2.自动为模块绑定他们声明的需要监听的事件。3.为资源的获取提供一个统一入口。于是,模块管理器是这个样子:

对于模块依赖的表示和声明的需要监听的事件,一开始我的设计是让模块类实现一个约定命名的方法,方法返回值就是需要的依赖或者需要监听的事件。后来考虑到模块的可移植性以及逻辑表述的明晰程度,决定让每个模块都使用一个和模块名相同的文件夹,文件夹下必须有一个info文件用来声明依赖和事件。

依赖声明如果使用的是非字符串索引数组,那么就是依赖的模块,否则需要使用字符创所以来表明依赖。像下面这样。

监听事件的声明:

模块依赖的自动加载这里有一个重要的问题,就是被依赖的模块应该有一个机会来扩展上层模块。其实上图中的system/upper_module_init就是这样一个事件,它传递的参数就是依赖当前模块的另一个模块的实例。另外还有一个事件 system/module_info_extend。这个是提供给模块来扩展其他所有模块的info文件的,无论是否依赖。怎样扩展呢?其实就是返回一组变量名,让你的模块可以读取到其他模块info文件中声明的这个变量,后面的DSL模块会讲到这个事件的应用。

管理器对资源的管理方面没有做什么特别的工作。在模块构造的时候,构造函数会传入整个管理器的实例的引用,这样模块就能通过管理器来使用资源或者其他模块提供的接口了。

【装饰】

当写完模块管理之后,我迫切地开始写Persistence和DSL两个模块。当时的心情,就好像要给一把好刀开锋一样。DSL模块是为了将“复杂的操作是由低级的反射组成的”这个想法展示得更彻底。它使用了上面所说的“system/module_info_extend”事件,是其他模块能使用 名为 ·logics·的变量名。向下面这样:

这个变量的声明表示:当前模块需要监听“@user//post”这个事件,当这个事件出发后,用模块的pass_args_to_book_put这个方法处理事件结果,然后触发“@book/123/put”这个事件,同时将上一结果作为参数传入。再将这个事件触发的结果传递给当前模块的test_book_pipe这个方法。最后将test_book_pipe返回的结果作为参数触发book_pipe_evet。

你应该很容易发现以 ->开头的指的是需要执行的方法,否则就是触发事件。通过这个变量,可以说所有当前模块的逻辑过程就已经清晰的表现出来了,而且想怎么组合,想怎么拆都很容易。

同时我还考虑了逻辑上的同步操作和顺序操作问题,因此这个数组中的任何一项值可以是事件或者方法数组,表示他们会在逻辑上的同步执行,返回结果放在同一个数组里传递给下一个方法或者事件。

至此,初衷里提到的所有想法基本都已完成了。

最后再看一下作为工具的persistence模块。它主要负责系统里实体增删改查的简化。同时还为了自动支持restful的接口。

它的声明:

可以看到它对info文件有扩展,对依赖它的模块也有扩展。它对info的扩展:

如果模块声明了modelNS变量,那么变量(可以是数组也可以是字符串)里的每一个字符串值就会被当做一个声明了的实体名字。

当用户使用 [实体名字]/[实体ID] 这样的URL时,它就会自动根据相应模块对该实体的描述(就是实体属性名字和数据库名称的对应关系,这个对应关系还支持实体间的关联)和请求的方法(GET PUT POST DELETE)进行操作了。当然如果用户自己实现了实体的get post put delete方法的话,就会调用用户自己的方法。大部分情况下,用户只需要提供一个实体名字和数据库键名与属性名的对应关系就行了。

有一点值得说的是,这个模块也是先将restful形式的路径转化成一个 @:model/:id/:action 形式的事件,然后再根据事件里的变量进行相应操作。所以如果其他模块也有需要对实体进行增伤改查的操作,直接出触发前面这个形式的事件就行了。

【总结】

在整个climbPHP的开发过程中我经历自己人生中很多事情,要谢谢我女朋友和家里人一直给我的支持和鼓励。不管别人觉得这个框架怎么样,至少我觉得自己做了一些有意思,有意义的事情,至少至少算是对自己想法的一个总结。希望看到的人都能不吝赐教。思想的诞生往往是孤独的,只有交流,才能让它活下去。谢谢。

github: https://github.com/sskyy/climbPHP

原文地址:https://www.cnblogs.com/sskyy/p/2785028.html