Phpunit: Serialization of 'Closure' is not allowed

Created on 2 Jan 2012  ·  12Comments  ·  Source: sebastianbergmann/phpunit

Although $GLOBAL Closures are no longer serialised since 01aa34783c9b19d39e568641f3f2dd0fc983b11a & #352 a $GLOBAL array which contain a Closure as a value will still cause the "Serialization of 'Closure' is not allowed" error.

$GLOBALS[] = array( 'foo' => function() { return 'bar'; }  );
typbug

Most helpful comment

I have this same error message whenever I instantiate a new MyRestClient(), the constructor of which contains $this->client = new \GuzzleHttp\Client();. It is also the same when I tried using a $x=function(){}; in the constructor of my class. I guess Guzzle client does something similar internally. This makes it impossible to ever instantiate from within a phpunit test. I'm so sad now :sob:

All 12 comments

Another case I'm getting it

/* test.php */
class closureTest extends PHPUnit_Framework_TestCase
{
    function testClosure() {
        $this->assertEquals(1, 1);
        call_user_func(function($a) {
            throw new \Exception("test");
        }, 10);
        $this->assertEquals(2, 2);
    }
}

and then run it in process isolation

phpunit --process-isolation test.php

you would get

There was 1 error:
1) phTest::testClosure
PHPUnit_Framework_Exception: PHP Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed' in -:37
Stack trace:
#0 -(37): serialize(Array)
#1 -(123): __phpunit_run_isolated_test()
#2 {main}
thrown in - on line 37
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.

I was having the same issue when using process isolation, to work around it I added __sleep to my testCase and cleaned up the closures in there. For me I had stored some mocks in properties of the testcase so it was my own fault really, I hope this helps someone.

I am having the same issue as well, and can replicate the issue @tmilos mentioned earlier. I don't know if this ticket is dead, but it would be nice if we could resolve this. I'm more than willing to put in a pull request if that's preferable.

Pull requests are always appreciated!

I know it's been a bit, but I was off-project for a while. Further to the comment by @tmilos, I can replicate the problem if I force a syntax error, for example:

/* test.php */
class closureTest extends PHPUnit_Framework_TestCase
{
    function testClosure() {
        call_user_func(function($a) {
            throw new \Exception("test");
        });
    }
}

Note the lack of a provided argument at the end of call_user_func. The serialization exception results from the attempt to serialize the PHPUnit_Framework_TestResult object. This serialization attempt can be found on line 42 of PHPUnit/Framework/Process/TestCaseMethod.tpl.dist. The offending part of the to-be-serialized object is a closure reference in the trace hierarchy of the Exception that was thrown:

[1] => Array
    (
        [function] => {closure}
        [class] => Issue451Test
        [type] => ->
        [args] => Array
            (
            )

)

My question then, as this is my first time patching PHPUnit, is short of stripping the closure or modifying it to be otherwise benign - which would involve the use of Reflection no doubt - is there some PHPUnit-best-practice way to deal with this? Any thoughts or input at least?

Also, I wrote a test under Regressions/GitHub. Is this acceptable?

@kunjalpopat, in reference to your issue, The first thing I would check is for exceptions. When in process isolation, thrown exceptions may give you a "Serialization of Closure" message. Try wrapping your test code in a try/catch and see if that fixes the problem, if it does, then you may want to look at trimming down the coverage of the try/catch until you find the problem area.

Problem lies in PHPUnit_Util_GlobalState::backupGlobals:

if ($key != 'GLOBALS' &&
    !in_array($key, $superGlobalArrays) &&
    !in_array($key, $blacklist) &&
    !$GLOBALS[$key] instanceof Closure) {  // <-- this is the problem
    self::$globals['GLOBALS'][$key] = serialize($GLOBALS[$key]);
}

I've made up some helper in PHPUnit_Util_GlobalState:

public static function checkIfThereIsClosureInIt($arr) {
    if ($arr instanceof Closure)
        return true;
    if (is_object($arr))
        $arr = get_object_vars($arr);
    if (is_array($arr))
        foreach ($arr as $x)
            if (PHPUnit_Util_GlobalState::checkIfThereIsClosureInIt($x))
                return true;
    return false;
}

and changed a little backupGlobals function:

foreach (array_keys($GLOBALS) as $key) {
    if ($key != 'GLOBALS' &&
        !in_array($key, $superGlobalArrays) &&
        !in_array($key, $blacklist) &&
        !PHPUnit_Util_GlobalState::checkIfThereIsClosureInIt($GLOBALS[$key])
//        !$GLOBALS[$key] instanceof Closure
    ) {
        self::$globals['GLOBALS'][$key] = serialize($GLOBALS[$key]);
    }
}

this version seems to be working (I don't catch that exception any more).
Since it's a recursion against $GLOBALS, so, it can give some extra heat to cpu.

The above code fix from stafr fixed the PHPunit issues I was having with the "Serialization of Closure" exceptions. Many thanks!

I have this same error message whenever I instantiate a new MyRestClient(), the constructor of which contains $this->client = new \GuzzleHttp\Client();. It is also the same when I tried using a $x=function(){}; in the constructor of my class. I guess Guzzle client does something similar internally. This makes it impossible to ever instantiate from within a phpunit test. I'm so sad now :sob:

Just adding my experience. Basically whenever you put anonymous function or class as test class attribute, phpunit will throw Serialization of 'Closure' is not allowed or Serialization of 'class@anonymous' is not allowed. So, if you need to use anonymous function or class, make sure its one time variable, not test class attribute.

Was this page helpful?
0 / 5 - 0 ratings