Generic collections in PHP

September 17, 2008 – 10:46 am Tags:

Today while reading PHP::Impact’s refactoring guideline post I was reminded about collections.

As you probably know, arrays are very flexible in PHP and probably a good choice for many data storing tasks.

Strictly typed languages usually use “generic” collection classes instead of arrays. They are kind of like PHP arrays which the programmer can tell which type of items to accept. This is of course only natural when you don’t have dynamic typing, but it can also be useful for avoiding programming errors, so I thought I’d try making a basic generic collection class in PHP…

A basic generic collection

The idea was to make a simple class that could be used as a generic collection. You would have to be able to tell it which type it stores, and it would need to make sure that it won’t accept any other types.

By using some features from SPL, we can also make it work more like an array and not a class: implementing Countable, IteratorAggregate and ArrayAccess makes it possible to handle classes similar to arrays in looping, count() calls and accessing them by index.

Code

As always, the code for the class is available here, in my svn repo.

The class gives you some basic methods for playing with it: add(), remove(), get(), exists(), count(), set(), which all should be quite self explanatory.

Here’s an usage example:

//This collection only accepts strings
$coll = new CU_Collection('string');
 
//Add a string and echo it
$coll->add('This is a string');
echo $coll[0];
 
//This will throw an InvalidArgumentException
$coll->add(1000);

The collection also works with classes: Instead of ‘string’, pass it ‘SomeClass’ in construct, and then it will only accept instances of SomeClass.

There are two limitations here, though: The collection only accepts numeric indexes, and you can’t use interfaces or base classes as the type. Adding interface support could probably be done through reflection, or by modifying the code which checks the type a bit.

Share this:
  1. 11 Responses to “Generic collections in PHP”

  2. Maybe it would be better for objects to use the instanceof check?
    That would work for interfaces and object inheritance.

    Unfortinatly you can’t do class casting in PHP to force a child object to be casted to the parent class (without dirty serialize hacks).

    By Harro on Sep 17, 2008

  3. Have you tried to extend or aggregate SplObjectStorage ?
    It’s a cool object to make collections ^^

    By julien-pauli on Sep 17, 2008

  4. I made some enhancements to your collection class:

    Any type that has anything that has a matching “is_*” function listed in link http://us2.php.net/manual/en/ref.var.php can be used. Anything else is assumed to be the name of a class or interface.

    Also, CU_Collection::isValidType() will return true for objects if the class or interface of the collection appears anywhere in the inheritance chain of the object.

    Demo: http://aheimlich.dreamhosters.com/generic-collections/

    By Aaron Heimlich on Sep 19, 2008

  5. I think it’s better with instanceof. I just had the weird notion that I might want to store precise classes in it, though I don’t know why… However, I don’t really see the point of your change to check for the is_something function – in my opinion it’s just doing the same the gettype comparison does, but in a different way.

    By Jani Hartikainen on Sep 20, 2008

  6. >However, I don’t really see the point of your
    >change to check for the is_something function –
    >in my opinion it’s just doing the same the
    >gettype comparison does, but in a different way.

    I quote from the gettype documentation page[1]:

    Warning

    Never use gettype() to test for a certain type, since the returned string may be subject to change in a future version. In addition, it is slow too, as it involves string comparison.

    Instead, use the is_* functions.

    [1] http://www.php.net/gettype

    By Aaron Heimlich on Sep 20, 2008

  7. Ah, of course. Makes sense now =)

    By Jani Hartikainen on Sep 20, 2008

  8. yeah this is very basic, and i agree with jani…

    By spaz on Oct 10, 2008

  9. its very useful

    By janet on Oct 26, 2009

  10. Nice simple and useful class. I added this indexOf() method below. I’m not sure if this is the right approach or if there is some other way of achieving this. Anyway here it is:

    /**
    * Returns the index of the first value matching the given value, else -1 if no match found.
    *
    * @param mixed $value
    * @param boolean $strict optional, if true then use === operator, else use == operator.
    * @throws InvalidArgumentException when wrong type
    */
    public function indexOf($value, $strict = false)
    {
    if (!$this->isValidType($value)) {
    throw new InvalidArgumentException(‘Trying to find a value of wrong type’);
    }
    if ($strict) {
    for ($i=0; $i _collection); $i++) {
    if ($this->_collection[$i] === $value) {
    return $i;
    }
    }
    }
    else {
    for ($i=0; $i _collection); $i++) {
    if ($this->_collection[$i] == $value) {
    return $i;
    }
    }
    }
    return -1;
    }

    By Craig on Dec 26, 2009

  11. I like how you and Aaron Heimlich cooperated to create this useful collection class. It’s very useful.

    I have one issue with both implementations. It is, unfortunately, not solveable due to PHP limitations (no templating). For example, if I want to create a class World with a collection of Person-objects and Thing-objects, I cannot force my World::setPersonsCollection() function to only accept a collection of Person-objects. The most I can specify is that it should be a Collection. Any further checking requires, well, further checking.

    I could solve this issue with creating a StringCollection class which internally uses a Collection( ‘string’ ), but this is requires a class per collection type…

    By Tomas on Oct 5, 2011

  12. offsetSet() doesn’t work with the []= operator.
    This is the fix:

    public function offsetSet($offset, $value) {
    		if (is_null($offset)) {
    			$this->add($value);
    		}
    		else {
    			// Place original code here
    		}
    	}

    By Craig on Nov 15, 2011

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)