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'; } );
A few people seem to be having this issue including me. http://stackoverflow.com/questions/4366592/symfony-2-doctrine-2-phpunit-3-5-serialization-of-closure-exception
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.
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: