67

So I am not sure exactly what I would have to show you guys, how ever if you need more code please do not hesitate to ask:

So this method will set up the initMailer for Zend with in our application:

protected function _initMailer()
{
    if ('testing' !==  APPLICATION_ENV) {
        $this->bootstrap('Config');
        $options = $this->getOptions();
        $mail = new Zend_Application_Resource_Mail($options['mail']);
    }elseif ('testing'  ===  APPLICATION_ENV) {
        //change the mail transport only if dev or test
        if (APPLICATION_ENV <> 'production') {

            $callback = function()
            {
                return 'ZendMail_' . microtime(true) .'.tmp';
            };

            $mail = new Zend_Mail_Transport_File(
                array('path' => '/tmp/mail/',
                        'callback'=>$callback
                )
            );

            Zend_Mail::setDefaultTransport($mail);
        }
    }


    return $mail;
}

You can see the closure that lies with in. When I run any tests that use this code I get:

Exception: Serialization of 'Closure' is not allowed 

and thus all the tests in relation to this "closure" fails. So I am here asking you guys what I should do.

For clarification on the above, all were doing is saying that any email we send out we want to store information about that email in a folder in the /tmp/mail/ directory in a file.

TheWebs
  • 12,470
  • 30
  • 107
  • 211

5 Answers5

43

Apparently anonymous functions cannot be serialized.

Example

$function = function () {
    return "ABC";
};
serialize($function); // would throw error

From your code you are using Closure:

$callback = function () // <---------------------- Issue
{
    return 'ZendMail_' . microtime(true) . '.tmp';
};

Solution 1 : Replace with a normal function

Example

function emailCallback() {
    return 'ZendMail_' . microtime(true) . '.tmp';
}
$callback = "emailCallback" ;

Solution 2 : Indirect method call by array variable

If you look at http://docs.mnkras.com/libraries_23rdparty_2_zend_2_mail_2_transport_2file_8php_source.html

   public function __construct($options = null)
   63     {
   64         if ($options instanceof Zend_Config) {
   65             $options = $options->toArray();
   66         } elseif (!is_array($options)) {
   67             $options = array();
   68         }
   69 
   70         // Making sure we have some defaults to work with
   71         if (!isset($options['path'])) {
   72             $options['path'] = sys_get_temp_dir();
   73         }
   74         if (!isset($options['callback'])) {
   75             $options['callback'] = array($this, 'defaultCallback'); <- here
   76         }
   77 
   78         $this->setOptions($options);
   79     }

You can use the same approach to send the callback

$callback = array($this,"aMethodInYourClass");
bluish
  • 26,356
  • 27
  • 122
  • 180
Baba
  • 94,024
  • 28
  • 166
  • 217
  • 6
    Thank you. Stupid that these cannot be serialized. – TheWebs Dec 05 '12 at 23:25
  • 1
    Am sure they are working on a patch .. here is a workaround concept http://www.htmlist.com/development/extending-php-5-3-closures-with-serialization-and-reflection/ or just use Objects .. would update with concept – Baba Dec 05 '12 at 23:28
  • 14
    @KyleAdams It makes perfect sense that they cannot be serialised, because you cannot serialise code (a closure), only values. A closure declared as `function() { /* do stuff */ }` is converted internally to an instance of the [`Closure`](http://php.net/closure) class, which is in many ways a standard object, the code in the body of the function becomes an internal "method". Just like any other object, when you serialise it the only thing that would get serislised in the properties, which are not meaningful in the context of a closure. Hence, serialisation is disallowed. – DaveRandom Dec 05 '12 at 23:40
  • 9
    Also consider what would happen if you closure was declared as `function() use(&$something) {}`, with a reference to a variable from the parent scope. This would be literally impossible to serialise, because when it was unserialised the referenced variable would not longer exist. – DaveRandom Dec 05 '12 at 23:44
27

Direct Closure serialisation is not allowed by PHP. But you can use powerful class like PHP Super Closure : https://github.com/jeremeamia/super_closure

This class is really simple to use and is bundled into the laravel framework for the queue manager.

From the github documentation :

$helloWorld = new SerializableClosure(function ($name = 'World') use ($greeting) {
    echo "{$greeting}, {$name}!\n";
});

$serialized = serialize($helloWorld);
danronmoon
  • 3,814
  • 5
  • 34
  • 56
Ifnot
  • 4,914
  • 4
  • 33
  • 47
12

As already stated: closures, out of the box, cannot be serialized.

However, using the __sleep(), __wakeup() magic methods and reflection u CAN manually make closures serializable. For more details see extending-php-5-3-closures-with-serialization-and-reflection

This makes use of reflection and the php function eval. Do note this opens up the possibility of CODE injection, so please take notice of WHAT you are serializing.

Andrei Surdu
  • 2,281
  • 3
  • 23
  • 32
ArjanW
  • 443
  • 4
  • 19
  • 2
    Or using `__sleep()` and `__wakeup()` you can exclude some properties, containing closures. – gorodezkiy Sep 22 '17 at 14:42
  • 3
    The author of that article created a library to allow serialization, https://github.com/jeremeamia/super_closure , and in the readme he points to an even newer project, https://github.com/opis/closure , that allows serialization **without the need of using *eval()***. – Kamafeather Apr 25 '18 at 16:26
  • 2
    *you*--come on, it's only two more keystrokes. – faintsignal Jun 28 '19 at 01:09
2

You have to disable Globals

 /**
 * @backupGlobals disabled
 */
Dumindu Perera
  • 1,571
  • 15
  • 13
-1

It is experimental, not secure it has several risks like having to use eval () which can be disabled. the best would be to write your script to serialized in a heredoc example :

$code = <<<CODE
 <?php
class \$gen_class_{$user}{
 ...
}
CODE;

you can more easily use your code by serializing it or by writing it with the extension '.class.php'. you will be able to invoke your script easily and make it persistent.

https://3v4l.org/jpHm9 UPDATE PHP 8

<?php

function closure_to_str($func)
{
    $refl = new \ReflectionFunction($func); // get reflection object
    $path = $refl->getFileName();  // absolute path of php file
    $begn = $refl->getStartLine(); // have to `-1` for array index
    $endn = $refl->getEndLine();
    $dlim = PHP_EOL;
    $list = explode($dlim, file_get_contents($path));         // lines of php-file source
    $list = array_slice($list, ($begn-1), ($endn-($begn-1))); // lines of closure definition
    $last = (count($list)-1); // last line number

    if((substr_count($list[0],'function')>1)|| (substr_count($list[0],'{')>1) || (substr_count($list[$last],'}')>1))
    { throw new \Exception("Too complex context definition in: `$path`. Check lines: $begn & $endn."); }

    $list[0] = ('function'.explode('function',$list[0])[1]);
    $list[$last] = (explode('}',$list[$last])[0].'}');


    return implode($dlim,$list);
}

$test = 10;
$dog2 = function($var=0) use ($test){ 
    $var = 10;
    echo $var . PHP_EOL;
    return $test . $var;
};

echo closure_to_str($dog2)."\n\n";

RETURN STRING

function($var=0) use ($test){ 
    $var = 10;
    echo $var . PHP_EOL;
    return $test . $var;
}
Darksynx
  • 68
  • 5