设为首页 收藏本站
查看: 852|回复: 0

[经验分享] PHP 事件驱动框架 实践

[复制链接]

尚未签到

发表于 2015-8-28 11:11:41 | 显示全部楼层 |阅读模式
  关于PHP事件驱动框架的一些基本信息请先看我上一篇博客 基于CodeIgniter的事件驱动扩展和开发规范
  这里将使用上一篇博客中的写的事件类和规范来写一个简单  用php 和 mysql 模拟文件系统的增删改查  例子。
  
  步骤

  
1.数据库设计和基本结构
    1.1我们用一张表来表示文件和文件夹,主要字段和意义如下表所示。

id文件的唯一标识id  
name文件名
uid文件创建者的id
created文件创建的时间戳
modified文件修改的时间戳
size    文件大小
type    文件类型
pid  文件父目录的id
path文件的物理位置
route文件从父目录到根节点的所有祖先id  
version文件版本号
  文件系统是一种简单的树形结构,特点是:文件夹节点有子节点,文件节点没有子节点。
  
  2.类设计和定义
    2.1要考虑的问题
      从要实现的操作“增、删、改、查”来看,要考虑的主要问题只有一个,就是在对文件夹节点进行删改操作时,如何让上层目录和下层文件或文件夹进行相应的更改。
    2.2希望的写法
      1.对文件类(也可以用来表示文件夹)有简单的save , set , delete 等方法。
      2.希望有一个类来表示文件的所有祖先节点,这个类可以是一个简单的文件类的集合。它在必要时可以将所有的祖先文件夹实例化。它能提供一些简单的集合操作方法。
      3.希望有一个类来表示文件的所有子文件和子文件夹,他能提供一些方法让我批量操作子文件或文件夹。
  
    2.3类定义
      这里的实体类,和实体集合类参考了前端框架backbone。
      我先定义了一个实体类,这个类有基本增删改查操作。
      



class Entity {
private $attributes = array();
private $is_deleted = false;
public function __construct() {
}
public function reset( $data ) {
$this -> chunk();
return $this -> set( $data );
}
public function get( $attr_name ) {
return isset( $this -> attributes[$attr_name] ) ? $this -> attributes[$attr_name]->value : false;
}
public function get_attr_detail( $attr_name ){
return isset( $this -> attributes[$attr_name] ) ? $this -> attributes[$attr_name] : false;
}
public function set() {
if( $this -> is_deleted ){
return false;
}
$args = func_get_args();
if( count( $args) == 1 ){
$attr_array = ( array ) $args[0];
}else if(  count( $args) == 2 ){
$attr_array = array($args[0] => $args[1] );
}
foreach( $attr_array as $key => $value ) {
$value_obj = new stdClass();
$value_obj ->  changed = false;
$value_obj ->  new = true;
if( isset($this -> attributes[$key])){
$value_obj -> new  = false;
if( $this -> attributes[$key] -> value !== $value ){
$value_obj -> changed  = true;
$value_obj -> last  = $this -> attributes[$key] -> value;
}
}
$value_obj -> value = $value;
$this -> attributes[$key] = $value_obj;
}
return $this;
}
public function chunk() {
$this -> attributes = array( );
return $this;
}
public function save() {
return false;
}
public function delete(){
$this -> deleted = true;
}
public function to_object() {
$object = new stdClass();
foreach( $this -> attributes as $key => $value_obj ) {
$object -> $key = $value_obj ->value;
}
return $object;
}
public function to_array() {
return ( array ) $this -> to_object();
}
public function is_empty(){
return empty( $this -> attributes );
}
public function is_delete(){
return $this -> deleted;
}
}
  
  然后定义了一个集合类,这个类实现了spl的IteratorAggregate 接口,使得php可以对它进行foreach操作。



class Entity_collection implements IteratorAggregate{
public $length = 0;
public $options = array(
'child_class' => "Entity"
);
public $children = array( );
public function __construct( $options ) {
$this -> options = array_merge( $this -> options, $options );
$this -> load_children( $this -> options );
}
private function load_children( &$options ){
if( !empty( $options['children_data'] ) ) {
$this -> reset_from_data( $options['children_data'] );
} else if( !empty( $options['children'] ) ) {
$this -> reset( $options['children'] );
}
}
public function to_array() {
$output = array( );
foreach( $this -> children as $child ) {
$output[] = $child -> to_object();
}
return $output;
}
public function reset( &$children ) {
$this -> children = $children;
$this -> length = count( $children );
}
public function reset_from_data( $children_data ) {
foreach( $children_data as $child_data ) {
$class_name = $this -> options['child_class'];
$children_obj = new $class_name( $child_data );
if( !$children_obj -> is_empty() ){
$this -> children[] = $children_obj;
}
}
$this -> length = count( $this -> children );
}
public function get( $id ){
foreach( $this -> children as &$child ){
if( $child -> get("id") == $id ){
return $child;
}
}
return false;
}
public function getIterator(){
return new ArrayIterator( $this -> children );
}
}
  定义了文件(文件夹)类,继承自实体类。这里注意里面使用另一个叫做file_database的单例,这个单例是用来进行具体数据库操作。单独提出来是为了提高扩展性。用户可以通过修改file_database类来应对自己的需求。



