使用 PHP 序列化维护对象关系

发布于 2024-11-28 02:26:57 字数 1251 浏览 1 评论 0原文

我遇到了一个关于实现 Serialized 接口的对象的非常棘手的问题。让我们举个例子:

class A {
    public $b;
}

class B {
    public $a;
}

$a = new A;
$b = new B;

$a->b = $b;
$b->a = $a;

echo serialize($a); // O:1:"A":1:{s:1:"b";O:1:"B":1:{s:1:"a";r:1;}}
echo serialize($b); // O:1:"B":1:{s:1:"a";O:1:"A":1:{s:1:"b";r:1;}}

$a = unserialize(serialize($a));
var_export($a === $a->b->a); // true

在这个例子中我们可以看到,当使用内置的PHP序列化功能时(无论是否使用__sleep()函数),A之间的交叉引用 & B 被保留 (r:1;)。

但是,如果我们想强制使用 Serialized 接口,就会出现问题:

class A implements Serializable  {
    public $b;

    public function serialize() { return serialize($this->b); }
    public function unserialize($s) { $this->b = unserialize($s); }
}

class B implements Serializable {
    public $a;

    public function serialize() { return serialize($this->a); }
    public function unserialize($s) { $this->a = unserialize($s); }
}

$a = new A;
$b = new B;

$a->b = $b;
$b->a = $a;

echo serialize($a); // infinite loop crashes PHP

因为每个对象序列化都是独立管理的,所以没有全局方法可以查看对象是否已经序列化,从而创建对其的引用。然后在无限循环中调用 serialize() 函数。

对于这个问题有好的解决方法吗?用于 serialize() 函数的模式?

I came across a quite tough problem to solve regarding objects implementing the Serializable interface. Let's take an example:

class A {
    public $b;
}

class B {
    public $a;
}

$a = new A;
$b = new B;

$a->b = $b;
$b->a = $a;

echo serialize($a); // O:1:"A":1:{s:1:"b";O:1:"B":1:{s:1:"a";r:1;}}
echo serialize($b); // O:1:"B":1:{s:1:"a";O:1:"A":1:{s:1:"b";r:1;}}

$a = unserialize(serialize($a));
var_export($a === $a->b->a); // true

We can see in this example, that when using the built-in PHP serialization feature (whether or not we use the __sleep() function), the cross references between A & B are preserved (r:1;).

However, if we want to enforce the use of the Serializable interface, the problem arises:

class A implements Serializable  {
    public $b;

    public function serialize() { return serialize($this->b); }
    public function unserialize($s) { $this->b = unserialize($s); }
}

class B implements Serializable {
    public $a;

    public function serialize() { return serialize($this->a); }
    public function unserialize($s) { $this->a = unserialize($s); }
}

$a = new A;
$b = new B;

$a->b = $b;
$b->a = $a;

echo serialize($a); // infinite loop crashes PHP

Because each object serialization is independently managed, there is no global way to see whether an object has already been serialized, to create a reference to it. The serialize() functions are then called in an infinite loop.

Is there a good workaround for this problem? A pattern to use for the serialize() functions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

黄昏下泛黄的笔记 2024-12-05 02:26:57

这是一个技巧:

function inStack( $cls, $func ) {
    $backTrace = debug_backtrace();
    for( $i = 2; $i < count( $backTrace ); ++$i  ) {
        if( isset( $backTrace[$i][ 'class' ] ) && $backTrace[$i][ 'class' ] == $cls &&
            isset( $backTrace[$i][ 'function' ] ) && $backTrace[$i][ 'function' ] == $func )
            return true;
    }
    return false;
}

class A implements Serializable  {
    public $b;

    public function serialize() {
        if( inStack( 'A', 'serialize' ) ) return '';
        return serialize( $this->b );
    }
    public function unserialize($s) {
        if( $s == '' ) return null;
        $b = unserialize( $s );
        if( $b !== null ) {
            $this->b = $b;
            $this->b->a = $this;
        }
    }
}

class B implements Serializable {
    public $a;

