Collection classes in php

The problem with arrays

In PHP arrays are a very flexible data structure, which accepts almost everything and can be combined any way you like. This is actually good: unlike other languages, PHP lets you even combine associative and numeric arrays, thus offering you a great deal of freedom. But there are times when you just need type-safety. One way to deal with it would surely be to check every item of the array when using it, thus creating some code snippets close to this:

$array = [1,2,3,4,5] foreach ($array as $item)  if (!is_int($item))  throw new \Exception('wrong type'); > //do your stuff here > 
  • in order to factorize all occurrences, you need some sort of globally accessible function
  • it is, in fact, the wrong place to check it. Ideally, you would want to check the type of a value when creating the array, and don’t bother when using it because you are sure that it’s ok.

The solution: collections

A collection is a class that abstracts the array for you. A basic collection class would look like this:

class IntCollection  private $values = []; public function addValue (int $val, $key = null): void  //the typehint ensures that you won't have any non-integer values if ($key === null)  $this->values[] = $val; > else  $this->values[$key] = $val; > > public function deleteValue($key): void  unset($this->values[$key]); > public function get($key): int  return $this->values[$key]; > > 

That’s already pretty cool, but not enough: you can’t use it as an array. This means for instance that

  • If you are refactoring, chances are that you have to change multiple code parts in order to replace an array with this object
  • you can’t count the items in it
  • you can’t use it in standard PHP control structures like foreach

The better solution

Luckily, PHP provides us with the tools to make all of this possible. For this, we need to refactor our collection class in order to use some of the core languages interfaces.

An interface is an API definition for a class. When implementing an interface, you tell the world that your class will work a certain, standardized and predefined way. When you look at it the first time, it may strike you as «just like an abstract class», but it’s not quite right:

  • all methods declared in an interface must be public
  • abstract classes can contain variables and implemented methods, interfaces can’t
  • a class can only extend one other class, but may implement as many interfaces as you like

In order to build our array-like, type-safe collection, we’ll look at three PHP interfaces

  • Countable => Tells the system your class can be used in functions like ‘count()’
  • Iterator => Tells the system that your object can be iterated through
  • ArrayAccess => implement this and you’ll be able to use your collection like an array (for instance accessing it via $collection[$key];)

Our finished IntCollection would look like this:

class IntCollection implements \Countable, \Iterator, \ArrayAccess  private $values = []; private $position = 0; /** * This constructor is there in order to be able to create a collection with * its values already added */ public function __construct(array $values = [])  foreach ($values as $value)  $this->offsetSet('', $value); > > /** * Implementation of method declared in \Countable. * Provides support for count() */ public function count()  return count($this->values); > /** * Implementation of method declared in \Iterator * Resets the internal cursor to the beginning of the array */ public function rewind()  $this->position = 0; > /** * Implementation of method declared in \Iterator * Used to get the current key (as for instance in a foreach()-structure */ public function key()  return $this->position; > /** * Implementation of method declared in \Iterator * Used to get the value at the current cursor position */ public function current()  return $this->values[$this->position]; > /** * Implementation of method declared in \Iterator * Used to move the cursor to the next position */ public function next()  $this->position++; > /** * Implementation of method declared in \Iterator * Checks if the current cursor position is valid */ public function valid()  return isset($this->values[$this->position]); > /** * Implementation of method declared in \ArrayAccess * Used to be able to use functions like isset() */ public function offsetExists($offset)  return isset($this->values[$offset]); > /** * Implementation of method declared in \ArrayAccess * Used for direct access array-like ($collection[$offset]); */ public function offsetGet($offset)  return $this->values[$offset]; > /** * Implementation of method declared in \ArrayAccess * Used for direct setting of values */ public function offsetSet($offset, $value)  if (!is_int($value))  throw new \InvalidArgumentException("Must be an int"); > if (empty($offset))  //this happens when you do $collection[] = 1; $this->values[] = $value; > else  $this->values[$offset] = $value; > > /** * Implementation of method declared in \ArrayAccess * Used for unset() */ public function offsetUnset($offset)  unset($this->values[$offset]); > > 

An even better solution: using the SPL

The SPL is a core library from PHP in which you’ll find many very useful things if you bother looking. Unfortunately, it is almost as widely unknown as it is useful. For our problem here, it contains a class called ArrayObject, which does everything we did by hand before and already is serializable. By extending the object, our final collection class will be written in literally no time:

class IntCollection extends \ArrayObject  public function offsetSet($index, $newval)  if (!is_int($newval))  throw new \InvalidArgumentException("Must be int"); > parent::offsetSet($index, $newval); > > 

Источник

Collection Classes in PHP

