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

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

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

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

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('0xX',$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('0xX',$ch);
    echo "\r\n";
  }
  echo '现在的文件位置是'.sprintf('0xX',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(
                '0xX',ftell($this ->_file));
            echo '解析数字时遇到错误',"\r\n";
            echo '在i和e之间不应该出现非数字字符'."\r\n";
            echo '意外的字符'.sprintf('0xX',$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('0xX',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('0xX',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("0xX",$pos);
    $hex_val = sprintf("0xX",$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相关内容感兴趣的读者可查看本站专题:《php curl用法总结》、《php字符串(string)用法总结》、《PHP数组(Array)操作技巧大全》、《php排序算法总结》、《PHP常用遍历算法与技巧总结》、《PHP数据结构与算法教程》、《php程序设计算法总结》、《PHP数学运算技巧总结》及《PHP运算与运算符用法总结》、

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

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

  1. ios – Swift中的UIView动画不起作用,错误的参数错误

    我正在尝试制作动画并使用下面的代码.我得到“无法使用类型’的参数列表调用’animateWithDuration'(FloatLiteralConvertible,延迟:FloatLiteralConvertible,选项:UIViewAnimationoptions,动画:()–>()–>$T4,完成:(Bool)–>(Bool)–>$T5)’“错误.这意味着我使用了错误的参数.我错了.请

  2. ios – 使用捕获列表中的无主内容导致崩溃,即使块本身也不会执行

    欣赏有关如何调试此内容的任何提示或有关导致崩溃的原因的解释……

  3. ios – Swift传递封闭与Params

    目前我传递一个闭包作为一个对象的属性,该对象不接受参数并且没有返回值,如下所示:到目前为止,这工作得很好.我希望能够在设置此闭包时传入一个参数,以便在MyClass的实例中使用.我正在寻找下面的SOMETHING,虽然我确定语法不正确:我如何将参数传递给可以在MyClass中使用的闭包–即可以在属性本身的didSet部分内使用的值,如第二个示例中所示?

  4. ios – 来自UIAlertController的self.navigationController?.popViewControllerAnimated

    我是新手,但我想我已经掌握了它.这让我的进步很难过.我想要做的是当我们无法找到他的查询的相关数据时向用户抛出错误消息,然后继续将他带回到之前的ViewController.但是,我在这方面遇到了麻烦.在我添加操作的行上,我收到以下错误:’UIViewController?’不是Void的子类型我该怎么做呢?

  5. ios – 如何添加@noescape注释到可选闭包

    我的功能有这个签名:而现在我想让自己在给定的关闭中逃脱自我.但是当我尝试这个:编译器抱怨:是否可以在可选参数中使用它?

  6. ios – Swift闭包为AnyObject

    如何将()–>()转换为AnyObject?我试图将它转换为:处理程序为AnyObject,但它给我一个错误说:()–>()不符合协议’AnyObject’解决方法HowcanIcast()->()intoAnyObject?

  7. ios – 在swift中将捕获列表正确放置在嵌套闭包中

    在Swift中为哪些嵌套闭包定义捕获的引用?如果[weakself]被捕获在只有内部最后面的闭包,GCD将保留ExampleDataSource,直到块完成执行,这就解释了为什么调试看起来像这样:同样的事情会发生,如果没有捕获列表被包括,我们从来没有可选地解开自己,尽管编译器,确实试图警告你!

  8. ios – 两个类,回调和单元测试

    而且,我不觉得这样的行为会很容易测试?

  9. 从iOS应用程序发送帖子到PHP脚本不工作…简单的解决方案就像

    我之前已经做了好几次了但是由于某些原因我无法通过这个帖子…我尝试了设置为_POST且没有的变量的PHP脚本……当它们未设置为发布时它工作精细.这是我的iOS代码:这里是PHP的一大块,POST变量不在正确的位置?我想这对于更有经验的开发人员来说是一个相当简单的答案,感谢您的帮助!解决方法$_POST是一个数组,而不是一个函数.您需要使用方括号来访问数组索引:

  10. 寒城攻略:Listo 教你 25 天学会 Swift 语言 - 18 Automatic Reference Counting

    //每个Person类的实例拥有一个被初始化为nil的apartment可选属性,因为一个人并不一定拥有一座公寓)}}classApartment{letnumber:Intinit{number=number}tenant:Person1?//声明Apartment2类中的tenant属性为弱引用varPin:Person2?鉴于这种关系,Customer类有一个可选类型属性card,而CreditCard类的customer属性则是非可选类型的classCustomer{varcard:CreditC

随机推荐

  1. PHP个人网站架设连环讲(一)

    先下一个OmnihttpdProffesinalV2.06,装上就有PHP4beta3可以用了。PHP4给我们带来一个简单的方法,就是使用SESSION(会话)级变量。但是如果不是PHP4又该怎么办?我们可以假设某人在15分钟以内对你的网页的请求都不属于一个新的人次,这样你可以做个计数的过程存在INC里,在每一个页面引用,访客第一次进入时将访问时间送到cookie里。以后每个页面被访问时都检查cookie上次访问时间值。

  2. PHP函数学习之PHP函数点评

    PHP函数使用说明,应用举例,精简点评,希望对您学习php有所帮助

  3. ecshop2.7.3 在php5.4下的各种错误问题处理

    将方法内的函数,分拆为2个部分。这个和gd库没有一点关系,是ecshop程序的问题。会出现这种问题,不外乎就是当前会员的session或者程序对cookie的处理存在漏洞。进过本地测试,includes\modules\integrates\ecshop.php这个整合自身会员的类中没有重写integrate.php中的check_cookie()方法导致,验证cookie时返回的username为空,丢失了登录状态,在ecshop.php中重写了此方法就可以了。把他加到ecshop.php的最后面去就可

  4. NT IIS下用ODBC连接数据库

    $connection=intodbc_connect建立数据库连接,$query_string="查询记录的条件"如:$query_string="select*fromtable"用$cur=intodbc_exec检索数据库,将记录集放入$cur变量中。再用while{$var1=odbc_result;$var2=odbc_result;...}读取odbc_exec()返回的数据集$cur。最后是odbc_close关闭数据库的连接。odbc_result()函数是取当前记录的指定字段值。

  5. PHP使用JpGraph绘制折线图操作示例【附源码下载】

    这篇文章主要介绍了PHP使用JpGraph绘制折线图操作,结合实例形式分析了php使用JpGraph的相关操作技巧与注意事项,并附带源码供读者下载参考,需要的朋友可以参考下

  6. zen_cart实现支付前生成订单的方法

    这篇文章主要介绍了zen_cart实现支付前生成订单的方法,结合实例形式详细分析了zen_cart支付前生成订单的具体步骤与相关实现技巧,需要的朋友可以参考下

  7. Thinkphp5框架实现获取数据库数据到视图的方法

    这篇文章主要介绍了Thinkphp5框架实现获取数据库数据到视图的方法,涉及thinkPHP5数据库配置、读取、模型操作及视图调用相关操作技巧,需要的朋友可以参考下

  8. PHP+jquery+CSS制作头像登录窗(仿QQ登陆)

    本篇文章介绍了PHP结合jQ和CSS制作头像登录窗(仿QQ登陆),实现了类似QQ的登陆界面,很有参考价值,有需要的朋友可以了解一下。

  9. 基于win2003虚拟机中apache服务器的访问

    下面小编就为大家带来一篇基于win2003虚拟机中apache服务器的访问。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. Yii2中组件的注册与创建方法

    这篇文章主要介绍了Yii2之组件的注册与创建的实现方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下

返回
顶部