Skip to content

Commit 46f67cb

Browse files
committed
DI: refactoring of lazyness
1 parent eb74866 commit 46f67cb

5 files changed

Lines changed: 144 additions & 147 deletions

File tree

.docs/README.md

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,53 @@ And automatically adds them to the event dispatcher. That's all. You don't have
3737

3838
## Configuration
3939

40+
**Default**
41+
42+
```neon
43+
events:
44+
lazy: true
45+
autoload: true
46+
debug: false
47+
loggers: []
48+
```
49+
4050
### Autoload
4151

42-
If you would like to add all subscribers by yourself, you have to disable `autoload`.
52+
Autoload option is enabled (`true`) as default. If you would like to add all subscribers by yourself, you have to disable `autoload`.
4353

4454
```neon
4555
events:
4656
autoload: true/false
4757
```
4858

49-
### Laziness
59+
### Lazy-loading
5060

51-
Lazy options is enabled (`true`) as default. But you can override it.
61+
Lazy option is enabled (`true`) as default. But you can override it.
5262

5363
```neon
5464
events:
5565
lazy: true/false
5666
```
5767

68+
### Debug
69+
70+
Debug option is disabled (`false`) as default. If you want to show Tracy panel, you have to enable it.
71+
72+
```neon
73+
events:
74+
debug: %debugMode%
75+
```
76+
77+
### Logging
78+
79+
You can log all events via loggers. Just add logger to the configuration.
80+
81+
```neon
82+
events:
83+
loggers:
84+
- App\Logger\FileLogger(%tempDir%/events.log)
85+
```
86+
5887
## Subscriber
5988

6089
```php
@@ -92,20 +121,17 @@ Get dispatcher from DI and dispatch your events
92121
```php
93122
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
94123