A Collection class is an OOP replacement for the traditional array data structure. Much like an array, a collection contains member elements, although these tend to be objects rather than simpler types such as strings and integers.

The general characteristics of a collection class are:

  • Establishes a wrapper around a array of objects.
  • Collections are mutable – new elements may be added and existing elements may be modified or removed.
  • Sorting algorithms are unstable (which means the order for equal elements is undefined).
  • Can use lazy instantiation to save system resources.

Problem with Arrays

Applications frequently have objects that contain a group of other objects, and this is a great place to make use of collections. For example, suppose we decide to create a book store system. Let’s say we’ve written a customer class which, among other things, holds a list of books that the customer would like to purchase:

 $customer = new Customer(1234); foreach ($customer->items as $item) < echo $item->name; >

If the most obvious approach (using an array) were the best possible approach, I wouldn’t write this article. The above example has these problems:

  • We’ve broken encapsulation – the array is exposed as a public member variable.
  • There is ambiguity in indexing and how to traverse the array to find a specific item.

Additionally, to make sure the array is available for any code that might access it, we must populate the list of information from the database at the same time as the customer information. This means that even if we want to print just the customer’s name, we must fetch all of the item information, unnecessarily increasing load on the database and potentially bogging down the entire application.

We can solve those problems by creating a collection class as an OOP wrapper around the array and use lazy instantiation. Lazy instantiation is the mechanism by which we create elements in the array only when we actually need it. It’s called “lazy” because the object determines on its own when to instantiate the component objects rather that blindly creating them when it is instantiated.

A Basic Collection Class

A collection class needs to expose methods that allow us to add, retrieve, and delete items, and it’s helpful to have a method that lets us know the size of the collection. So, a basic class would start like this:

 public function deleteItem($key) < >public function getItem($key) < >>

The $items array provides a location in which to store the objects that are members of the collection. addItem() lets us add a new object to the collection, deleteItem() removes an object, and getItem() returns an object.

With addItem() , we add an object to the collection by putting it in the $items array at a specified location specified by $key (if no key is provided, we let PHP pick the next available index). If an attempt is made to add an object using a key that already exists, an exception should be thrown to prevent inadvertently overwriting existing information:

public function addItem($obj, $key = null) < if ($key == null) < $this->items[] = $obj; > else < if (isset($this->items[$key])) < throw new KeyHasUseException("Key $key already in use."); >else < $this->items[$key] = $obj; > > >

The deleteItem() and getItem() methods take the key as a parameter indicating which items are targeted for removal or retrieval. An exception should be thrown if an invalid key is supplied.

public function deleteItem($key) < if (isset($this->items[$key])) < unset($this- >items[$key]); > else < throw new KeyInvalidException("Invalid key $key."); >> public function getItem($key) < if (isset($this->items[$key])) < return $this->items[$key]; > else < throw new KeyInvalidException("Invalid key $key."); >>

Because the $key parameter to the addItem() method is optional, we won’t necessarily know the key used for each item in the collection. Adding a method that can provide a list of keys to any external code that might need it is a good idea. The keys can be returned as an array:

public function keys() < return array_keys($this->items); >

It might also be helpful to know how many items are in the collection.

public function length() < return count($this->items); >

And because getItem() and deleteItem() can throw an exception if an invalid key is passed, a means of determining whether a given key exists in the collection is also a good idea.

public function keyExists($key) < return isset($this->items[$key]); >

To use the Collection class as it stands now, create the file Collection.php and save the code for the Collection class in it. Create files for the KeyInvalidException and KeyhasUseException classes, too (they can be simple sub-classes of the base Exception class). Be sure to add require statements if you’re not using an autoloader, and then try the following test code:

name = $name; $this->number = $number; > public function __toString() < return $this->name . " is number " . $this->number; > > $c = new Collection(); $c->addItem(new Salut("Steve", 14), "steve"); $c->addItem(new Salut("Ed", 37), "ed"); $c->addItem(new Salut("Bob", 49), "bob"); $c->deleteItem("steve"); try < $c->getItem("steve"); > catch (KeyInvalidException $e)

This example may not be particularly interesting, but it should give you an idea of how the class is used.

Conclusion

Collections can be seen as more-specialized way of working with lists for which certain contracts are guaranteed. A Collection class is a very useful OO alternative to the traditional array, and one that can be implemented in virtually any application you might build. It provides careful management of its members and a consistent API that makes it easy to write code that uses the class.

Share This Article

To whom it may concern, I’m Alireza. For a significant chunk of my waking hours I’m a PHP expert, Author, Speaker and independent consultant on the design of enterprise web applications with Master’s degrees in Computer Science. I

Источник

Читайте также:  Php connection string to mysql database
Оцените статью