• Stars
    star
    1,719
  • Rank 27,061 (Top 0.6 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 14 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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.

More Repositories

1

mu

The 3-LOC ยต (mu) PHP Micro-framework. Just cuz.
PHP
280
star
2

xstatic

Static Proxies (like Laravel "Facades") in any PHP project
PHP
104
star
3

php-design-patterns

Design patterns implemented using traits in PHP 5.4
PHP
101
star
4

iter8

PHP library for iterable/generator transformations and operations
PHP
49
star
5

FunctionParser

Parses PHP functions/methods/closures to get the code
PHP
49
star
6

JeoPHPardy

A Jeopardy-like game and score board for hosting PHP trivia games.
PHP
45
star
7

slack-block-kit

DEPRECATED: Use https://github.com/slack-php/slack-php-block-kit instead
PHP
30
star
8

php-func-mocker

[DEPRECATED] Overwrite/mock PHP functions from the global namespace used in another namespace for the purposes of testing.
PHP
19
star
9

FunnyFacesL4ExampleApp

Example Laravel application using the AWS SDK for PHP
PHP
18
star
10

microlib-config

A small, PHP, configuration library consisting mostly of functions.
PHP
15
star
11

tweetmvc-core

TweetMVC [DEPRECATED] - See jeremeamia/mu
PHP
13
star
12

kohana-less

A Ko3 module for easy inclusion and use of LESS
PHP
11
star
13

sunshinephp-guzzle-examples

Example code for my SunshinePHP Guzzle Tutorial
PHP
10
star
14

phacture

Building PHP objects since 2013.
PHP
10
star
15

recursed

Using recursion to look for recursion in PHP code.
PHP
9
star
16

Add-Another

A jQuery plugin that grants the ability for duplicating HTML content. This is useful for constructing repeated form elements like a multiple file uploader.
PHP
9
star
17

guzzle-swagger-lite

A "lite" Guzzle-based Swagger client that works with JSON services.
PHP
8
star
18

functions.php

Collection of interesting PHP functions
PHP
5
star
19

DesertCodeCampPHPMVC

A sample project for the PHP MVC presentation at the April 2011 Desert Code Camp
PHP
3
star
20

TraitsLoggerDemo

TraitsLogger
PHP
3
star
21

ASU-CS-Sample-MovieLibrary

Sample Project showing Unit Testing for an ASU CSE class
PHP
3
star
22

Blog-Demo-for-CSE-294

This was built from scratch as a sample project for the ASU CSE 294 class
PHP
2
star
23

acclimate-api

[Deprecated] Please use https://github.com/jeremeamia/acclimate-container
PHP
2
star
24

Presentation-for-CSE-294

This project contains my powerpoint and code samples for my presentation
PHP
1
star
25

azphp-s3-upload-demo

Demo code for presentation about uploading to S3 from PHP at azPHP
PHP
1
star