设为首页 收藏本站
查看: 998|回复: 0

[经验分享] PHP用闭包思想写的torrent文件解析工具

[复制链接]

尚未签到

发表于 2015-8-25 09:37:51 | 显示全部楼层 |阅读模式
  
  本文原载于我的wp博客 http://mi-wiki.info/wp/2011/12/23/php-torrent-file-parser-again/
  转载请注明。
  


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






1 function count_down($count)
2 {
3     return $func = function()
4         use($count,$func)
5     {
6         if(--$count > 0)
7             $func();
8         echo "wow\n";
9     };
10 }
11 $foo = count_down(3);
12 $foo();





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

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

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







function count_down($count)
{
    return $foo = function()
        use(&$count,&$foo)
    {
        echo $count."\n";
        if(--$count > 0)
            $foo();
    };
}
$foo = count_down(4);
$foo();



  像上面这样写就对了。
下面是另一种方法:







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






function count_down_again($count)
{
    return function()use(&$count)
    {
        printf("wow %d\n",$count);
        return --$count;
    };
}
$foo = count_down_again(5);
while($foo() >0);


这样才正确。


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






class Foo
{
    public function __invoke($count)
    {
        if($count > 0)
            $this($count - 1);
        echo "wow\n";
    }
}
$foo = new Foo();
$foo(4);





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

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

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




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






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解析工具的代码如下:








<?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,而没有报错。但是此时文件实际上已经到末尾了。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-103883-1-1.html 上篇帖子: PHP中的__clone() 下篇帖子: [PHP]PHP定时任务的实现
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表