قالب وردپرس درنا توس
Home / Tips and Tricks / How to Write Your Own Repeatable Objects in PHP – CloudSavvy IT

How to Write Your Own Repeatable Objects in PHP – CloudSavvy IT



PHP logo

PHP allows you to create iterable objects. These can be used within loops in place of scalar arrays. Iterables are often used as object collections. They allow you to type in that object while retaining looping support.

Simple repetition

To iterate over an array in PHP, use a foreach loop:

foreach (["1", "2", "3"] as $i) {
    echo ($i . " ");
}

This example would broadcast 1 2 3

You can also repeat an object:

$cls = new StdClass();
$cls -> foo = "bar";
foreach ($cls as $i) {
    echo $i;
}

This example would broadcast bar

The collection problem

For base classes with public properties, a plain foreach works well. Now let’s take a look at another class:

class UserCollection {
 
    protected array $items = [];
 
    public function add(UserDomain $user) : void {
        $this -> items[] = $user;
    }
 
    public function containsAnAdmin() : bool {
        return (count(array_filter(
            $this -> items,
            fn (UserDomain $i) : bool => $i -> isAdmin()
        )) > 0);
    }
 
}

This class represents a collection of UserDomain cases. Since PHP does not support typed arrays, classes like these are needed if you want to type an array that can contain only one type of value. Collections also help you create utilities, such as containsWithAdmin, which facilitate natural interaction with array items.

Unfortunately, trying to repeat this collection will not produce the desired results. Ideally, iteration should work on it $items array, not the class itself.

Implementing Iterator

Natural iteration can be added with the Iterator couple. By implementing Iterator, you can control PHP behavior when using instances of your class foreach

Iterator has five methods you should implement:

  • current() : mixed – Get the item at the current position in the iteration.
  • key() : scalar – Get the key at the current position in the iteration.
  • next() : void – Go to the next position in the iteration.
  • rewind() : void – Rewind the position to the start of the iteration.
  • valid() : bool – See if the current position has a value.

These methods can be confusing at first. However, implementing it is simple: you specify what you want at each stage of a foreach performance.

Every time your item is used foreach rewind() will be called. The valid() The next method is called, which informs PHP if there is a value at the current position. If there is, current() and key() are called to get the value and key in that position. finally, the next() method is called to advance the position pointer. The loop returns to calling valid() to see if there is another item available.

Here’s a typical implementation of this Iterator

class DemoIterator {
 
    protected int $position = 0;
 
    protected array $items = ["cloud", "savvy"];
 
    public function rewind() : void {
        echo "Rewinding";
        $this -> position = 0;
    }
 
    public function current() : string {
        echo "Current";
        return $this -> items[$this -> position];
    }
 
    public function key() : int {
        echo "Key";
        return $this -> position;
    }
 
    public function next() : void {
        echo "Next";
        ++$this -> position;
    }
 
    public function valid() : void {
        echo "Valid";
        return isset($this -> items[$this -> position]);
    }
 
}

This is what would happen on repeat DemoIterator

$i = new DemoIterator();
foreach ($i as $key => $value) {
    echo "$key $value";
}
 
// EMITS:
// 
// Rewind
// Valid Current Key
// 0 cloud
// Next
// 
// Valid Current Key
// 1 savvy
// Next
// 
// Valid

Your iterator should keep track of the loop position, check if there is an element at the current loop position (via valid()) and return the key and value at the current position.

PHP will not try to access the key or value if the loop position is invalid. Return false from valid() ends it immediately foreach loop. This is usually the case when you get to the end of the array.

IteratorAggregate

Writing iterators quickly becomes repetitive. Most follow the exact recipe above. IteratorAggregate is an interface that allows you to quickly create iterable objects.

Implement it getIterator() method and return a Traversable (the basic interface of IteratorIt is used as an iterator when your object is used foreachThis is usually the easiest way to add iteration to a collection class:

class UserCollection implements IteratorAggregate {
 
    protected array $items = [];
 
    public function add(UserDomain $User) : void {
        $this -> items[] = $user;
    }
 
    public function getIterator() : Traversable {
        return new ArrayIterator($this -> items);
    }
 
}
 
$users = new UserCollection();
$users -> add(new UserDomain("James"));
$users -> add(new UserDomain("Demo"));
 
foreach ($users as $user) {
    echo $user -> Name;
}

When working with collections, three lines of code are usually enough to set up iteration! A ArrayIterator will be returned if the TraversableThis is a class that automatically creates a Iterator from an array. You can now iterate over the logical values ​​within your object, instead of the direct properties of the object.

Using prebuilt PHP iterators

You have to write your own iterators if you have complex logic. It is rarely necessary to start from scratch as PHP comes with several advanced iterators provided by SPL.

The built-in classes include DirectoryIterator FilesystemIterator GlobIterator and various recursive iterators. Here are some of the most useful generic iterators.

LimitIterator

The LimitIterator lets you iterate over a subset of an array. You don’t need to split the array or manually position it within your foreach

$arr = new ArrayIterator(["a", "b", "c", "d"]);
foreach (new LimitIterator($arr, 0, 2) as $val) {
    echo $val;
}

This example would broadcast a bNotice that LimitIterator accepts another Iterator, not an array. The example uses ArrayIterator, the built-in class that a Iterator from an array.

InfiniteIterator

InfiniteIterator never ends the loop, so you should break out manually. Otherwise, the iterator will automatically return to the beginning of the array when the end is reached.

This iterator is especially useful when working with time-based values. Here’s an easy way to put together a three-year calendar that also includes the LimitIterator described above:

$months = [
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];
$infinite = new InfiniteIterator(new ArrayIterator($months));
foreach (new LimitIterator($infinite, 0, 36) as $month) {
    echo $month;
}

That yields three years worth of months.

FilterIterator

FilterIterator is an abstract class that you need to extend. Implement it accept method to filter out unwanted values ​​to be skipped during iteration.

class DemoFilterIterator extends FilterIterator {
 
    public function __construct() {
        parent::__construct(new ArrayIterator([1, 10, 4, 6, 3]));
    }
 
    public function accept() {
        return ($this -> getInnerIterator() -> current() < 5);
    }
 
}
 
$demo = new DemoFilterIterator();
 
foreach ($demo as $val) {
    echo $val;
}

This example would broadcast 1 4 3

The matrix values ​​greater than 5 are filtered out and do not appear in the foreach

The “iterable” type

Sometimes you write a generic function that contains a foreach loop but knows nothing about the values ​​it will loop. An example is an abstract error handler that simply dumps the received values.

You can type a hint iterable in these scenarios. This is a pseudo-type that one array or any object being implemented Traversable

function handleBadValues(iterable $values) : void {
    foreach ($values as $value) {
        var_dump($value);
    }
}

The iterable type is so vague that you should think carefully before using it. Nevertheless, it can be useful as a last resort if you want to ensure that an input value works foreach

Conclusion

By using iterators you can write clean code that is modular. You can move methods that work on arrays of objects to special collection classes. These can then be individually typed while remaining fully compatible with foreach

Usually iteration support can be added by implementing IteratorAggregate and returning a ArrayIterator configured with the items in your collection. PHP’s other iterator types, not usually used by developers, can greatly simplify more specific loops. They provide complex logical behaviors without manual pointer tracking in your foreach pronunciation.


Source link