Giter Site home page Giter Site logo

super_closure's Introduction

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.

super_closure's People

Contributors

asgrim avatar benmorel avatar dortort avatar drbyte avatar florianv avatar grahamcampbell avatar jeremeamia avatar jrbasso avatar lstrojny avatar mnapoli avatar nikic avatar ntzm avatar remicollet avatar remo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

super_closure's Issues

Serializing Anonymous Classes

It looks like Phil's RFC to add anonymous classes is going to make it into php 7.0. I wonder if it would be possible for super closure to serialize anonymous classes too? I haven't looked into the fine details, but I'm just throwing this out there as a cool idea. Any thoughts?

Please use tagged release instead of "dev-master"

Some developer prefer to use "minimum-stability": "stable" on for ongoing project, however v1.0.0 depends on "nikic/php-parser": "dev-master" which disallow us to pull the latest v4.0.8 update which is just released. Please update to one of the available version https://github.com/nikic/PHP-Parser/releases and tag 1.0.1 to solve this.

  Problem 1
    - jeremeamia/SuperClosure 1.0.0 requires nikic/php-parser dev-master -> no matching package found.
    - laravel/framework v4.0.8 requires jeremeamia/superclosure 1.0.* -> satisfiable by jeremeamia/SuperClosure[1.0.0].
    - Installation request for laravel/framework 4.0.8 -> satisfiable by laravel/framework[v4.0.8].

Reference discussion: laravel/laravel#2094

Will this still work in Laravel 5.7

I'm including code in my queue job that has multiple closures, and it would be drastic/significant work to change all the logic. Is requiring the package in Laravel 5.7 all that is necessary, or is there something more that needs to be done, as I am getting the expected:
Serialization of 'Closure' is not allowed: line 138 of C:\xampp\htdocs\explore\vendor\laravel\framework\src\Illuminate\Queue\Queue.php

Including the code would be beyond the scope of this question, as I am reading in other issues how Laravel has removed super closure from it's dependencies, and I just need to know if this is still a relevant package.

I kinda assume that it is, so my next question after hours of trying to figure out what closure it's balking at is: is there some way to figure out what closure it's complaining about?

Thank you and happy holidays

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

Serialization of 'Closure' is not allowed

Hi, first thank you for this awesome library!

I've a problem serializing this context:

$options['foo'] = function () {
    // do something.
};

$serializable = (new Serializer)->serialize(function () use ($options) {
    // do something.
});

// throws: Serialization of closure failed: Serialization of 'Closure' is not allowed

It could happen in $this context too. Any suggestion?

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

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)

[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?

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. #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.

Improving speed

I was looking at the code and I think a better way to improve the speed by avoiding most reflection

Once you have the code for the closure as a string e.g.

function($foo) use ($bar, $baz) {
    return $foo + $bar;
};

You should rewrite it with preg_replace to:

function($foo) use ($state) {
    return $foo + $state->bar + $state->baz;
};

Then you can recreate a closure object using:

$state = $this->state;
$func = null;
eval('$func = function($foo) use ($state) {
    return $foo + $state->bar + $state->baz;
};);

then you can just call $func() as if it were the original closure rather than using a reflection object. This way, you only need to use reflection when serializing, and not when unserialzing. This would vastly improve speed whenever you're not serializing and unserializing the object in the same request.

a more flexible approach?

I think a better way, using what you already have may be a custom serialize function.

Essentially it would just be a method you could call serialize($obj) and it would automagically replace any closure objects with a super_closure object.

Here's a basic overview of how it might work but I'm not sure if this would prevent any issues:

class Serializer {
    private $serializeObj;

    public function __construct() {
        $self = $this;
        $sthis->serializeObj = function() use ($self) {
            foreach ($this as $key => &$value) {
                if ($value instanceof Closure) $value = $self->replace($value);
                else $self->serialize($value, false); 
            }
        };
    }

    public function serialize($obj, $return = true) {
        if (is_scalar($obj)) return;
        else if (is_array($obj)) {
            foreach ($obj as &$value) {
                if ($value instanceof Closure) $value = $this->replace($value);
                else $this->serialize($value, false); 
            }
        }
        else if (is_object()) {
            $serialize = $this->serializeObj->bindTo($obj, $obj);
            $serialize();
        }

        if ($return) return serialize($obj);

    }


    public function replace(Closure $closure) {
        return new Super_Closure($closure);
    }
}

This iterates over arrays/objects and with the correct replace() implementation would replace any closure instances with a super_closure. I'm using Closure::bindTo to also replace closures that are stored in private properties on objects.

