Tukio 2.0 released - Event Dispatcher for PHP

in PHP17 days ago

I've just released version 2.0 of Crell/Tukio! Available now from your favorite Packagist.org. Tukio is a feature-complete, easy to use, robust Event Dispatcher for PHP, following PSR-14. It began life as the PSR-14 reference implementation.

Tukio 2.0 is almost a rewrite, given the amount of cleanup that was done. But the final result is a library that is vastly more robust and vastly easier to use than version 1, while still producing near-instant listener lookups.

Some of the major improvements include:

  • It now uses Topological sorting internally, rather than priority sorting. Both are still supported, but the internal representation has changed. The main benefits are cycle detection and support for multiple before/after rules per listener.
  • The API has been greatly simplified, thanks to PHP 8 and named arguments. It's now down to essentially two methods -- listener() and listenerService(), both of which should be used with named arguments for maximum effect. The old API methods are still supported, but deprecated to allow users to migrate to the new API.
  • Tukio can now auto-derive more information about your listeners, making registration even easier.
  • It now uses the powerful Crell/AttributeUtils library for handling attribute-based registration. That greatly simplified a lot of code while making several new features easy.
  • Attributes are now supported on the class level, not just method. That makes building single-method listener services trivially easy.

Listener classes

The last point bears extra mention. While Tukio supports numerous ways of organizing and configuring your listenres, the recommended way to register a listener with is now to use this pattern, with attributes:

#[ListenerPriority(priority: 5)]
#[ListenerAfter(OtherListener::class)]
class CollectListener
{
    public function __construct(public readonly Dep $someDependency) {}

    public function __invoke(CollectingEvent $event): void
    {
        $event->add(static::class);
    }
}

$provider->listenerService(CollectListener::class);

Now, ensure that CollectListener and OtherListener are both registered with your DI container using their class names. That's it, that's all, you're done. The __invoke() method will be registered as the listener method to call, while you can specify any dependencies it requires in the constructor. The DI container should auto-wire them for you. (If it doesn't, get a better DI container.) Now, any time the CollectingEvent event is fired, the CollectListener service will be loaded, given its dependencies, and then invoked.

Event Optimization: not new, but so so cool!

Tukio supports both runtime and compilable listener providers using the same API. In most cases, you'll want to use the compiled provider for better performance. However, you can get even more performance by telling Tukio ahead of time which events it should expect to see. (Odds are this can be automated by a scan of your codebase, but manually also works.) It will then build a direct lookup table in the compiled listener. The result is a constant-time simple array lookup for those events, also known as "virtually instantaneous." For example:

use Crell\Tukio\ProviderBuilder;
use Crell\Tukio\ProviderCompiler;

$builder = new ProviderBuilder();

$builder->listener('listenerA', priority: 100);
$builder->listener('listenerB', after: 'listenerA');
$builder->listener([Listen::class, 'listen']);
$builder->listenerService(MyListener::class);
$builder->addSubscriber('subscriberId', Subscriber::class);

// Here's where you specify what events you know you will have.
// Returning the listeners for these events will be near instant.
$builder->optimizeEvent(EvenOne::class);
$builder->optimizeEvent(EvenTwo::class);

$compiler = new ProviderCompiler();

// Write the generated compiler out to a file.
$filename = 'MyCompiledProvider.php';
$out = fopen($filename, 'w');

$compiler->compileAnonymous($builder, $out);

fclose($out);

Backward compatibility

Tukio v2 should be 99% a drop-in replacement for Tukio v1. I deliberately tried to keep the old API intact for now to make upgrading easier, though it is marked @deprecated to encourage developers to migrate to the more robust new API methods. If something isn't a clean drop-in, let me know on GitHub and I'll see if it's resolvable.

There is one potential BC challenge: In Tukio v1, if two listeners were specified without any ordering information, they would almost always end up triggering in the order in which they were added. That was not guaranteed, however, and the documentation warned against relying on it. Tukio v2 uses a new, topological sort-based sorting algorithm that is considerably more robust; however, the predictability of "lexically first, fires first" is no longer there. The order in which un-ordered listeners will trigger is unpreditable, though it should be stable. If you find you were inadvertently relying on the implicit ordering before, the fix is to add before/after rules to your listeners to make the intended ordering explicit.

Give it a try in your project today!

Sort:  

Thanks for your contribution to the STEMsocial community. Feel free to join us on discord to get to know the rest of us!

Please consider delegating to the @stemsocial account (85% of the curation rewards are returned).

You may also include @stemsocial as a beneficiary of the rewards of this post to get a stronger support.