侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Extending PHP 5.3 Closures with Serialization and Reflection[CP]

php  /  管理员 发布于 5年前   302
from:http://www.htmlist.com/development/extending-php-5-3-closures-with-serialization-and-reflection/

By Jeremy Lindblom
On January 28th, 2010

PHP 5.3 has brought with it some powerful and much-needed features like late static bindings, namespaces, and closures (also referred to as anonymous functions and lambda functions). Anyone who is experienced with JavaScript or who has worked with programming languages like Scheme or Lisp should realize the value that anonymous functions can bring to PHP. The PHP Manual explains closures like this:

    Anonymous functions, also known as closures, allow the creation of functions which have no specified name. They are most useful as the value of callback parameters, but they have many other uses. Closures can also be used as the values of variables; PHP automatically converts such expressions into instances of the Closure internal class.

PHP has very few predefined classes that are part of the core language, so naturally I was intrigued by the Closure class. The PHP Manual has this to say about the class:

    The predefined final class Closure was introduced in PHP 5.3.0. It is used for internal implementation of anonymous functions. The class has a constructor forbidding the manual creation of the object (issues E_RECOVERABLE_ERROR) and the __invoke() method with the calling magic.

The invoke magic method is also a new feature in PHP 5.3. It is called when an object is used in the context of a function (e.g. $object($parameter);). Since Closure objects will be used like functions, this is a critical feature of the Closure object. The Closure class may be perfectly equipped to act like an anonymous function, but it does not provide any extra utility beyond that. A var_dump() of a closure will reveal the functions parameters, but there is no way to get any other information about the Closure (like the actual code of the function). Trying to serialize the Closure throws an Exception and json_encode() just returns an empty JSON string. To make matters worse, the Closure class is final, so there is no way to extend it.

That simply wasn’t going to cut it for me. I wanted to make my own Closure class that was at least able to do the following:

    Invoke the Closure (with __invoke()) just like the PHP Closure class
    Retrieve the actual code of the Closure
    Retrieve detailed data about the Closure’s parameters
    Retrieve the names and values of any variables inherited from the Closure’s parent’s scope (with the use construct)
    Serialize and unserialize the Closure

I decided to play around with the Reflection API to see what kind of information I could get from the Closure. A Closure is a function, so using the ReflectionFunction class provides the same information about Closures as it does about any other functions. PHP 5.3 also added the isClosure() method to the ReflectionFunction (in case you weren’t convinced that reflection would be helpful). After some tinkering, it became apparent that I would be able to accomplish all of my desires. I will walk you through the construction of my “SuperClosure” class and explain how the creative use of Reflection allows us to find ways around the problems normally blocking the ability to have my desired features.
1. Grant the ability to invoke the Closure

The first goal was to create the basic SuperClosure class that both encapsulates and allows invocation of the Closure. To do this I wrote a simple class with a constructor and the magic __invoke() method. I also included an accessor method (getter) for the actual Closure. In the constructor I created an instance of the ReflectionFunction class and stored it as a class member that helped allow invocation of the closure with a variable number of arguments. It also helped me accomplish other things later. The following code shows the basic class upon which I will be building. The complete will be shown at the end of the article.
view sourceprint?
01.class SuperClosure {02. 03.protected $closure = NULL;04.protected $reflection = NULL;05. 06.public function __construct($function)07.{08.if ( ! $function instanceOf Closure)09.throw new InvalidArgumentException();10. 11.$this->closure = $function;12.$this->reflection = new ReflectionFunction($function);13.}14. 15.public function __invoke()16.{17.$args = func_get_args();18.return $this->reflection->invokeArgs($args);19.}20. 21.public function getClosure()22.{23.return $this->closure;24.}25.}

The __invoke() method allows the SuperClosure object to be used exactly as if it was a Closure object. I had to recreate this functionality for the SuperClosure class since I was not able to extend the Closure to begin with. In order to pass arguments through to the real Closure, I used a combination of the func_get_args() function and the invokeArgs() method of ReflectionFunction to ensure that any variable number of arguments could be used. I could have also used call_user_func_array() function, but I prefer Reflection, and since I already had an instance of the ReflectionFunction, the invokeArgs() method seemed like a better choice.
2. Retrieve the actual code of the function