Nested closures not working.

Nested closures not working... First works but the nestes does not.
Inspired on your work I created my own serializable closure, take a look here:
https://github.com/salojc2006/Webos/blob/master/src/Webos/Closure.php

Bellow a test code to understand the issue:

#!/usr/bin/php
<?php
// CHANGE PATH TO AUTOLOAD!
require_once('/home/salomon/dev/php/repos/vendor/autoload.php');
error_reporting(E_ALL);

use SuperClosure\Serializer;
use SuperClosure\SerializableClosure;

class MyTestClass {
	private $onTest = null;
	
	public function __construct() {
		// FIRST CLOSURE WORKS
		$this->onTest(function() {
			echo "first test!\n";
			// HERE IS THE PROBLEM!!
			// NESTED CLOSURE NOT WORKS!
			$this->onTest(function() {
				echo "second test!\n";
			});
		});
	}
	
	public function onTest(callable $callback) {
		echo "adding closure!\n";
		$this->onTest = new SerializableClosure($callback, new Serializer());
		//print_r($callback);
	}
	
	public function test() {
		call_user_func_array($this->onTest, []);
	}
}

$t = new MyTestClass();
$s = serialize($t);
$t2 = unserialize($s);
/// die();
$t2->test();
$t->test();

I hope my little contribution be helpful for you

1.x Is Not A Good Branch Name

1.x means packagist is treating this as 1-dev. Surely you want this to be treated as 1.0-dev, so you need to name the branch 1.0..

Namespace issue

Hello again, I'm unfortunately facing another issue. PHP 5.4.16, Yii Framework 1.1

PHP Error[2]: include(AppCheckInsController.php): failed to open stream: No such file or directory
    in file .../common/vendor/yiisoft/yii/framework/YiiBase.php at line 427
#0 .../common/vendor/yiisoft/yii/framework/YiiBase.php(427): autoload()
#1 unknown(0): autoload()
#2 unknown(0): spl_autoload_call()
#3 .../common/vendor/jeremeamia/SuperClosure/src/SerializableClosure.php(130): Closure->bindTo()
#4 unknown(0): SuperClosure\SerializableClosure->unserialize()
#5 .../common/vendor/jeremeamia/SuperClosure/src/Serializer.php(96): unserialize()
#6 .../common/components/MongoMQMessageWrapper.php(77): SuperClosure\Serializer->unserialize()

The namespace of the class from where the closure has been serialized is something like \backend\controllers\AppCheckInsController - looking how both AstAnalyzer and TokenAnalyzer are storing the binding information, this information is getting lost:

