Completed
Push — master ( 7e3d04...dc7866 )
by Changwan
06:39
created

Container::createDescriptor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace Wandu\DI;
3
4
use Psr\Container\ContainerExceptionInterface;
5
use Psr\Container\ContainerInterface as PsrContainerInterface;
6
use Wandu\DI\Contracts\ResolverInterface;
7
use Wandu\DI\Exception\CannotChangeException;
8
use Wandu\DI\Exception\CannotFindParameterException;
9
use Wandu\DI\Exception\CannotResolveException;
10
use Wandu\DI\Exception\NullReferenceException;
11
use Wandu\DI\Exception\RequirePackageException;
12
use Wandu\DI\Resolvers\BindResolver;
13
use Wandu\DI\Resolvers\CallableResolver;
14
use Wandu\DI\Resolvers\InstanceResolver;
15
16
class Container implements ContainerInterface
17
{
18
    /** @var \Wandu\DI\ContainerInterface */
19
    public static $instance;
20
    
21
    /** @var \Wandu\DI\Descriptor[] */
22
    protected $descriptors = [];
23
    
24
    /** @var array */
25
    protected $caches = [];
26
27
    /** @var \Wandu\DI\ServiceProviderInterface[] */
28
    protected $providers = [];
29
30
    /** @var array */
31
    protected $aliases = [];
32
33
    /** @var array */
34
    protected $aliasIndex = [];
35
    
36
    /** @var bool */
37
    protected $isBooted = false;
38
39 76
    public function __construct()
40
    {
41 76
        $this->caches = [
42 76
            Container::class => $this,
43 76
            ContainerInterface::class => $this,
44 76
            PsrContainerInterface::class => $this,
45 76
            'container' => $this,
46
        ];
47 76
    }
48
49
    /**
50
     * @return \Wandu\DI\ContainerInterface
51
     */
52 4
    public function setAsGlobal()
53
    {
54 4
        $instance = static::$instance;
55 4
        static::$instance = $this;
56 4
        return $instance;
57
    }
58
59 6
    public function __clone()
60
    {
61 6
        $this->caches[Container::class] = $this;
62 6
        $this->caches[ContainerInterface::class] = $this;
63 6
        $this->caches[PsrContainerInterface::class] = $this;
64 6
        $this->caches['container'] = $this;
65 6
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function __call($name, array $arguments)
71
    {
72
        return $this->call($this->get($name), $arguments);
0 ignored issues
show
Documentation introduced by
$this->get($name) is of type *, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 2
    public function offsetExists($name)
79
    {
80 2
        return $this->has($name) && $this->get($name) !== null;
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86 15
    public function offsetGet($name)
87
    {
88 15
        return $this->get($name);
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 6
    public function offsetSet($name, $value)
95
    {
96 6
        $this->instance($name, $value);
97 6
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102 1
    public function offsetUnset($name)
103
    {
104 1
        $this->destroy($name);
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 47
    public function get($name)
111
    {
112 47
        while (isset($this->aliases[$name])) {
113 11
            $name = $this->aliases[$name];
114
        }
115 47
        if (array_key_exists($name, $this->caches)) {
116 5
            return $this->caches[$name];
117
        }
118
        try {
119
            try {
120 46
                $instance = $this->descriptor($name)->createInstance($this);
121 14
            } catch (NullReferenceException $e) {
122 13
                if (!class_exists($name)) {
123 1
                    throw $e;
124
                }
125 12
                $this->bind($name);
126 44
                $instance = $this->descriptor($name)->createInstance($this);
127
            }
128 5
        } catch (CannotFindParameterException $e) {
129 4
            throw new CannotResolveException($name, $e->getParameter());
130
        }
131 42
        return $instance;
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function assert(string $name, string $package)
138
    {
139
        try {
140
            return $this->get($name);
141
        } catch (ContainerExceptionInterface $e) {
142
            throw new RequirePackageException($name, $package);
143
        }
144
    }
145
    
146
    /**
147
     * {@inheritdoc}
148
     */
149 20
    public function has($name)
150
    {
151
        return
152 20
            array_key_exists($name, $this->caches) ||
153 18
            array_key_exists($name, $this->descriptors) || 
154 15
            class_exists($name) ||
155 20
            isset($this->aliases[$name]);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 50
    public function destroy(...$names)
162
    {
163 50
        foreach ($names as $name) {
164 50
            if (array_key_exists($name, $this->caches)) {
165
                throw new CannotChangeException($name);
166
            }
167 50
            if (array_key_exists($name, $this->descriptors)) {
168 5
                if ($this->descriptors[$name]->frozen) {
169 2
                    throw new CannotChangeException($name);
170
                }
171
            }
172 50
            unset($this->descriptors[$name]);
173
        }
174 50
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179 30
    public function instance(string $name, $value): Descriptor
180
    {
181 30
        return $this->createDescriptor($name, new InstanceResolver($value), class_exists($name) ? $name : null);
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 12
    public function closure(string $name, callable $handler): Descriptor
188
    {
189 12
        return $this->createDescriptor($name, new CallableResolver($handler), class_exists($name) ? $name : null);
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     */
195 25
    public function bind(string $name, string $className = null): Descriptor
196
    {
197 25
        if (isset($className)) {
198 14
            $this->alias($className, $name);
199 14
            return $this->createDescriptor($name, new BindResolver($className), $className);
200
        }
201 16
        return $this->createDescriptor($name, new BindResolver($name), $name);
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207 23
    public function alias(string $alias, string $target)
208
    {
209 23
        if (!array_key_exists($target, $this->aliasIndex)) {
210 23
            $this->aliasIndex[$target] = [];
211
        }
212 23
        $this->aliasIndex[$target][] = $alias;
213 23
        $this->aliases[$alias] = $target;
214 23
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 46
    public function descriptor(string $name): Descriptor
220
    {
221 46
        while (isset($this->aliases[$name])) {
222 1
            $name = $this->aliases[$name];
223
        }
224 46
        if (!array_key_exists($name, $this->descriptors)) {
225 13
            throw new NullReferenceException($name);
226
        }
227 45
        return $this->descriptors[$name];
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233 6
    public function with(array $arguments = []): ContainerInterface
234
    {
235 6
        $new = clone $this;
236 6
        foreach ($arguments as $name => $argument) {
237 6
            if ($argument instanceof Descriptor) {
238
                 $new->createDescriptor($name, $argument);
0 ignored issues
show
Documentation introduced by
$argument is of type object<Wandu\DI\Descriptor>, but the function expects a object<Wandu\DI\Contracts\ResolverInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
239
            } else {
240 6
                $new->instance($name, $argument);
241
            }
242
        }
243 6
        return $new;
244
    }
245
    
246
    /**
247
     * {@inheritdoc}
248
     */
249 5
    public function create(string $className, array $arguments = [])
250
    {
251
        try {
252 5
            return (new Descriptor($className, new BindResolver($className)))->assign($arguments)->createInstance($this);
253 4
        } catch (CannotFindParameterException $e) {
254 3
            throw new CannotResolveException($className, $e->getParameter());
255
        }
256
    }
257
258
    /**
259
     * {@inheritdoc}
260
     */
261 9
    public function call(callable $callee, array $arguments = [])
262
    {
263
        try {
264 9
            return (new Descriptor(null, new CallableResolver($callee)))->assign($arguments)->createInstance($this);
265 5
        } catch (CannotFindParameterException $e) {
266 3
            throw new CannotResolveException($callee, $e->getParameter());
0 ignored issues
show
Documentation introduced by
$callee is of type callable, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
267
        }
268
    }
269
270
    /**
271
     * {@inheritdoc}
272
     */
273 5
    public function register(ServiceProviderInterface $provider)
274
    {
275 5
        $provider->register($this);
276 5
        $this->providers[] = $provider;
277 5
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282 3
    public function boot()
283
    {
284 3
        if (!$this->isBooted) {
285 3
            foreach ($this->providers as $provider) {
286 3
                $provider->boot($this);
287
            }
288 3
            $this->isBooted = true;
289
        }
290 3
        return $this;
291
    }
292
293 50
    protected function createDescriptor($name, ResolverInterface $resolver, $className = null): Descriptor
294
    {
295 50
        $this->destroy($name);
296 50
        return $this->descriptors[$name] = new Descriptor($className, $resolver);
297
    }
298
}
299