PHP Conference Japan 2024

SplObjectStorage 类

(PHP 5 >= 5.1.0, PHP 7, PHP 8)

简介

SplObjectStorage 类提供了一个从对象到数据或(忽略数据时)一个对象集的映射。这种双重用途在许多涉及需要唯一标识对象的场景中非常有用。

类概要

class SplObjectStorage implements Countable, SeekableIterator, Serializable, ArrayAccess {
/* 方法 */
public addAll(SplObjectStorage $storage): int
public attach(object $object, mixed $info = null): void
public contains(object $object): bool
public count(int $mode = COUNT_NORMAL): int
public current(): object
public detach(object $object): void
public getHash(object $object): string
public getInfo(): mixed
public key(): int
public next(): void
public offsetExists(object $object): bool
public offsetGet(object $object): mixed
public offsetSet(object $object, mixed $info = null): void
public offsetUnset(object $object): void
public removeAll(SplObjectStorage $storage): int
public rewind(): void
public seek(int $offset): void
public serialize(): string
public setInfo(mixed $info): void
public unserialize(string $data): void
public valid(): bool
}

示例

示例 #1 SplObjectStorage 作为集合

<?php
// 作为对象集合
$s = new SplObjectStorage();

$o1 = new stdClass;
$o2 = new stdClass;
$o3 = new stdClass;

$s->attach($o1);
$s->attach($o2);

var_dump($s->contains($o1));
var_dump($s->contains($o2));
var_dump($s->contains($o3));

$s->detach($o2);

var_dump($s->contains($o1));
var_dump($s->contains($o2));
var_dump($s->contains($o3));
?>

以上示例将输出

bool(true)
bool(true)
bool(false)
bool(true)
bool(false)
bool(false)

示例 #2 SplObjectStorage 作为映射

<?php
// 作为对象到数据的映射
$s = new SplObjectStorage();

$o1 = new stdClass;
$o2 = new stdClass;
$o3 = new stdClass;

$s[$o1] = "对象 1 的数据";
$s[$o2] = array(1,2,3);

if (isset(
$s[$o2])) {
var_dump($s[$o2]);
}
?>

以上示例将输出

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}

变更日志

版本 描述
8.4.0 实现 SeekableIterator,之前只实现了 Iterator

目录

添加注释

用户贡献的注释 11 条注释

Adam Monsen
10 年前
请注意 SplObjectStorage 中一些不一致/令人惊讶的行为以保持向后兼容性。您无法正确地使用带有键/值语法的 foreach。

<?php
$spl
= new SplObjectStorage ();
$keyForA = new StdClass();
$keyForB = new StdClass();
$spl[$keyForA] = 'value a';
$spl[$keyForB] = 'value b';
foreach (
$spl as $key => $value)
{
// $key 不是对象,$value 是!
// 必须使用标准数组访问来获取字符串。
echo $spl[$value] . "\n"; // 打印 "value a",然后 "value b"
}
// 使用这种形式的 foreach 可能更清晰:
foreach ($spl as $key)
{
// $key 是一个对象。
// 使用标准数组访问来获取值。
echo $spl[$key] . "\n"; // 打印 "value a",然后 "value b"
}
?>

参见 https://bugs.php.net/bug.php?id=49967
rafal at pawlukiewicz dot com
5 年前
SplObjectStorage 类可以很好地用于观察者模式,例如

<?php
class Subject implements \SplSubject
{
private
$observers;

public function
__construct()
{
$this->observers = new \SplObjectStorage;
}

public function
attach(\SplObserver $observer)
{
$this->observers->attach($observer);
}

public function
detach(\SplObserver $observer)
{
$this->observers->detach($observer);
}

public function
notify()
{
foreach (
$this->observers as $observer) {
$observer->update($this);
}
}
}
?>
frame86 at live dot com
8 年前
请记住,foreach() 会在迭代之前复制您的数组,SplObjectStorage 不会。如果您在迭代中有一个子调用,该子调用也再次对对象存储调用 foreach(),则迭代器位置会发生冲突!

为了安全起见,请使用
<?php
foreach(clone $myStorage as $obj) {

}
?>
m dot drewek at smf dot de
9 年前
请注意,SplObjectStorage 在 5.4.0 中引入了一个错误,破坏了覆盖 getHash() 的派生类的克隆实例中的对象查找。

这是一个已确认的错误: https://bugs.php.net/bug.php?id=67582

示例
<?php
class MyObjectStorage extends SplObjectStorage {
// 使用一些(有效的)测试方法覆盖 getHash()
public function getHash($object) { return get_class($object); }
}

class
TestObject {}

$list = new MyObjectStorage(); // 如果使用 "new SplObjectStorage()" 则没有问题
$list->attach(new TestObject());

foreach(
$list as $x) var_dump($list->offsetExists($x)); // TRUE

$list2 = clone $list;
foreach(
$list2 as $x) var_dump($list2->offsetExists($x)); // FALSE
?>
grzeniufication
7 年前
<?php
/**
* 对于简单的用例(您希望将对象保存在映射中)
* 我建议坚持使用普通的旧数组。只需使用对象
* 哈希作为数组键。
*/
$entity1 = new stdClass();
$entity2 = new stdClass();
$entities = [];
$entities[spl_object_hash($entity1)] = $entity1;
$entities[spl_object_hash($entity2)] = $entity2;

// 对象哈希很难区分,因此您可以对其运行哈希函数
// 以提高可读性。
$entities[md5(spl_object_hash($entity1))] = $entity1;
$entities[md5(spl_object_hash($entity2))] = $entity2;

