【问题标题】:Use of PHP Magic Methods __sleep and __wakeupPHP 魔术方法 __sleep 和 __wakeup 的使用
【发布时间】:2012-07-24 11:56:28
【问题描述】:

PHP 中的__sleep__wakeup 魔术方法有什么用?我阅读了 PHP 文档,但仍然不清楚:

class sleepWakeup {

    public function __construct() {
        // constructor //
    }

    public function __sleep() {
        echo 'Time to sleep.';
    }

    public function __wakeup() {
        echo 'Time to wakeup.';
    }

}

$ob = new sleepWakeup();

// call __sleep method
echo $ob->__sleep();

echo "\n";

// call __wakeup method
echo $ob->__wakeup();

此示例代码打印:

Time to sleep.
Time to wakeup.

如果我将 __sleep__wakeup 重命名为 foobar,那么它会做同样的事情。这两种方法的正确用法是什么?

【问题讨论】:

  • 你应该改用Serializable

标签: php oop magic-methods


【解决方案1】:

如前所述,__sleep() 在您 serialize() 一个对象时被调用,__wakeup() 在您 unserialize() 它之后被调用。

序列化用于持久化对象:您将获得一个对象表示为字符串,然后可以将其存储在$_SESSION、数据库、cookies 或您想要的任何其他地方。

资源价值

但是,serialize()不能序列化(即转换为文本表示形式)resource type 的值。这就是为什么在unserialize()ing 之后所有这些值都会丢失。

对象图

或成员,以及成员的成员和……无限

另外一点,也许更重要的一点是,serialize() 将遍历$obj 的整个对象图,如果你对其进行序列化。这在您需要时非常有用,但如果您只需要对象的一部分,并且某些链接对象是“运行时特定的”并且在许多对象之间共享,而且还由其他对象共享,您可能不希望这种行为。

PHP 正确处理循环图!含义:如果 $a 的(成员)链接到 $b,并且 $b 链接到 $a 的处理正确,但无论层级有多深。

示例 - 会话特定(共享)对象

例如,$database 对象被 $obj->db 引用,但也被其他对象引用。您将希望 $obj->db 成为相同的对象 - 在 unserialize()ing 之后 - 您的下一个会话中的所有其他对象都具有,而不是数据库对象的孤立实例。

在这种情况下,您将拥有__sleep() 方法,例如:

/**
/* DB instance will be replaced with the one from the current session once unserialized()
 */
public function __sleep() {
    unset($this->db);
}

然后像这样恢复它:

public function __wakeup() {
    $this->db = <acquire this session's db object>
}

另一种可能性是,对象是需要注册的某些(全局)数据结构的一部分。您当然可以手动执行此操作:

$obj = unserialize($serialized_obj);
Thing::register($obj);

但是,如果它是需要在该注册表中的对象契约的一部分,那么将这个神奇的调用留给对象的用户并不是一个好主意。理想的解决方案是,如果对象关心它的职责,即在Thing 中注册。这就是 __wakeup() 允许您对您的客户透明地做的事情(即他不再需要担心这种神奇的依赖)。

同样,如果合适,您可以使用__sleep() 来“取消注册”对象。 (对象在序列化时不会被销毁,但在您的上下文中可能有意义。)

关闭

最后但同样重要的是,闭包也支持序列化。这意味着您必须在 __wakeup() 中重新创建所有附加的闭包。

【讨论】:

  • 我不认为__sleep() 可以作为您的第一个代码示例。 doc 说,它“应该返回一个数组,其中包含应该序列化的对象的所有变量的名称”。
【解决方案2】:

它们很像钩子函数,我们可以根据需要使用它们。我想出了这个简单的实时示例。现在尝试在两种情况下执行此代码:

class demoSleepWakeup {
    public $resourceM;
    public $arrayM;

    public function __construct() {
        $this->resourceM = fopen("demo.txt", "w");
        $this->arrayM = array(1, 2, 3, 4); // Enter code here
    }

    public function __sleep() {
        return array('arrayM');
    }

    public function __wakeup() {
        $this->resourceM = fopen("demo.txt", "w");
    }
}

$obj = new demoSleepWakeup();
$serializedStr = serialize($obj);
var_dump($obj);
var_dump($serializedStr);
var_dump(unserialize($serializedStr));

场景 1:

首先通过注释__sleep()__wakeup() 方法,检查输出。反序列化时会发现资源丢失。

场景 2:

现在尝试运行它,取消注释它们,你会发现第一个和最后一个 var_dump 转储的对象是相同的。

【讨论】:

    【解决方案3】:

    在对象上调用 serialize() 和 unserialize() 时使用这些方法,以确保您有一个钩子来删除一些属性,如数据库连接并在加载时将它们设置回来。将对象存储在会话中时会发生这种情况。

    【讨论】:

      【解决方案4】:

      从 PHP 7.4 开始,将会有新的方法 __serialize() 和 __unserialize() 可用,它们应该会稍微改变 __sleep 和 __wakeup 魔术方法的使用。

      PHP目前提供了两种自定义序列化的机制 对象:__sleep()/__wakeup() 魔术方法,以及 可序列化的接口。不幸的是,这两种方法都有问题 这将在下面讨论。该 RFC 建议添加一个 避免这些问题的新的自定义序列化机制。

      PHP RFC 手册https://wiki.php.net/rfc/custom_object_serialization 中的更多内容。

      // Returns array containing all the necessary state of the object.
      public function __serialize(): array;
      
      // Restores the object state from the given data array.
      public function __unserialize(array $data): void;
      

      用法与 Serializable 接口非常相似。从实际的角度来看,主要区别在于,不是在 Serializable::serialize() 中调用 serialize(),而是直接将应该序列化为数组的数据返回。

      以下示例说明了如何使用 __serialize()/__unserialize(),以及它们在继承下如何组合:

      class A {
          private $prop_a;
          public function __serialize(): array {
              return ["prop_a" => $this->prop_a];
          }
          public function __unserialize(array $data) {
              $this->prop_a = $data["prop_a"];
          }
      }
      class B extends A {
          private $prop_b;
          public function __serialize(): array {
              return [
                  "prop_b" => $this->prop_b,
                  "parent_data" => parent::__serialize(),
              ];
          }
          public function __unserialize(array $data) {
              parent::__unserialize($data["parent_data"]);
              $this->prop_b = $data["prop_b"];
          }
      }
      

      这通过将实际的序列化和反序列化留给序列化程序的实现来解决 Serializable 的问题。这意味着我们不必再共享序列化状态,从而避免与反向引用排序相关的问题。它还允许我们将 __unserialize() 调用延迟到反序列化结束。

      【讨论】:

        【解决方案5】:

        试试这个

        <?php
          $ob = new sleepWakeup();
          $safe_me = serialize($ob);
          $ob = unserialize($safe_me);
        ?>
        

        【讨论】:

          猜你喜欢
          • 2017-03-11
          • 2011-08-29
          • 2011-06-10
          • 2015-09-13
          • 1970-01-01
          • 2015-07-23
          • 2012-04-05
          • 2023-03-14
          • 2010-10-27
          相关资源
          最近更新 更多