Secondly, I added code that allowed the SuperClosure class to find and store the actual code defining the closure. This feature is actually the key to doing the serialization later and is the most complicated part of the program. To accomplish this portion of the program, I used the instance of ReflectionFunction from Step 1 and the SplFileObject class, an SPL class which provides a nice object-oriented interface for dealing with files. The getFileName(), getStartLine(), and getEndLine() methods of the ReflectionFunction class allowed me to retrieve all the information I needed to find the source code of the Closure function. Using the SplFileObject to open and read from the source file and in combination with some string manipulation, I was able to obtain the closure’s source code. The following code shows the protected _fetchCode() method used to parse the closure’s code out of its source file:
view sourceprint?
01.protected function _fetchCode()02.{03.// Open file and seek to the first line of the closure04.$file = new SplFileObject($this->reflection->getFileName());05.$file->seek($this->reflection->getStartLine()-1);06. 07.// Retrieve all of the lines that contain code for the closure08.$code = '';09.while ($file->key() < $this->reflection->getEndLine())10.{11.$code .= $file->current();12.$file->next();13.}14. 15.// Only keep the code defining that closure16.$begin = strpos($code, 'function');17.$end = strrpos($code, '}');18.$code = substr($code, $begin, $end - $begin + 1);19. 20.return $code;21.}

The only limitations with the current version of this function are that you cannot have multiple closures on a single line and you cannot use the word “function” anywhere besides the actual closure’s declaration.
3. Retrieve detailed data about the Closure’s parameters

Retrieving information about the closure’s parameters was as simple as adding a method that simply returns the result of the getParameters() method the SuperClosure’s instance of ReflectionFunction. That’s all. The getParameters() method returns ReflectionParameter objects which have several methods for getting information about the parameters including their names, values, default values, and more.
4. Retrieve the names and values of any variables inherited from the Closure’s parent’s scope

One of the biggest problems I had was retrieving the names and values of the variables added to the Closure’s scope with the use construct. There isn’t a documented way of doing this as far as I know, but I was able to figure out a way to do it after playing around some more with the ReflectionFunction class (it is just so helpful). These variables are actually included in the results of the getStaticVariables() method. If the closure declares any variables with the static keyword, these will also be included in the results. To get around this I added the _fetchUsedVariables() method that uses a combination of the getStaticVariables() method and some string manipulation of the Closure’s code (from Step 2) to find only the variables that were inherited from the parent’s scope. The following code shows the _fetchUsedVariables() method:
view sourceprint?
01.protected function _fetchUsedVariables()02.{03.// Make sure the use construct is actually used04.$use_index = stripos($this->code, 'use');05.if ( ! $use_index)06.return array();07. 08.// Get the names of the variables inside the use statement09.$begin = strpos($this->code, '(', $use_index) + 1;10.$end = strpos($this->code, ')', $begin);11.$vars = explode(',', substr($this->code, $begin, $end - $begin));12. 13.// Get the static variables of the function via reflection14.$static_vars = $this->reflection->getStaticVariables();15. 16.// Only keep the variables that appeared in both sets17.$used_vars = array();18.foreach ($vars as $var)19.{20.$var = trim($var, ' $&amp;');21.$used_vars[$var] = $static_vars[$var];22.}23. 24.return $used_vars;25.}

5. Grant the ability to serialize and unserialize the Closure

Finally, I implemented the serialization capabilities. Since an actual closure object cannot be serialized (it gives a fatal error), I had to be creative to come up with a way to do my own serialization. The code I wrote for Step 2 and Step 4 made this possible. The sleep magic method allows you to hook into the serialization process, so I used this method to prepare my SuperClosure class for serialization. In order for my class to be serialized, I needed to first stop the Closure object and the ReflectionFunction object representing the Closure from being serialized. The __sleep() method should return an array of the class members you are serializing, so I simply left those variables out of the list. Because I was serializing the Closure’s code and static variables, it made possible in the __wakeup() magic method to recreate the Closure object using the code. To do this I had to use the extract() function on my array of used variables to import them back into the scope. Then I performed an eval() operation on the Closure’s code in order to recreate the Closure. eval() is always a risky operation, but in this case it makes sense. If you are planning to use this code for any of your own projects, I urge you to take precautions. The following code shows the complete SuperClosure class with the additions of the __sleep() and __wakeup() methods.
view sourceprint?
001.class SuperClosure {002. 003.protected $closure = NULL;004.protected $reflection = NULL;005.protected $code = NULL;006.protected $used_variables = array();007. 008.public function __construct($function)009.{010.if ( ! $function instanceOf Closure)011.throw new InvalidArgumentException();012. 013.$this->closure = $function;014.$this->reflection = new ReflectionFunction($function);015.$this->code = $this->_fetchCode();016.$this->used_variables = $this->_fetchUsedVariables();017.}018. 019.public function __invoke()020.{021.$args = func_get_args();022.return $this->reflection->invokeArgs($args);023.}024. 025.public function getClosure()026.{027.return $this->closure;028.}029. 030.protected function _fetchCode()031.{032.// Open file and seek to the first line of the closure033.$file = new SplFileObject($this->reflection->getFileName());034.$file->seek($this->reflection->getStartLine()-1);035. 036.// Retrieve all of the lines that contain code for the closure037.$code = '';038.while ($file->key() < $this->reflection->getEndLine())039.{040.$code .= $file->current();041.$file->next();042.}043. 044.// Only keep the code defining that closure045.$begin = strpos($code, 'function');046.$end = strrpos($code, '}');047.$code = substr($code, $begin, $end - $begin + 1);048. 049.return $code;050.}051. 052.public function getCode()053.{054.return $this->code;055.}056. 057.public function getParameters()058.{059.return $this->reflection->getParameters();060.}061. 062.protected function _fetchUsedVariables()063.{064.// Make sure the use construct is actually used065.$use_index = stripos($this->code, 'use');066.if ( ! $use_index)067.return array();068. 069.// Get the names of the variables inside the use statement070.$begin = strpos($this->code, '(', $use_index) + 1;071.$end = strpos($this->code, ')', $begin);072.$vars = explode(',', substr($this->code, $begin, $end - $begin));073. 074.// Get the static variables of the function via reflection075.$static_vars = $this->reflection->getStaticVariables();076. 077.// Only keep the variables that appeared in both sets078.$used_vars = array();079.foreach ($vars as $var)080.{081.$var = trim($var, ' $&amp;');082.$used_vars[$var] = $static_vars[$var];083.}084. 085.return $used_vars;086.}087. 088.public function getUsedVariables()089.{090.return $this->used_variables;091.}092. 093.public function __sleep()094.{095.return array('code', 'used_variables');096.}097. 098.public function __wakeup()099.{100.extract($this->used_variables);101. 102.eval('$_function = '.$this->code.';');103.if (isset($_function) AND $_function instanceOf Closure)104.{105.$this->closure = $_function;106.$this->reflection = new ReflectionFunction($_function);107.}108.else109.throw new Exception();110.}111.}

Conclusion

With some cleverness and reflection, I was able to create the SuperClosure class and extend the Closure’s normal capabilities. Although my class does not enhance the typical use of a PHP Closure it does make it possible to serialize and transport a closure and provides an interface for examining the parameters and inherited variables. This means that we can do:
view sourceprint?
1.$closure = new SuperClosure(
2.function($num1, $num2) {return $num1 + $num2;}
3.);
4.$serialized_closure = serialize($closure);
5.$unserialized_closure = unserialize($serialized_closure);
6.echo $unserialized_closure(1, 5);

without worrying about errors. Also, it would technically be possible to send closures through a remote service.

From what I have read in the PHP Manual, this class may have to be changed in the future. The Manual states this regarding anonymous functions:

    Anonymous functions are currently implemented using the Closure class. This is an implementation detail and should not be relied upon.

So according to this, future versions of PHP might implement closures differently, and my class would have to be rewritten.

If you would like a copy of the code from this article (and an example of its use) you can find it on my Github account.

  • 上一条:
    SAE上部署WeiPHP2.0
    下一条:
    有效防御PHP木马攻击的技巧
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 用Time Warden监控PHP中的代码处理时间(0个评论)
    • 在PHP中使用array_pop + yield实现读取超大型目录功能示例(0个评论)
    • Property Hooks RFC在PHP 8.4中越来越接近现实(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-11
    • 2017-12
    • 2018-01
    • 2018-02
    • 2018-03
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-09
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-09
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客