Skip to content

Commit 4ddf5e4

Browse files
committed
Lazy container listeners
1 parent f8510f3 commit 4ddf5e4

File tree

10 files changed

+465
-330
lines changed

10 files changed

+465
-330
lines changed

app/config.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use Colors\Color;
44
use function DI\object;
55
use function DI\factory;
6+
use function PhpSchool\PhpWorkshop\Event\containerListener;
67
use Interop\Container\ContainerInterface;
78
use League\CommonMark\DocParser;
89
use League\CommonMark\Environment;
@@ -260,5 +261,35 @@
260261
'@shakeyShane' => 'Shane Osbourne',
261262
'@chris3ailey' => 'Chris Bailey'
262263
],
263-
'appContributors' => []
264+
'appContributors' => [],
265+
266+
'eventListeners' => [
267+
'prepare-solution' => [
268+
'verify.start' => [
269+
containerListener(PrepareSolutionListener::class),
270+
],
271+
'run.start' => [
272+
containerListener(PrepareSolutionListener::class),
273+
],
274+
],
275+
'code-patcher' => [
276+
'run.start' => [
277+
containerListener(CodePatchListener::class, 'patch'),
278+
],
279+
'verify.pre.execute' => [
280+
containerListener(CodePatchListener::class, 'patch'),
281+
],
282+
'verify.post.execute' => [
283+
containerListener(CodePatchListener::class, 'revert'),
284+
],
285+
'run.finish' => [
286+
containerListener(CodePatchListener::class, 'revert'),
287+
],
288+
],
289+
'self-check' => [
290+
'verify.post.check' => [
291+
containerListener(SelfCheckListener::class)
292+
],
293+
],
294+
],
264295
];

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
"autoload" : {
4141
"psr-4" : {
4242
"PhpSchool\\PhpWorkshop\\": "src"
43-
}
43+
},
44+
"files": [
45+
"src/Event/functions.php"
46+
]
4447
},
4548
"autoload-dev": {
4649
"psr-4": {

src/Event/ContainerListenerHelper.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\Event;
4+
5+
/**
6+
* @author Aydin Hassan <[email protected]>
7+
*/
8+
class ContainerListenerHelper
9+
{
10+
/**
11+
* @var string
12+
*/
13+
private $service;
14+
15+
/**
16+
* @var string
17+
*/
18+
private $method;
19+
20+
/**
21+
* @param $service
22+
* @param string $method
23+
*/
24+
public function __construct($service, $method = '__invoke')
25+
{
26+
$this->service = $service;
27+
$this->method = $method;
28+
}
29+
30+
/**
31+
* @return string
32+
*/
33+
public function getService()
34+
{
35+
return $this->service;
36+
}
37+
38+
/**
39+
* @return string
40+
*/
41+
public function getMethod()
42+
{
43+
return $this->method;
44+
}
45+
}

src/Event/functions.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\Event;
4+
5+
if (!function_exists('PhpSchool\PhpWorkshop\Event\containerListener')) {
6+
7+
/**
8+
* @param string $service
9+
* @param string $method
10+
* @return ContainerListenerHelper
11+
*/
12+
function containerListener($service, $method = '__invoke')
13+
{
14+
return new ContainerListenerHelper($service, $method);
15+
}
16+
}

src/Factory/EventDispatcherFactory.php

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44

55
use Interop\Container\ContainerInterface;
66
use PhpSchool\PhpWorkshop\Event\EventDispatcher;
7+
use PhpSchool\PhpWorkshop\Event\ContainerListenerHelper;
78
use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException;
8-
use PhpSchool\PhpWorkshop\Listener\CodePatchListener;
9-
use PhpSchool\PhpWorkshop\Listener\PrepareSolutionListener;
10-
use PhpSchool\PhpWorkshop\Listener\SelfCheckListener;
119
use PhpSchool\PhpWorkshop\ResultAggregator;
10+
use PhpSchool\PhpWorkshop\Utils\Collection;
1211

1312
/**
1413
* Class EventDispatcherFactory
@@ -27,36 +26,59 @@ public function __invoke(ContainerInterface $container)
2726
{
2827
$dispatcher = new EventDispatcher($container->get(ResultAggregator::class));
2928

30-
$prepareSolutionListener = $container->get(PrepareSolutionListener::class);
31-
$dispatcher->listen('verify.start', $prepareSolutionListener);
32-
$dispatcher->listen('run.start', $prepareSolutionListener);
33-
34-
$codePatcherListener = $container->get(CodePatchListener::class);
35-
$dispatcher->listen('verify.pre.execute', [$codePatcherListener, 'patch']);
36-
$dispatcher->listen('verify.post.execute', [$codePatcherListener, 'revert']);
37-
$dispatcher->listen('run.start', [$codePatcherListener, 'patch']);
38-
$dispatcher->listen('run.finish', [$codePatcherListener, 'revert']);
39-
40-
$dispatcher->listen('verify.post.check', $container->get(SelfCheckListener::class));
41-
4229
//add listeners from config
4330
$eventListeners = $container->has('eventListeners') ? $container->get('eventListeners') : [];
4431

4532
if (!is_array($eventListeners)) {
4633
throw InvalidArgumentException::typeMisMatch('array', $eventListeners);
4734
}
48-
49-
array_walk($eventListeners, function ($listeners, $eventName) use ($dispatcher, $container) {
50-
if (!is_array($listeners)) {
51-
throw InvalidArgumentException::typeMisMatch('array', $listeners);
35+
36+
array_walk($eventListeners, function ($events) {
37+
if (!is_array($events)) {
38+
throw InvalidArgumentException::typeMisMatch('array', $events);
5239
}
40+
});
5341

42+
$eventListeners = $this->mergeListenerGroups($eventListeners);
43+
44+
array_walk($eventListeners, function ($listeners, $eventName) use ($dispatcher, $container) {
5445
$this->attachListeners($eventName, $listeners, $container, $dispatcher);
5546
});
5647

5748
return $dispatcher;
5849
}
5950

51+
/**
52+
* @param array $listeners
53+
* @return array
54+
*/
55+
private function mergeListenerGroups(array $listeners)
56+
{
57+
$listeners = new Collection($listeners);
58+
59+
return $listeners
60+
->keys()
61+
->reduce(function (Collection $carry, $listenerGroup) use ($listeners) {
62+
$events = new Collection($listeners->get($listenerGroup));
63+
64+
return $events
65+
->keys()
66+
->reduce(function (Collection $carry, $event) use ($events) {
67+
$listeners = $events->get($event);
68+
69+
if (!is_array($listeners)) {
70+
throw InvalidArgumentException::typeMisMatch('array', $listeners);
71+
}
72+
73+
return $carry->set(
74+
$event,
75+
array_merge($carry->get($event, []), $listeners)
76+
);
77+
}, $carry);
78+
}, new Collection)
79+
->getArrayCopy();
80+
}
81+
6082
/**
6183
* @param string $eventName
6284
* @param array $listeners
@@ -71,26 +93,32 @@ private function attachListeners(
7193
EventDispatcher $dispatcher
7294
) {
7395
array_walk($listeners, function ($listener) use ($eventName, $dispatcher, $container) {
74-
if (is_callable($listener)) {
75-
return $dispatcher->listen($eventName, $listener);
96+
if ($listener instanceof ContainerListenerHelper) {
97+
if (!$container->has($listener->getService())) {
98+
throw new InvalidArgumentException(
99+
sprintf('Container has no entry named: "%s"', $listener->getService())
100+
);
101+
}
102+
103+
return $dispatcher->listen($eventName, function (...$args) use ($container, $listener) {
104+
$service = $container->get($listener->getService());
105+
106+
if (!method_exists($service, $listener->getMethod())) {
107+
throw new InvalidArgumentException(
108+
sprintf('Method "%s" does not exist on "%s"', $listener->getMethod(), get_class($service))
109+
);
110+
}
111+
112+
$service->{$listener->getMethod()}(...$args);
113+
});
76114
}
77115

78-
if (!is_string($listener)) {
116+
if (!is_callable($listener)) {
79117
throw new InvalidArgumentException(
80118
sprintf('Listener must be a callable or a container entry for a callable service.')
81119
);
82120
}
83121

84-
if (!$container->has($listener)) {
85-
throw new InvalidArgumentException(sprintf('Container has no entry named: "%s"', $listener));
86-
}
87-
88-
$listener = $container->get($listener);
89-
90-
if (!is_callable($listener)) {
91-
throw InvalidArgumentException::typeMisMatch('callable', $listener);
92-
}
93-
94122
return $dispatcher->listen($eventName, $listener);
95123
});
96124
}

src/Utils/ArrayObject.php

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class ArrayObject implements IteratorAggregate, Countable
2525
*
2626
* @param array $array
2727
*/
28-
public function __construct(array $array)
28+
public function __construct(array $array = [])
2929
{
3030
$this->array = $array;
3131
}
@@ -42,6 +42,59 @@ public function map(callable $callback)
4242
return new static (array_map($callback, $this->array));
4343
}
4444

45+
/**
46+
* Run a callable over each item in the array and flatten the results by one level returning a new instance of
47+
* `ArrayObject` with the flattened items.
48+
*
49+
* @param callable $callback
50+
* @return static
51+
*/
52+
public function flatMap(callable $callback)
53+
{
54+
return $this->map($callback)->collapse();
55+
}
56+
57+
/**
58+
* Collapse an array of arrays into a single array returning a new instance of `ArrayObject`
59+
* with the collapsed items.
60+
*
61+
* @return static
62+
*/
63+
public function collapse()
64+
{
65+
$results = [];
66+
67+
foreach ($this->array as $item) {
68+
if (!is_array($item)) {
69+
continue;
70+
}
71+
72+
$results = array_merge($results, $item);
73+
}
74+
75+
return new static($results);
76+
}
77+
78+
/**
79+
* Reduce the items to a single value.
80+
*
81+
* @param callable $callback
82+
* @param mixed $initial
83+
* @return mixed
84+
*/
85+
public function reduce(callable $callback, $initial = null)
86+
{
87+
return array_reduce($this->array, $callback, $initial);
88+
}
89+
90+
/**
91+
* @return static
92+
*/
93+
public function keys()
94+
{
95+
return new static(array_keys($this->array));
96+
}
97+
4598
/**
4699
* Implode each item together using the provided glue.
47100
*
@@ -75,6 +128,36 @@ public function append($value)
75128
return new static(array_merge($this->array, [$value]));
76129
}
77130

131+
/**
132+
* Get an item at the given key.
133+
*
134+
* @param mixed $key
135+
* @param mixed $default
136+
* @return mixed
137+
*/
138+
public function get($key, $default = null)
139+
{
140+
if (isset($this->array[$key])) {
141+
return $this->array[$key];
142+
}
143+
144+
return $default;
145+
}
146+
147+
/**
148+
* Set the item at a given offset and return a new instance.
149+
*
150+
* @param mixed $key
151+
* @param mixed $value
152+
* @return static
153+
*/
154+
public function set($key, $value)
155+
{
156+
$items = $this->array;
157+
$items[$key] = $value;
158+
return new static($items);
159+
}
160+
78161
/**
79162
* Return an iterator containing all the items. Allows to `foreach` over.
80163
*

src/Utils/Collection.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\Utils;
4+
5+
/**
6+
* @author Aydin Hassan <[email protected]>
7+
*/
8+
class Collection extends ArrayObject
9+
{
10+
11+
}

0 commit comments

Comments
 (0)