`
cleni
  • 浏览: 985 次
  • 性别: Icon_minigender_1
  • 来自: 大庆
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

Extending PHP 5.3 Closures with Serialization and Reflection[CP]

阅读更多
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 closure
04.$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 closure
08.$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 closure
16.$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 used
04.$use_index = stripos($this->code, 'use');
05.if ( ! $use_index)
06.return array();
07. 
08.// Get the names of the variables inside the use statement
09.$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 reflection
14.$static_vars = $this->reflection->getStaticVariables();
15. 
16.// Only keep the variables that appeared in both sets
17.$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 closure
033.$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 closure
037.$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 closure
045.$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 used
065.$use_index = stripos($this->code, 'use');
066.if ( ! $use_index)
067.return array();
068. 
069.// Get the names of the variables inside the use statement
070.$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 reflection
075.$static_vars = $this->reflection->getStaticVariables();
076. 
077.// Only keep the variables that appeared in both sets
078.$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.else
109.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.
分享到:
评论

相关推荐

    Extending the Linear Model with R

    《Extending the Linear Model with R》这本书主要探讨了如何通过R语言来扩展传统的线性模型,涵盖了一系列高级统计方法,包括广义线性模型、混合效应模型以及非参数回归等。这些模型和技术对于处理复杂数据集及进行...

    advanced php programming 和extending and embedding php

    《Advanced PHP Programming》和《Extending and Embedding PHP》是两本PHP开发领域的经典之作,对于深入理解PHP的高级概念和技术具有极高的价值。这两本书分别涵盖了PHP编程的深度探索和扩展嵌入技术,旨在帮助...

    advance php programming 和 extending and embedding php 高手必看的二本书

    通往php之路的web高手必看之书,本人在研究了...《extending and embedding php》 这本书是分析php源码,教你写扩展的,比较低层。 两本都是sams出版的极品,已转成PDF方便打印, 是英文版的,看清楚,原计原味噢 - -!

    Sams Extending and Embedding PHP

    《Sams Extending and Embedding PHP》是一本深入探讨PHP扩展和嵌入技术的专业书籍,主要面向PHP开发者,特别是那些希望提升PHP性能、定制化PHP功能或者将PHP整合到其他应用环境中的高级开发者。这本书详细讲解了...

    Extending.and.Embedding.PHP

    《Extending and Embedding PHP》是一本专注于PHP内核、扩展开发及嵌入技术的权威指南,适合那些希望深入理解PHP工作原理并进行定制化开发的程序员。这本书详细阐述了如何构建自己的PHP扩展,以及如何将PHP嵌入到...

    php extending and embedding

    ### PHP扩展与嵌入知识点详解 #### PHP的生命周期与SAPI的作用 PHP作为一种广泛使用的脚本语言,在Web开发领域有着举足轻重的地位。对于PHP的深入理解和掌握,特别是其内部工作原理的理解,对于开发者来说至关重要...

    Programming Excel With Vba And .net.chm

    Programming Excel with VBA and .NET Preface Part I: Learning VBA Chapter 1. Becoming an Excel Programmer Section 1.1. Why Program? Section 1.2. Record and Read Code Section 1.3. Change ...

    Extending and Embedding PHP-english.7z

    《Extending and Embedding PHP》是一本专注于PHP扩展开发的重要参考资料,主要面向已经熟悉PHP基础并希望深入理解其内部机制,以及想要开发自定义扩展的开发者。这本书详细介绍了如何使用C++来编写PHP的扩展模块,...

    [R.Books]Extending the Linear Model with R

    本书《Extending the Linear Model with R: Generalized Linear, Mixed Effects and Nonparametric Regression Models》是由Julian J. Faraway所著,它是一本专注于使用R语言扩展线性模型的统计学高级教材。书中的...

    Extending and Embedding PHP

    Teach user how to extend for PHP scripts

    Extending Unity with Editor Scripting 无水印pdf

    Extending Unity with Editor Scripting 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请...

    Software Evolution with UML and XML(1)

    Design of Embedded Systems Chapter XII - Describing and Extending Classes with XMI—An Industrial Experience Index List of Figures List of Tables List of Definitions

    Software Evolution with UML and XML(2)

    Design of Embedded Systems Chapter XII - Describing and Extending Classes with XMI—An Industrial Experience Index List of Figures List of Tables List of Definitions

    Extending Unity with Editor Scripting 彩色高清无水印

    《Extending Unity with Editor Scripting 彩色高清无水印》是一本专注于Unity游戏引擎编辑器扩展的专业书籍,旨在帮助开发者充分利用Unity的Editor脚本功能,创建高效且定制化的开发工具。通过本书,读者可以深入...

    Extending_and_Embedding_PHP.chm

    英语php教材,英语能力强的朋友可以读。 很适合初学者。

    LINQ to Objects Using C# 4.0: Using and Extending LINQ to Objects and Parallel LINQ

    Next, you’ll drill down to detailed techniques for applying and extending these features with C# 4.0 and C# 3.0—including code examples that reflect the realities of production development. ...

    Extending Unity with Editor Scripting

    《Extending Unity with Editor Scripting》是关于如何使用Unity编辑器脚本扩展Unity引擎的一份详细指南,这本书由Angelo Tadres撰写,并由Packt Publishing出版。书中提供了丰富的信息,旨在帮助Unity(通常称为U3D...

    Extending the linear model with R

    《使用R扩展线性模型》一书,作为统计科学领域的重要参考文献,深入探讨了如何利用R语言这一强大的统计分析工具来扩展传统的线性模型,涵盖广义线性模型、混合效应模型以及非参数回归模型等高级统计方法。...

    Extending ArcGIS with Python

    - **栅格类**:提供了处理栅格数据的各种类,如Raster、RasterBand等。 #### 6. **地图自动化** - **ArcPy Mapping模块**:用于自动化地图制作过程,如修复断开的数据源链接、批量创建地图册等。 - **创建脚本工具...

Global site tag (gtag.js) - Google Analytics