C:32:"SuperClosure\SerializableClosure":945:{a:5:{s:4:"code";s:56:"function () use($check_in) { $check_in->delete(); };";s:7:"context";a:1:{s:8:"check_in";O:10:"AppCheckIn":15:{s:10:"page_title";N;s:19:"neighbourhood_title";N;s:5:"email";N;s:9:"has_image";N;s:19:"CActiveRecord_new";b:0;s:26:"CActiveRecord_attributes";a:11:{s:2:"id";s:4:"1015";s:11:"app_user_id";s:1:"1";s:7:"page_id";N;s:12:"neighbour_id";s:1:"1";s:8:"datetime";s:19:"2015-11-03 13:03:34";s:4:"type";s:0:"";s:11:"description";s:43:"What the food fuck is this extra test hoot?";s:8:"question";s:1:"1";s:5:"image";N;s:5:"thumb";s:0:"";s:7:"deleted";i:1;}s:23:"CActiveRecord_related";a:0:{}s:17:"CActiveRecord_c";N;s:18:"CActiveRecord_pk";s:4:"1015";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:"binding";N;s:5:"scope";s:21:"AppCheckInsController";s:8:"isStatic";b:0;}}

(essentially looking at this part:)

s:7:"binding";N;s:5:"scope";s:21:"AppCheckInsController";s:8:"isStatic";b:0;

Is there anything I'm handling wrong (or maybe the framework), or, in any case, do you think there is a workaround for that issue? Thank you.

PHP5: Cannot bind an instance to a static closure

When serializing anonymous functions, PHP throws an exception:
"Cannot bind an instance to a static closure"

This is a PHP error in the ClosureAnalyzer::isClosureStatic::58 and is caused by this line:

$closure = @$closure->bindTo(new \stdClass);

By removing this line and the following 4. The problem is resolved.

In PHP7, this is not an issue by the way, so a version check could be useful. It's also not possible to catch the exception.

2.0 Is Broken?

image

This results in php saying serialization of a closure is not allowed.

runing composer update on php 7 gives errors with Closure Analyser

I'm on Laravel 5.2. I switched from php56 to php70 because of requirements. After running composer install I got error like below:

composer install -v
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Dependency resolution completed in 0.004 seconds
Analyzed 206 packages to resolve dependencies
Analyzed 572 rules to resolve dependencies
Nothing to install or update
Generating autoload files
> post-install-cmd: Illuminate\Foundation\ComposerScripts::postInstall
> post-install-cmd: php artisan optimize
PHP Fatal error: Cannot declare class SuperClosure\Analyzer\ClosureAnalyzer, because the name is already in use in /var/www/[project_directory]/config/vendor/jeremeamia/SuperClosure/src/Analyzer/ClosureAnalyzer.php on line 5
Script php artisan optimize handling the post-install-cmd event returned with an error

[RuntimeException]
Error Output: PHP Fatal error: Cannot declare class SuperClosure\Analyzer\ClosureAnalyzer, because the name is already in use in /var/www/[project_directory]/config/vendor/jeremeam ia/SuperClosure/src/Analyzer/ClosureAnalyzer.php on line 5

Exception trace:
() at /usr/share/php/Composer/EventDispatcher/EventDispatcher.php:209 Composer\EventDispatcher\EventDispatcher->doDispatch() at /usr/share/php/Composer/EventDispatcher/EventDispatcher.php:94
Composer\EventDispatcher\EventDispatcher->dispatchScript() at /usr/share/php/Composer/Installer.php:337
Composer\Installer->run() at /usr/share/php/Composer/Command/InstallCommand.php:134
Composer\Command\InstallCommand->execute() at /usr/share/php/Symfony/Component/Console/Command/Command.php:256
Symfony\Component\Console\Command\Command->run() at /usr/share/php/Symfony/Component/Console/Application.php:841
Symfony\Component\Console\Application->doRunCommand() at /usr/share/php/Symfony/Component/Console/Application.php:189
Symfony\Component\Console\Application->doRun() at /usr/share/php/Composer/Console/Application.php:166
Composer\Console\Application->doRun() at /usr/share/php/Symfony/Component/Console/Application.php:120
Symfony\Component\Console\Application->run() at /usr/share/php/Composer/Console/Application.php:99
Composer\Console\Application->run() at /usr/bin/composer:44

Short confirmation about php:
php -i phpinfo() PHP Version => 7.0.14-2+deb.sury.org~xenial+1
Any suggestions?

"Undefined Variable Error" thrown for used variables whose value are null

I am curious as to why isset is being used on line 153 of the closure parser where the getUsedVariables method is called, as opposed to array_key_exists. The effect is that used variables whose value are null are not passed through which becomes an issue during the unserialization process, since they can not be found. An undefined variable error is thrown instead.

if (isset($usedVarValues[$name])) {

fails to serialize Anahkiasen/polyglot

I was having a problem with confide which seems to be caused by a combination of super_close and polyglot. Original report can be found here: Zizaco/confide#522 (comment)

I can easily reproduce this if I install https://github.com/Anahkiasen/polyglot and then run this code:

$lang = App::make('translator');
$closure = new \Illuminate\Support\SerializableClosure(function () use ($lang) { });
serialize($closure);

Which will show me Serialization of 'Closure' is not allowed

I like all the libraries I'm using, any idea on how to get them working together?

Note

Not really a issue, however I would just like to point out that all POI (Php Object Injection) vulns are much easier to exploit if serialization and unserialization of closures is possible in native PHP.

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

Error when using Zend Guard

Hi,

I'm using Laravel and Zend Guard 7 to encode my project.

But I alway receiver error "Serialization of closure failed: The closure was not found within the abstract syntax tree."

Can you help me fix it.

Thank you.

Failing on HHVM

PHPUnit 5.1.0 by Sebastian Bergmann and contributors.

.................................................F                50 / 50 (100%)

Time: 4.51 seconds, Memory: 16.91Mb

There was 1 failure:

1) SuperClosure\Test\Integ\SerializationTest::testClosuresInContextAreUnboxedBackToClosures
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'Closure'
+'Closure$SuperClosure\Test\Integ\SerializationTest::testClosuresInContextAreUnboxedBackToClosures;1315444220'

/home/travis/build/jeremeamia/super_closure/tests/Integ/SerializationTest.php:194
/home/travis/build/jeremeamia/super_closure/tests/Integ/SerializationTest.php:157

FAILURES!
Tests: 50, Assertions: 112, Failures: 1.

Serialization of closure failed

Get this message when trying to serialize example:
$serializer1 = new SuperClosure\Serializer(null, $yourSecretSigningKey);
$data = $serializer1->serialize(function () {echo "Hello!\n";});

PHP error:  Serialization of closure failed: The file containing the closure, "PATH_LARAVEL/vendor/psy/psysh/src/Psy/ExecutionLoop/Loop.php(76) : eval()'d code" did not exist. in PATH_LARAVEL/vendor/jeremeamia/SuperClosure/src/SerializableClosure.php on line 93

Is it trying to write to file without a permission?

Add more tests

  • Complete tests for ClosureParser.
  • Test serialization of closure with 5.4 keywords like callable.
  • Test serialization of closure that reference classes by name in the parameters or body.
  • Test serialization of a closure that uses another closure.

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:

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.

Ideas - Just thinking out loud

  • Take a look at Opis\Closure
    • Uses stream wrapper to load code instead of eval(), allows for multiple (un)serializations.
    • Compare Token Parser logic
  • See if we can determine if a closure is static with code parsing, instead of with this awful function
  • Next major version
    • See if we can get the token parser's feature up-to-par
    • Make PHP-Parser an optional dependency
    • Perhaps join projects with Opis\Closure

Breaking character in serialized closure

As of the beta of version 2, there is a breaking character in the serialized closure when I store it in the database. It's hard to track, but it broke my insert-query. Because of this the serialized string was partially stored in the db. Weird enough it didn't break my query.

If I look at the serialized closure in the browser, there are a few breaking characters which translate to the html-tag

I could base64 the code, that was a dirty workaround.

A part of the serialized string here:

....."serializer";O:23:"SuperClosure\Serializer":1:{s:33:"�SuperClosure\Serializer�analyzer";O:33:"SuperClosure\Analyzer\AstAnalyzer":....

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]

Congrats

Sorry, didn't know where else to leave the message and I was just about to get going but wanted to leave my comment since I thought it useful for the Readme.

I think one of the most basic and common use cases for your "super closures" is refactoring out switch statements or multiple if/elses. This is easily done with polymorphism, but what about the associative arrays/hashmap solution? which is simpler, faster, easier to read, very common in functional world.

php would just force me to do this:

$socialServiceMap = [
  'twitter' => function ($params) {
    return new TwitterSocialService($params);
  },
  'instagram' => function ($params) {
    return new InstagramSocialService($params);
  }
];

on the same method, so as to not require serialization.

Serialization of 'Generator' is not allowed

I get this error, obviously, while trying to serialize a generator instance. This might be a pretty rare use-case, but is there any support for serializing generators or a way around the issue? My source code is here https://github.com/low-ghost/PhpMultithread (subject to change), but the general idea is I'm using generators for coroutines that check conditions of external processes (allowing a hacked together multithreading). I can pass a serialized closure to be executed in the background, but if that closure itself should spawn a child-process, it would have to support generators.

Let me know if I can be more clear about something and no biggie if this is simply unsupported.

__debugInfo fails after unserialize()

SerializableClosure::serialize() does not account for SerializerInterface $serializer = null, in the constructor. If this parameter isn't provided then the default serializer, which the constructor creates, is not created during SerializableClosure::unserialize().

PHP Fatal error:  Call to a member function getData() on null in /[redacted]/vendor/jeremeamia/SuperClosure/src/SerializableClosure.php on line 164
PHP Stack trace:
PHP   1. {main}() /[redacted]/debug.php:0
PHP   2. print_r() /[redacted]/debug.php:20
PHP   3. SuperClosure\SerializableClosure->__debugInfo() /[redacted]/debug.php:20

Fatal error: Call to a member function getData() on null in /[redacted]/vendor/jeremeamia/SuperClosure/src/SerializableClosure.php on line 164

Call Stack:
    0.0003     230504   1. {main}() /[redacted]/debug.php:0
    0.0308    3387808   2. print_r() /[redacted]/debug.php:20
    0.0308    3388216   3. SuperClosure\SerializableClosure->__debugInfo() /[redacted]/debug.php:20

Resolve magic constants before serialization

Possible sources for data?

  • __LINE__ - From the AST ($node->attribute('startLine'))
  • __FILE__ - From Reflection::getFileName()
  • __DIR__ - From dirname(Reflection::getFileName())
  • __FUNCTION__ - Always {closure}
  • __CLASS__ - ???
  • __TRAIT__ - ???
  • __METHOD__ - ???
  • __NAMESPACE__ - From Reflection::getNamespaceName()

Will need to write another visitor to handle resolving these.

SuperClosure does not load php5 custom extensions

Hi there I am using super_closure in Laravel4 and it looks like super_closure does not know my custom function in php5 installed via extension.

PHP Fatal error: Call to undefined function solve_simplex() in /var/www/my_site/vendor/jeremeamia/SuperClosure/src/Jeremeamia/SuperClosure/SerializableClosure.php(99) : eval()'d code on line 2
PHP Stack trace:
PHP 1. {main}() /var/www/my_site/artisan:0
PHP 2. Symfony\Component\Console\Application->run() /var/www/my_site/artisan:59
PHP 3. Symfony\Component\Console\Application->doRun() /var/www/my_site/vendor/symfony/console/Symfony/Component/Console/Application.php:121
PHP 4. Symfony\Component\Console\Application->doRunCommand() /var/www/my_site/vendor/symfony/console/Symfony/Component/Console/Application.php:191
PHP 5. Illuminate\Console\Command->run() /var/www/my_site/vendor/symfony/console/Symfony/Component/Console/Application.php:887
PHP 6. Symfony\Component\Console\Command\Command->run() /var/www/my_site/vendor/laravel/framework/src/Illuminate/Console/Command.php:96
PHP 7. Illuminate\Console\Command->execute() /var/www/my_site/vendor/symfony/console/Symfony/Component/Console/Command/Command.php:241
PHP 8. Illuminate\Queue\Console\WorkCommand->fire() /var/www/my_site/vendor/laravel/framework/src/Illuminate/Console/Command.php:108
PHP 9. Illuminate\Queue\Worker->pop() /var/www/my_site/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php:64
PHP 10. Illuminate\Queue\Worker->process() /var/www/my_site/vendor/laravel/framework/src/Illuminate/Queue/Worker.php:71
PHP 11. Illuminate\Queue\Jobs\BeanstalkdJob->fire() /var/www/my_site/vendor/laravel/framework/src/Illuminate/Queue/Worker.php:119
PHP 12. Illuminate\Queue\Jobs\Job->resolveAndFire() /var/www/my_site/vendor/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php:50
PHP 13. IlluminateQueueClosure->fire() /var/www/my_site/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php:96
PHP 14. Jeremeamia\SuperClosure\SerializableClosure->__invoke() /var/www/my_site/vendor/laravel/framework/src/Illuminate/Queue/IlluminateQueueClosure.php:16
PHP 15. ReflectionFunction->invokeArgs() /var/www/my_site/vendor/jeremeamia/SuperClosure/src/Jeremeamia/SuperClosure/SerializableClosure.php:64
PHP 16. Jeremeamia\SuperClosure\SerializableClosure::{closure:/var/www/my_site/vendor/jeremeamia/SuperClosure/src/Jeremeamia/SuperClosure/SerializableClosure.php(99) : eval()'d code:1-4}() / var/www/my_site/vendor/jeremeamia/SuperClosure/src/Jeremeamia/SuperClosure/SerializableClosure.php:64
{"error":{"type":"Symfony\Component\Debug\Exception\FatalErrorException","message":"Call to undefined function solve_simplex()","file":"/var/www/my_site/vendor/jeremeamia/ SuperClosure/src/Jeremeamia/SuperClosure/SerializableClosure.php(99) : eval()'d code","line":2}}

if I use function solve_simplex() anywhere else in my code it is working.

In closure function extension_loaded is returning that my extension is not loaded. I do not know why because it is inserted in php.ini on extension= line...
Thanks

email queue exception 'ErrorException' with message 'Undefined variable: event'

I'm trying to do an email queue through an event handler and I'm getting exception 'ErrorException' with message 'Undefined variable: event' in /vendor/jeremeamia/SuperClosure/src/SerializableClosure.php(153) : eval()'d code:2 below is the sample code

\Mail::queue(
            'emails.report_notification',
            [
                'first_name'    => $event->first_name,
                'download_url'  => $event->download_url,
            ],

            function ($message) {
                $message->subject( 'Report' );
                $message->from(
                                '[email protected]',
                                'admin'
                );
                $message->to( $event->email );
            }
        );

"Serialize" to javascript?

I was wondering - since you tokenize the closure and make sense of it in serialized form - is it perhaps possible to "compile" this to javascript closure?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.