Serialize Closures in PHP using this neat package
It’s widely known that PHP can’t serialize closures directly. If you try to serialize a closure in PHP, it will result in a fatal error. Take below for example.
<?php
$func = function () {
echo 'hello!';
};
$func(); // prints "hello!"
$result = serialize($func);
// Fatal error: Uncaught exception 'Exception'
// with message 'Serialization of 'Closure' is not allowed'
?>
There are many ways to overcome this issue. One of which you can write your own implementation by using _sleep() and _wakeup() magic methods along with the use of PHP Reflection in order to serialize anonymous functions.
The other and more holistic way is to use some of the readymade PHP packages to accomplish this. This package from Opis is my favorite among all. It’s a library that aims to overcome PHP’s limitations regarding closure serialization by providing a wrapper that will make all closures serializable.
From their documentation here are library’s key features:
- Serialize any closure
- Serialize arbitrary objects
- Doesn’t use eval for closure serialization or unserialization
- Works with any PHP version that has support for closures
- Supports PHP 7.0 syntax
- Handles all variables referenced/imported in
use()
and automatically wraps all referenced/imported closures for proper serialization - Handles recursive closures
- Handles magic constants like
__FILE__
,__DIR__
,__LINE__
,__NAMESPACE__
,__CLASS__
,__TRAIT__
,__METHOD__
and__FUNCTION__
. - Automatically resolves all class names, function names and constant names used inside the closure
- Track closure’s residing source by using the
#trackme
directive - Simple and very fast parser
- Any error or exception, that might occur when executing an unserialized closure, can be caught and treated properly
- You can serialize/unserialize any closure unlimited times, even those previously unserialized (this is possible because
eval()
is not used for unserialization) - Handles static closures
- Supports cryptographically signed closures
- Provides a reflector that can give you information about the serialized closure
- Provides an analyzer for SuperClosure library
- Automatically detects when the scope and/or the bound object of a closure needs to be serialized in order for the closure to work after deserialization
To serialize a closure, you can wrap the one into an Opis\Closure\SerializableClosure
object, then serialize the wrapper object using the standard serialize.
use Opis\Closure\SerializableClosure;
// Recursive factorial closure
$factorial = function ($n) use (&$factorial) {
return $n <= 1 ? 1 : $factorial($n - 1) * $n;
};
// Wrap the closure
$wrapper = new SerializableClosure($factorial);
// Now it can be serialized
$serialized = serialize($wrapper);
Unserializing closures
The package unserialize the closures using the standard unserialize function. Once the wrapper was unserialized, you can directly invoke the wrapper, or you can extract the serialized closure by calling the getClosure method.
// Unserialize the closure
$wrapper = unserialize($serialized);
// You can directly invoke the wrapper...
echo $wrapper(5); //> 120
// Or, the recommended way, extract the closure object
$closure = $wrapper->getClosure();
echo $closure(5); //> 120
Due to the fact that Opis Closure
doesn’t use eval in the serialization-deserialization process, all closures can be serialized and unserialized an infinite amount of times.
// Once again, but this time using the previously unserialized closure
$wrapper = new SerializableClosure($closure);
$serialized = serialize($wrapper);
$wrapper = unserialize($serialized);
$closure = $wrapper->getClosure();
// Now watch this...
echo $closure(5); //> 120
// It worked!
You can also serialize/unserialize arbitrary objects using this package. You can do this by using Opis\Closure\serialize
function.
use function Opis\Closure\{serialize as s, unserialize as u}
class A
{
private $closure;
public function __construct()
{
$this->closure = function(){
echo 'It works!';
};
}
public function test()
{
//call closure
($this->closure)();
}
}
$obj = new A();
u(s($obj))->test(); // It works
You can install the package from the composer.
composer require opis/closure
Or you could directly reference it into your composer.json
file as a dependency
{
"require": {
"opis/closure": "^3.1"
}
}
You can read more about the package on the official documentation.
Like this article?
Buy me a coffee👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.