SPONSORS

1086 views · 163 days ago

![Iterator in PHP](https://images.ctfassets.net/vzl5fkwyme3u/5I2KRmarVm4Gg0GuYcOeSk/b8c5156475053a19537059c19f244d61/AdobeStock_144145979.jpeg?w=1000)

Every time I see this

```php

$users = [new User(), new User()];

```

I see a lost opportunity to use Iterator.

### Why Iterators?

Collections are an awesome way to organize your previously no-named array. There is a couple of reasons why you should use iterators. One of reason stays for behavior, you can specify exact behavior on standard calls such as next, current, valid etc. Other reason could be that you want to ensure that collection contains an only specific type of an object.

Understand a suffer from using an array of unknown value types.

Very common in the PHP world arrays are used to store all kind of data, in many dimensions in many nested forms. Arrays introduced infinite flexibility to the developer, but because of that, they become very evil.

### Example:

- Your function (getUsers) returns an array of User objects.

- Another function (setUsersToActiveState) using getUsers output array and set all users active status to true.

- setUsersToActiveState loop through the array and expect to call a specific method on array item. For example, the method name is getActiveStatus.

- If given array is an array of desired objects which have a callable method getActiveStatus, all fine. But if not exception will be thrown.

- How we can ensure that given array is always an array of objects of a specific type?

```php

public function getUsers(): array

{

/ **

here happen something which gets users from database

....

**/

return $userArray;

}

public function setUsersToActiveState()

{

$users = $this->getUsers();

/ ** @var User $param */

foreach ($users as $user) {

if(!$user->getActiveStatus()) {

$user->setActiveStatus(true);

}

}

}

```

There immediately two problems occurred.

1. One is the problem of type. Our IDE doesn't know what's inside array of $users, so because of that IDE can't suggest us how to use $user element. (I put this comment block / ** @var User $param */ above foreach, it works for phpStorm and I guess for some other IDEs)

2. Your colleagues! How they possibly know what's inside array if there is no any hint.

3. Bonus problem, getUsers can return literally any array and there won't be warning in the system.

### Solution

```php

/ / Create a collection which accepts only Users

class UsersCollection implements \IteratorAggregate

{

/ ** @var array */

private $users = [];

public function getIterator() : UserIterator

{

return new UserIterator($this);

}

public function getUser($position)

{

if (isset($this->users[$position])) {

return $this->users[$position];

}

return null;

}

public function count() : int

{

return count($this->users);

}

public function addUser(User $users)

{

$this->users[] = $users;

}

}

/ / Create an Iterator for User

class UserIterator implements \Iterator

{

/ ** @var int */

private $position = 0;

/ ** @var UsersCollection */

private $userCollection;

public function __construct(UsersCollection $userCollection)

{

$this->userCollection = $userCollection;

}

public function current() : User

{

return $this->userCollection->getUser($this->position);

}

public function next()

{

$this->position++;

}

public function key() : int

{

return $this->position;

}

public function valid() : bool

{

return !is_null($this->userCollection->getUser($this->position));

}

public function rewind()

{

$this->position = 0;

}

}

```

### Tests

Off course there is the tests to ensure that our Collection and Iterator works like a charm. For this example I using syntax for PHPUnit framework.

```php

class UsersCollectionTest extends TestCase

{

/ **

* @covers UsersCollection

*/

public function testUsersCollectionShouldReturnNullForNotExistingUserPosition()

{

$usersCollection = new UsersCollection();

$this->assertEquals(null, $usersCollection->getUser(1));

}

/ **

* @covers UsersCollection

*/

public function testEmptyUsersCollection()

{

$usersCollection = new UsersCollection();

$this->assertEquals(new UserIterator($usersCollection), $usersCollection->getIterator());

$this->assertEquals(0, $usersCollection->count());

}

/ **

* @covers UsersCollection

*/

public function testUsersCollectionWithUserElements()

{

$usersCollection = new UsersCollection();

$usersCollection->addUser($this->getUserMock());

$usersCollection->addUser($this->getUserMock());

$this->assertEquals(new UserIterator($usersCollection), $usersCollection->getIterator());

$this->assertEquals($this->getUserMock(), $usersCollection->getUser(1));

$this->assertEquals(2, $usersCollection->count());

}

private function getUserMock()

{

/ / returns the mock of User class

}

}

class UserIteratorTest extends MockClass

{

/ **

* @covers UserIterator

*/

public function testCurrent()

{

$iterator = $this->getIterator();

$current = $iterator->current();

$this->assertEquals($this->getUserMock(), $current);

}

/ **

* @covers UserIterator

*/

public function testNext()

{

$iterator = $this->getIterator();

$iterator->next();

$this->assertEquals(1, $iterator->key());

}

/ **

* @covers UserIterator

*/

public function testKey()

{

$iterator = $this->getIterator();

$iterator->next();

$iterator->next();

$this->assertEquals(2, $iterator->key());

}

/ **

* @covers UserIterator

*/

public function testValidIfItemInvalid()

{

$iterator = $this->getIterator();

$iterator->next();

$iterator->next();

$iterator->next();

$this->assertEquals(false, $iterator->valid());

}

/ **

* @covers UserIterator

*/

public function testValidIfItemIsValid()

{

$iterator = $this->getIterator();

$iterator->next();

$this->assertEquals(true, $iterator->valid());

}

/ **

* @covers UserIterator

*/

public function testRewind()

{

$iterator = $this->getIterator();

$iterator->rewind();

$this->assertEquals(0, $iterator->key());

}

private function getIterator() : UserIterator

{

return new UserIterator($this->getCollection());

}

private function getCollection() : UsersCollection

{

$userItems[] = $this->getUserMock();

$userItems[] = $this->getUserMock();

$usersCollection = new UsersCollection();

foreach ($userItems as $user) {

$usersCollection->addUser($user);

}

return $usersCollection;

}

private function getUserMock()

{

/ / returns the mock of User class

}

}

```

### Usage

```php

public function getUsers(): UsersCollection

{

$userCollection = new UsersCollection();

/ **

here happen something which gets users from database

....

**/

foreach ($whatIGetFromDatabase as $user) {

$userCollection->addUser($user);

}

return $userCollection;

}

public fucntion setUsersToActiveState()

{

$users = $this->getUsers();

foreach ($users as $user) {

if(!$user->getActiveStatus()) {

$user->setActiveStatus(true);

}

}

}

```

As you can see setUsersToActiveState remains the same, we only do not need to specify for our IDE or collagues what type $users variable is.

### Extending functionalities

Believe or not you can reuse this two objects and just change names of variables to fit most of the needs. But if you want any more complex functionality, than feel free to add it in iterator or collection.

#### Example 1

For example, let's say that userCollection accepts only users with age more than 18. Implementation will happen in UsersCollection class in the method addUser.

```php

public function addUser(User $users)

{

if ($user->getAge() > 18) {

$this->users[] = $users;

}

}

```

#### Example 2

You need to add bulk users. Then you can expand your userCollection with additional method addUsers and it might look like this.

```php

public function addUsers(array $users)

{

foreach($users as $user) {

$this->addUser(User $users);

}

}

```

SPONSORS