PHP Exception Handling - ErrorException

PHP Exception Handling – ClosedGeneratorException

Moving along in our detailed PHP Exception Handling series, today we’ll take a look at the PHP ClosedGeneratorException. A closed generator exception occurs when attempting to perform a traversal on a generator that has already been closed or terminated.

Throughout this article we’ll explore the ClosedGeneratorException in more detail, digging into where it resides in the PHP Exception Hierarchy, along with some functional code samples that will illustrate how these errors are commonly thrown, so let’s get going!

The Technical Rundown

  • All PHP errors implement the Throwable interface, or are extended from another inherited class therein.
  • Exception implements the Throwable interface.
  • ClosedGeneratorException extends the Exception class.

When Should You Use It?

To understand what might cause a ClosedGeneratorException we first need to understand the purpose of generators in PHP and how they work. Put simply, a generator is an easy way to implement an iterator, without the need to implement all the normal methods and functionality that the Iterator interface normally requires.

To illustrate the differences between iterators and generators, we’ve got three different examples. The full working code sample can be found below, provided for easy copy-and-pasting if you wish to try it out yourself. After this code block we’ll go over the code in more detail and explain what’s going on:


We’ll begin with a typical iterator class that inherits from the Iterator interface. Note: PHP includes a number of built-in iterators, including one for iterating over simple collections like Arrays. However, for our purposes, we’ve created our own iterator to show how they typically work under the hood.

Nothing too fancy going on here. The basic purpose of this class is to accept a collection of elements, iterating through each subsequent element every time next() is called. The current element is retrieved via the current() method.

To test out this iterator (along with our generators) we have a Book class that we’ll use to create a collection of books:

Now, the executeExamples() function creates the baseline $books collection and passed it to each of our example functions:

We begin with the iteratorExample() function, which passes the $books collection to a new CollectionIterator instance, then tests that everything works as expected by iterating a couple times and outputting the results before rewinding and checking that the next() iteration retrieves the first element again:

Sure enough, the output shows we iterated through the first two elements, rewound, then retrieved the first element once again, as intended:

Generators

Iterators are all well and good, but their code and usage can become rather complex if we’re not careful. Hence, the introduction of generators in PHP 5.5, which aim to simplify the code necessary to create iterators. Generators rely on the yield keyword, which you’re probably familiar with from many other languages. Its purpose is to pause execution at the yield statement location, return the currently yielded value, then resume execution from the previous pause point during the next call/iteration.

Creating a generator is as easy as pie: Just create a normal function and add a yield statement where you want execution to pause and an iterable or sequential value to be returned. For example, here’s our basic generator function that simply iterates over a collection and yields each element, in order:

The generatorExample() function uses the generator() function and performs similar logic to our iteratorExample() function. It outputs the first two elements, then performs a rewind to reset the iterator state, then tries to output the first element again.

Running this function works fine until we hit the rewind() method call, which throws an Exception because we’re not allowed to rewind a generator that has already yielded at least one value:

Generators were built with this restriction in mind because their purpose is to be one-time sources of iterable data — resetting their state and iterating again is contrary to those principles.

For our last example we’ve created the generatorSelfClosing() function, which is similar to our previous generator except it intentionally closes (terminates) itself after the first iteration. The return statement triggers a closing and termination of the active generator:

This time, in our test function we’ll try iterating over our elements after the generator has already been closed/terminated:

The expectation is that the first element is retrieved fine, but the second element retrieval will return null, since we explicitly closed the generator by returning null on the second iteration call. Then, as the output shows, we end up throwing a ClosedGeneratorException exception when we attempt to iterate through our now-closed generator:

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application’s entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you’ll be constantly aware of your application’s health, including any issues that may arise. Best of all, with Airbrake’s robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.