异常:不允许序列化'Closure'

67

所以我不确定我需要向你们展示什么,但如果你们需要更多的代码,请不要犹豫,尽管问我:

这个方法将在我们的应用程序中设置Zend的initMailer:

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;
}

你可以看到内部的闭包。当我运行任何使用这段代码的测试时,我会得到:

Exception: Serialization of 'Closure' is not allowed 

因此,与此“封闭”相关的所有测试均失败。因此,我在这里询问大家应该怎么做。

为了澄清上述内容,我们所做的一切就是说,我们发送的任何电子邮件都希望将有关该电子邮件的信息存储在位于/tmp/mail/目录中的文件夹中。

5个回答

43

显然,匿名函数无法被序列化。

示例

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

根据你的代码,你正在使用Closure:

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

解决方案1:替换为普通函数

示例

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

解决方案2:通过数组变量间接调用方法

如果你查看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     }
您可以使用相同的方法来发送回调。
$callback = array($this,"aMethodInYourClass");

6
谢谢。这些东西不能序列化,真是太愚蠢了。 - TheWebs
1
我相信他们正在开发一个补丁..这里有一个解决方法的概念http://www.htmlist.com/development/extending-php-5-3-closures-with-serialization-and-reflection/或者只需使用对象..会更新概念。 - Baba
14
@KyleAdams 是有道理的,因为代码(闭包)本身无法被序列化,只有值可以序列化。声明为 function() { /* do stuff */ } 的闭包会在内部转换为 Closure 类的一个实例,在很多方面都类似于标准对象,函数体中的代码成为了内部“方法”。就像任何其他对象一样,当你将其序列化时,唯一会被序列化的是属性,而这些属性在闭包的上下文中没有意义。因此,禁止对闭包进行序列化。 - DaveRandom
9
同时考虑一下,如果闭包声明为 function() use(&$something) {},并引用了来自父级作用域的变量,会发生什么情况。这将是完全不可能进行序列化的,因为在反序列化时,被引用的变量将不存在。 - DaveRandom

27

PHP不允许直接序列化闭包(Direct Closure serialisation),但是您可以使用像PHP Super Closure这样强大的类:https://github.com/jeremeamia/super_closure

这个类非常容易使用,而且已经被捆绑在Laravel框架的队列管理器中。

从Github文档中可以看到:

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

$serialized = serialize($helloWorld);

6
还有一个叫做 https://github.com/opis/closure 的东西,它不使用 eval()。 - Francisco Luz

12

如前所述:闭包不能直接序列化。

但是,使用__sleep()__wakeup()魔术方法和反射可以手动使闭包可序列化。有关更多详细信息,请参见extending-php-5-3-closures-with-serialization-and-reflection

这利用了反射和php的eval函数。请注意,这会打开代码注入的可能性,请注意您要序列化的内容。


2
或者使用__sleep()__wakeup(),您可以排除一些包含闭包的属性。 - gorodezkiy
3
那篇文章的作者创建了一个库来允许序列化,https://github.com/jeremeamia/super_closure ,在自述文件中他指向了一个更新的项目, https://github.com/opis/closure ,该项目可以在*不需要使用eval()***的情况下实现序列化。 - Kamafeather
2
你——来吧,只需要再按两次键。 - faintsignal

2

您需要禁用全局变量

 /**
 * @backupGlobals disabled
 */

-1

这是实验性的,不安全,有几个风险,比如必须使用 eval(),而 eval() 可能会被禁用。 最好的方法是编写您的脚本以在 heredoc 中进行序列化 例如:

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

你可以通过序列化或将代码写成'.class.php'的扩展名来更轻松地使用你的代码。这样你就能够轻松调用你的脚本并使其持久化。

https://3v4l.org/jpHm9 更新 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";

返回字符串

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接