Completed
Pull Request — master (#67)
by Eric
03:10
created

Jarvis::hydrate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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
36
    const DEFAULT_DEBUG = false;
37
    const CONTAINER_PROVIDER_FQCN = ContainerProvider::class;
38
39
    private $masterSetter = false;
40
41
    /**
42
     * Creates an instance of Jarvis. It can take settings as first argument.
43
     * List of accepted options:
44
     *   - providers (type: string|array): fqcn of your container provider
45
     *   - extra
46
     *
47
     * @param  array $settings Your own settings to modify Jarvis behavior
48
     */
49
    public function __construct(array $settings = [])
50
    {
51
        parent::__construct();
52
53
        $this['settings'] = $settings;
54
        $providers = array_merge([static::CONTAINER_PROVIDER_FQCN], (array) ($settings['providers'] ?? []));
55
        foreach (array_unique($providers) as $classname) {
56
            $this->hydrate(new $classname());
57
        }
58
    }
59
60
    public function __destruct()
61
    {
62
        $this->masterBroadcast(BroadcasterInterface::TERMINATE_EVENT);
63
    }
64
65
    /**
66
     * This method is an another way to get a locked value.
67
     *
68
     * Example: $this['foo'] is equal to $this->foo, but it ONLY works for locked values.
69
     *
70
     * @param  string $key The key of the locked value
71
     * @return mixed
72
     * @throws \InvalidArgumentException if the requested key is not associated to a locked service
73
     */
74
    public function __get(string $key)
75
    {
76
        if (!isset($this->locked[$key])) {
77
            throw new \InvalidArgumentException(sprintf('"%s" is not a key of a locked value.', $key));
78
        }
79
80
        $this->masterSet($key, $this[$key]);
81
82
        return $this->$key;
83
    }
84
85
    /**
86
     * Sets new attributes to Jarvis. Note that this method is reserved to Jarvis itself only.
87
     *
88
     * @param string $key   The key name of the new attribute
89
     * @param mixed  $value The value to associate to provided key
90
     * @throws \LogicException if this method is not called by Jarvis itself
91
     */
92
    public function __set(string $key, $value)
93
    {
94
        if (!$this->masterSetter) {
95
            throw new \LogicException('You are not allowed to set new attribute into Jarvis.');
96
        }
97
98
        $this->$key = $value;
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function offsetSet($id, $v): void
105
    {
106
        parent::offsetSet($id, $v);
107
108
        if (!($v instanceof \Closure)) {
109
            return;
110
        }
111
112
        $refMethod = new \ReflectionMethod($v, '__invoke');
113
        if (null === $returntype = $refMethod->getReturnType()) {
114
            return;
115
        }
116
117
        $alias = $returntype->getName();
118
        if (
119
            $alias === $id
120
            || (!class_exists($alias) && !interface_exists($alias))
121
        ) {
122
            return;
123
        }
124
125
        if (!isset($this[$alias])) {
126
            $this->alias($alias, $id);
127
        } else {
128
            unset($this[$alias]);
129
        }
130
    }
131
132
    /**
133
     * @param  Request|null $request
134
     * @return Response
135
     */
136
    public function run(Request $request = null): Response
137
    {
138
        $request = $request ?? $this['request'];
139
        $event = $event = new RunEvent($request);
140
141
        try {
142
            $this->masterBroadcast(BroadcasterInterface::RUN_EVENT, $event);
143
            if ($response = $event->response()) {
144
                return $response;
145
            }
146
147
            [$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...
148
            $event = new ControllerEvent($this['callbackResolver']->resolve($callback), $arguments);
149
            $this->masterBroadcast(BroadcasterInterface::CONTROLLER_EVENT, $event);
150
151
            $response = $this['callbackResolver']->resolveAndCall($event->callback(), $event->arguments());
152
            $event = new ResponseEvent($request, $response);
153
            $this->masterBroadcast(BroadcasterInterface::RESPONSE_EVENT, $event);
154
        } catch (\Throwable $throwable) {
155
            $event = new ExceptionEvent($throwable);
156
            $this->masterBroadcast(BroadcasterInterface::EXCEPTION_EVENT, $event);
157
        }
158
159
        return $event->response();
160
    }
161
162
    /**
163
     * Sets new attribute into Jarvis.
164
     *
165
     * @param  string $key   The name of the new attribute
166
     * @param  mixed  $value The value of the new attribute
167
     * @return self
168
     */
169
    private function masterSet(string $key, $value): void
170
    {
171
        $this->masterSetter = true;
172
        $this->$key = $value;
173
        $this->masterSetter = false;
174
    }
175
}
176