11

I would like to get all the instances of an object of a certain class.

For example:

class Foo {
}

$a = new Foo();
$b = new Foo();

$instances = get_instances_of_class('Foo');

$instances should be either array($a, $b) or array($b, $a) (order does not matter).

A plus is if the function would return instances which have a superclass of the requested class, though this isn't necessary.

One method I can think of is using a static class member variable which holds an array of instances. In the class's constructor and destructor, I would add or remove $this from the array. This is rather troublesome and error-prone if I have to do it on many classes.

hakre
  • 193,403
  • 52
  • 435
  • 836
strager
  • 88,763
  • 26
  • 134
  • 176
  • 2
    Why do you feel that you need this? – troelskn Jan 24 '09 at 12:25
  • 2
    @troelskn, I need this because I am making an event system and need to be able to sent events to all objects of a certain class (a global notification, if you will, which is dynamically bound). – strager Jan 24 '09 at 19:39

4 Answers4

17

If you derive all your objects from a TrackableObject class, this class could be set up to handle such things (just be sure you call parent::__construct() and parent::__destruct() when overloading those in subclasses.

class TrackableObject
{
    protected static $_instances = array();

    public function __construct()
    {
        self::$_instances[] = $this;
    }

    public function __destruct()
    {
        unset(self::$_instances[array_search($this, self::$_instances, true)]);
    }

    /**
     * @param $includeSubclasses Optionally include subclasses in returned set
     * @returns array array of objects
     */
    public static function getInstances($includeSubclasses = false)
    {
        $return = array();
        foreach(self::$_instances as $instance) {
            if ($instance instanceof get_class($this)) {
                if ($includeSubclasses || (get_class($instance) === get_class($this)) {
                    $return[] = $instance;
                }
            }
        }
        return $return;
    }
}

The major issue with this is that no object would be automatically picked up by garbage collection (as a reference to it still exists within TrackableObject::$_instances), so __destruct() would need to be called manually to destroy said object. (Circular Reference Garbage Collection was added in PHP 5.3 and may present additional garbage collection opportunities)

Kenzal Hunter
  • 816
  • 2
  • 7
  • 11
6

Here's a possible solution:

function get_instances_of_class($class) {
    $instances = array();

    foreach ($GLOBALS as $value) {
        if (is_a($value, $class) || is_subclass_of($value, $class)) {
            array_push($instances, $value);
        }
    }

    return $instances;
}

Edit: Updated the code to check if the $class is a superclass.

Edit 2: Made a slightly messier recursive function that checks each object's variables instead of just the top-level objects:

function get_instances_of_class($class, $vars=null) {
    if ($vars == null) {
        $vars = $GLOBALS;
    }

    $instances = array();

    foreach ($vars as $value) {
        if (is_a($value, $class)) {
            array_push($instances, $value);
        }

        $object_vars = get_object_vars($value);
        if ($object_vars) {
            $instances = array_merge($instances, get_instances_of_class($class, $object_vars));
        }
    }

    return $instances;
}

I'm not sure if it can go into infinite recursion with certain objects, so beware...

Paige Ruten
  • 172,675
  • 36
  • 177
  • 197
  • This doesn't work as well as I would like. Most of my instances are members of some other classes. – strager Jan 24 '09 at 06:26
  • I've updated my answer with a recursive function that looks for instances in all the members of all objects (and all the members of those members, ...). – Paige Ruten Jan 24 '09 at 06:55
  • There's another problem: temporary variables. I don't think you can cover all corners with $GLOBALS, which would introduce unexpected bugs most likely. I'm sure your method would be useful for someone else, though, with less specific needs. – strager Jan 24 '09 at 07:20
  • `get_object_vars` will only return the public members. you would have to use Reflection to find instances in non-public members. – Gordon Feb 16 '12 at 20:50
2

As far as I know, the PHP runtime does not expose the underlying object space, so it would not be possible to query it for instances of an object.

DForck42
  • 19,789
  • 13
  • 59
  • 84
Cody Caughlan
  • 32,456
  • 5
  • 63
  • 68
2

I need this because I am making an event system and need to be able to sent events to all objects of a certain class (a global notification, if you will, which is dynamically bound).

I would suggest having a separate object where you register objects with (An observer pattern). PHP has built-in support for this, through spl; See: SplObserver and SplSubject.

troelskn
  • 115,121
  • 27
  • 131
  • 155
  • I would like a system more like Qt's signals and slots. The behavior I want in particular is as such: I have ChatRoom classes, which fire an event messageSent when someone sends a message. I would like a MessageFilter class to filter ANY message sent through ANY ChatRoom. – strager Jan 27 '09 at 21:55
  • 1
    So you want a global observer? You can either create a global variable, or you can pass the same instance to all ChatRoom's – troelskn Jan 28 '09 at 15:11