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都可以。謝謝。

 

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM