Skip to content

Instantly share code, notes, and snippets.

@bnf
Last active January 31, 2019 13:27
Show Gist options
  • Save bnf/bbc431b6e3c31613c01fb685a6fa30cb to your computer and use it in GitHub Desktop.
Save bnf/bbc431b6e3c31613c01fb685a6fa30cb to your computer and use it in GitHub Desktop.
Chained PSR-14 ListenerProviderInteface's, autoconfigured using container-interop/service-providers
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use Bnf\Di\Container;
use Crell\Tukio\Dispatcher;
use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
class Provider implements ListenerProviderInterface
{
/** @var ?ListenerProviderInterface */
private $previousProvider = null;
/** @var array */
private $listeners = [];
public function __construct(ListenerProviderInterface $previous = null)
{
$this->previousProvider = $previous;
}
public function getListenersForEvent(object $event) : iterable
{
if ($this->previousProvider !== null) {
yield from $this->previousProvider->getListenersForEvent($event);
}
foreach ($this->listeners as $eventType => $listeners) {
foreach ($listeners as $listener) {
yield $listener;
}
}
}
public function attach(string $eventType, callable $listener): void {
$this->listeners[$eventType][] = $listener;
}
}
class Package1Provider extends Provider {};
class Package2Provider extends Provider {};
class Package3Provider extends Provider {};
$core = new class implements ServiceProviderInterface {
public function getFactories()
{
return [
EventDispatcherInterface::class => function (ContainerInterface $container): EventDispatcherInterface {
return new Dispatcher($container->get(ListenerProviderInterface::class));
},
];
}
public function getExtensions()
{
return [];
}
};
$package1 = new class implements ServiceProviderInterface {
public function getFactories()
{
return [];
}
public function getExtensions()
{
return [
// Concatenate package2 ListenerProvider to (possibly) existing ListenerProviders
ListenerProviderInterface::class => function (ContainerInterface $container, ListenerProviderInterface $previous = null): ListenerProviderInterface {
$provider = new Package1Provider($previous);
$provider->attach(stdClass::class, function() {
echo 'a';
});
return $provider;
},
];
}
};
$package2 = new class implements ServiceProviderInterface {
public function getFactories()
{
return [];
}
public function getExtensions()
{
return [
// Concatenate package2 ListenerProvider to (possibly) existing ListenerProviders
ListenerProviderInterface::class => function (ContainerInterface $container, ListenerProviderInterface $previous = null): ListenerProviderInterface {
$provider = new Package2Provider($previous);
$provider->attach(stdClass::class, function() {
echo 'b';
});
return $provider;
},
];
}
};
class ListenerProviderChain implements ListenerProviderInterface
{
/** @var ListenerProviderInterface */
protected $a, $b;
public function __construct(ListenerProviderInterface $a, ListenerProviderInterface $b)
{
$this->a = $a;
$this->b = $b;
}
public function getListenersForEvent(object $event) : iterable
{
yield from $this->a->getListenersForEvent($event);
yield from $this->b->getListenersForEvent($event);
}
}
$package3 = new class implements ServiceProviderInterface {
public function getFactories()
{
return [
Package3Provider::class => function (ContainerInterface $container): Package3Provider {
$provider = new Package3Provider();
$provider->attach(stdClass::class, function() {
echo 'c';
});
return $provider;
},
];
}
public function getExtensions()
{
return [
// Concatenate package3 provider using a helper class to existing providers.
// That allows Package3Provider to be configured and instantiated separtely (if that's needed)
// This pattern means that using 5 providers would result in 4 classes that chain these:
// (C=chain class, P=provider class, D=dispatcher):
// D
// |
// C4
// / \
// C3 P5
// / \
// C2 P4
// / \
// C1 P3
// / \
// P1 P2
ListenerProviderInterface::class => function (ContainerInterface $container, ListenerProviderInterface $previous = null): ListenerProviderInterface {
$provider = $container->get(Package3Provider::class);
return $previous === null ? $provider : new ListenerProviderChain($previous, $provider);
},
];
}
};
// Will output 'abc'
(new Container([$core, $package1, $package2, $package3]))->get(EventDispatcherInterface::class)->dispatch(new stdClass);
echo PHP_EOL;
// Will output 'cba'
(new Container([$core, $package3, $package2, $package1]))->get(EventDispatcherInterface::class)->dispatch(new stdClass);
echo PHP_EOL;
// Will output 'a'
(new Container([$core, $package1]))->get(EventDispatcherInterface::class)->dispatch(new stdClass);
echo PHP_EOL;
// Will output 'b'
(new Container([$core, $package2]))->get(EventDispatcherInterface::class)->dispatch(new stdClass);
echo PHP_EOL;
{
"name": "bnf/psr14-service-provider-example",
"private": true,
"require": {
"psr/event-dispatcher": "0.7.0",
"container-interop/service-provider": "^0.4.0",
"crell/tukio": "^0.7.2",
"bnf/di": "^0.1.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment