Serialize closures. Not maintained. Consider using opis/closure.

Last update: Jun 25, 2022

PHP SuperClosure

Total Downloads Build Status MIT License Gitter

A PHP Library for serializing closures and anonymous functions.


No Longer Maintained

This software is no longer maintained. Consider using opis/closure instead.

The rest of the README will remain intact as it was prior to the software being abandoned.


Introduction

Once upon a time, I tried to serialize a PHP Closure object. As you can probably guess, it doesn't work at all. In fact, you get a very specific error message from the PHP Runtime:

Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed'

Even though serializing closures is "not allowed" by PHP, the SuperClosure library makes it possible. Here's the way you use it:

use SuperClosure\Serializer;

$serializer = new Serializer();

$greeting = 'Hello';
$hello = function ($name = 'World') use ($greeting) {
    echo "{$greeting}, {$name}!\n";
};

$hello('Jeremy');
//> Hello, Jeremy!

$serialized = $serializer->serialize($hello);
// ...
$unserialized = $serializer->unserialize($serialized);

$unserialized('Jeremy');
//> Hello, Jeremy!

Yep, pretty cool, right?

Features

SuperClosure comes with two different Closure Analyzers, which each support different features regarding the serialization of closures. The TokenAnalyzer is not as robust as the AstAnalyzer, but it is around 20-25 times faster. Using the table below, and keeping in mind what your closure's code looks like, you should choose the fastest analyzer that supports the features you need.

Supported Features Via AstAnalyzer Via TokenAnalyzer
Regular closures (anonymous functions)
$fn = function (...) {...};
Yes Yes
Closures with context
$fn = function () use ($a, $b, ...) {...};
Yes Yes
Recursive closures
$fn = function () use (&$fn, ...) {...};
Yes Yes
Closures bound to an object
$fn = function () {$this->something(); ...};
Yes Yes
Closures scoped to an object
$fn = function () {self::something(); ...};
Yes Yes
Static closures (i.e, preserves the `static`-ness)
$fn = static function () {...};
Yes --
Closures with class name in params
$fn = function (Foo $foo) {...};
Yes --
Closures with class name in body
$fn = function () {$foo = new Foo; ...};
Yes --
Closures with magic constants
$fn = function () {$file = __FILE__; ...};
Yes --
Performance Slow Fast

Caveats

  1. For any variables used by reference (e.g., function () use (&$vars, &$like, &$these) {…}), the references are not maintained after serialization. The only exception to this is recursive closure references.
  2. If you have two closures defined on a single line (why would you do this anyway?), you will not be able to serialize either one since it is ambiguous which closure's code should be parsed (they are anonymous functions after all).
  3. Warning: The eval() function is required to unserialize the closure. This function is considered dangerous by many, so you will have to evaluate what precautions you may need to take when using this library. You should only unserialize closures retrieved from a trusted source, otherwise you are opening yourself up to code injection attacks. It is a good idea sign serialized closures if you plan on storing or transporting them. Read the Signing Closures section below for details on how to do this.
  4. Cannot serialize closures that are defined within eval()'d code. This includes re-serializing a closure that has been unserialized.

Analyzers

You can choose the analyzer you want to use when you instantiate the Serializer. If you do not specify one, the AstAnalyzer is used by default, since it has the most capabilities.

use SuperClosure\Serializer;
use SuperClosure\Analyzer\AstAnalyzer;
use SuperClosure\Analyzer\TokenAnalyzer;

// Use the default analyzer.
$serializer = new Serializer();

// Explicitly choose an analyzer.
$serializer = new Serializer(new AstAnalyzer());
// OR
$serializer = new Serializer(new TokenAnalyzer());

Analyzers are also useful on their own if you are just looking to do some introspection on a Closure object. Check out what is returned when using the AstAnalyzer:

use SuperClosure\Analyzer\AstAnalyzer;

class Calculator
{
    public function getAdder($operand)
    {
        return function ($number) use ($operand) {
            return $number + $operand;
        };
    }
}

$closure = (new Calculator)->getAdder(5);
$analyzer = new AstAnalyzer();

var_dump($analyzer->analyze($closure));
// array(10) {
//   'reflection' => class ReflectionFunction#5 (1) {...}
//   'code' => string(68) "function ($number) use($operand) {
//     return $number + $operand;
// };"
//   'hasThis' => bool(false)
//   'context' => array(1) {
//     'operand' => int(5)
//   }
//   'hasRefs' => bool(false)
//   'binding' => class Calculator#2 (0) {...}
//   'scope' => string(10) "Calculator"
//   'isStatic' => bool(false)
//   'ast' => class PhpParser\Node\Expr\Closure#13 (2) {...}
//   'location' => array(8) {
//     'class' => string(11) "\Calculator"
//     'directory' => string(47) "/Users/lindblom/Projects/{...}/SuperClosureTest"
//     'file' => string(58) "/Users/lindblom/Projects/{...}/SuperClosureTest/simple.php"
//     'function' => string(9) "{closure}"
//     'line' => int(11)
//     'method' => string(22) "\Calculator::{closure}"
//     'namespace' => NULL
//     'trait' => NULL
//   }
// }

Signing Closures

Version 2.1+ of SuperClosure allows you to specify a signing key, when you instantiate the Serializer. Doing this will configure your Serializer to sign any closures you serialize and verify the signatures of any closures you unserialize. Doing this can help protect you from code injection attacks that could potentially happen if someone tampered with a serialized closure. Remember to keep your signing key secret.

$serializer1 = new SuperClosure\Serializer(null, $yourSecretSigningKey);
$data = $serializer1->serialize(function () {echo "Hello!\n";});
echo $data . "\n";
// %rv9zNtTArySx/1803fgk3rPS1RO4uOPPaoZfTRWp554=C:32:"SuperClosure\Serializa...

$serializer2 = new SuperClosure\Serializer(null, $incorrectKey);
try {
    $fn = $serializer2->unserialize($data);
} catch (SuperClosure\Exception\ClosureUnserializationException $e) {
    echo $e->getMessage() . "\n";
}
// The signature of the closure's data is invalid, which means the serialized
// closure has been modified and is unsafe to unserialize.

Installation

To install the Super Closure library in your project using Composer, simply require the project with Composer:

$ composer require jeremeamia/superclosure

You may of course manually update your require block if you so choose:

{
    "require": {
        "jeremeamia/superclosure": "^2.0"
    }
}

Please visit the Composer homepage for more information about how to use Composer.

Why would I need to serialize a closure?

Well, since you are here looking at this README, you may already have a use case in mind. Even though this concept began as an experiment, there have been some use cases that have come up in the wild.

For example, in a video about Laravel and IronMQ by UserScape, at about the 7:50 mark they show how you can push a closure onto a queue as a job so that it can be executed by a worker. This is nice because you do not have to create a whole class for a job that might be really simple.

Or... you might have a dependency injection container or router object that is built by writing closures. If you wanted to cache that, you would need to be able to serialize it.

In general, however, serializing closures should probably be avoided.

Tell me about how this project started

It all started back in the beginning of 2010 when PHP 5.3 was starting to gain traction. I set out to prove that serializing a closure could be done, despite that PHP wouldn't let me do it. I wrote a blog post called Extending PHP 5.3 Closures with Serialization and Reflection on my former employers' blog, HTMList, showing how it could be done. I also released the code on GitHub.

Since then, I've made a few iterations on the code, and the most recent iterations have been more robust, thanks to the usage of the fabulous nikic/php-parser library.

Who is using SuperClosure?

  • Laravel - Serializes a closure to potentially push onto a job queue.
  • HTTP Mock for PHP - Serialize a closure to send to remote server within a test workflow.
  • Jumper - Serialize a closure to run on remote host via SSH.
  • nicmart/Benchmark - Uses the ClosureParser to display a benchmarked Closure's code.
  • florianv/business - Serializes special days to store business days definitions.
  • zumba/json-serializer - Serializes PHP variables into JSON format.
  • PHP-DI - Compiles closure definitions into optimized PHP code.
  • Please let me know if and how your project uses Super Closure.

Alternatives

This year the Opis Closure library has been introduced, that also provides the ability to serialize a closure. You should check it out as well and see which one suits your needs the best.

If you wish to export your closures as executable PHP code instead, you can check out the brick/varexporter library.

GitHub

https://github.com/jeremeamia/super_closure
Comments
  • 1. Magic invocation breaks pass by-ref

    When invoking the closure through the __invoke magic method, values which should be passed by-ref are, instead, passed by-value.

    Test code:

      use Jeremeamia\SuperClosure\SerializableClosure;
    
      $closure = function(&$ref = null) {
        $ref = "new value!";
      };
    
      $sclosure = new SerializableClosure($closure);
    
    
      $var1 = null;
      $closure($var1);
    
      $var2 = null;
      $sclosure($var2);
    
      var_dump($var1, $var2);
    

    Outputs:

    string 'new value!' (length=10)
    null
    

    Expected:

    string 'new value!' (length=10)
    string 'new value!' (length=10)
    

    Now, I'm not so sure about previous versions, but a patch I've applied to SerializableClosure.php that works for 5.4 is as follows:

        public function __invoke(&$arg0 = null, &$arg1 = null, &$arg2 = null, &$arg3 = null, &$arg4 = null, &$arg5 = null, &$arg6 = null, &$arg7 = null, &$arg8 = null, &$arg9 = null)
        {
          $stack = debug_backtrace(0);
          return $this->getReflection()->invokeArgs($stack[0]['args']);
        }
    

    Granted, this is pretty hacky (and limits the caller to "only" 10 by-ref arguments), but it gets the job done in about the only way I could figure out.

    I didn't put it through as much testing as I probably should have, but what I did test is that: a) It allows for references to be passed through properly (outside the known limitation of by-ref parameters needing to be explicitly listed). b) By-val parameters are not actually passed by-ref if the closure itself does not expect them to be. c) Actual parameter counts/requirements aren't impacted.

    With the patch in place, the test code above outputs what we'd expect:

    string 'new value!' (length=10)
    string 'new value!' (length=10)
    
    Reviewed by Ceiu at 2014-01-20 19:52
  • 2. Release 2.2.0

    I think we're ready to release 2,2,0 now @jeremeamia. There are two things potentially stopping us though:

    1. https://github.com/jeremeamia/super_closure/pull/53 might need reviewing first.
    2. You might want to wait for the final PHP 7.0.0 release before we say that 2.2.0 is 100% PHP 7 compatible.
    Reviewed by GrahamCampbell at 2015-11-04 23:10
  • 3. [Proposal] Add bindTo support

    Hey friend!

    I'm trying to change the binding of the decorated Closure, with much difficulty. It's private, so a subclass of SerializableClosure wont work. Would you be open to be adding bindTo support?

    Reviewed by assertchris at 2015-03-14 02:53
  • 4. SuperClosure v2.0-alpha1 Feedback

    I recently tagged v2.0-alpha1. Please read the release notes, check out the code, and let me know what you think. I'd love some feedback, suggestions, ideas, etc. Thanks.

    Reviewed by jeremeamia at 2014-01-14 03:37
  • 5. Cannot instantiate interface PhpParser\Parser

    Hello Jeremeamia, I notice an issue with version 2.2.0 of your project. The function below wouldn't work

    private function getFileAst(\ReflectionFunction $reflection)
        {
            $fileName = $reflection->getFileName();
            if (!file_exists($fileName)) {
                throw new ClosureAnalysisException(
                    "The file containing the closure, \"{$fileName}\" did not exist."
                );
            }
    
    
            return $this->getParser()->parse(file_get_contents($fileName)); // This line doesn't work
        }
    

    With version v2.1.0 of nikic/php-parser, Parser is no longer a class but an interface and so it cannot be instantiated. We now have class Multiple implements Parser { }.

    For a work around, either do a

    1. use PhpParser\Parser\Multiple as CodeParser; or
    2. Bind the Parser interface to the concrete class Multiple so that it can be instantiated.

    I am sure you forgot to make that change. With older versions of nickic/php-parser, Parser was a concrete class. Thanks

    Reviewed by divostar at 2016-06-28 05:08
  • 6. self:: usage throws error when unserializing

    I recently started using your Super Closure PHP module in our Yii 1.1 framework when implementing a worker queue. I got a problem there when I used self:: in the closures I want to serialize. In the moment the unserialized closure is called, I get a fatal PHP error:

    PHP Fatal error: Call to undefined method SuperClosure\SerializableClosure::_sendNotifications() in ~~[...]~~/common/vendor/jeremeamia/SuperClosure/src/SerializableClosure.php(153) : eval()'d code on line 2

    Let me provide some information: the closure passed looks like

            function($users) use ($push_notification,$item_id,$item_type,$link_type,$silent,$app_notification_id){
                self::_sendNotifications($users, $push_notification, $item_id, $item_type, $link_type, $silent, $app_notification_id);
            }
    

    This is how I unserialize and run the closure:

    $this->body = (new SuperClosure\Serializer())->unserialize($this->body['_payload']);
    $this->exitCode = call_user_func_array($this->body, is_array($this->params) ? $this->params : array());
    

    The serialized string contains the following:

    C:32:"SuperClosure\SerializableClosure":1113:{a:5:{s:4:"code";s:234:"function ($users) use($push_notification, $item_id, $item_type, $link_type, $silent, $app_notification_id) { self::_sendNotifications($users, $push_notification, $item_id, $item_type, $link_type, $silent, $app_notification_id); };";s:7:"context";a:6:{s:17:"push_notification";O:16:"PushNotification":12:{s:11:"page_search";N;s:19:"CActiveRecord_new";b:0;s:26:"CActiveRecord_attributes";a:7:{s:6:"status";i:3;s:16:"is_category_text";i:0;s:4:"week";i:0;s:4:"text";s:19:"guest4 followed you";s:4:"date";s:19:"2015-11-27 11:16:39";s:16:"app_notification";s:3:"314";s:2:"id";s:3:"412";}s:23:"CActiveRecord_related";a:0:{}s:17:"CActiveRecord_c";N;s:18:"CActiveRecord_pk";s:3:"412";s:21:"CActiveRecord_alias";s:1:"t";s:15:"CModel_errors";a:0:{}s:19:"CModel_validators";N;s:17:"CModel_scenario";s:6:"update";s:14:"CComponent_e";N;s:14:"CComponent_m";N;}s:7:"item_id";s:1:"1";s:9:"item_type";s:4:"user";s:9:"link_type";s:12:"user_profile";s:6:"silent";b:1;s:19:"app_notification_id";s:3:"314";}s:7:"binding";N;s:5:"scope";s:23:"PushNotificationService";s:8:"isStatic";b:1;}}
    

    I'm using the AstAnalyzer, but as I understood the documentation, it should actually work with both analyzers. I'm running on PHP 5.4.16.

    [reported in my email to @jeremeamia on 11/27, copy here for tracking reasons]

    Reviewed by jfsalzmann at 2015-12-04 16:39
  • 7. Serilization error

    Hi,

    I'm using Laravel and trying to send an email with Mail::queue.

    Here's my code

    Mail::queue('emails.payment.invoice', [], function($message) use ($user, $pdf)
    {
        $message->to($user->email, $user->name);
        $message->subject('Invoice');
    
        $message->attachData($pdf, 'Invoice');
    });
    

    Underneath this uses super_closure to serialize the closure.

    But when Laravel tries to json_encode the returned serialized closure it fails with.

    http://cl.ly/image/3p29212k2M0k

    So I tried to figure out what was wrong with the UTF-8. So I used iconv -f UTF-8 input.txt -o output.txt, which gave me:

    iconv: text.txt:1:2967: cannot convert
    iconv: -o: No such file or directory
    

    The entire serialized closure can be found here: http://laravel.io/bin/lnJ93

    Any help is appreciated

    Reviewed by andheiberg at 2014-07-11 18:37
  • 8. Composer update error - update dependencies?

    Hi - just got this error when running composer update on a project:

    [Composer\DependencyResolver\SolverProblemsException]

    Problem 1
      - jeremeamia/SuperClosure 1.0.1 requires nikic/php-parser ~0.9 -> no matching package found.
    

    ...

    Potential causes:

    • A typo in the package name
    • The package is not available in a stable-enough version according to your minimum-stability setting see https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion for more details.

    Have other people noticed this issue? Is it possible you need to update dependencies for this project? My project is built on Laravel 4 - Laravel itself requires the nikic parser but only requires "*" rather than "~0.9". That seems to work without any problem. I'm not 100% enlightened on how composer does its business, however, so maybe I'm way off the mark.

    Reviewed by penoonan at 2014-02-07 00:04
  • 9. Fix char / string

    Using PHP 8

    1) SuperClosure\Test\Unit\SerializerTest::testUnserializingFailsWithInvalidSignature
    Only the first byte will be assigned to the string offset
    
    /dev/shm/BUILD/super_closure-5707d5821b30b9a07acfb4d76949784aaa0e9ce9/tests/Unit/SerializerTest.php:30
    
    
    Reviewed by remicollet at 2021-03-29 13:14
  • 10. Arrow functions serialization

    It would be nice to have it at 7.4 final release

    https://wiki.php.net/rfc/arrow_functions

    $serializer = new SuperClosure\Serializer();
    $b = 5;
    $test = fn($a) => $a * 5;
    $c = $serializer->serialize($test);
    

    Expected: serialized closure Actual result: ClosureAnalysisException: The closure was not found within the abstract syntax tree

    Reviewed by arokettu at 2019-08-05 15:01
  • 11. Add context-free code getter for ClosureParser

    Hi,

    This PR adds "context-free" code getter, i.e. closure's code which doesn't depend on context via $this and use (...). If it is not possible, getter raises InvalidArgumentException.

    I suppose it could be useful in case of moving closure's code to somewhere like cache with assurance that we are able to move code without breaking something.

    I'm not absolutely sure that it's OK, though.

    Reviewed by unkind at 2014-08-28 21:22
PHP remote closure executor

Jumper Allow you to execute PHP Closure in other distant computer via SSH and without client/server setup. Source computer dependency: PHP >= 5.3 (so

Apr 5, 2022
This is a library to serialize PHP variables in JSON format

This is a library to serialize PHP variables in JSON format. It is similar of the serialize() function in PHP, but the output is a string JSON encoded. You can also unserialize the JSON generated by this tool and have you PHP content back.

Feb 12, 2022
This is the official place where Nemesysco's LVA7 and QA7 dockers will be hosted and maintained
This is the official place where Nemesysco's LVA7 and QA7 dockers will be hosted and maintained

LVA-Dockers This is the official place where Nemesysco's LVA7 and QA7 dockers will be hosted and maintained - It is still under construction! We are w

Feb 17, 2022
This component provides a collection of functions/classes using the symfony/intl package when the Intl extension is not installed.

Symfony Polyfill / Intl: ICU This package provides fallback implementations when the Intl extension is not installed. It is limited to the "en" locale

Jun 30, 2022
This is an experiment to export all RFCs from the PHP wiki into Git, including the change history for each RFC (along with the date and author of each change). This is not meant to replace the wiki.

PHP Requests for Comments (RFCs) About This repository is an experiment to export all RFCs from the PHP wiki into Git, including the change history fo

Jun 20, 2022
This is a plugin for pocketmine-mp, when locking a player's items helps players not to lose items or throw things around causing server lag.

[] LockedItem| v1.0.0 Player's item lock Features Player's item lock Players aren't afraid of losing items For Devolopers You can access to LockedItem

Jan 4, 2022
Magento 2 Module Experius Page Not Found 404. This module saves all 404 url to a database table

Magento 2 Module Experius Page Not Found 404 This module saves all 404 urls to a database table. Adds an admin grid with 404s It includes a count so y

Jun 20, 2022
Magento commands to find translations that are present in one CSV file but not in another, and to translate CSV dicts with DeepL
Magento commands to find translations that are present in one CSV file but not in another, and to translate CSV dicts with DeepL

Hyvä Themes - Magento translation CSV comparison command hyva-themes/magento2-i18n-csv-diff This module adds the bin/magento i18n:diff-csv and i18n:tr

Apr 7, 2022
Allows installing Drupal extensions event if not compatible with installed drupal/core package

mglaman/composer-drupal-lenient Lenient with it, Drupal 10 with it. Why? The Drupal community introduced a lenient Composer facade that modified the d

May 24, 2022
This repository demonstrates exemplary implementation of chat using HTTP and Websocket servers in PHP using Kraken Framework components.
This repository demonstrates exemplary implementation of chat using HTTP and Websocket servers in PHP using Kraken Framework components.

This repository demonstrates exemplary implementation of chat using HTTP and Websocket servers in PHP using Kraken Framework components.

Aug 11, 2021
A research raw data repository for researchers of Arba Minch University built using Codeigniter which follows MVC architecture. The front-end is build using Bootstrap.

Arba Minch University Dataset Repository This system is a research dataset repository for Arba Minch University researchers and is build using Codeign

Jun 15, 2022
Nuber is an open source container management platform it provides a front end to manage your own cloud infrastructure, using Linux Containers virtualization technology

Nuber is an open source container management platform it provides a front end to manage your own cloud infrastructure, using Linux Containers virtualization technology

May 6, 2022
A PHP library to convert text to speech using various services

speaker A PHP library to convert text to speech using various services

Jun 17, 2022
Laradeploy offers you to automate deployment using a GitHub webhook.

Introduction Laradeploy offers you to automate deployment using a GitHub webhook. Simple and fast just make a git push to GitHub deploy the new modifi

Feb 21, 2022
Xenon\LaravelBDSms is a sms gateway package for sending text message to Bangladeshi mobile numbers using several gateways like sslcommerz, greenweb, dianahost,metronet in Laravel framework

Xenon\LaravelBDSms is a sms gateway package for sending text message to Bangladeshi mobile numbers using several gateways for Laravel. You should use

Jun 14, 2022
Rafel is Remote Access Tool Used to Control Victims Using WebPanel With More Advance Features..
 Rafel is Remote Access Tool Used to Control Victims Using WebPanel With More Advance Features..

Rafel is Remote Access Tool Used to Control Victims Using WebPanel With More Advance Features..

Jun 26, 2022
This is a PocketMine plugin that helps staffs track players using commands.
This is a PocketMine plugin that helps staffs track players using commands.

Track This is a PocketMine plugin that helps staffs track players using commands. Features Allows selected staffs to watch players use commands to fac

Jun 23, 2022
Fly50W is a new language which helps you build simple apps using more than 500k lines of code easily.

Fly50W is a new language which helps you build simple apps using more than 500k lines of code easily. Installation

Jun 22, 2022