PHPackages                             mano/generator-plus - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. mano/generator-plus

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

mano/generator-plus
===================

GeneratorPlus is a PHP library that wraps and extends the functionality of generators, providing enhanced features for more powerful and flexible generator usage.

00PHP

Since Jun 27Pushed 2y ago1 watchersCompare

[ Source](https://github.com/hejnym/generator-plus)[ Packagist](https://packagist.org/packages/mano/generator-plus)[ RSS](/packages/mano-generator-plus/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

GeneratorPlus
=============

[](#generatorplus)

`GeneratorPlus` is a wrapper around the native `Generator` class that provides additional methods for more control over the iteration process.

Main Purposes
-------------

[](#main-purposes)

### 1. Fix the problem with calling `send` method in a `foreach` loop

[](#1-fix-the-problem-with-calling-send-method-in-a-foreach-loop)

In native PHP, calling the `send` method inside a `foreach` loop is problematic because it advances the generator to the next yield, disrupting the loop's iteration. `GeneratorPlus` solves this issue with the `sendInForeach` method, which ensures you can send values into the generator without moving to the next yield, maintaining the integrity of the loop.

**Problem Example:**In this example, calling `send` inside the `foreach` loop causes the generator to advance to the next `yield` statement, disrupting the loop and skipping iterations, leading to unintended behavior.

```
function getGenerator(): \Generator {
    $sent = [];
    $sent[] = yield 1;
    $sent[] = yield 2;
    $sent[] = yield 3;
    $sent[] = yield 4;
    $sent[] = yield 5;

    return $sent;
}

$generator = getGenerator();

$items = [];
foreach ($generator as $item) {
    $items[] = $item;
    $generator->send(9); // This advances the generator, skipping iterations
}

var_dump($items); // Outputs [1, 3, 5];
var_dump($generator->getReturn()); // Outputs [9, null, 9, null, 9];
```

### 2. Provide mechanism to communicate with the generator caller during the Loop

[](#2-provide-mechanism-to-communicate-with-the-generator-caller-during-the-loop)

Unlike the native `getReturn` method, which only allows communication at the end of the generator's execution, `GeneratorPlus` provides a mechanism to communicate with the generator caller during the loop. This reverse communication allows for more dynamic interactions and event handling within the generator lifecycle.

Installation
------------

[](#installation)

You can install `GeneratorPlus` via Composer:

```
composer require mano/generator-plus
```

Usage
-----

[](#usage)

### Creating a GeneratorPlus Instance

[](#creating-a-generatorplus-instance)

To create an instance of `GeneratorPlus`, use the `createFromCallable` method, which takes a closure that returns a generator. There are two reasons why a closure must be used instead of creating it directly from the generator:

- Generators cannot be cloned, so using a closure ensures that the original generator is not modified.
- The EventDispatcher must be passed to the generator.

```
use Mano\GeneratorPlus\GeneratorPlus;
use Mano\GeneratorPlus\EventDispatcher\GeneratorEventDispatcher;

$generatorPlus = GeneratorPlus::createFromCallable(function(GeneratorEventDispatcher $eventDispatcher) {
    yield 1;
    $sent = yield 2;

    if ($sent === 'foo') {
        yield 7;
    }

    $eventDispatcher->dispatch(new MyCustomEvent('There is just one item left!'));

    yield 3;

    return 'bar';
});
```

### Attaching Events

[](#attaching-events)

You can attach events to the generator lifecycle using the `attachEvent` method:

```
use Mano\GeneratorPlus\EventDispatcher\GeneratorPlusEvent;

$generatorPlus->attachEvent(MyCustomEvent::class, function(MyCustomEvent $event) {
    echo "Oh my! The generator wants something! " . $event->getMessage();
});
```

### Iterating with `sendInForeach`

[](#iterating-with-sendinforeach)

The `sendInForeach` method allows you to send values into the generator within a `foreach` loop without disrupting the loop's iteration:

```
function getGenerator(): \Generator {
    $sent = [];
    $sent[] = yield 1;
    $sent[] = yield 2;
    $sent[] = yield 3;
    $sent[] = yield 4;
    $sent[] = yield 5;

    return $sent;
}

$generator = getGenerator();

$items = [];
foreach ($generator as $item) {
    $items[] = $item;
    $generator->sendInForeach(9); // This does not advance the generator
}

var_dump($items); // Outputs [1, 2, 3, 4, 5];
var_dump($generator->getReturn()); // Outputs [9, 9, 9, 9, 9];
```

### Real-World Example

[](#real-world-example)

When dealing with batch processing in Doctrine, `GeneratorPlus` can come in handy. Usually, you need to flush and clear the entity manager after some batch size to prevent memory issues. Employing the generator's event dispatcher can convey the message that one chunk has been processed to fire an event that clears the entity manager. If you clear the entity manager in the middle of a batch, residual objects would be detached from the manager, leading to errors.

```
class SomeRepository
{
	public function getSomeEntityBasedOnCondition(GeneratorEventDispatcher $eventDispatcher = null)
	{
		$counter = 0;

		while (true) {
			$qb = $this->entityManager->createQueryBuilder()
				->where('...') // select all entities that have not been updated yet
				->setMaxResults(100);

			$result = $qb->getQuery()->getResult();

			if (count($result) === 0) {
				break;
			}

			foreach ($result as $item) {
				yield $item;
				$counter++;
			}

			if($eventDispatcher !== null) {
				// let the client code know about reaching the end of the batch
				$eventDispatcher->dispatch(new MyCustomFlushEvent($counter));
			}

		}
	}
}

class SomeController
{
	public function __construct(private SomeRepository $repository)
	{
	}

	public function someAction()
	{
		$generatorPlus = GeneratorPlus::createFromCallable(function (EventDispatcher $eventDispatcher) {
			return $this->repository->getSomeEntityBasedOnCondition($eventDispatcher);
		});

		// Or use a different style if not any other arguments needed
		// $generatorPlus = GeneratorPlus::createFromCallable([$this->repository, 'getSomeEntityBasedOnCondition']);

		$generatorPlus->attachEvent(MyCustomFlushEvent::class, function (MyCustomFlushEvent $event) {
			// flush at the end of the batch
			$this->entityManager->flush();
			if ($event->getCount() % 500 === 0) {
				// clear the entity manager
				$this->entityManager->clear();
			}
		});

		foreach ($generatorPlus as $item) {
			// some batch action
			$item->setFoo(...);
		}
	}
}
```

License
-------

[](#license)

This project is licensed under the MIT License. See the LICENSE file for details.

Contributing
------------

[](#contributing)

Contributions are welcome! Please submit a pull request or open an issue to discuss any changes.

###  Health Score

11

—

LowBetter than 0% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity0

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity18

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/132727?v=4)[Mano](/maintainers/mano)[@mano](https://github.com/mano)

---

Top Contributors

[![hejnym](https://avatars.githubusercontent.com/u/48101674?v=4)](https://github.com/hejnym "hejnym (8 commits)")

### Embed Badge

![Health badge](/badges/mano-generator-plus/health.svg)

```
[![Health](https://phpackages.com/badges/mano-generator-plus/health.svg)](https://phpackages.com/packages/mano-generator-plus)
```

###  Alternatives

[bueltge/must-use-loader

Load Must-Use Plugins inside subdirectories with caching. For delete the cache: if you view the Must Use plugin list in the network administration.

6115.7k2](/packages/bueltge-must-use-loader)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
