PHP基于閉包思想實(shí)現(xiàn)的BT(torrent)文件解析工具實(shí)例詳解
本文實(shí)例講述了PHP基于閉包思想實(shí)現(xiàn)的torrent文件解析工具。分享給大家供大家參考,具體如下:
PHP對(duì)靜態(tài)詞法域的支持有點(diǎn)奇怪,內(nèi)部匿名函數(shù)必須在參數(shù)列表后面加上use關(guān)鍵字,顯式的說明想要使用哪些外層函數(shù)的局部變量。
function count_down($count)
{
return $func = function()
use($count,$func)
{
if(--$count > 0)
$func();
echo "wow\n";
};
}
$foo = count_down(3);
$foo();
我本來是想這樣的。但是不行,會(huì)在第7行調(diào)用$func的時(shí)候報(bào)錯(cuò)。
錯(cuò)誤是Fatal error: Function name must be a string in - on line 7
反復(fù)試驗(yàn)后發(fā)覺,外部的匿名函數(shù)應(yīng)該通過引用傳值傳給內(nèi)部,否則是不行的:
function count_down($count)
{
return $foo = function()
use(&$count,&$foo)
{
echo $count."\n";
if(--$count > 0)
$foo();
};
}
$foo = count_down(4);
$foo();
像上面這樣寫就對(duì)了。
下面是另一種方法:
function count_down_again($count)
{
return function()use($count)
{
printf("wow %d\n",$count);
return --$count;
};
}
$foo = count_down_again(5);
while($foo() >0);
不過,這段代碼有點(diǎn)小錯(cuò)誤。編譯雖然沒錯(cuò),但是$foo函數(shù)每次返回的都是4.
也就是use關(guān)鍵字看上去像是支持靜態(tài)詞法域的,在這個(gè)例子上,它只是對(duì)外層函數(shù)使用的變量作了一個(gè)簡單拷貝。
讓我們稍微修改一下,把第3行的use($count)改為use(&$count):
function count_down_again($count)
{
return function()use(&$count)
{
printf("wow %d\n",$count);
return --$count;
};
}
$foo = count_down_again(5);
while($foo() >0);
這樣才正確。
我個(gè)人使用的方式是基于類的,做成了類似下面的形式:
class Foo
{
public function __invoke($count)
{
if($count > 0)
$this($count - 1);
echo "wow\n";
}
}
$foo = new Foo();
$foo(4);
這樣做的行為也是正確的。
這樣不會(huì)像前一個(gè)例子那樣失去了遞歸調(diào)用的能力。
雖然這是一個(gè)類,但是只不過是在手動(dòng)實(shí)現(xiàn)那些支持閉包和靜態(tài)詞法域的語言中,編譯器自動(dòng)實(shí)現(xiàn)的動(dòng)作。
其實(shí)今天早上,我本來準(zhǔn)備用類scheme的風(fēng)格寫一個(gè)解析器的。可能稍微晚點(diǎn)吧。scheme風(fēng)格的函數(shù)式編程是這樣的:
function yet_another_count_down($func,$count)
{
$func($count);
if($count > 0)
yet_another_count_down($func,$count - 1);
}
yet_another_count_down(function($var){echo $var."\n";},6);
它不是很依賴靜態(tài)詞法域,雖然scheme對(duì)靜態(tài)詞法域的支持還是很不錯(cuò)的。它主要還是利用了first-class-function。當(dāng)然,這也是一種典型的閉包。
我實(shí)現(xiàn)的torrent解析工具的代碼如下:
<?php
$file_name = '1.torrent';
$file = fopen($file_name,'r');
$nil = new Parser($file);//構(gòu)造解析器
$nil = $nil();//進(jìn)行解析
$pos = ftell($file);
echo '讀取到文件位置'.sprintf('0x%08X',$pos)."\r\n";
fseek($file,0,SEEK_END);
echo '還剩下'.(ftell($file) - $pos).'字節(jié)未讀取'."\r\n";
if(!feof($file))
{
echo '文件還未結(jié)束,再讀一個(gè)字符:';
$ch = fgetc($file);
if(is_string($ch) && ereg('\w',$ch))
{
echo $ch."\r\n";
}
else
{
printf('0x%02X',$ch);
echo "\r\n";
}
echo '現(xiàn)在的文件位置是'.sprintf('0x%08X',ftell($file))."\r\n";
echo '文件'.(feof($file)?'已結(jié)束':'還未結(jié)束')."\r\n";
}
fclose($file);//解析器后面不再工作了,此時(shí)可以釋放文件指針了。
$info = @$nil['value'][0]['info'];
if(!$info)
{
echo '這是一個(gè)有效的B-Encoding文件,但它不是一個(gè)有效的種子文件';
exit();
}
$name = $info['name.utf-8'] ?$info['name.utf-8']:$info['name'];
if(!$name)
{
echo '這是一個(gè)有效的B-Encoding文件,但它不是一個(gè)有效的種子文件';
exit();
}
echo $name."\r\n";
if($info['files'])
{
$index = 0;
foreach($info['files'] as $f)
{
$index += 1;
$path = $f['path.utf8'] ?$f['path.utf8'] :$f['path'];
if(!$path)
{
echo '文件列表中的第'.$index."個(gè)文件不含目錄\r\n";
continue;
}
if(0 === strpos($path[0],"_____padding_file_"))continue;
$under_folder = false;
foreach($path as $item)
{
if($under_folder)
{
echo '/';
}else{
$under_folder = true;
}
echo $item;
}
echo "\r\n";
}
}
else
{
echo "僅有一個(gè)文件\r\n";
}
class Parser
{
private $_file;
public function __construct($file)
{
$this ->_file = $file;
}
public function __invoke($parent = array())
{
$ch = $this ->read();
switch($ch)
{
case 'i':
{
$n = $ch;
while(($ch = $this ->read()) != 'e')
{
if(!is_numeric($ch))
{
echo '在';
echo sprintf(
'0x%08X',ftell($this ->_file));
echo '解析數(shù)字時(shí)遇到錯(cuò)誤',"\r\n";
echo '在i和e之間不應(yīng)該出現(xiàn)非數(shù)字字符'."\r\n";
echo '意外的字符'.sprintf('0x%02X',$ch);
exit();
}
else
{
$n .= $ch;
}
}
$n += 0;
$offset = count($parent['value']);
$parent['value'][$offset] = $n;
return $parent;
}
break;
case 'd':
{
$node = array();
//這個(gè)$node變量作為字典對(duì)象準(zhǔn)備加入到$parent的孩子節(jié)點(diǎn)中去
//$node['type'] = 'd';
while('e' != ($tmp = $this($node)))
{//每次給$node帶來一個(gè)新孩子
$node = $tmp;
}
$child_count = count($node['value']);
if($child_count % 2 != 0)
{
echo '解析結(jié)尾于';
echo sprintf('0x%08X',ftell($this ->_file));
echo '的字典時(shí)遇到錯(cuò)誤:'."\r\n";
echo '字典的對(duì)象映射不匹配';
exit();
}
$product = array();
for($i = 0; $i < $child_count; $i += 2)
{
$key = $node['value'][$i];
$value = $node['value'][$i + 1];
if(!is_string($key))
{
echo '無效的字典結(jié)尾于';
echo sprintf('0x%08X',ftell($this ->_file));
echo ":\r\n";
echo '解析[k => v]配對(duì)時(shí)遇到錯(cuò)誤,k應(yīng)為字符串';
exit();
}
$product[$key] = $value;
}
/*
* 思想是這樣的:子節(jié)點(diǎn)想要加入父節(jié)點(diǎn)時(shí),
* 往父節(jié)點(diǎn)的value數(shù)組添加。
* 當(dāng)父節(jié)點(diǎn)收集好所需的信息后,
* 父節(jié)點(diǎn)自身再從它的value節(jié)點(diǎn)整合內(nèi)容
* 對(duì)于字典和列表統(tǒng)一這樣處理會(huì)大大降低代碼量
*/
$offset = count($parent['value']);
$parent['value'][$offset] = $product;
return $parent;
}
break;
case 'l';
{
$node = array();
while('e' != ($tmp = $this($node)))
{
$node = $tmp;
}
$offset = count($parent['value']);
$parent['value'][$offset] = $node['value'];
return $parent;
}
break;
case 'e':
return 'e';
break;
default:
{
if(!is_numeric($ch))
{
$this ->unexpected_character(
ftell($this ->_file) - 1,$ch);
}
$n = $ch;
while(($ch = $this ->read()) != ':')
{
$n .= $ch;
if(!is_numeric($n))
{
unexpected_character(
ftell($this ->_file) - 1,$ch);
}
}
$n += 0;
$str = '';
for(; $n > 0; --$n)
{
$str .= $this ->read();
}
$offset = count($parent['value']);
$parent['value'][$offset] = $str;
return $parent;
}
break;
}
}
/*
* read函數(shù)包裹了$this ->_file變量
*/
function read()
{
if(!feof($this ->_file))
{
return fgetc($this ->_file);
}else{
echo '意外的文件結(jié)束';
exit();
}
}
/*
* unexpected_character函數(shù)接收2個(gè)參數(shù)
* 它用于指明腳本在何處遇到了哪個(gè)不合法的字符,
* 并在返回前終止腳本的運(yùn)行。
*/
function unexpected_character($pos,$val)
{
$hex_pos = sprintf("0x%08X",$pos);
$hex_val = sprintf("0x%02X",$val);
echo 'Unexpected Character At Position ';
echo $hex_pos.' , Value '.$hex_val."\r\n";
echo "Analysing Process Teminated.";
exit();
}
}
?>
這里很有趣的是,明明我對(duì)文件調(diào)用了fseek($file,0,SEEK_END);移動(dòng)到文件末尾了,但是feof還是報(bào)告說文件沒有結(jié)束,并且fgetc返回一個(gè)0,而沒有報(bào)錯(cuò)。但是此時(shí)文件實(shí)際上已經(jīng)到末尾了。
更多關(guān)于PHP相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《php curl用法總結(jié)》、《php字符串(string)用法總結(jié)》、《PHP數(shù)組(Array)操作技巧大全》、《php排序算法總結(jié)》、《PHP常用遍歷算法與技巧總結(jié)》、《PHP數(shù)據(jù)結(jié)構(gòu)與算法教程》、《php程序設(shè)計(jì)算法總結(jié)》、《PHP數(shù)學(xué)運(yùn)算技巧總結(jié)》及《PHP運(yùn)算與運(yùn)算符用法總結(jié)》、
希望本文所述對(duì)大家PHP程序設(shè)計(jì)有所幫助。
- PHP閉包(Closure)使用詳解
- PHP閉包函數(shù)詳解
- php的閉包(Closure)匿名函數(shù)詳解
- PHP閉包函數(shù)傳參及使用外部變量的方法
- 淺談PHP 閉包特性在實(shí)際應(yīng)用中的問題
- PHP中的閉包(匿名函數(shù))淺析
- PHP閉包實(shí)例解析
- php的閉包(Closure)匿名函數(shù)初探
- PHP 閉包詳解及實(shí)例代碼
- php基于閉包實(shí)現(xiàn)函數(shù)的自調(diào)用(遞歸)實(shí)例分析
- PHP 閉包獲取外部變量和global關(guān)鍵字聲明變量的區(qū)別講解
- 淺析PHP中的閉包和匿名函數(shù)
- PHP閉包定義與使用簡單示例
相關(guān)文章
PHP讀取網(wǎng)頁文件內(nèi)容的實(shí)現(xiàn)代碼(fopen,curl等)
php小偷程序中經(jīng)常需要獲取遠(yuǎn)程網(wǎng)頁的內(nèi)容,下面是一些實(shí)現(xiàn)代碼,需要的朋友可以慘況下。2011-06-06
Http 1.1 Etag 與 Last-Modified提高php效率
2008-01-01
php class中public,private,protected的區(qū)別以及實(shí)例分析
本篇文章是對(duì)php class中public,private,protected的區(qū)別以及實(shí)例進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
php+resumablejs實(shí)現(xiàn)的分塊上傳 斷點(diǎn)續(xù)傳功能示例
這篇文章主要介紹了php+resumablejs實(shí)現(xiàn)的分塊上傳 斷點(diǎn)續(xù)傳功能,結(jié)合實(shí)例形式分析了php+resumablejs文件傳輸?shù)木唧w實(shí)現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
php將csv文件導(dǎo)入到mysql數(shù)據(jù)庫的方法
這篇文章主要介紹了php將csv文件導(dǎo)入到mysql數(shù)據(jù)庫的方法,通過讀取csv文件到數(shù)組再調(diào)用while循環(huán)實(shí)現(xiàn)插入數(shù)據(jù)到數(shù)據(jù)庫,是非常實(shí)用的技巧,需要的朋友可以參考下2014-12-12
PHP實(shí)現(xiàn)蛇形矩陣,回環(huán)矩陣及數(shù)字螺旋矩陣的方法分析
這篇文章主要介紹了PHP實(shí)現(xiàn)蛇形矩陣,回環(huán)矩陣及數(shù)字螺旋矩陣的方法,結(jié)合具體實(shí)例形式分析了蛇形矩陣,回環(huán)矩陣及數(shù)字螺旋矩陣的概念、表示方法及php實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-05-05

