📜 ⬆️ ⬇️

Getting ready for a PHP interview: Everything about iteration and a little about the “iterable” pseudotype

It is no secret that they like to ask tricky questions at interviews. Not always adequate, not always relevant to reality, but the fact remains that they are asking. Of course, a question is a question, and sometimes a question, seemingly stupid at first glance, is actually aimed at checking how well you know the language you write.

And, of course, no matter how strange and inappropriate the questions at the interview seem to be, you need to come all the same prepared, knowing the language for programming on which you are going to pay.

image
')
The third part of a series of articles is devoted to one of the most voluminous concepts in modern PHP - iteration, iterators and iterated entities. I tried to reduce in one text a certain minimum of knowledge about this question, suitable for self-preparation for an interview for the position of a PHP developer.

Two previous parts:



PHP arrays


Let's start from the beginning.

PHP has arrays. Arrays in PHP are associative, that is, they store pairs (key, value), where the key must be an int or string, and the value can be of any type.

Example:

$arr = ['foo' => 'bar', 'baz' => 42, 'arr' => [1, 2, 3]]; 

The key and value are separated by the symbol "=>". Sometimes the key is otherwise called the "index", in PHP these are equivalent terms.

A fairly complete set of operations is defined on arrays in PHP:

 //    $arr['new'] = 'some value'; //      $arr[] = 'another value'; //      echo $arr['foo']; echo $arr[$bar]; //     unset($arr['foo']); // ""  [$foo, $bar, $baz] = $arr; 

There are also many functions for working with arrays - tens and hundreds of them!

However, perhaps the most important feature of arrays in PHP is the ability to sequentially walk through all the elements of an array, obtaining all key-value pairs in order.

Array iteration


The process of passing through an array is called “iteration” (or “brute force”) (by the way, each step, getting each individual key-value pair is also “iteration”), and the array itself is thus iterative (“iterated”) the essence.

The simplest example of the iteration process is, of course, a joint loop implemented by the foreach operator:

 foreach ($arr as $key=>$val) { echo $key . '=>' . $val; echo "\n"; } 

Notice the same "=>" sign that separates the key and the value in the loop header.

But how does PHP understand - which element of the array should be taken at a specific step of the loop? Which one to take next? And when to stop?

To answer this question, one should be aware of the existence of a so-called “internal pointer” that exists in each array. This invisible pointer points to the “current” element and is able to move one step forward - to the next element or again to drop to the first element.

For direct work with an internal pointer in PHP, there are functions that are easiest to learn by example:

 $arr = [1, 2, 3]; //   ,      reset($arr); // key()    ,     ,  null         while ( null !== ($key = key($arr)) ) { // current()    ,      echo $key . '=>' . current($arr); echo "\n"; // next()         next($arr); } 

It is easy to see that the example code shown is actually equivalent to the foreach cycle that was previously used, and that foreach is syntactic sugar for the reset (), key (), current (), next () functions (and there are also functions end () and prev ( ) - to organize the search in the reverse order).

This statement was correct before PHP 7, but now the situation is a bit wrong - the foreach loop stopped using the same internal pointer as reset (), next () and other iteration functions, so it stopped changing its position.

Subtotal


So let's summarize how iteration through arrays in PHP is organized:


Such a device allows us to organize an iteration over an array (enumeration of its elements) as a loop. But it is important to understand that the foreach loop, although similarly structured, does not work with the same internal pointer as the reset (), key (), current (), etc. functions, but with its own local for the loop.

Iteration over objects


Objects, like arrays, are iterable entities. A crawl of objects follows their properties visible in this context, and the keys are the names of the properties.

 class Foo { public $first = 1; public $second = 2; protected $third = 3; public function iterate() { foreach ($this as $key => $value) { echo $key . '=>' . $value; echo "\n"; } } } $foo = new Foo; foreach ($foo as $key => $value) { echo $key . '=>' . $value; echo "\n"; } /*   first=>1 second=>2 */ $foo->iterate(); /*   first=>1 second=>2 third=>3 */ 

However, such an iteration, according to visible properties, is often completely useless. The most common example is an object that stores a set of values ​​in an internal secure storage. For example, like this:

 class Storage { protected $storage = []; public function set($key, $val) { $this->storage[$key] = $val; } public function get($key) { return $this->storage[$key]; } } 

