Collections

Collection classes are iterable and countable. The raw data for each member is stored in an array, and members are instantiated on request only. There is generally a collection class for each Domain class and it may include custom logic that applies only to a collection of the given objects.

Custom logic may relate to sorting, filtering, adding members, removing members, comparing collections, and more. For methods that filter or sort the collection, I find it easiest and safest to pass out a read-only clone, rather than reorder the underlying data.

Collections are commonly returned by a mapper. On construction a collection takes two parameters: a factory for creating objects of the given type, and an array such as a database record set, where each element is a sub-array with the raw data for a single object. The code below is for a Foo collection. Notice that most of the functionality is implemented in the AbstractCollection.

namespace Core\Domain\Collection;

use Core\Domain\Collection\iCollection;
use Core\Domain\iDomain;

class Foo extends AbstractCollection implements iCollection\Foo 
{
    public function add(iDomain\Foo $foo)
    {
        $this->doAdd($foo);
    }
} 

The role of the iCollection\Foo interface is to ensure that only iDomain\Foo objects are added to the collection. In addition, it makes way for collection substitutes such as collection proxies. Following is an interface for a Foo collection.

namespace Core\Domain\Collection\iCollection;

use Core\Domain\iDomain;

interface Foo extends Collection
{
    public function add(iDomain\Foo $foo);
}

Collection Proxies

In the same way that there are proxies for Domain objects, there are proxies for collections of Domain objects too. The following class diagram shows how concrete collections (e.g. Core\Domain\Collection\Foo) relate to their proxies (Core\Domain\Collection\Proxy\Foo).


Class Diagram: Collections and their Proxies

Both a collection and its proxy inherit most of their characteristics through a common hierarchy of interfaces. The key difference is that when the collection extends its parent it becomes an \Iterator, and when the proxy extends its parent it becomes an \IteratorAggregate. The 'iterator aggregate' is a proxy for a real iterator, the actual collection.

From the constructor of the AbstractCollectionProxy you can see that proxies are injected with:

  • The Mapper that must be used to retrieve the collection,
  • The name of the method to call on the Mapper, and
  • An array of arguments that must be provided to the method.

When client code attempts to iterate the proxy or access collection methods, the proxy is realised and calls are passed to the true collection. Any attempts to access custom logic methods are passed from the proxy to the real collection using method call overloading.

Here is the code for a Foo collection proxy:

namespace Core\Domain\Collection\Proxy;

use Core\Domain\Collection\iCollection;
use Core\Domain\iDomain;

class Foo extends AbstractCollectionProxy implements iCollection\Foo
{
    public function add(iDomain\Foo $foo)
    {
        $this->getIterator()->add($foo);
    }
}

Collection proxies are used extensively by factories as a proxy for multi-valued properties. Under normal operation, it is not possible for the factory to know which multi-valued properties will be accessed on the object under construction. Instead of retrieving and attaching a large number of properties which may not be used, the factory provides a collection proxy which can efficiently retrieve the collection if/when required.