Sunday, December 26, 2021

How to Use Yield and Generators in PHP

How to Use Yield and Generators in PHP

We have written multiple tutorials about functions in PHP. This includes an overview of basic concepts like function return values and parameters and anonymous functions in PHP. One thing common in all the functions that we defined in those two tutorials is that they had a single return value. Functions in PHP always return a single value and don't execute any code that comes after the return statement. Unless they are generator functions!

In this tutorial, we will delve a bit deeper into the topic of functions in PHP and learn about the yield keyword and generator functions in PHP.

The Yield Keyword and Generator Functions in PHP

The word yield means produce or generate which is basically what the keyword does in PHP. It generates a value for use outside your function without entirely stopping its execution and returning. It gives you the ability to pause the execution while you do something else with the returned value. Functions which uses the yield keyword are called generator functions.

A yield statement looks similar to the return statement but it is different in the way it behaves. The following example will help you understand the difference easily.

We have created two functions called generator_loop() and simple_loop(). They look exactly the same except for the fact that the generator loop uses yield while the simple loop uses a return statement. The return statement causes the simple_loop() function to simply output the value of $i after first iteration over the for loop. However, a call to generator_loop() creates a Generator object.

We can iterate over this generator object using a foreach loop and that is what we do next. HWe simply echo the values as we iterate over them.

Advantages of Using Generators

One thing that will cross your mind now is that we can modify the simple_loop() function to create an array and then iterate over the array using a foreach loop as shown below.

Now, we are getting the same output as our generator function so what's the point of using yield to define a generator function and loop over the values?

It might not be directly obvious but one of the advantages of using generators is low memory usage. The second version of our simple_loop() function creates and returns an entire array. This might not cause a problem in this simple case but it will become an issue if you have to iterate over a very large number of items.

Another advantage of not being forced to create an entire array beforehand is that you can easily loop over an unknown number of elements or stream of data. I will use random alphabetic string as our data to illustrate this point. You can read this post to learn more about generating random alphanumeric string in PHP.

We write get_data() as a generator function that yields a random string of 16 characters returned by another function. You should note that unlike our first example, the generator function here contains an infinite loop. Therefore, it will keep generating random strings as long as we want.

We keep iterating over these strings inside our foreach loop until we encounter a string that contains the word apple. Running this code three times gets us to a string containing apple in different number of attempts. In the above example, we could be getting our data from somewhere else like a live data stream and keep processing it as long as we wanted.

As you have seen, generators yield one value at a time. This can also prove useful in situations where you are writing code to create list of items that need to be processed. Lets say you want to create a sequence of a million numbers, strings or something else. You can use generators to create and process them one at a time without waiting for the whole sequence to be ready.

Yielding Values by Reference

Now we will see how to write a generator function that can yield values by reference. This simply involves prepending an ampersand to the function name. Yielding values by reference allows you to make changes to the yielded variable during iterations and have the values stick inside the generator.

This time we created another function to generate random hexadecimal strings and we check within the function if any of those strings contains the word decade. We keep yielding a new random string as long as the string does not contain the word decade. Please note that the string is generated inside the foreach loop while the check is made inside the get_alt_data() function.

Yielding Key-Value Pairs

Generator functions can also yield key-value pairs. You can iterate over them just like you would iterate over key-value pairs in an associative array. The keys will be numeric and start from zero by default but you can also create your own custom keys. In the following example, I use the default keys to eliminate the need for using a counter variable.

The numeric keys will auto-increment by 1 after each iteration so they will act like a counter themselves. All we have to do is increment the $idx value by 1 to get the proper count because the keys start from 0.

Generator Delegation

Generator delegation was implemented in PHP 7.0. This allows you to yield values from another generator, array or Traversable object within your outer generator function. One of the advantages of using generator delegation is that it allows you to refactor your code and improve readability. Here is an example that creates two different types of data inside a generator function yielded by other generator functions.

In the above example, we have a generate_string() function that outputs a random string of 16 characters. The check_name() function is our generator that yields a string which contains our specified name. It then goes on to yield the number of attempts. The divide_in_parts() function is our other generator function that yields three different combinations of numbers that add up to given number.

We run a for loop inside our main_function() which accepts an array of mixed values. After that we use yield from with our two functions alternately.

There are two things worth keeping in mind here. First, the functions yield all the values that they can in each run. This is why the check_name() function outputs two values and divide_in_parts() outputs three values. Second, the keys start from zero with each call to either of these functions. Expecting the keys to increase with every call might give you strange results.

Final Thoughts

This tutorial was meant to introduce you to basic concepts related to yield and generators in PHP. You should now have a better understanding of the purpose of the yield keyword and generator functions and how to define your own generator functions. We have also covered some advantages of using generators like low memory usage, the ability to pause function execution and generate values for consumption on demand. Next sections discussed how to use generators to yield values by reference or to produce key-value pairs. Finally, we moved to generator delegation that can help you write better and more readable code.


No comments:

Post a Comment