PHPackages                             hiraeth/signal - 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. hiraeth/signal

ActiveOpus-package[Utility &amp; Helpers](/categories/utility)

hiraeth/signal
==============

A generic callback wrapper which enables lazy DI instantiation

2.0.0(2y ago)18.8k↓50%5MITPHP

Since Nov 4Pushed 2y ago1 watchersCompare

[ Source](https://github.com/hiraeth-php/signal)[ Packagist](https://packagist.org/packages/hiraeth/signal)[ RSS](/packages/hiraeth-signal/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (4)Versions (3)Used By (5)

Signal
======

[](#signal)

Signal is a generic callback wrapper / proxy which enables custom resolution, including lazy instantiation and dependency injection in places where it might not otherwise be supported.

If you think you know what the above means and want to skip the explanation you can simply:

Install
-------

[](#install)

```
composer require hiraeth/signal

```

Othewise, read on to understand...

What's the Problem?
-------------------

[](#whats-the-problem)

I was looking at using `igorw/evenement` and saw this:

```
$emitter->on('user.created', function (User $user) use ($logger) {
    $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
});
```

Lots of libraries support generic callbacks which are usually shown as anonymous functions. This is great if you write a lot of custom wiring, not so great if you separate wiring/configuration data from wiring logic, and use classes, e.g.:

FILE: `config.php`

```
return [
	'events' => [
		'user.created' => 'UserCreatedHandler'
	]
]
```

Now when you read the config, you could do:

```
foreach ($config['events'] as $event => $handler) {
	$emitter->on($event, $container->get($handler))
}
```

But then you're resolving all your handlers and their dependencies up front. So maybe instead you do:

```
foreach ($config['events'] as $event => $handler) {
	$emitter->on($event, function(...$params) use ($container, $handler) {
		$handler = $container->get($handler);
		return $handler(...$params);
	}
}
```

OK, that's not so bad, but now you're kind of assuming your handlers will always implement `__invoke`. And what if you need to handle different callback styles elsewhere? Now you have custom proxy callbacks all over.

Is There a Better Way?
----------------------

[](#is-there-a-better-way)

Yes! **ONE CALLBACK (RESOLVER) TO RULE THEM ALL!**

```
$signal = new Hiraeth\Utils\Signal(function($signal) use ($container) {
	if (is_string($signal)) {
		if (function_exists($signal)) {
			return $signal;
		}

		if (strpos($signal, '::') !== FALSE) {
			list($class, $method) = explode('::', $signal);

			return [$container->get($class), $method];
		}

		if (class_exists($signal)) {
			return [$container->get($signal), '__invoke'];
		}
	}

	return NULL;
});
```

Then:

```
foreach ($config['events'] as $event => $handler) {
	$emitter->on($event, $signal->create($handler))
}
```

Isn't that nice?

If you want to make it even nicer, you can move your resolver functionality into a separate class that implements `__invoke($signal)`:

```
$resolver = new Resolver($container);
$signal   = new Hiraeth\Utils\Signal($resolver);
```

Is that All?
------------

[](#is-that-all)

No... because whatever `$handler` is gets passed to your custom resolver, you can do whatever you want when you resolve the handler. For example, maybe you want to handle "artisan" callbacks (not sure why, but whatever):

```
$signal = str_replace('@', '::', $signal);
```

Maybe you want to create URL callbacks:

```
if (preg_match('#^https?://#', $signal)) {
	$client = $container->get('APIClient');
	$client->setUrl($signal);

	return function() use ($client) {
		$client->setData(func_get_args());
		$client->send();
	};
}
```

Who the hell knows! The world is your oyster.

OK, Aren't You Still Instantiating Everything Up Front?
-------------------------------------------------------

[](#ok-arent-you-still-instantiating-everything-up-front)

No... `$signal->create($handler)` does not return the resolved handler. Rather, it merely tracks the `$handler` and returns a proxy callback in its place, so the handler isn't resolved until it actually needs to be. See the one test it has:

```
class SignalTest extends PHPUnit\Framework\TestCase
{
	public function testProxy()
	{
		//
		// Create a new instance of signal with a totally useless
		// resolver that always returns the same callback.
		//

		$signal = new Hiraeth\Utils\Signal(function($signal) use (&$target) {
			$this->assertSame($signal, 'fake_signal');

			return $target = new class {
				public function __invoke($foo, $bar)
				{
					return $foo . ' ' . $bar;
				}
			};
		});

		//
		// Create a new proxy for the signal as $foobar
		// this does not yet call the resolver, rather $foobar
		// contains a callback that will call the resolver when
		// it gets called.
		//

		$foobar = $signal->create('fake_signal');

		$this->assertNotSame($target, $foobar);

		//
		// Calling foobar resolves the signal to a target handler
		// our anonymous class above, and calls it to get the result.
		//

		$result = $foobar('foo', 'bar');

		$this->assertSame($result, 'foo bar');

		//
		// Once resolved, calling $signal->create() will return the
		// resolved handler directly without a proxy.
		//

		$newbar = $signal->create('fake_signal');

		$this->assertSame($target, $newbar);
	}
}
```

Run The Test Yourself
---------------------

[](#run-the-test-yourself)

```
php vendor/bin/phpunit --bootstrap vendor/autoload.php test/cases

```

###  Health Score

28

—

LowBetter than 54% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity25

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 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.

###  Release Activity

Cadence

Every ~131 days

Total

2

Last Release

795d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4ab2b91daf2aa6e4c7f98f785a3135daa3e9998bd7021d89df05a374e28ecde5?d=identicon)[mattsah](/maintainers/mattsah)

---

Top Contributors

[![mattsah](https://avatars.githubusercontent.com/u/586346?v=4)](https://github.com/mattsah "mattsah (18 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/hiraeth-signal/health.svg)

```
[![Health](https://phpackages.com/badges/hiraeth-signal/health.svg)](https://phpackages.com/packages/hiraeth-signal)
```

###  Alternatives

[symfony/dependency-injection

Allows you to standardize and centralize the way objects are constructed in your application

4.2k431.1M7.5k](/packages/symfony-dependency-injection)[illuminate/contracts

The Illuminate Contracts package.

704122.9M10.1k](/packages/illuminate-contracts)[illuminate/container

The Illuminate Container package.

31278.1M2.0k](/packages/illuminate-container)[ecotone/ecotone

Supporting you in building DDD, CQRS, Event Sourcing applications with ease.

558549.8k17](/packages/ecotone-ecotone)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

728272.9k20](/packages/civicrm-civicrm-core)[internal/dload

Downloads binaries.

98142.7k10](/packages/internal-dload)

PHPackages © 2026

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