95-
class OrderModel
124+
class UserFacade
96125
{
97126

98-
/** @var EventDispatcherInterface */
99-
private $eventDispatcher;
100-
101-
public function __construct(EventDispatcherInterface $eventDispatcher)
127+
public function __construct(
128+
private EventDispatcherInterface $eventDispatcher
129+
)
102130
{
103-
$this->eventDispatcher = $eventDispatcher;
104131
}
105132

106133
public function createOrder(Order $order): void
107134
{
108-
// Create order
109135
$this->eventDispatcher->dispatch(new OrderCreatedEvent($order));
110136
}
111137

@@ -114,22 +140,8 @@ class OrderModel
114140

115141
## Extra
116142

117-
The goal of this library is to be the most tiniest and purest adaptation of [Symfony Event-Dispatcher](https://github.com/symfony/event-dispatcher) to [Nette Framework](https://github.com/nette/).
143+
The goal of this library is to be the simplest and purest adaptation of [Symfony Event-Dispatcher](https://github.com/symfony/event-dispatcher) to [Nette Framework](https://github.com/nette/).
118144

119145
As you can see only one `Extension` class is provided. Nette has many single packages and here comes the [`event-dispatcher-extra`](https://github.com/contributte/event-dispatcher-extra) package.
120146

121147
This extra repository contains useful events for **application**, **latte** and many others. [Take a look](https://github.com/contributte/event-dispatcher-extra).
122-
123-
## Compatibility
124-
125-
How to make this extension work with other Symfony/EventDispatcher implementations.
126-
127-
### Kdyby/Events
128-
129-
Kdyby/Events has a conflict with this package because of it's `SymfonyDispatcher` proxy class. To avoid the conflict simply add this to your config.neon:
130-
131-
```neon
132-
services:
133-
events.symfonyProxy:
134-
autowired: false
135-
```

src/DI/EventDispatcherExtension.php

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22

33
namespace Contributte\EventDispatcher\DI;
44

5-
use Contributte\EventDispatcher\EventDispatcher;
6-
use Contributte\EventDispatcher\LazyEventDispatcher;
5+
use Contributte\EventDispatcher\Diagnostics\DebugDispatcher;
6+
use Contributte\EventDispatcher\Diagnostics\TracyDispatcher;
7+
use Contributte\EventDispatcher\LazyListener;
8+
use Contributte\EventDispatcher\Tracy\EventPanel;
79
use Nette\DI\CompilerExtension;
10+
use Nette\DI\Container;
811
use Nette\DI\Definitions\ServiceDefinition;
12+
use Nette\DI\Definitions\Statement;
913
use Nette\DI\ServiceCreationException;
14+
use Nette\PhpGenerator\ClassType;
1015
use Nette\Schema\Expect;
1116
use Nette\Schema\Schema;
1217
use stdClass;
18+
use Symfony\Component\EventDispatcher\EventDispatcher;
1319
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1420
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
21+
use Tracy\Bar;
1522

1623
/**
1724
* @method stdClass getConfig()
@@ -24,6 +31,8 @@ public function getConfigSchema(): Schema
2431
return Expect::structure([
2532
'lazy' => Expect::bool(true),
2633
'autoload' => Expect::bool(true),
34+
'debug' => Expect::bool(false),
35+
'loggers' => Expect::arrayOf(Expect::type(Statement::class)),
2736
]);
2837
}
2938

@@ -32,16 +41,31 @@ public function loadConfiguration(): void
3241
$builder = $this->getContainerBuilder();
3342
$config = $this->getConfig();
3443

35-
$eventDispatcherDefinition = $builder->addDefinition($this->prefix('dispatcher'))
36-
->setType(EventDispatcherInterface::class);
44+
// Original dispatcher
45+
$outerDispatcher = $dispatcherDef = $builder->addDefinition($this->prefix('dispatcher'))
46+
->setType(EventDispatcherInterface::class)
47+
->setFactory(EventDispatcher::class)
48+
->setAutowired(false);
49+
50+
// Dispatcher for logging
51+
if ($config->loggers !== []) {
52+
$loggingDispatcherDef = $builder->addDefinition($this->prefix('dispatcher.logging'))
53+
->setFactory(DebugDispatcher::class, [$outerDispatcher])
54+
->setAutowired(false);
55+
$outerDispatcher = $loggingDispatcherDef;
56+
}
3757

38-
if ($config->lazy === true) {
39-
$eventDispatcherDefinition
40-
->setFactory(LazyEventDispatcher::class);
41-
} else {
42-
$eventDispatcherDefinition
43-
->setFactory(EventDispatcher::class);
58+
// Dispatcher for Tracy bar
59+
if ($config->debug === true) {
60+
$tracyDispatcherDef = $builder->addDefinition($this->prefix('dispatcher.tracy'))
61+
->setType(EventDispatcherInterface::class)
62+
->setFactory(TracyDispatcher::class, [$outerDispatcher])
63+
->setAutowired(false);
64+
$outerDispatcher = $tracyDispatcherDef;
4465
}
66+
67+
// Only outer dispatcher should be autowired
68+
$outerDispatcher->setAutowired();
4569
}
4670

4771
public function beforeCompile(): void
@@ -57,6 +81,23 @@ public function beforeCompile(): void
5781
}
5882
}
5983

84+
public function afterCompile(ClassType $class): void
85+
{
86+
$config = $this->getConfig();
87+
$builder = $this->getContainerBuilder();
88+
$initialization = $this->getInitialization();
89+
90+
if ($config->debug) {
91+
$initialization->addBody(
92+
// @phpstan-ignore-next-line
93+
$builder->formatPhp('?->addPanel(?);', [
94+
$builder->getDefinitionByType(Bar::class),
95+
new Statement(EventPanel::class, [$builder->getDefinition($this->prefix('dispatcher.tracy'))]),
96+
])
97+
);
98+
}
99+
}
100+
60101
/**
61102
* Collect listeners and subscribers
62103
*/
@@ -82,36 +123,43 @@ private function doBeforeCompileLaziness(): void
82123
assert($dispatcher instanceof ServiceDefinition);
83124

84125
$subscribers = $builder->findByType(EventSubscriberInterface::class);
85-
foreach ($subscribers as $name => $subscriber) {
126+
foreach ($subscribers as $serviceName => $subscriber) {
86127
assert($subscriber instanceof ServiceDefinition);
87128
$events = call_user_func([$subscriber->getEntity(), 'getSubscribedEvents']); // @phpstan-ignore-line
88129
assert(is_array($events));
89130

90-
/**
91-
* ['eventName' => 'methodName']
92-
* ['eventName' => ['methodName', $priority]]
93-
* ['eventName' => [['methodName1', $priority], ['methodName2']]]
94-
*/
95-
foreach ($events as $event => $args) {
96-
if (is_string($args)) {
97-
if (!method_exists((string) $subscriber->getType(), $args)) {
98-
throw new ServiceCreationException(sprintf('Event listener %s does not have callable method %s', $subscriber->getType(), $args));
131+
foreach ($events as $event => $params) {
132+
if (is_string($params)) { // ['eventName' => 'methodName']
133+
if (!method_exists((string) $subscriber->getType(), $params)) {
134+
throw new ServiceCreationException(sprintf('Event listener %s does not have callable method %s', $subscriber->getType(), $params));
99135
}
100136

101-
$dispatcher->addSetup('addSubscriberLazy', [$event, $name]);
102-
} elseif (is_string($args[0])) {
103-
if (!method_exists((string) $subscriber->getType(), $args[0])) {
104-
throw new ServiceCreationException(sprintf('Event listener %s does not have callable method %s', $subscriber->getType(), $args[0]));
137+
$dispatcher->addSetup('addListener', [
138+
'eventName' => $event,
139+
'listener' => new Statement(LazyListener::class, [$serviceName, $params, $builder->getDefinitionByType(Container::class)]),
140+
'priority' => 0,
141+
]);
142+
} elseif (is_string($params[0])) { // ['eventName' => ['methodName', $priority]]
143+
if (!method_exists((string) $subscriber->getType(), $params[0])) {
144+
throw new ServiceCreationException(sprintf('Event listener %s does not have callable method %s', $subscriber->getType(), $params[0]));
105145
}
106146

107-
$dispatcher->addSetup('addSubscriberLazy', [$event, $name]);
108-
} else {
109-
foreach ($args as $arg) {
110-
if (!method_exists((string) $subscriber->getType(), $arg[0])) {
111-
throw new ServiceCreationException(sprintf('Event listener %s does not have callable method %s', $subscriber->getType(), $arg[0]));
147+
$dispatcher->addSetup('addListener', [
148+
'eventName' => $event,
149+
'listener' => new Statement(LazyListener::class, [$serviceName, $params[0], $builder->getDefinitionByType(Container::class)]),
150+
'priority' => $params[1] ?? 0,
151+
]);
152+
} elseif (is_array($params[0])) { // ['eventName' => [['methodName1', $priority], ['methodName2']]]
153+
foreach ($params as $listener) {
154+
if (!method_exists((string) $subscriber->getType(), $listener[0])) {
155+
throw new ServiceCreationException(sprintf('Event listener %s does not have callable method %s', $subscriber->getType(), $listener[0]));
112156
}
113157

114-
$dispatcher->addSetup('addSubscriberLazy', [$event, $name]);
158+
$dispatcher->addSetup('addListener', [
159+
'eventName' => $event,
160+
'listener' => new Statement(LazyListener::class, [$serviceName, $listener[0], $builder->getDefinitionByType(Container::class)]),
161+
'priority' => $listener[1] ?? 0,
162+
]);
115163
}
116164
}
117165
}

src/EventDispatcher.php

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/LazyEventDispatcher.php

Lines changed: 0 additions & 82 deletions
This file was deleted.

src/LazyListener.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Contributte\EventDispatcher;
4+
5+
use Nette\DI\Container;
6+
7+
class LazyListener
8+
{
9+
10+
private ?object $service = null;
11+
12+
public function __construct(
13+
private readonly string $serviceName,
14+
private readonly string $methodName,
15+
private readonly Container $container
16+
)
17+
{
18+
}
19+
20+
public function __invoke(): mixed
21+
{
22+
if ($this->service === null) {
23+
$this->service = $this->container->getService($this->serviceName);
24+
}
25+
26+
return $this->service->{$this->methodName}(...func_get_args()); // @phpstan-ignore-line
27+
}
28+
29+
}

0 commit comments

Comments
 (0)