例外:不允许序列化“闭包”

Posted

技术标签:

【中文标题】例外:不允许序列化“闭包”【英文标题】:Exception: Serialization of 'Closure' is not allowed 【发布时间】:2012-11-23 22:15:50 【问题描述】:

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

所以这个方法会在我们的应用程序中为 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/ 目录中的文件夹中。

【问题讨论】:

【参考方案1】:

显然匿名函数不能被序列化。

例子

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

从你的代码中你正在使用闭包:

$callback = function () // <---------------------- Issue

    return 'ZendMail_' . microtime(true) . '.tmp';
;

解决方案一:替换成普通函数

例子

function emailCallback() 
    return 'ZendMail_' . microtime(true) . '.tmp';

$callback = "emailCallback" ;

方案二:数组变量间接调用方法

如果你看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");

【讨论】:

谢谢。愚蠢的是这些不能被序列化。 我确定他们正在开发一个补丁 .. 这是一个解决方法概念 htmlist.com/development/… 或者只是使用对象 .. 会随着概念更新 @KyleAdams 不能序列化它们是完全有道理的,因为你不能序列化代码(闭包),只能序列化值。声明为function() /* do stuff */ 的闭包在内部转换为Closure 类的实例,这在许多方面是标准对象,函数体中的代码成为内部“方法”。就像任何其他对象一样,当您序列化它时,唯一会在属性中序列化的东西,这在闭包的上下文中没有意义。因此,不允许序列化。 还要考虑如果你的闭包被声明为function() use(&amp;$something) ,并引用来自父作用域的变量会发生什么。这实际上是不可能序列化的,因为当它被反序列化时,引用的变量将不再存在。【参考方案2】:

PHP 不允许直接闭包序列化。但是你可以使用像 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);

【讨论】:

还有github.com/opis/closure不使用eval()【参考方案3】:

如前所述:开箱即用的闭包无法序列化。

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

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

【讨论】:

或者使用__sleep()__wakeup()可以排除一些属性,包含闭包。 那篇文章的作者创建了一个库以允许序列化,github.com/jeremeamia/super_closure,在自述文件中,他指出了一个更新的项目,github.com/opis/closure,它允许序列化而不需要使用 eval()。 ? ——来吧,再敲两下就行了。【参考方案4】:

你必须禁用全局

 /**
 * @backupGlobals disabled
 */

【讨论】:

【参考方案5】:

它是实验性的,不安全它有几个风险,比如必须使用可以禁用的 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;

【讨论】:

以上是关于例外:不允许序列化“闭包”的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 无法为序列化准备路由 [api/user]。使用闭包

Laravel Mail::queue 错误:闭包序列化失败,即使是最基本的用法

Spark闭包与序列化

Spark闭包与序列化

无法为序列化准备路由 [api/user]。使用闭包

Laravel - 错误“无法为序列化准备路由 [/]。使用闭包。”