Test Failed
Pull Request — master (#37)
by Divine Niiquaye
03:14
created

Container::set()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 15
c 5
b 0
f 0
dl 0
loc 26
ccs 16
cts 16
cp 1
rs 9.2222
cc 6
nc 8
nop 3
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2021 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade\DI;
19
20
use Rade\DI\Definitions\{DefinitionInterface, ShareableDefinitionInterface};
21
use Rade\DI\Exceptions\{ContainerResolutionException, FrozenServiceException, NotFoundServiceException};
22
23
/**
24
 * Dependency injection container.
25
 *
26
 * @author Divine Niiquaye Ibok <[email protected]>
27
 */
28
class Container extends AbstractContainer implements \ArrayAccess
29
{
30
    /** @var array<string,string> internal cached services */
31
    protected array $methodsMap = [];
32
33
    public function __construct()
34
    {
35
        if (empty($this->types)) {
36
            $this->type(self::SERVICE_CONTAINER, Resolver::autowireService(static::class));
37
        }
38
39
        parent::__construct();
40
    }
41
42
    /**
43
     * Sets a new service to a unique identifier.
44
     *
45
     * @param string $offset The unique identifier for the parameter or object
46
     * @param mixed  $value  The value of the service assign to the $offset
47
     *
48
     * @throws FrozenServiceException Prevent override of a frozen service
49
     */
50
    public function offsetSet($offset, $value): void
51
    {
52
        $this->autowire($offset, $value);
53
    }
54
55
    /**
56
     * Gets a registered service definition.
57 106
     *
58
     * @param string $offset The unique identifier for the service
59 106
     *
60 106
     * @throws NotFoundServiceException If the identifier is not defined
61 106
     *
62
     * @return mixed The value of the service
63
     */
64
    #[\ReturnTypeWillChange]
65
    public function offsetGet($offset)
66
    {
67
        return $this->get($offset);
68
    }
69
70
    /**
71 63
     * Checks if a service is set.
72
     *
73 63
     * @param string $offset The unique identifier for the service
74 63
     */
75
    public function offsetExists($offset): bool
76
    {
77
        return $this->has($offset);
78
    }
79
80
    /**
81
     * Unset a service by given offset.
82
     *
83
     * @param string $offset The unique identifier for service definition
84
     */
85 55
    public function offsetUnset($offset): void
86
    {
87 55
        $this->removeDefinition($offset);
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     *
93
     * @throws FrozenServiceException if definition has been initialized
94
     */
95 7
    public function definition(string $id)
96
    {
97 7
        if (\array_key_exists($id, $this->privates) || isset($this->methodsMap[$id])) {
98
            throw new FrozenServiceException(\sprintf('The "%s" internal service is meant to be private and out of reach.', $id));
99
        }
100
101
        return parent::definition($id);
102
    }
103
104
    /**
105 6
     * {@inheritdoc}
106
     */
107 6
    public function keys(): array
108 6
    {
109
        return [...parent::keys(), ...\array_keys($this->methodsMap)];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(parent::key...eys($this->methodsMap)) returns the type array<integer,array> which is incompatible with the return type mandated by Rade\DI\ContainerInterface::keys() of array<integer,string>.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function get(string $id, int $invalidBehavior = /* self::EXCEPTION_ON_MULTIPLE_SERVICE */ 1)
116 17
    {
117
        if (isset($this->services[$id])) {
118 17
            return $this->services[$id];
119
        }
120
121
        if (\array_key_exists($id, $this->aliases)) {
122
            return $this->services[$id = $this->aliases[$id]] ?? $this->get($id);
123
        }
124
125
        return self::SERVICE_CONTAINER === $id ? $this : parent::get($id, $invalidBehavior);
126
    }
127 11
128
    /**
129 11
     * {@inheritdoc}
130
     */
131 11
    public function has(string $id): bool
132
    {
133
        return parent::has($id) || \array_key_exists($this->aliases[$id] ?? $id, $this->methodsMap);
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     *
139
     * @throws \ReflectionException
140
     */
141
    protected function doCreate(string $id, $definition, int $invalidBehavior)
142
    {
143
        if ($definition instanceof DefinitionInterface) {
144
            if ($definition instanceof ShareableDefinitionInterface) {
145
                if ($definition->isAbstract()) {
146
                    throw new ContainerResolutionException(\sprintf('Resolving an abstract definition %s is not supported.', $id));
147
                }
148
149 10
                if (!$definition->isPublic()) {
150
                    $this->removeDefinition($id); // Definition service available once, else if shareable, accessed from cache.
151 10
                }
152 2
153
                if (!$definition->isShared()) {
154
                    return $definition->build($id, $this->resolver);
155 8
                }
156
            }
157 7
158 1
            $definition = $definition->build($id, $this->resolver);
159
        } elseif (\is_callable($definition)) {
160
            $definition = $this->resolver->resolveCallable($definition);
161 6
        } elseif (!$definition) {
162 6
            try {
163
                if ($id !== $anotherService = $this->resolver->resolve($id)) {
164
                    return $this->services[$id] = $anotherService;
165 5
                }
166
            } catch (ContainerResolutionException $e) {
167
                // Skip error throwing while resolving
168
            }
169
170
            if (self::NULL_ON_INVALID_SERVICE !== $invalidBehavior) {
171 24
                throw $this->createNotFound($id);
172
            }
173 24
174
            return null;
175
        }
176
177
        return $this->definitions[$id] = $this->services[$id] = $definition;
178
    }
179
}
180