通過PHP的Wrapper無縫遷移原有專案到新服務的實現方法
出於效能和安全方面的考慮,公司的平臺上禁用了本地檔案讀寫和對外的資料抓取.相應的,我們提供了對應的服務來做同樣的事情.新服務的介面和原來不太一樣.
專門為我們平臺開發的程式當然不會存在問題,但是有大量的已有的程式和開源專案,就面臨著繁雜的遷移工作.
Wrapper
其實從PHP4.3開始,PHP就支援Wrapper了,這意味著使用者可以自定義和過載協議.
只需要使用 stream_wrapper_register
函式就可以註冊一個協議,對這個協議的相關操作,PHP都會回撥相關的函式.
手冊上給了一個例子. 它註冊了一個叫var的協議,然後對這個協議操作都會回撥VariableStream class
varname = $url["host"]; $this->position = 0; return true; } function stream_read($count) { $ret = substr($GLOBALS[$this->varname],$this->position,$count); $this->position += strlen($ret); return $ret; } function stream_write($data) { $left = substr($GLOBALS[$this->varname],$this->position); $right = substr($GLOBALS[$this->varname],$this->position + strlen($data)); $GLOBALS[$this->varname] = $left . $data . $right; $this->position += strlen($data); return strlen($data); } function stream_tell() { return $this->position; } function stream_eof() { return $this->position >= strlen($GLOBALS[$this->varname]); } function stream_seek($offset,$whence) { switch ($whence) { case SEEK_SET: if ($offset < strlen($GLOBALS[$this->varname]) && $offset >= 0) { $this->position = $offset; return true; } else { return false; } break; case SEEK_CUR: if ($offset >= 0) { $this->position += $offset; return true; } else { return false; } break; case SEEK_END: if (strlen($GLOBALS[$this->varname]) + $offset >= 0) { $this->position = strlen($GLOBALS[$this->varname]) + $offset; return true; } else { return false; } break; default: return false; } } } stream_wrapper_register("var","VariableStream") or die("Failed to register protocol"); $myvar = ""; $fp = fopen("var://myvar","r+"); fwrite($fp,"line1\n"); fwrite($fp,"line2\n"); fwrite($fp,"line3\n"); rewind($fp); while (!feof($fp)) { echo fgets($fp); } fclose($fp); var_dump($myvar); ?>
回撥class裡邊能實現的介面列表在這裡: http://cn2.php.net/manual/en/class.streamwrapper.php
需要注意的一些問題
建構函式
首先是,wrapper class
很特別,它的建構函式並不是每次都呼叫的.只有在你的操作觸發了stream_open相關的操作時才會呼叫,比如你用file_get_contents
了.而當你的操作觸發和stream無關的函式時,比如file_exists會觸發url_stat方法,這個時候建構函式是不會被呼叫的.
讀實現
wrapper裡邊有position和seek等概念,但是很多服務其實是一次性就讀取全部資料的,這個可以在stream_open
url_stat的實現
在wrapper class的實現中,url_stat的實現是個難點.必須正確的實現url_stat才能使is_writable
和is_readable等查詢檔案元資訊的函式正常工作.
而我們需要為我們的虛裝置偽造這些值.以mc為例,我給大家一些參考資料.
url_stat應該返回一個數組,分13個項,內容如下:
dev 裝置號- 寫0即可
ino inode號 - 寫0即可
mode 檔案mode - 這個是檔案的許可權控制符號,稍後詳細說明
nlink link - 寫0即可.
uid uid - Linux上用posix_get_uid可以取到,windows上為0
gid gid - Linux上用posix_get_gid可以取到,windows上為0
rdev 裝置型別 - 當為inode裝置時有值
size 檔案大小
atime 最後讀時間 格式為unix時間戳
mtime 最後寫時間
ctime 建立時間
blksize blocksize of filesystem IO 寫零即可
blocks number of 512-byte blocks allocated 寫零即可
其中mode的值必須寫對
如果是檔案,其值為
0100000 + 檔案許可權 ; 如 0100000 + 0777;
如果是目錄,其值為
040000 + 目錄許可權 ; 如 0400000 + 0777;
可以過載標準協議
根據實際測試來看,用stream_wrapper_unregister
可以解除安裝掉http等內建協議.這就方便我們完全無縫的替換使用者的一些操作,比如file_get_contents(‘http://sae.sina.com.cn')
到我們自己實現的服務上.
知識點補充:
php wrapper實現
【背景】
做一個thrift client的wrapper,用以實現對於伺服器的重試邏輯。
【關鍵點】
1. wrapper要求跟用client一樣方便。
2. 當某個伺服器掛掉之後可以隨機選另一臺重試。
3. 用到的php幾個關鍵特性: __call()(magic function,當訪問的物件函式不存在時會呼叫這個),ReflectionClass 反射類及其其成員函式newInstanceArgs,call_user_func_array回撥函式。
直接看程式碼吧(某位牛人寫的,not me):
#!/usr/bin/env php <?php namespace wrapper; error_reporting(E_ALL); require_once '/usr/local/Cellar/thrift/0.9.1/Thrift/ClassLoader/ThriftClassLoader.php'; use Thrift\ClassLoader\ThriftClassLoader; $GEN_DIR = realpath(dirname(__FILE__).'/..').'/gen-php'; $loader = new ThriftClassLoader(); $loader->registerNamespace('Thrift','/usr/local/Cellar/thrift/0.9.1/'); $loader->registerDefinition('xiaoju',$GEN_DIR); $loader->register(); use Thrift\Protocol\TBinaryProtocol; use Thrift\Transport\TSocket; use Thrift\Transport\THttpClient; use Thrift\Transport\TBufferedTransport; use Thrift\Exception\TException; class RetryWrapper { public function __construct($classname,$hosts) { $this->clazz = new \ReflectionClass($classname); $this->hosts = $hosts; } public function __call($method,$args) { shuffle($this->hosts); foreach ($this->hosts as $key => $host) { try { return $this->inner_call($host,$method,$args); } catch (TException $ex) { $msg = $ex->getMessage(); if (!strstr($msg,'TSocket')) { throw $ex; } } } throw new TException("all server down!"); } public function inner_call($host,$args) { $tmp = explode(":",$host); $socket = new TSocket($tmp[0],(int)$tmp[1]); $transport = new TBufferedTransport($socket,1024,1024); $protocol = new TBinaryProtocol($transport); $client = $this->clazz->newInstanceArgs(array($protocol)); $transport->open(); $result = call_user_func_array(array($client,$method),$args); $transport->close(); return $result; } } $hosts = array('localhost:9090','localhost:9091'); $wrapper = new RetryWrapper("\xxx\xx\MessageServiceClient",$hosts,3); $data = array('businessId' => 300100001,'phones' => array('2','2','3'),'message' => 'asdfqer') ; $message = new \xxx\xx\Message($data); print $wrapper->sendMessage($message); print "\n"; ?>
總結
到此這篇關於通過PHP的Wrapper無縫遷移原有專案到新服務的實現方法的文章就介紹到這了,更多相關php wrapper 遷移新服務內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!