SplObjectStorage クラス
(PHP 5 >= 5.1.0, PHP 7, PHP 8)
はじめに
SplObjectStorage クラスは、オブジェクトをデータに対応させたり、 データを渡さずオブジェクトセットとして使用したりします。 これらはどちらも、オブジェクトを一意に特定したい場合に便利です。
クラス概要
/* メソッド */
}例
例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) }
目次
- SplObjectStorage::addAll — 別のストレージからすべてのオブジェクトを追加する
- SplObjectStorage::attach — オブジェクトをストレージに追加する
- SplObjectStorage::contains — ストレージに特定のオブジェクトが含まれるかどうかを調べる
- SplObjectStorage::count — ストレージ内のオブジェクトの数を返す
- SplObjectStorage::current — 現在のストレージの要素を返す
- SplObjectStorage::detach — オブジェクトをストレージから取り除く
- SplObjectStorage::getHash — 中に含むオブジェクトの一意な識別子を算出する
- SplObjectStorage::getInfo — イテレータの現在のエントリに関連づけられたデータを返す
- SplObjectStorage::key — イテレータの現在位置を返す
- SplObjectStorage::next — 次のエントリに移動する
- SplObjectStorage::offsetExists — オブジェクトがストレージ内に存在するかどうかを調べる
- SplObjectStorage::offsetGet — オブジェクトに関連づけられたデータを返す
- SplObjectStorage::offsetSet — ストレージ内のオブジェクトにデータを関連づける
- SplObjectStorage::offsetUnset — ストレージからオブジェクトを取り除く
- SplObjectStorage::removeAll — 別のストレージに含まれているオブジェクトを現在のストレージから取り除く
- SplObjectStorage::removeAllExcept — 別のストレージに含まれているもの以外のすべてのオブジェクトを現在のストレージから取り除く
- SplObjectStorage::rewind — イテレータをストレージの最初の要素に巻き戻す
- SplObjectStorage::seek — 位置を移動する
- SplObjectStorage::serialize — ストレージをシリアライズする
- SplObjectStorage::setInfo — イテレータの現在のエントリに関連づけるデータを設定する
- SplObjectStorage::unserialize — ストレージの文字列表現をアンシリアライズする
- SplObjectStorage::valid — イテレータの現在のエントリが有効かどうかを返す
+add a note
User Contributed Notes 11 notes
Adam Monsen ¶
10 years ago
Note some inconsistent/surprising behavior in SplObjectStorage to preserve backwards compatibility. You can't properly use foreach with key/value syntax.
<?php
$spl = new SplObjectStorage ();
$keyForA = new StdClass();
$keyForB = new StdClass();
$spl[$keyForA] = 'value a';
$spl[$keyForB] = 'value b';
foreach ($spl as $key => $value)
{
// $key is NOT an object, $value is!
// Must use standard array access to get strings.
echo $spl[$value] . "\n"; // prints "value a", then "value b"
}
// it may be clearer to use this form of foreach:
foreach ($spl as $key)
{
// $key is an object.
// Use standard array access to get values.
echo $spl[$key] . "\n"; // prints "value a", then "value b"
}
?>
See https://bugs.php.net/bug.php?id=49967
rafal at pawlukiewicz dot com ¶
5 years ago
SplObjectStorage class can be nicely used in Observer pattern, for example:
<?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 years ago
Keep in mind that foreach() will copy your array before iterating, SplObjectStorage does not. If you have a sub call within an iteration that also calls foreach() on the object storage again, the iterator position collides!
To be safe use:
<?php
foreach(clone $myStorage as $obj) {
}
?>
m dot drewek at smf dot de ¶
9 years ago
Please note that SplObjectStorage has a Bug introduced with 5.4.0, breaking object lookup in cloned instances of derived classes that overwrite getHash().
This is a confirmed Bug: https://bugs.php.net/bug.php?id=67582
Example:
<?php
class MyObjectStorage extends SplObjectStorage {
// Overwrite getHash() with just some (working) test-method
public function getHash($object) { return get_class($object); }
}
class TestObject {}
$list = new MyObjectStorage(); // No issues if using "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 years ago
<?php
/**
* For simple use cases (where you want to keep objects in a map)
* I would suggest to stick to an plain old array. Just use the object
* hash as array key.
*/
$entity1 = new stdClass();
$entity2 = new stdClass();
$entities = [];
$entities[spl_object_hash($entity1)] = $entity1;
$entities[spl_object_hash($entity2)] = $entity2;
// object hashes are hard to distinguish so you could run a hash function
// on them for better readability.
$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 years ago
For anyone having issues with SplObjectStorages containing corrupt member variables after garbage collection (FatalErrorException after serializing): we used following fix to great effect
<?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 years ago
I needed to merge SplObjectStorages.
<?php
// As an object set
$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);
// Another one object set
$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);
/**
* Merge SplObjectStorage
*
* @param how many SplObjectStorage params as you want
* @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();
?>
Will output :
6
<?php
$merge->rewind();
while($merge->valid()) {
$object = $merge->current();
var_dump($object);
$merge->next();
}
?>
Will ouput :
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"
}
My two cents.
Marius ¶
10 years ago
Do not use SplObjectStorage::detach when forach'ing over items in the storage as this skips the second (and only second) element.
Example:
<?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 "== Left in storage ==\n";
foreach ($container as $item) {
echo $item->i . "\n";
}
/* Outputs:
1
3
4
5
== Left in storage ==
2
*/
?>
jan at odvarko dot cz ¶
3 years ago
If you assign an array() to an object in SplObjectStorage and then try to modify its individual elements, you'll probably find it doesn't work.
Instead, you can use ArrayObject(), which will emulate array behaviour.
<?php
$storage = new SplObjectStorage();
$obj1 = new StdClass();
$obj2 = new StdClass();
$storage[$obj1] = array();
$storage[$obj2] = new ArrayObject();
$storage[$obj1]['person'] = 'Jana'; // Won't work (PHP Notice: Indirect modification of overloaded element of SplObjectStorage has no effect)
$storage[$obj2]['person'] = 'Jana'; // Works
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 years ago
if you're looking for a ResourceStorage, check https://gist.github.com/divinity76/b8041e073b74bdeab562a075fc94217f
(i needed it for socket programming with socket_select())