class File extends Entity{
private $file_database;
public function __construct( $fid = false ){
parent::__construct();
$this -> file_database = File_database::get_instance();
if( $fid ){
if( is_object( $fid ) || is_array( $fid ) ){
$data = $fid;
}else{
$data = $this -> file_database -> load_file( $fid );
}
$this -> reset( $data );
}
}
public function save(){
$data = $this -> to_object();
$id = $this -> get( 'id' );
if( $id ){
$new_data = $this -> file_database -> update_file( $data );
}else{
$new_data = $this -> file_database -> insert_file( $data );
}
if( $new_data ){
$this -> reset( $new_data );
}else{
//错误处理
        }
return $this;
}
public function delete(){
}
}
  文件祖先类,这里注意它不是直接集成自实体集合类。



class File_ancenstors implements IteratorAggregate{
public $file = false;
public $ancestors;
public function __construct( $fid ){
$this -> file = new File( $fid );
$this -> load_ancestor_entities( $this -> file -> get( 'route' ) );
}
private function load_ancestor_entities( $route ){
$ancestor_ids = explode( '/', $route );
$collection_options = array(
'child_class' => "File",
'children_data' => $ancestor_ids
);
$this -> ancestors = new Entity_collection( $collection_options );
return $this;
}
public function getIterator(){
return $this -> ancestors;
}
}
  子文件(不包括后代文件)集合类



class File_children extends Entity_collection{
private $file_database;
public function __construct( $fid ){
$this -> file_database = File_database::get_instance();
$options = array(
'child_class' => 'File',
);
parent::__construct( $options );
$this -> load_children_entities( $fid );
}
public function load_children_entities( $fid ){
$children_data = $this -> file_database -> load_children_files( $fid );
$this -> reset_from_data( $children_data  );
return $this;
}
}
  最后数据库操作类



class File_database{
const table = 'files';
static $instance = false;
private $CI = false;
static function get_instance(){
if( !File_database::$instance ){
new File_database();
}
return File_database::$instance;
}
private $db;
public function __construct(){
$this -> CI = &get_instance();
$this -> db = $this -> CI -> db;
self::$instance = &$this;
}
public function load_file( $fid ){
$mid = $this -> CI -> user -> id % var_get("user_mod");
$table_name = self::table ."_{$mid}";
return $this -> db -> get_where( $table_name, array( "id" => $fid ) ) -> row();
}
public function update_file( $file ){
if( !isset( $file -> id ) ){
return false;
}
$update_data = ( array ) $file;
$mid = $update_data['uid'] % var_get("user_mod");
$table_name = self::table."_{$mid}";
$this -> db -> where( "id", $update_data['id'] );
unset( $update_data['id'] );
$update_result = $this -> db -> update( $table_name, $update_data );
return $update_result ? $file : false;
}
public function insert_file( $file ){
if( isset( $file -> id ) ){
return false;
}
$insert_data = ( array ) $file;
$insert_data['id'] = $this -> generate_file_id();
$mid = $insert_data['uid'] % var_get("user_mod");
$table_name = self::table."_{$mid}";
$insert_result = $this -> db -> insert( $table_name, $insert_data );
if( $insert_result ){
$this -> db -> where( 'id', $insert_data['id'] );
$new_data = $this -> db -> get( $table_name ) -> row();
}
return $insert_result ? $new_data : false;
}
private function generate_file_id(){
do{
$fid = $this -> salt( 16 );
}while( $this -> file_id_exist( $fid ) );
return $fid;
}
private function salt( $length = 16 ){
return substr( md5( uniqid( rand(), true ) ), 0, $length );
}
public function file_id_exist( $fid ){
if( $this-> db -> query( "SELECT id FROM files WHERE id = '{$fid}'" ) -> num_rows() ){
return true;
}
return false;
}
public function load_children_files( $fid ){
$mid = $this -> CI -> user -> id % var_get("user_mod");
$table_name = self::table ."_{$mid}";
$this -> db -> where( 'pid', $fid );
$mysql_result = $this -> db -> get( $table_name );
return $mysql_result ? $mysql_result -> result() : array();
}
}
  
  
  
  3.模块定义
    3.1希望的写法
  
      1.希望在写对任何一个节点的逻辑操作时,无论增删改查,我都可以先只考虑考虑当前节点。通过抛出事件让自己的模块,或者其他扩展模块来决定要不要进行相应的其他操作。
  
      2.在写任何一个针对文件某一变化(如修改、删除)时,代码不关心只对自己模块内的变化负责。
    3.2模块实现
      这里我使用CodeIgniter作为基础框架,读者也可以直接自己移植。具体的内容请看注释。



