zoukankan      html  css  js  c++  java
  • PHP 事件驱动框架 实践

    关于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都可以。谢谢。

     

        

     

  • 相关阅读:
    Redis 思维导图 (解析版)
    一张图片了解redis
    Redis 思维导图
    计算机网络协议
    IT笔面试题
    Hadoop集群搭建
    天涯论坛只看楼主
    齐秦&r大约在冬季现场版
    郁可唯茶汤现场版
    MTK平台电路设计01
  • 原文地址:https://www.cnblogs.com/sskyy/p/2574364.html
Copyright © 2011-2022 走看看