How to organize an iteration on such an object that does not have public properties? And how do you generally organize an iteration on some of your own non-standard algorithm?

Interface iterator


To implement its own iteration algorithms, PHP (or more precisely SPL) provides a special Iterator interface consisting of five methods:

 //       public function current(); //       public function key(); //    ""    public function next(): void; //    ""    public function rewind(): void; //    -      ? public function valid(): bool 

Your class must implement these methods and then you will be able to iterate objects of this class using the foreach loop in accordance with the implemented algorithm.

NB “Pointer”, which is mentioned here in the description of Iterator interface methods, is a pure abstraction, in contrast to the actually existing internal array pointer. It depends only on you exactly how you implement this abstraction, only the result is important - for example, a sequential call to the rewind () and current () methods must return the value of the first element.

The simplest example of implementing an Iterator interface
 class Example implements Iterator { protected $storage = []; public function set($key, $val) { $this->storage[$key] = $val; } public function get($key) { return $this->storage[$key]; } public function current() { return current($this->storage); } public function key() { return key($this->storage); } public function next(): void { next($this->storage); } public function rewind(): void { reset($this->storage); } public function valid(): bool { return null !== key($this->storage); } } $test = new Example; $test->set('foo', 'bar'); $test->set('baz', 42); foreach ($test as $key => $val) { echo $key . '=>' . $val; echo "\n"; } 


Traversable and IteratorAggregate


Strictly speaking, the Traversable interface allows us to iterate using foreach, and Iterator is its successor. The peculiarity of Traversable is that it cannot be implemented directly (a sort of “abstract interface”) and you should use the Iterator interface or its “younger brother” IteratorAggregate in your applications. About him and talk.

The SPL includes several built-in iterator classes that allow you to wrap some other entity into an iterator object, such as an array:

 $iterator = new ArrayIterator([1, 2, 3]); foreach ($iterator as $key => $val) { // ... } 

The list of such ready-for-iterator wrappers is quite large and includes such useless classes as DirectoryIterator (iterates over a list of files in a given directory), RecursiveArrayIterator (recursive bypass of nested arrays), FilterIterator (crawling with dropping unwanted values) and others, again dozens of them .

Using ready-made iterators and the IteratorAggregate interface allows us to significantly simplify the creation of our own iterator classes. So, a very long class under the spoiler above can be reduced to about this:

 class Example implements IteratorAggregate { protected $storage = []; public function set($key, $val) { $this->storage[$key] = $val; } public function get($key) { return $this->storage[$key]; } public function getIterator(): Traversable { return new ArrayIterator($this->storage); } } 

- the result will be the same as with the manual implementation of the Iterator interface.

And the generators?


Well, of course. We use them through foreach!

 class Generator implements Iterator 

However, generators are a topic for a separate article. For now, suffice it to say that there is nothing magic in the mechanism of the generators - the iterator interface is used for iteration. With the exception of one “but” - the generator cannot be “rewound to the beginning,” if the iteration has already begun, the call to the rewind () method will throw an exception.

Type iterable


Before PHP 7.1 there was a strange picture. On the one hand, there were iterable objects implementing Traversable via Iterator or IteratorAggregate. On the same side were the generators, as using the same mechanism. And on the other side there are arrays and “native” iteration on the visible properties of objects. In fact, there were two types of iterable entities that have identical behavior, but have nothing in common.

In 7.1, finally, this illogicality was eliminated and we had another “pseudotype” (or, more specifically, a custom type) “iterable”.

When once we wait for the type statement in PHP, the iterable type definition can be written like this:

 type iterable = array | Traversable; 

This type combines arrays and all Traversable heirs and indicates the type of values ​​that can be iterated with foreach:

 function doSomething(iterable $it) { foreach ($it as $key=>$val) { // do something } } 

And what happens?


It turns out such a type diagram:

 iterable ---> array --> Traversable ---> Iterator --> IteratorAggregate --> Generator 

It is worth noting that objects that allow native iteration in their visible properties (“just an object” type) are not included in the iterable type. However, the practical value of the iteration on such objects is not particularly great, so there is no reason to be upset ...

What else to read?



Successes on interview and in work!

Source: https://habr.com/ru/post/324934/


All Articles