Building Custom Collection class in PHP
Introduction:
A Collection class is an OOP-Replacement for the PHP array data structure, which provides a convenient wrapper for working with arrays of data. Collections offer a more structured approach to handling data, allowing for advanced operations such as filtering, mapping, reducing, and sorting with ease.
By encapsulating array manipulation logic within methods, Collections promote cleaner and more readable code, enhancing code maintainability and facilitating common array operations in a more expressive manner.
Implementation:
Let’s start by defining a Collection class.
<?php
class Collection {
public function __construct(protected array $elements = []) {
//
}
}
- The
protected array $elements = []
part is a constructor property promotion feature introduced in PHP 8. - With constructor property promotion, the
$elements
property is automatically created and initialized when an object of theCollection
class is instantiated.
Now, we need a way to access our Collection objects as arrays, and that can be done by implementing the PHP ArrayAccess
interface.
The ArrayAccess
interface requires the implementation of the following methods:
offsetExists($offset)
: This method determines whether a given offset exists in the object.offsetGet($offset)
: This method retrieves the value at a specified offset.offsetSet($offset, $value)
: This method sets the value at a specified offset.offsetUnset($offset)
: This method unsets the value at a specified offset.
<?php
class Collection implements ArrayAccess{
public function __construct(protected array $elements = []) {
//
}
public function offsetExists(mixed $offset): bool {
return array_key_exists($offset, $this->elements);
}
public function offsetGet(mixed $offset): mixed {
return $this->offsetExists($offset)
? $this->elements[$offset]
: null;
}
public function offsetSet(mixed $offset, mixed $value): void {
is_null($offset)
? $this->elements[] = $value
: $this->elements[$offset] = $value;
}
public function offsetUnset(mixed $offset): void {
if($this->offsetExists($offset)) {
unset($this->elements[$offset]);
}
}
}
offsetExists($offset)
Method:
- This method checks if a given offset exists within the object.
array_key_exists($offset, $this->elements)
: It checks if the offset exists within the$elements
property of the object.- Returns
true
if the offset exists in the elements array,false
otherwise.
offsetGet($offset)
Method:
- This method retrieves the value at a specified offset.
- It first checks if the offset exists by calling
offsetExists($offset)
. - If the offset exists, it returns the corresponding value from the elements array; otherwise, it returns
null
.
offsetSet($offset, $value)
Method:
- This method sets the value at a specified offset.
- If the offset is
null
, it means the value should be appended to the elements array. - If the offset is not
null
, it sets the value at the specified offset within the elements array.
offsetUnset($offset)
Method:
- This method removes the value at a specified offset.
- It first checks if the offset exists by calling
offsetExists($offset)
. - If the offset exists, it removes the value at that offset from the elements array using
unset
.
Next, we need to iterate through our Collection objects internally and that can be done by implementing the PHP Iterator
interface.
The Iterator
interface requires the implementation of the following methods:
current():
- This method returns the current element in the iteration.
key():
- This method returns the key of the current element in the iteration.
next():
- This method moves the internal pointer to the next element in the iteration.
rewind():
- This method resets the internal pointer to the first element in the iteration.
valid():
- This method checks if the current position of the internal pointer is valid (i.e., if there is an element at the current position).
<?php
class CustomCollection implements ArrayAccess, Iterator {
public function __construct(protected array $elements = []) {
//
}
public function current(): mixed {
return current($this->elements);
}
public function key(): mixed {
return key($this->elements);
}
public function next(): void {
next($this->elements);
}
public function rewind(): void {
reset($this->elements);
}
public function valid(): bool {
return isset($this->elements[$this->key()]);
}
}
current()
Method:
- returns the current element of the internal
$elements
array using thecurrent
function.
key()
Method:
- returns the key of the current element in the internal
$elements
array using thekey
function.
next()
Method:
- advances the internal pointer of the
$elements
array to the next element using thenext
function.
rewind()
Method:
- resets the internal pointer of the
$elements
array to the first element using thereset
function.
valid()
Method:
- checks if the current key is set in the
$elements
array, verifying if the current position of the internal pointer is valid by usingisset
.
Let’s now implement some array methods:
<?php
class Collection implements ArrayAccess, Iterator {
public function __construct(protected array $elements = [])
{
//
}
public function map(callable $callback): static {
$keys = array_keys($this->elements);
$items = array_map($callback, $this->elements, $keys);
return new static(array_combine($keys, $items));
}
public function filter(?callable $callback, int $mode = 0): static {
return new static(array_filter($this->elements, $callback, $mode));
}
public function reduce(callable $callback, mixed $initial = null): mixed {
return array_reduce($this->elements, $callback, $initial);
}
public function sum(?string $key = null) {
return $this->reduce(fn ($sum, $item) => $sum + (is_null($item) ? $item : $item[$key]), 0);
}
public function flip(): static {
return new static(array_flip($this->elements));
}
public function push(... $elements): static {
foreach($elements as $element) {
$this->elements[] = $element;
}
return $this;
}
}
map
Method:
- The
map
method applies a callback function to each element in the collection. - It retrieves the keys of the elements array.
- It uses
array_map
to apply the provided callback function to each element along with its key. - Finally, it creates a new instance of
Collection
with the mapped elements.
filter
Method:
- The
filter
method filters elements in the collection based on a callback function. - It uses
array_filter
to filter the elements based on the provided callback function.
reduce
Method:
- The
reduce
method reduces the collection to a single value using a callback function. - It uses PHP’s
array_reduce
function to reduce the elements to a single value using the given callback function. - The initial value for reduction can be optionally provided.
sum
Method:
- The
sum
method calculates the sum of values in the collection, optionally by a specific key. - It uses the
reduce
method with a callback function that calculates the sum based on the specified key. - The initial value for the sum is set to 0.
flip
Method:
- The
flip
method exchanges keys with values in the collection. - It uses
array_flip
to flip the keys and values in the elements array. - It returns a new instance of
Collection
with the flipped elements.
push
Method:
- The
push
method takes a variable number of arguments using the...$elements
syntax, which allows you to pass in multiple elements to be added to the collection. - The method iterates over each element passed in the argument list.
- For each element, it appends (pushes) the element to the end of the internal
$elements
array of theCollection
object. - After adding all the elements, the method returns
$this
, which refers to the current instance of theCollection
. This allows method chaining, where you can call multiple methods on the same object in a sequence.
Testing:
Let’s put our implementation to the test:
- Creating a New Collection Object:
<?php
$collection = new CustomCollection([1, 5, 10, -1, 11]);
2. Looping Over the Collection Object:
<?php
foreach($collection as $key => $value) {
echo $key . " => " . $value . PHP_EOL;
}
/* OUTPUT
0 => 1
1 => 5
2 => 10
3 => -1
4 => 11
*/
3. Using the map
Method:
<?php
$c = $collection->map(function($element) {
return $element * 2;
});
/* OUTPUT
[2, 10, 20, -2, 22]
*/
4. Using the filter
Method:
<?php
$c = $collection->filter(function($element) {
return $element > 5;
});
/* OUTPUT
[10, 11]
*/
5. Using the reduce
Method:
<?php
$c = $collection->reduce(function($carry, $element) {
return $carry + $element;
}, 0);
/* OUTPUT
26
*/
6. Using the push
Method:
<?php
$collection->push(12, 13, -2);
/* OUTPUT
[1, 5, 10, -1, 11, 12, 13, -2]
*/
Summary:
This Custom Collection
class in PHP offers a versatile and efficient way to manage collections of elements with essential functionalities like mapping, filtering, reducing, and more. By providing methods for common data manipulation operations, such as transforming elements, selecting subsets based on criteria, and aggregating values, this class empowers developers to handle data with ease and flexibility. With the ability to chain methods and perform various operations on collections effortlessly, theCollection
class streamlines data handling tasks, making it a valuable tool for efficient and structured data manipulation in PHP applications.