PHP基于闭包思想实现的BT(torrent)文件解析工具实例详解

2025-05-29 0 75

本文实例讲述了PHP基于闭包思想实现的torrent文件解析工具。分享给大家供大家参考,具体如下:

PHP对静态词法域的支持有点奇怪,内部匿名函数必须在参数列表后面加上use关键字,显式的说明想要使用哪些外层函数的局部变量。

?

1

2

3

4

5

6

7

8

9

10

11

12
function count_down($count)

{

return $func = function()

use($count,$func)

{

if(--$count > 0)

$func();

echo "wow\\n";

};

}

$foo = count_down(3);

$foo();

我本来是想这样的。但是不行,会在第7行调用$func的时候报错。

错误是Fatal error: Function name must be a string in – on line 7

反复试验后发觉,外部的匿名函数应该通过引用传值传给内部,否则是不行的:

?

1

2

3

4

5

6

7

8

9

10

11

12
function count_down($count)

{

return $foo = function()

use(&$count,&$foo)

{

echo $count."\\n";

if(--$count > 0)

$foo();

};

}

$foo = count_down(4);

$foo();

像上面这样写就对了。

下面是另一种方法:

?

1

2

3

4

5

6

7

8

9

10
function count_down_again($count)

{

return function()use($count)

{

printf("wow %d\\n",$count);

return --$count;

};

}

$foo = count_down_again(5);

while($foo() >0);

不过,这段代码有点小错误。编译虽然没错,但是$foo函数每次返回的都是4.

也就是use关键字看上去像是支持静态词法域的,在这个例子上,它只是对外层函数使用的变量作了一个简单拷贝。

让我们稍微修改一下,把第3行的use($count)改为use(&$count):

?

1

2

3

4

5

6

7

8

9

10
function count_down_again($count)

{

return function()use(&$count)

{

printf("wow %d\\n",$count);

return --$count;

};

}

$foo = count_down_again(5);

while($foo() >0);

这样才正确。

我个人使用的方式是基于类的,做成了类似下面的形式:

?

1

2

3

4

5

6

7

8

9

10

11
class Foo

{

public function __invoke($count)

{

if($count > 0)

$this($count - 1);

echo "wow\\n";

}

}

$foo = new Foo();

$foo(4);

这样做的行为也是正确的。

这样不会像前一个例子那样失去了递归调用的能力。

虽然这是一个类,但是只不过是在手动实现那些支持闭包和静态词法域的语言中,编译器自动实现的动作。

其实今天早上,我本来准备用类scheme的风格写一个解析器的。可能稍微晚点吧。scheme风格的函数式编程是这样的:

?

1

2

3

4

5

6

7
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);

它不是很依赖静态词法域,虽然scheme对静态词法域的支持还是很不错的。它主要还是利用了first-class-function。当然,这也是一种典型的闭包

我实现的torrent解析工具的代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227
<?php

$file_name = '1.torrent';

$file = fopen($file_name,'r');

$nil = new Parser($file);//构造解析器

$nil = $nil();//进行解析

$pos = ftell($file);

echo '读取到文件位置'.sprintf('0x%08X',$pos)."\\r\\n";

fseek($file,0,SEEK_END);

echo '还剩下'.(ftell($file) - $pos).'字节未读取'."\\r\\n";

if(!feof($file))

{

echo '文件还未结束,再读一个字符:';

$ch = fgetc($file);

if(is_string($ch) && ereg('\\w',$ch))

{

echo $ch."\\r\\n";

}

else

{

printf('0x%02X',$ch);

echo "\\r\\n";

}

echo '现在的文件位置是'.sprintf('0x%08X',ftell($file))."\\r\\n";

echo '文件'.(feof($file)?'已结束':'还未结束')."\\r\\n";

}

fclose($file);//解析器后面不再工作了,此时可以释放文件指针了。

$info = @$nil['value'][0]['info'];

if(!$info)

{

echo '这是一个有效的B-Encoding文件,但它不是一个有效的种子文件';

exit();

}

$name = $info['name.utf-8'] ?$info['name.utf-8']:$info['name'];

if(!$name)

{

echo '这是一个有效的B-Encoding文件,但它不是一个有效的种子文件';

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."个文件不含目录\\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 "仅有一个文件\\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 '解析数字时遇到错误',"\\r\\n";

echo '在i和e之间不应该出现非数字字符'."\\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();

//这个$node变量作为字典对象准备加入到$parent的孩子节点中去

//$node['type'] = 'd';

while('e' != ($tmp = $this($node)))

{//每次给$node带来一个新孩子

$node = $tmp;

}

$child_count = count($node['value']);

if($child_count % 2 != 0)

{

echo '解析结尾于';

echo sprintf('0x%08X',ftell($this ->_file));

echo '的字典时遇到错误:'."\\r\\n";

echo '字典的对象映射不匹配';

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 '无效的字典结尾于';

echo sprintf('0x%08X',ftell($this ->_file));

echo ":\\r\\n";

echo '解析[k => v]配对时遇到错误,k应为字符串';

exit();

}

$product[$key] = $value;

}

/*

* 思想是这样的:子节点想要加入父节点时,

* 往父节点的value数组添加。

* 当父节点收集好所需的信息后,

* 父节点自身再从它的value节点整合内容

* 对于字典和列表统一这样处理会大大降低代码量

*/

$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函数包裹了$this ->_file变量

*/

function read()

{

if(!feof($this ->_file))

{

return fgetc($this ->_file);

}else{

echo '意外的文件结束';

exit();

}

}

/*

* unexpected_character函数接收2个参数

* 它用于指明脚本在何处遇到了哪个不合法的字符,

* 并在返回前终止脚本的运行。

*/

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();

}

}

?>

这里很有趣的是,明明我对文件调用了fseek($file,0,SEEK_END);移动到文件末尾了,但是feof还是报告说文件没有结束,并且fgetc返回一个0,而没有报错。但是此时文件实际上已经到末尾了。

希望本文所述对大家PHP程序设计有所帮助。

原文链接:http://www.cnblogs.com/bombless/archive/2011/12/23/php-closure.html

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 PHP基于闭包思想实现的BT(torrent)文件解析工具实例详解 https://www.kuaiidc.com/93774.html

相关文章

发表评论
暂无评论