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