SplObjectStorage 类

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

介绍

SplObjectStorage 类提供了一个从对象到数据或(忽略数据时)对象集的映射。这种双重用途在许多情况下可能很有用,这些情况涉及需要唯一标识对象的需要。

类概要

class SplObjectStorage implements Countable, Iterator, 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 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] = "data for object 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)
}

目录

添加笔记

用户贡献的笔记 12 个笔记

65
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
26
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);
}
}
}
?>
12
frame86 at live dot com
7 年前
请记住,foreach() 会在迭代之前复制您的数组,SplObjectStorage 不会。如果您在迭代中有一个子调用,该子调用也对对象存储再次调用 foreach(),则迭代器位置会冲突!

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

}
?>
10
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
?>
9
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);
4
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;

}
}

?>
5
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"
}

我的两分钱。
4
Marius
9 年前
不要在遍历存储中的项目时使用 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
*/
?>
0
jan at odvarko dot cz
3 年前
如果您将数组() 分配给 SplObjectStorage 中的对象,然后尝试修改其各个元素,您可能会发现它不起作用。
相反,您可以使用 ArrayObject(),它将模拟数组行为。

<?php

$storage
= new SplObjectStorage();

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

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

$storage[$obj1]['person'] = 'Jana'; // 不起作用(PHP 注意:SplObjectStorage 重载元素的间接修改无效)
$storage[$obj2]['person'] = 'Jana'; // 起作用

var_dump($storage[$obj1]['person']); // NULL(PHP 注意:未定义索引:person)
var_dump($storage[$obj2]['person']); // string(4) "Jana"

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

(我需要它来进行 socket 编程,使用 socket_select())
0
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());
?>
-9
randallgirard at hotmail dot com
14 年前
关于 SplObjectStorage,我有两点需要注意:

#1:存储的是对对象本身的引用(不仅仅是用于比较对象的哈希值),并且必须在销毁对象并执行析构函数之前将其移除。

#2:必须调用 SplObjectStorage::rewind() 来初始化迭代器,并且在 SplObjectStorage::current() 返回对象(我认为这是检索对象的唯一方法?)之前调用,而不是像数组那样自动从第一个元素开始。这个假设是基于 SplObjectStorage::current() 在对象包含后,在调用 SplObjectStorage::rewind() 之前返回 NULL。注意,在遍历或获取对象之前,始终使用 REWIND。

<?php

class foo {
public function
__destruct() {
print(
"--- DESTRUCTOR FIRED!!<br />\r\n");
}
}

# 创建对象和存储
$bar = new foo();
$s = new SplObjectStorage();

# 提前重绕作为测试
$s->rewind();

# 附加对象
$s->attach($bar, array('test'));

# 取消设置对象;析构函数不会触发
unset($bar);
print(
"Object has been unset<br />\r\n");

# 首先演示必须调用 REWIND 来初始化迭代器
$obj = $s->current();
var_dump($obj);
print(
"- Note the NULL (from \$s->current())<br />\r\n");

# 初始化,然后分离当前(也是唯一的)对象
$s->rewind();
$s->detach( $s->current() );

# 现在应该执行析构函数

?>

输出

Object has been unset
NULL - Note the NULL (from $s->current())
--- DESTRUCTOR FIRED!!
To Top