<?php
/**
* @author 侯振宇
* @date 2012-6-26
* @encode UTF-8
*/
class File_model extends CI_Model{
public function __construct(  ){
parent::__construct( );
//file factory 包含了和文件相关的类
$this -> load -> library("File_factory");
}
//声明本模块需要进行权限验证的接口
public function auth(){
return array(
'main/file_list' => array(
'file_owner_validate' => array(
'validate' => 'is_operator_file_owner'
)
),
'main/file_update' => array(
'file_owner_validate' => array(
'validate' => 'is_operator_file_owner'
)
)
);
}
//声明模块要监听的事件,可以是内部事件,也可以是外部事件。
//这里监听的都是内部的文件操作的事件,如果修改,增加。为的是分离祖先后者后台的连带操作。
public function listen(){
return array(
"file_update" => "react_file_update",
"file_insert" => "react_file_insert",
"file_remove" => "react_file_remove",
);
}
//文件更新后   更新父目录的size等属性。
public function react_file_update( $file ){
//size
if( $file -> get_attr_detail("size") -> changed ){
$size_change_value = $file -> get("size") - $file -> get_attr_detail("size") -> last;
$ancestors = new File_ancenstors( $file->get("id") );
foreach( $ancestors as &$ancestor ){
$ancestor -> set( "size", $ancestor -> get("size") + $size_change_value );
$ancestor -> save();
}
}
}
//文件插入后,更新文件route。当然这个操作你可以在文件新建的时候就写好,这里只是示例。
public function react_file_insert( $file ){
$pfile = new File( $file -> get("pid") );
$file_route = explode('/', $pfile -> get('route') );
array_push( $file_route,  $file -> get("pid") );
$file_route_str = implode('/', array_filter( $file_route) );
$file -> set("route" , $file_route_str) -> save();
}
public function is_operator_file_owner(){
//根据自己情况进行判断
return true;
}
//文件的一个列表
public function file_list( $fid ){
$files = new File_children( $fid );
return $files -> to_array();
}
//单个文件载入
public function file_load($fid){
$file = new File( $fid );
return $file -> to_object();
}
//某一文件的祖先
public function file_ancestors( $fid ){
$file_ancenstors = new File_ancenstors( $fid );
return  $file_ancenstors -> ancestors -> to_array()  ;
}
//创建文件
public function file_new(){
//测试例子
$file_attrs = array(
'name' =>  "new_test",
'uid' => 151,
'created' => now(),
'pid' => '8a0341838c007cf4',
);
$file = new File( $file_attrs );
$file -> save();
$this -> event -> trigger("file_insert", $file );
return $file;
}
//创建文件夹
public function folder_new( $pid, $name ){
$file_attrs = array(
'name' =>  $name,
'uid' => $this -> user -> id,
'created' => now(),
'pid' => $pid,
);
$file = new File( $file_attrs );
$file -> save();
$this -> event -> trigger("file_insert", $file );
return  $file -> to_object() ;
}

//文件更新
public function file_update( $fid, $data ){
$file = new File( $fid );
$file -> set( $data );
$file -> save();
$this -> event -> trigger( "file_update", $file );
}
}
?>
  
  总结

  事件驱动  相比 模块间通过接口来沟通 的最大好处应该是进一步降低了耦合,同时使系统的逻辑更容易阅读。
  
  希望读者能够仔细阅读代码,能给我提出一些您的宝贵建议。毕竟我不希望我的博客只是放出一个现成的东西,编程让大家测试一下而已。贵在交流。
  我有两个项目会基于事件驱动来做,非商业的,开源的,个人觉得还比较好玩。有兴趣参与的请与我联系。留言或者email都可以。谢谢。
  
      
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-105462-1-1.html 上篇帖子: 怎样提高php运行速度? 下篇帖子: PHP数据类型转换
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表