Completed
Pull Request — master (#67)
by Eric
02:06
created

Jarvis::buildEventReceivers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Jarvis;
6
7
use Jarvis\Skill\DependencyInjection\Container;
8
use Jarvis\Skill\DependencyInjection\ContainerProvider;
9
use Jarvis\Skill\EventBroadcaster\BroadcasterInterface;
10
use Jarvis\Skill\EventBroadcaster\BroadcasterTrait;
11
use Jarvis\Skill\EventBroadcaster\ControllerEvent;
12
use Jarvis\Skill\EventBroadcaster\EventInterface;
13
use Jarvis\Skill\EventBroadcaster\ExceptionEvent;
14
use Jarvis\Skill\EventBroadcaster\PermanentEventInterface;
15
use Jarvis\Skill\EventBroadcaster\ResponseEvent;
16
use Jarvis\Skill\EventBroadcaster\RunEvent;
17
use Jarvis\Skill\EventBroadcaster\SimpleEvent;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\Response;
20
21
/**
22
 * Jarvis. Minimalist dependency injection container.
23
 *
24
 * @property bool                                              $debug
25
 * @property Request                                           $request
26
 * @property \Jarvis\Skill\Routing\Router                      $router
27
 * @property \Symfony\Component\HttpFoundation\Session\Session $session
28
 * @property \Jarvis\Skill\Core\CallbackResolver               $callbackResolver
29
 *
30
 * @author Eric Chau <[email protected]>
31
 */
32
class Jarvis extends Container implements BroadcasterInterface
33
{
34
    use BroadcasterTrait {
35
        broadcast as traitBroadcast;
36
    }
37
38
    const DEFAULT_DEBUG = false;
39
    const CONTAINER_PROVIDER_FQCN = ContainerProvider::class;
40
41
    private $masterSetter = false;
42
43
    /**
44
     * Creates an instance of Jarvis. It can take settings as first argument.
45
     * List of accepted options:
46
     *   - providers (type: string|array): fqcn of your container provider
47
     *   - extra
48
     *
49
     * @param  array $settings Your own settings to modify Jarvis behavior
50
     */
51
    public function __construct(array $settings = [])
52
    {
53
        parent::__construct();
54
55
        $this['settings'] = $settings;
56
        $providers = array_merge([static::CONTAINER_PROVIDER_FQCN], (array) ($settings['providers'] ?? []));
57
        foreach (array_unique($providers) as $classname) {
58
            $this->hydrate(new $classname());
59
        }
60
    }
61
62
    public function __destruct()
63
    {
64
        $this->masterBroadcast(BroadcasterInterface::TERMINATE_EVENT);
65
    }
66
67
    /**
68
     * This method is an another way to get a locked value.
69
     *
70
     * Example: $this['foo'] is equal to $this->foo, but it ONLY works for locked values.
71
     *
72
     * @param  string $key The key of the locked value
73
     * @return mixed
74
     * @throws \InvalidArgumentException if the requested key is not associated to a locked service
75
     */
76
    public function __get(string $key)
77
    {
78
        if (!isset($this->locked[$key])) {
79
            throw new \InvalidArgumentException(sprintf('"%s" is not a key of a locked value.', $key));
80
        }
81
82
        $this->masterSet($key, $this[$key]);
83
84
        return $this->$key;
85
    }
86
87
    /**
88
     * Sets new attributes to Jarvis. Note that this method is reserved to Jarvis itself only.
89
     *
90
     * @param string $key   The key name of the new attribute
91
     * @param mixed  $value The value to associate to provided key
92
     * @throws \LogicException if this method is not called by Jarvis itself
93
     */
94
    public function __set(string $key, $value)
95
    {
96
        if (!$this->masterSetter) {
97
            throw new \LogicException('You are not allowed to set new attribute into Jarvis.');
98
        }
99
100
        $this->$key = $value;
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function offsetSet($id, $v): void
107
    {
108
        parent::offsetSet($id, $v);
109
110
        if (!($v instanceof \Closure)) {
111
            return;
112
        }
113
114
        $refMethod = new \ReflectionMethod($v, '__invoke');
115
        if (null === $returntype = $refMethod->getReturnType()) {
116
            return;
117
        }
118
119
        $alias = $returntype->getName();
120
        if (
121
            $alias === $id
122
            || (!class_exists($alias) && !interface_exists($alias))
123
        ) {
124
            return;
125
        }
126
127
        if (!isset($this[$alias])) {
128
            $this->alias($alias, $id);
129
        } else {
130
            unset($this[$alias]);
131
        }
132
    }
133
134
    /**
135
     * @param  Request|null $request
136
     * @return Response
137
     */
138
    public function run(Request $request = null): Response
139
    {
140
        $request = $request ?? $this['request'];
141
        $event = $event = new RunEvent($request);
142
143
        try {
144
            $this->masterBroadcast(BroadcasterInterface::RUN_EVENT, $event);
145
            if ($response = $event->response()) {
146
                return $response;
147
            }
148
149
            [$callback, $arguments] = $this['router']->match($request->getMethod(), $request->getPathInfo());
0 ignored issues
show
Bug introduced by
The variable $callback does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $arguments does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
150
            $event = new ControllerEvent($this['callbackResolver']->resolve($callback), $arguments);
151
            $this->masterBroadcast(BroadcasterInterface::CONTROLLER_EVENT, $event);
152
153
            $response = $this['callbackResolver']->resolveAndCall($event->callback(), $event->arguments());
154
            $event = new ResponseEvent($request, $response);
155
            $this->masterBroadcast(BroadcasterInterface::RESPONSE_EVENT, $event);
156
        } catch (\Throwable $throwable) {
157
            $event = new ExceptionEvent($throwable);
158
            $this->masterBroadcast(BroadcasterInterface::EXCEPTION_EVENT, $event);
159
        }
160
161
        return $event->response();
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    public function broadcast(string $name, EventInterface $event = null): void
168
    {
169
        if (!$this->masterEmitter && in_array($name, BroadcasterInterface::RESERVED_EVENT_NAMES)) {
170
            throw new \LogicException(sprintf(
171
                'You\'re trying to broadcast "$name" but "%s" are reserved event names.',
172
                implode('|', BroadcasterInterface::RESERVED_EVENT_NAMES)
173
            ));
174
        }
175
176
        $this->traitBroadcast($name, $event);
177
    }
178
179
    /**
180
     * Sets new attribute into Jarvis.
181
     *
182
     * @param  string $key   The name of the new attribute
183
     * @param  mixed  $value The value of the new attribute
184
     */
185
    private function masterSet(string $key, $value): void
186
    {
187
        $this->masterSetter = true;
188
        $this->$key = $value;
189
        $this->masterSetter = false;
190
    }
191
192
    /**
193
     * Enables master emitter mode.
194
     */
195
    private function masterBroadcast(string $name, EventInterface $event = null): void
196
    {
197
        $this->masterEmitter = true;
198
        $this->broadcast($name, $event);
199
        $this->masterEmitter = false;
200
    }
201
}
202