1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types = 1); |
4
|
|
|
|
5
|
|
|
namespace Jarvis; |
6
|
|
|
|
7
|
|
|
use FastRoute\Dispatcher; |
8
|
|
|
use Jarvis\Skill\DependencyInjection\Container; |
9
|
|
|
use Jarvis\Skill\DependencyInjection\ContainerProvider; |
10
|
|
|
use Jarvis\Skill\DependencyInjection\ContainerProviderInterface; |
11
|
|
|
use Jarvis\Skill\EventBroadcaster\AnalyzeEvent; |
12
|
|
|
use Jarvis\Skill\EventBroadcaster\ControllerEvent; |
13
|
|
|
use Jarvis\Skill\EventBroadcaster\BroadcasterInterface; |
14
|
|
|
use Jarvis\Skill\EventBroadcaster\EventInterface; |
15
|
|
|
use Jarvis\Skill\EventBroadcaster\ExceptionEvent; |
16
|
|
|
use Jarvis\Skill\EventBroadcaster\PermanentEventInterface; |
17
|
|
|
use Jarvis\Skill\EventBroadcaster\ResponseEvent; |
18
|
|
|
use Jarvis\Skill\EventBroadcaster\SimpleEvent; |
19
|
|
|
use Symfony\Component\HttpFoundation\ParameterBag; |
20
|
|
|
use Symfony\Component\HttpFoundation\Request; |
21
|
|
|
use Symfony\Component\HttpFoundation\Response; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Jarvis. Minimalist dependency injection container. |
25
|
|
|
* |
26
|
|
|
* @property boolean $debug |
27
|
|
|
* @property \Jarvis\Skill\Routing\Router $router |
28
|
|
|
* @property \Symfony\Component\HttpFoundation\Request $request |
29
|
|
|
* @property \Symfony\Component\HttpFoundation\Session\Session $session |
30
|
|
|
* @property \Jarvis\Skill\Core\CallbackResolver $callbackResolver |
31
|
|
|
* @property \Jarvis\Skill\Core\ScopeManager $scopeManager |
32
|
|
|
* @property \Symfony\Component\HttpFoundation\ParameterBag $settings |
33
|
|
|
* |
34
|
|
|
* @author Eric Chau <[email protected]> |
35
|
|
|
*/ |
36
|
|
|
class Jarvis extends Container implements BroadcasterInterface |
37
|
|
|
{ |
38
|
|
|
const DEFAULT_DEBUG = false; |
39
|
|
|
const CONTAINER_PROVIDER_FQCN = ContainerProvider::class; |
40
|
|
|
const DEFAULT_SCOPE = 'default'; |
41
|
|
|
|
42
|
|
|
private $receivers = []; |
43
|
|
|
private $permanentEvents = []; |
44
|
|
|
private $computedReceivers = []; |
45
|
|
|
private $masterEmitter = false; |
46
|
|
|
private $masterSet = false; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Creates an instance of Jarvis. It can take settings as first argument. |
50
|
|
|
* List of accepted options: |
51
|
|
|
* - container_provider (type: string|array): fqcn of your container provider |
52
|
|
|
* |
53
|
|
|
* @param array $settings Your own settings to modify Jarvis behavior |
54
|
|
|
*/ |
55
|
|
|
public function __construct(array $settings = []) |
56
|
|
|
{ |
57
|
|
|
parent::__construct(); |
58
|
|
|
|
59
|
|
|
$this['settings'] = new ParameterBag($settings); |
60
|
|
|
$this->lock('settings'); |
61
|
|
|
|
62
|
|
|
$this['debug'] = $this->settings->getBoolean('debug', static::DEFAULT_DEBUG); |
63
|
|
|
$this->lock('debug'); |
64
|
|
|
|
65
|
|
|
if (!$this->settings->has('container_provider')) { |
66
|
|
|
$this->settings->set('container_provider', [static::CONTAINER_PROVIDER_FQCN]); |
67
|
|
|
} else { |
68
|
|
|
$containerProvider = (array) $this->settings->get('container_provider'); |
69
|
|
|
array_unshift($containerProvider, static::CONTAINER_PROVIDER_FQCN); |
70
|
|
|
$this->settings->set('container_provider', $containerProvider); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
foreach ($this->settings->get('container_provider') as $classname) { |
74
|
|
|
$this->hydrate(new $classname()); |
75
|
|
|
} |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
public function __destruct() |
79
|
|
|
{ |
80
|
|
|
$this->masterBroadcast(BroadcasterInterface::TERMINATE_EVENT); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* This method is an another way to get a locked value. |
85
|
|
|
* |
86
|
|
|
* Example: $this['foo'] is equal to $this->foo, but it ONLY works for locked values. |
87
|
|
|
* |
88
|
|
|
* @param string $key The key of the locked value |
89
|
|
|
* @return mixed |
90
|
|
|
* @throws \InvalidArgumentException if the requested key is not associated to a locked service |
91
|
|
|
*/ |
92
|
|
|
public function __get(string $key) |
93
|
|
|
{ |
94
|
|
|
if (!isset($this->locked[$key])) { |
95
|
|
|
throw new \InvalidArgumentException(sprintf('"%s" is not a key of a locked value.', $key)); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$this->masterSetter($key, $this[$key]); |
99
|
|
|
|
100
|
|
|
return $this->$key; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Sets new attributes to Jarvis. Note that this method is reserved to Jarvis itself only. |
105
|
|
|
* |
106
|
|
|
* @param string $key The key name of the new attribute |
107
|
|
|
* @param mixed $value The value to associate to provided key |
108
|
|
|
* @throws \LogicException if this method is not called by Jarvis itself |
109
|
|
|
*/ |
110
|
|
|
public function __set(string $key, $value) |
111
|
|
|
{ |
112
|
|
|
if (!$this->masterSet) { |
113
|
|
|
throw new \LogicException('You are not allowed to set new attribute into Jarvis.'); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
$this->$key = $value; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @param Request|null $request |
121
|
|
|
* @return Response |
122
|
|
|
*/ |
123
|
|
|
public function analyze(Request $request = null): Response |
124
|
|
|
{ |
125
|
|
|
$request = $request ?? $this->request; |
126
|
|
|
$response = null; |
127
|
|
|
|
128
|
|
|
try { |
129
|
|
|
$this->masterBroadcast(BroadcasterInterface::ANALYZE_EVENT, $analyzeEvent = new AnalyzeEvent($request)); |
130
|
|
|
|
131
|
|
|
if ($response = $analyzeEvent->response()) { |
132
|
|
|
return $response; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$routeInfo = $this->router->match($request->getMethod(), $request->getPathInfo()); |
136
|
|
|
if (Dispatcher::FOUND === $routeInfo[0]) { |
137
|
|
|
$callback = $this->callbackResolver->resolve($routeInfo[1]); |
138
|
|
|
|
139
|
|
|
$event = new ControllerEvent($callback, $routeInfo[2]); |
140
|
|
|
$this->masterBroadcast(BroadcasterInterface::CONTROLLER_EVENT, $event); |
141
|
|
|
|
142
|
|
|
$response = call_user_func_array($event->callback(), $event->arguments()); |
143
|
|
|
|
144
|
|
|
if (is_scalar($response)) { |
145
|
|
|
$response = new Response((string) $response); |
146
|
|
|
} |
147
|
|
|
} elseif (Dispatcher::NOT_FOUND === $routeInfo[0] || Dispatcher::METHOD_NOT_ALLOWED === $routeInfo[0]) { |
148
|
|
|
$response = new Response(null, Dispatcher::NOT_FOUND === $routeInfo[0] |
149
|
|
|
? Response::HTTP_NOT_FOUND |
150
|
|
|
: Response::HTTP_METHOD_NOT_ALLOWED |
151
|
|
|
); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$this->masterBroadcast(BroadcasterInterface::RESPONSE_EVENT, new ResponseEvent($request, $response)); |
155
|
|
|
} catch (\Throwable $throwable) { |
|
|
|
|
156
|
|
|
$exceptionEvent = new ExceptionEvent($throwable); |
157
|
|
|
$this->masterBroadcast(BroadcasterInterface::EXCEPTION_EVENT, $exceptionEvent); |
158
|
|
|
$response = $exceptionEvent->response(); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
return $response; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* {@inheritdoc} |
166
|
|
|
*/ |
167
|
|
|
public function on(string $eventName, $receiver, int $priority = BroadcasterInterface::RECEIVER_NORMAL_PRIORITY) |
168
|
|
|
{ |
169
|
|
|
if (!isset($this->receivers[$eventName])) { |
170
|
|
|
$this->receivers[$eventName] = [ |
171
|
|
|
BroadcasterInterface::RECEIVER_LOW_PRIORITY => [], |
172
|
|
|
BroadcasterInterface::RECEIVER_NORMAL_PRIORITY => [], |
173
|
|
|
BroadcasterInterface::RECEIVER_HIGH_PRIORITY => [], |
174
|
|
|
]; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$this->receivers[$eventName][$priority][] = $receiver; |
178
|
|
|
$this->computedReceivers[$eventName] = null; |
179
|
|
|
|
180
|
|
|
if (isset($this->permanentEvents[$eventName])) { |
181
|
|
|
$event = $this->permanentEvents[$eventName]; |
182
|
|
|
|
183
|
|
|
call_user_func_array($this->callbackResolver->resolve($receiver), [$event]); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
return $this; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* {@inheritdoc} |
191
|
|
|
*/ |
192
|
|
|
public function broadcast(string $eventName, EventInterface $event = null) |
193
|
|
|
{ |
194
|
|
|
if (!$this->masterEmitter && in_array($eventName, BroadcasterInterface::RESERVED_EVENT_NAMES)) { |
195
|
|
|
throw new \LogicException(sprintf( |
196
|
|
|
'You\'re trying to broadcast "$eventName" but "%s" are reserved event names.', |
197
|
|
|
implode('|', BroadcasterInterface::RESERVED_EVENT_NAMES) |
198
|
|
|
)); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
if (isset($this->receivers[$eventName])) { |
202
|
|
|
$event = $event ?? new SimpleEvent(); |
203
|
|
|
if ($event instanceof PermanentEventInterface && $event->isPermanent()) { |
204
|
|
|
$this->permanentEvents[$eventName] = $event; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
foreach ($this->buildEventReceivers($eventName) as $receiver) { |
208
|
|
|
call_user_func_array($this->callbackResolver->resolve($receiver), [$event]); |
209
|
|
|
|
210
|
|
|
if ($event->isPropagationStopped()) { |
211
|
|
|
break; |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
return $this; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* @param ContainerProviderInterface $provider |
221
|
|
|
* @return self |
222
|
|
|
*/ |
223
|
|
|
public function hydrate(ContainerProviderInterface $provider): Jarvis |
224
|
|
|
{ |
225
|
|
|
$provider->hydrate($this); |
226
|
|
|
|
227
|
|
|
return $this; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Enables master emitter mode. |
232
|
|
|
* |
233
|
|
|
* @return self |
234
|
|
|
*/ |
235
|
|
|
private function masterBroadcast(string $eventName, EventInterface $event = null): Jarvis |
236
|
|
|
{ |
237
|
|
|
$this->masterEmitter = true; |
238
|
|
|
$this->broadcast($eventName, $event); |
239
|
|
|
$this->masterEmitter = false; |
240
|
|
|
|
241
|
|
|
return $this; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Sets new attribute into Jarvis. |
246
|
|
|
* |
247
|
|
|
* @param string $key The name of the new attribute |
248
|
|
|
* @param mixed $value The value of the new attribute |
249
|
|
|
* @return self |
250
|
|
|
*/ |
251
|
|
|
private function masterSetter(string $key, $value): Jarvis |
252
|
|
|
{ |
253
|
|
|
$this->masterSet = true; |
254
|
|
|
$this->$key = $value; |
255
|
|
|
$this->masterSet = false; |
256
|
|
|
|
257
|
|
|
return $this; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Builds and returns well ordered receivers collection that match with provided event name. |
262
|
|
|
* |
263
|
|
|
* @param string $eventName The event name we want to get its receivers |
264
|
|
|
* @return array |
265
|
|
|
*/ |
266
|
|
|
private function buildEventReceivers(string $eventName): array |
267
|
|
|
{ |
268
|
|
|
return $this->computedReceivers[$eventName] = $this->computedReceivers[$eventName] ?? array_merge( |
269
|
|
|
$this->receivers[$eventName][BroadcasterInterface::RECEIVER_HIGH_PRIORITY], |
270
|
|
|
$this->receivers[$eventName][BroadcasterInterface::RECEIVER_NORMAL_PRIORITY], |
271
|
|
|
$this->receivers[$eventName][BroadcasterInterface::RECEIVER_LOW_PRIORITY] |
272
|
|
|
); |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
|
Scrutinizer analyzes your
composer.json
/composer.lock
file if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.