46

What is an elegant way to remove an object from an array of objects in PHP?

class Data{

  private $arrObservers;

  public add(Observer $o) {  
    array_push($this->arrObservers, $o);  
  }    
  public remove(Observer $o) {  
    // I NEED THIS CODE to remove $o from $this->arrObservers
  }  
  
}
0

12 Answers 12

64

You can do

function unsetValue(array $array, $value, $strict = TRUE)
{
    if(($key = array_search($value, $array, $strict)) !== FALSE) {
        unset($array[$key]);
    }
    return $array;
}

You can also use spl_object_hash to create a hash for the objects and use that as array key.

However, PHP also has a native Data Structure for Object collections with SplObjectStorage:

$a = new StdClass; $a->id = 1;
$b = new StdClass; $b->id = 2;
$c = new StdClass; $c->id = 3;

$storage = new SplObjectStorage;
$storage->attach($a);
$storage->attach($b);
$storage->attach($c);
echo $storage->count(); // 3

// trying to attach same object again
$storage->attach($c);
echo $storage->count(); // still 3

var_dump( $storage->contains($b) ); // TRUE
$storage->detach($b);
var_dump( $storage->contains($b) ); // FALSE

SplObjectStorage is Traversable, so you can foreach over it as well.

On a sidenote, PHP also has native interfaces for Subject and Observer.

Sign up to request clarification or add additional context in comments.

2 Comments

+1 that's the best approach. Not to mention that as of PHP 5.3, SplObjectStorage implements ArrayAccess.
Given the nature of the question, think if search_array() returns false, the best thing to do would be to throw new OutOfBoundsException("No matching Observer found attached to the subject."). When considered carefully, it would be an exceptional situation for an attached Observer to no longer exist! :-)
22

I agree with the answers above, but for the sake of completeness (where you may not have unique IDs to use as a key) my preferred methods of removing values from an array are as follows:

/**
 * Remove each instance of a value within an array
 * @param array $array
 * @param mixed $value
 * @return array
 */
function array_remove(&$array, $value)
{
    return array_filter($array, function($a) use($value) {
        return $a !== $value;
    });
}

/**
 * Remove each instance of an object within an array (matched on a given property, $prop)
 * @param array $array
 * @param mixed $value
 * @param string $prop
 * @return array
 */
function array_remove_object(&$array, $value, $prop)
{
    return array_filter($array, function($a) use($value, $prop) {
        return $a->$prop !== $value;
    });
}

Which are used in the following way:

$values = array(
    1, 2, 5, 3, 5, 6, 7, 1, 2, 4, 5, 6, 6, 8, 8,
);
print_r(array_remove($values, 6));

class Obj {
    public $id;
    public function __construct($id) {
        $this->id = $id;
    }
}
$objects = array(
    new Obj(1), new Obj(2), new Obj(4), new Obj(3), new Obj(6), new Obj(4), new Obj(3), new Obj(1), new Obj(5),
);
print_r(array_remove_object($objects, 1, 'id'));

Comments

8

I recommend using the ID (if you have one, anything that will be unique to that object should work within reason) of the object as the array key. This way you can address the object within the array without having to run through a loop or store the ID in another location. The code would look something like this:

$obj_array[$obj1->getId()] = $obj1;
$obj_array[$obj2->getId()] = $obj2;
$obj_array[$obj3->getId()] = $obj3;

unset($obj_array[$object_id]);

UPDATE:

class Data{

  private $arrObservers;

  public add(Observer $o) {  
    $this->arrObservers[$o->getId()] = $o;  
  }    
  public remove(Observer $o) {  
    unset($this->arrObservers[$o->getId()]);
  }  

}

1 Comment

You will need a getId() method for the Observer object, though.
5

unset($myArray[$index]); where $index is the index of the element you want to remove. If you wan't a more specific answer, show some code or describe what you're trying to do.

2 Comments

This is ok ,but How do I find the index of an object? Search by values in the array ?
@daniphp You can compar objects with ===.
4
$obj_array['obj1'] = $obj1;
$obj_array['obj2'] = $obj2;
$obj_array['obj3'] = $obj3;
unset($obj_array['obj3']);

1 Comment

-1 because OP gives object to the function, not the key and hence this example is incomplete.
1

For remove an object from a multi dimensional array you can use this:

$exampleArray= [
    [
      "myKey"=>"This is my key",
      "myValue"=>"10"
    ],
    [
      "myKey"=>"Oh!",
      "myValue"=>"11"
    ]
];

With array_column you can specify your key column name:

if(($key = array_search("Oh!", array_column($exampleArray, 'myKey'))) !== false) {
    unset($exampleArray[$key]);
}

And this will remove the indicated object.

Comments

0

Use this for your internal object storage instead: https://www.php.net/manual/en/class.splobjectstorage.php

Comments

0
 function obj_array_clean ($array, $objId)
 {
    $new = array() ;
    foreach($array as $value)
    {
        $new[$value->{$objId}] = $value;
    }
    $array = array_values($new);
    return $array;
 }

 $ext2 = obj_array_clean($ext, 'OnjId');
  • It will remove the duplicate object "OnjId" from array objects $array.

Comments

0

If you want to remove one or more objects from array of objects (using spl_object_hash to determine if objects are the same) you can use this method:

$this->arrObservers = Arr::diffObjects($this->arrObservers, [$o]);

from this library.

Comments

0

Reading the Observer pattern part of the GoF book? Here's a solution that will eliminate the need to do expensive searching to find the index of the object that you want to remove.

public function addObserver(string $aspect, string $viewIndex, Observer $view)
{   
    $this->observers[$aspect][$viewIndex] = $view;
}

public function removeObserver(string $aspect, string $viewIndex)
{
    if (!isset($this->observers[$aspect])) {
        throw new OutOfBoundsException("No such aspect ({$aspect}) of this Model exists: " . __CLASS__);
    }
    
    if (!isset($this->observers[$aspect][$viewIndex])) {
        throw new OutOfBoundsException("No such View for ({$viewIndex}) was added to the aspect ({$aspect}) of this Model:" . __CLASS__);
    }

    unset($this->observers[$aspect][$viewIndex]);
}

You can loose the "aspect" dimension if you are not using that way of keeping track of which Views are updated by specific Models.

public function addObserver(string $viewIndex, Observer $view)
{   
    $this->observers[$viewIndex] = $view;
}

public function removeObserver(string $viewIndex)
{   
    if (!isset($this->observers[$viewIndex])) {
        throw new OutOfBoundsException("No such View for ({$viewIndex}) was added to this Model:" . __CLASS__);
    }

    unset($this->observers[$viewIndex]);
}

Summary

Build in a way to find the element before assigning the object to the array. Otherwise, you will have to discover the index of the object element first.

If you have a large number of object elements (or, even more than a handful), then you may need to resort to finding the index of the object first. The PHP function array_search() is one way to start with a value, and get the index/key in return.

https://www.php.net/manual/en/function.array-search.php

Do be sure to use the strict argument when you call the function.

If the third parameter strict is set to true then the array_search() function will search for identical elements in the haystack. This means it will also perform a strict type comparison of the needle in the haystack, and objects must be the same instance.

Comments

-1

Try this, will solve your problem.

class Data{
  private $arrObservers;

  public add(Observer $o) {  
    array_push($this->arrObservers,$o);  
  }    

  public remove($Observer $o) {  
    unset($this->arrObservers[$o]);  
  }  
}

1 Comment

array_push will assign a numeric key, so you wouldnt find $o as key, even if arrays could use objects for keys
-1

I believe this is the best way

$index = array_search($o, $this->arrObservers, true);

unset($this->arrObservers[$index]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.