InstanceContainerTrait::setupInstanceContainer()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * Webino™ (http://webino.sk)
4
 *
5
 * @link        https://github.com/webino/instance-container
6
 * @copyright   Copyright (c) 2019 Webino, s.r.o. (http://webino.sk)
7
 * @author      Peter Bačinský <[email protected]>
8
 * @license     BSD-3-Clause
9
 */
10
11
namespace Webino;
12
13
/**
14
 * Class InstanceContainerTrait
15
 * @package instance-container
16
 */
17
trait InstanceContainerTrait
18
{
19
    /**
20
     * Instances bindings.
21
     *
22
     * @var array
23
     */
24
    protected $bindings = [];
25
26
    /**
27
     * @var array
28
     */
29
    protected $instances = [];
30
31
    /**
32
     * @var array
33
     */
34
    protected $aliases = [];
35
36
    /**
37
     * General instance container setup.
38
     */
39
    protected function setupInstanceContainer(): void
40
    {
41
        $this->on(CreateInstanceEvent::class, [$this, 'onInstanceMake'], CreateInstanceEvent::MAIN);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

41
        $this->/** @scrutinizer ignore-call */ 
42
               on(CreateInstanceEvent::class, [$this, 'onInstanceMake'], CreateInstanceEvent::MAIN);
Loading history...
42
    }
43
44
    /**
45
     * Handle instance creation.
46
     *
47
     * @internal
48
     * @param CreateInstanceEventInterface $event
49
     * @return mixed New instance
50
     */
51
    public function onInstanceMake(CreateInstanceEventInterface $event)
52
    {
53
        $class = $event->getClass();
54
55
        $factoryMethod = $this->getFactoryMethod($class);
56
        if ($factoryMethod) {
57
            return $factoryMethod($event);
58
        }
59
60
        if (isset($this->bindings[$class])) {
61
            $binding = $this->bindings[$class];
62
63
            if ($binding instanceof InstanceFactoryInterface) {
64
                $factory = $binding;
65
            } elseif (is_callable($binding)) {
66
                return $binding($event);
67
            } else {
68
                $factory = $this->get($binding);
69
            }
70
71
            return $factory->createInstance($event);
72
        }
73
74
        $parameters = $event->getParameters();
75
        return new $class(...$parameters);
76
    }
77
78
    /**
79
     * Returns true if the container can return an instance for the given identifier, false otherwise.
80
     *
81
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception,
82
     * it does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
83
     *
84
     * @api
85
     * @param string $id Identifier of the entry to look for
86
     * @return bool
87
     */
88
    public function has($id)
89
    {
90
        return !empty($this->instances[(string) $id]) || $this->getClass($id);
91
    }
92
93
    /**
94
     * Returns entry of the container by its identifier.
95
     *
96
     * @api
97
     * @param string $id Identifier of the entry to look for
98
     * @throws InstanceNotFoundException No entry was found for identifier
99
     * @throws InstanceContainerException Error while retrieving the entry
100
     * @return mixed Instance
101
     */
102
    public function get($id)
103
    {
104
        $id = (string) $id;
105
106
        if (isset($this->aliases[$id])) {
107
            while (isset($this->aliases[$id])) {
108
                $id = $this->aliases[$id];
109
            }
110
        }
111
112
        if (isset($this->instances[$id])) {
113
            return $this->instances[$id] ?: null;
114
        }
115
116
        return $this->instances[$id] = $this->make($id);
117
    }
118
119
    /**
120
     * Set entry instance.
121
     *
122
     * @api
123
     * @param string $id Instance identifier
124
     * @param mixed $instance Container entry instance
125
     * @return void
126
     */
127
    public function set(string $id, $instance): void
128
    {
129
        $this->instances[$id] = $instance ?? false;
130
    }
131
132
    /**
133
     * Bind provider to an entry instance.
134
     *
135
     * @api
136
     * @param string $id Instance identifier
137
     * @param mixed $binding
138
     */
139
    public function bind(string $id, $binding): void
140
    {
141
        $this->bindings[$id] = $binding;
142
    }
143
144
    /**
145
     * Set alias to an entry instance.
146
     *
147
     * @api
148
     * @param string $id Instance id
149
     * @param string $alias Instance alias
150
     */
151
    public function alias(string $id, string $alias): void
152
    {
153
        $this->aliases[$alias] = $id;
154
    }
155
156
    /**
157
     * Creates new instance.
158
     *
159
     * @api
160
     * @param string $id Instance id
161
     * @param array<int, mixed> $parameter Optional parameters
162
     * @throws InstanceNotFoundException No entry was found for identifier
163
     * @throws InstanceContainerException Error while retrieving the entry
164
     * @return mixed New instance
165
     */
166
    public function make(string $id, ...$parameter)
167
    {
168
        $class = $this->getClass($id);
169
        if (!$class) {
170
            throw (new InstanceNotFoundException('Expected class with name %s'))
171
                ->format($class);
172
        }
173
174
        try {
175
            if ($this instanceof EventEmitterInterface) {
176
                $event = new CreateInstanceEvent($this, $class, $parameter);
177
            } else {
178
                throw (new InstanceContainerException('Expected container implements %s'))
179
                    ->format(EventEmitterInterface::class);
180
            }
181
182
            $this->emit($event, function ($result) use ($event) {
183
                if ($result && !$event->getInstance()) {
184
                    $event->setInstance($result);
185
                }
186
            });
187
188
            return $event->getInstance();
189
        } catch (\Throwable $exc) {
190
            throw (new InstanceContainerException('Cannot create valid instance for class %s', 0, $exc))
191
                ->format($class);
192
        }
193
    }
194
195
    /**
196
     * Returns class for an entry id.
197
     *
198
     * @param string $id Instance id
199
     * @return string|null Class name
200
     */
201
    protected function getClass(string $id): ?string
202
    {
203
        $class = $id;
204
        if (interface_exists($id)) {
205
            // get default implementation class name from interface
206
            $class = substr($id, 0, strlen($id) - 9);
207
        }
208
        return class_exists($class) ? $class : null;
209
    }
210
211
    /**
212
     * Returns factory method callable.
213
     *
214
     * @param mixed $class
215
     * @return callable|null
216
     */
217
    protected function getFactoryMethod($class): ?callable
218
    {
219
        if (method_exists($class, 'create')) {
220
            $callback = "$class::" . 'create';
221
            if (is_callable($callback)) {
222
                return $callback;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $callback returns the type string which is incompatible with the type-hinted return callable|null.
Loading history...
223
            }
224
        }
225
        return null;
226
    }
227
}
228