    public function serialize() {
        if( inStack( 'B', 'serialize' ) ) return '';
        return serialize( $this->a );
    }
    public function unserialize($s) {
        if( $s == '' ) return null;
        $a = unserialize( $s );
        if( $a !== null ) {
            $this->a = $a;
            $this->a->b = $this;
        }
    }
}

$a = new A;
$b = new B;

$a->b = $b;
$b->a = $a;

$a = unserialize( serialize( $a ) );
var_dump( $a === $a->b->a ); //true

Here is a trick:

function inStack( $cls, $func ) {
    $backTrace = debug_backtrace();
    for( $i = 2; $i < count( $backTrace ); ++$i  ) {
        if( isset( $backTrace[$i][ 'class' ] ) && $backTrace[$i][ 'class' ] == $cls &&
            isset( $backTrace[$i][ 'function' ] ) && $backTrace[$i][ 'function' ] == $func )
            return true;
    }
    return false;
}

class A implements Serializable  {
    public $b;

    public function serialize() {
        if( inStack( 'A', 'serialize' ) ) return '';
        return serialize( $this->b );
    }
    public function unserialize($s) {
        if( $s == '' ) return null;
        $b = unserialize( $s );
        if( $b !== null ) {
            $this->b = $b;
            $this->b->a = $this;
        }
    }
}

class B implements Serializable {
    public $a;

    public function serialize() {
        if( inStack( 'B', 'serialize' ) ) return '';
        return serialize( $this->a );
    }
    public function unserialize($s) {
        if( $s == '' ) return null;
        $a = unserialize( $s );
        if( $a !== null ) {
            $this->a = $a;
            $this->a->b = $this;
        }
    }
}

$a = new A;
$b = new B;

$a->b = $b;
$b->a = $a;

$a = unserialize( serialize( $a ) );
var_dump( $a === $a->b->a ); //true
空城之時有危險 2024-12-05 02:26:57

Doctrine 项目在其关于 序列化实体

可序列化不适用于任何潜在的循环对象
参考文献(至少我们还没有找到方法,如果你找到了,请
联系我们)。

所以,据我所知,目前这个问题还没有通用的解决方案。

This problem has been named cyclic object references by the Doctrine Project in their documentation on Serializing entities:

Serializable does not work well with any potential cyclic object
references (at least we did not find a way yet, if you did, please
contact us).

So, as far as I know, there is currently no generic solution to this problem.

狼亦尘 2024-12-05 02:26:57

标准 PHP 序列化提供两种引用,它们仅以顺序方式针对单个序列化调用起作用:递归和递归引用。

当您实现 Serialized 接口时,只要您提供一个存储,就可以将其扩展到多个对象和序列化步骤。例如,当内存中的每个对象都有一个标识符时,中央序列化服务可以在序列化时填充这些标识符,并在反序列化时以不同的顺序解包。

该服务可以负责决定从存储中获取具有多个引用的对象 - 或者如果已经反序列化 - 运行时对象。由于服务是中心的,因此它可以轻松决定,并且 Serialized 方法可以只使用该服务。

这也允许循环引用。但是,由于映射的原因,您需要自己实现这一点。

从字面上看,学说项目实际上已经有了这个。它的对象序列化形式是数据库本身。只是为了打开一点视野,这肯定不是您想要的。

Standard PHP serialization offers two kind of references which work only per the single serialization call in a sequential manner: recursion and recursion ref.

When you implement the Serializable interface, you can potentially stretch this across multiple objects and serialization steps as long as you offer one store. When each object in memory for example has an identifier, a central serilaization service can be stuffed with these on serialization and unpacked in even different order on unserialization.

The service can take care of deciding to fetch an object with multiple references to it from the store - or if already unserialized - the runtime object. As the service is central, it can easily decide and the Serializable methods can just consume this service.

This does also allow cyclic references. However, you need to implement this on your own because of the mapping.

Taken by the word, the doctrine project actually has this already. Its form of object serialization is the database itself. Just to open the view a little, sure this is not what you look for.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文