print_r($entities);
kris dot lamote at it-kitchen dot be
9 年前
对于任何在垃圾回收后遇到 SplObjectStorages 包含损坏的成员变量(序列化后出现 FatalErrorException)问题的人:我们使用了以下修复程序,效果很好
<?php

class FixedSplObjectStorage extends SplObjectStorage
{

public function
serialize()
{
$goodPortion = 'N;;m:a:0:{}';
$startKey = 'N;;m:a:';

$serialized = parent::serialize();

$startPos = strpos($serialized, $startKey);

if (
$startPos !== false) {
$serialized = substr_replace($serialized, $goodPortion, $startPos, -1);

}

return
$serialized;

}
}

?>
inwebo at gmail dot fr
12 年前
我需要合并 SplObjectStorages。
<?php
// 作为一个对象集
$SplObjectStorage_1 = new SplObjectStorage();

$object1 = new StdClass;
$object1->attr = 'obj 1';
$object2 = new StdClass;
$object2->attr = 'obj 2';
$object3 = new StdClass;
$object3->attr = 'obj 3';

$SplObjectStorage_1->attach($object1);
$SplObjectStorage_1->attach($object2);
$SplObjectStorage_1->attach($object3);

// 另一个对象集
$SplObjectStorage_2 = new SplObjectStorage();

$object4 = new StdClass;
$object4->attr = 'obj 4';
$object5 = new StdClass;
$object5->attr = 'obj 5';
$object6 = new StdClass;
$object6->attr = 'obj 6';

$SplObjectStorage_2->attach($object4);
$SplObjectStorage_2->attach($object5);
$SplObjectStorage_2->attach($object6);

/**
* 合并 SplObjectStorage
*
* @param 任意数量的 SplObjectStorage 参数
* @return SplObjectStorage
*/
function mergeSplObjectStorage() {

$buffer = new SplObjectStorage();

if(
func_num_args() > 0 ) {
$args = func_get_args();
foreach (
$args as $objectStorage) {
foreach(
$objectStorage as $object) {
if(
is_object( $object ) ) {
$buffer->attach($object);
}
}
}
}
else{
return
FALSE;
}
return
$buffer;
}

$merge = mergeSplObjectStorage($SplObjectStorage_1, $SplObjectStorage_2);

?>
<?php
echo $merge->count();
?>
将会输出
6

<?php
$merge
->rewind();
while(
$merge->valid()) {
$object = $merge->current();
var_dump($object);
$merge->next();
}
?>
将会输出
object(stdClass)#2 (1) {
["attr"]=>
string(5) "obj 1"
}
object(stdClass)#3 (1) {
["attr"]=>
string(5) "obj 2"
}
object(stdClass)#4 (1) {
["attr"]=>
string(5) "obj 3"
}
object(stdClass)#6 (1) {
["attr"]=>
string(5) "obj 4"
}
object(stdClass)#7 (1) {
["attr"]=>
string(5) "obj 5"
}
object(stdClass)#8 (1) {
["attr"]=>
string(5) "obj 6"
}

我的两分钱。
Marius
10 年前
在存储中遍历项目时,不要使用 SplObjectStorage::detach,因为这会跳过第二个(并且仅第二个)元素。

示例

<?php

class A {
public
$i;
public function
__construct($i) {
$this->i = $i;
}
}

$container = new \SplObjectStorage();

$container->attach(new A(1));
$container->attach(new A(2));
$container->attach(new A(3));
$container->attach(new A(4));
$container->attach(new A(5));

foreach (
$container as $item) {
echo
$item->i . "\n";
$container->detach($item);
}
echo
"== 存储中剩余的 ==\n";
foreach (
$container as $item) {
echo
$item->i . "\n";
}
/* 输出:
1
3
4
5
== 存储中剩余的 ==
2
*/
?>
jan at odvarko dot cz
3 年前
如果您在 SplObjectStorage 中将一个 array() 分配给一个对象,然后尝试修改其各个元素,您可能会发现它不起作用。
相反,您可以使用 ArrayObject(),它将模拟数组行为。

<?php

$storage
= new SplObjectStorage();

$obj1 = new StdClass();
$obj2 = new StdClass();

$storage[$obj1] = array();
$storage[$obj2] = new ArrayObject();

$storage[$obj1]['person'] = 'Jana'; // 无法正常工作 (PHP Notice: Indirect modification of overloaded element of SplObjectStorage has no effect)
$storage[$obj2]['person'] = 'Jana'; // 可以正常工作

var_dump($storage[$obj1]['person']); // NULL (PHP Notice: Undefined index: person)
var_dump($storage[$obj2]['person']); // string(4) "Jana"

?>
divinity76 at gmail dot com
7 年前
如果您正在寻找 ResourceStorage,请查看 https://gist.github.com/divinity76/b8041e073b74bdeab562a075fc94217f

(我需要它用于 socket_select() 的套接字编程)
Jan Walther
13 年前
我重写了一些脚本,并将对象存储与数组更改为 SplObjectStorage。在某些时候,我需要 array_rand() 的支持,但我没有找到返回 SplObjectStorage 对象的随机附加对象的函数。

所以这是我用于随机访问 SplObjectStorage 的解决方案

<?php
$o1
= new StdClass;
$o2 = new StdClass;
$s = new SplObjectStorage;
$s->attach($o1);
$s->attach($o2);

$random = rand(0,$s->count()-1);
$s->rewind();
for(
$i=0;$i<$random;$i++) {
$s->next();
}
var_dump($s->current());
?>
To Top