Failed Conditions
Push — master ( fa2e68...636df0 )
by Arnold
03:02
created

src/Container.php (2 issues)

Labels
Severity
1
<?php declare(strict_types=1);
2
3
namespace Jasny\Container;
4
5
use Improved as i;
6
use Interop\Container\ContainerInterface as InteropContainer;
7
use Jasny\Container\Autowire\AutowireInterface;
8
use Psr\Container\ContainerInterface;
9
use Jasny\Container\Exception\NotFoundException;
10
use Jasny\Container\Exception\NoSubContainerException;
11
12
/**
13
 * This class is a minimalist dependency injection container.
14
 * It has compatibility with container-interop's ContainerInterface and delegate-lookup feature.
15
 */
16
class Container implements InteropContainer, AutowireContainerInterface
17
{
18
    /**
19
     * The delegate lookup.
20
     *
21
     * @var ContainerInterface
22
     */
23
    protected $delegateLookupContainer;
24
25
    /**
26
     * The array of closures defining each entry of the container.
27
     *
28
     * @var \Closure[]
29
     */
30
    protected $callbacks;
31
32
    /**
33
     * The array of entries once they have been instantiated.
34
     *
35
     * @var mixed[]
36
     */
37
    protected $instances = [];
38
39
40
    /**
41
     * Class constructor
42
     *
43
     * @param \Traversable<\Closure>|\Closure[] $entries Entries must be passed as an array of anonymous functions.
44
     * @param ContainerInterface $delegateLookupContainer Optional delegate lookup container.
45
     */
46 14
    public function __construct(iterable $entries, ContainerInterface $delegateLookupContainer = null)
47
    {
48 14
        $this->callbacks = is_array($entries) ? $entries : iterator_to_array($entries);
49 14
        $this->delegateLookupContainer = $delegateLookupContainer ?: $this;
50 14
    }
51
52
53
    /**
54
     * Get an instance from the container.
55
     *
56
     * @param string $identifier
57
     * @return mixed
58
     * @throws NotFoundException
59
     * @throws NoSubContainerException
60
     */
61 13
    public function get($identifier)
62
    {
63 13
        i\type_check($identifier, 'string');
1 ignored issue
show
The function type_check was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

63
        /** @scrutinizer ignore-call */ 
64
        i\type_check($identifier, 'string');
Loading history...
64
65 13
        if (array_key_exists($identifier, $this->instances)) {
66 3
            return $this->instances[$identifier];
67
        }
68
69 13
        if (!isset($this->callbacks[$identifier])) {
70 4
            return $this->getSub($identifier);
71
        }
72
73 11
        $instance = $this->callbacks[$identifier]($this->delegateLookupContainer);
74 11
        $this->assertType($instance, $identifier);
75
76 9
        $this->instances[$identifier] = $instance;
77
78 9
        return $instance;
79
    }
80
81
    /**
82
     * Check if the container has an entry.
83
     *
84
     * @param string $identifier
85
     * @return bool
86
     * @throws NotFoundException
87
     * @throws NoSubContainerException
88
     */
89 3
    public function has($identifier): bool
90
    {
91 3
        i\type_check($identifier, 'string');
1 ignored issue
show
The function type_check was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

91
        /** @scrutinizer ignore-call */ 
92
        i\type_check($identifier, 'string');
Loading history...
92
93 3
        return isset($this->callbacks[$identifier]) || $this->hasSub($identifier);
94
    }
95
96
    /**
97
     * Instantiate a new object, autowire dependencies.
98
     *
99
     * @param string $class
100
     * @param mixed ...$args
101
     * @return object
102
     * @throws NotFoundException
103
     * @throws NoSubContainerException
104
     */
105 3
    public function autowire(string $class, ...$args)
106
    {
107 3
        return $this->get(AutowireInterface::class)->instantiate($class, ...$args);
108
    }
109
110
111
    /**
112
     * Check the type of the instance if the identifier is an interface or class name.
113
     *
114
     * @param mixed  $instance
115
     * @param string $identifier
116
     * @throws \TypeError
117
     */
118 11
    protected function assertType($instance, string $identifier): void
119
    {
120 11
        if (ctype_upper($identifier[0]) &&
121 11
            strpos($identifier, '.') === false &&
122 11
            (class_exists($identifier) || interface_exists($identifier)) &&
123 11
            !is_a($instance, $identifier)
124
        ) {
125 2
            $type = (is_object($instance) ? get_class($instance) . ' ' : '') . gettype($instance);
126 2
            throw new \TypeError("Entry is a $type, which does not implement $identifier");
127
        }
128 9
    }
129
130
131
    /**
132
     * Get an instance from a subcontainer.
133
     *
134
     * @param string $identifier
135
     * @return mixed
136
     * @throws NotFoundException
137
     * @throws NoSubContainerException
138
     */
139 4
    protected function getSub(string $identifier)
140
    {
141 4
        [$subcontainer, $containerId, $subId] = $this->findSubContainer($identifier);
142
143 4
        if (!isset($subcontainer)) {
144 2
            throw new NotFoundException("Entry \"$identifier\" is not defined.");
145
        }
146
147 2
        if (!$subcontainer instanceof ContainerInterface) {
148 1
            throw new NoSubContainerException("Entry \"$containerId\" is not a PSR-11 compatible container");
149
        }
150
151 1
        return $subcontainer->get($subId);
152
    }
153
154
    /**
155
     * Get an instance from a subcontainer.
156
     *
157
     * @param string $identifier
158
     * @return bool
159
     * @throws NotFoundException
160
     * @throws NoSubContainerException
161
     */
162 3
    protected function hasSub(string $identifier): bool
163
    {
164 3
        [$subcontainer, , $subId] = $this->findSubContainer($identifier);
165
166 3
        return isset($subcontainer) && $subcontainer instanceof ContainerInterface && $subcontainer->has($subId);
167
    }
168
169
    /**
170
     * Find an subcontainer iterating through the identifier parts.
171
     *
172
     * @param string $identifier
173
     * @return array  [subcontainer, container id, subidentifier]
174
     * @throws NotFoundException
175
     * @throws NoSubContainerException
176
     */
177 7
    protected function findSubContainer(string $identifier): array
178
    {
179 7
        $containerId = null;
180 7
        $subcontainer = null;
181 7
        $parts = explode('.', $identifier);
182 7
        $subParts = [];
183
184 7
        while ($parts !== []) {
185 7
            array_unshift($subParts, array_pop($parts));
186 7
            $containerId = join('.', $parts);
187
188 7
            if (isset($this->callbacks[$containerId])) {
189 4
                $subcontainer = $this->get($containerId);
190 4
                break;
191
            }
192
        }
193
194 7
        return isset($subcontainer) ? [$subcontainer, $containerId, join('.', $subParts)] : [null, null, null];
195
    }
196
}
197