Issues (23)

src/Container.php (2 issues)

Labels
Severity
1
<?php
2
3
/**
4
 * This file is part of slick/di package
5
 *
6
 * For the full copyright and license information, please view the LICENSE.md
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Di;
11
12
use ReflectionClass;
13
use ReflectionException;
14
use Slick\Di\Definition\CreateDefinitionsMethods;
15
use Slick\Di\Definition\ObjectDefinition;
16
use Slick\Di\Definition\Scope;
17
use Slick\Di\Exception\NotFoundException;
18
use Slick\Di\Inspector\ConstructorArgumentInspector;
0 ignored issues
show
The type Slick\Di\Inspector\ConstructorArgumentInspector was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
20
/**
21
 * Container
22
 *
23
 * @package Slick\Di
24
 * @author  Filipe Silva <[email protected]>
25
 */
26
class Container implements ContainerInterface
27
{
28
    use CreateDefinitionsMethods;
29
30
    /**
31
     * @var array
32
     */
33
    protected array $definitions = [];
34
35
    /**
36
     * @var array
37
     */
38
    protected static array $instances = [];
39
40
    /**
41
     * @var null|ContainerInterface
42
     */
43
    protected mixed $parent;
44
45
    /**
46
     * Creates a dependency container
47
     */
48
    public function __construct()
49
    {
50
        $this->parent = array_key_exists('container', self::$instances)
51
            ? self::$instances['container']
52
            : null;
53
54 74
        self::$instances['container'] = $this;
55
    }
56 74
57 74
    /**
58 74
     * Finds an entry of the container by its identifier and returns it.
59 72
     *
60 72
     * @param string $id Identifier of the entry to look for.
61
     *
62 74
     * @return mixed Entry.
63 74
     *@throws NotFoundException  No entry was found for this identifier.
64 74
     *
65 74
     */
66 74
    public function get(string $id): mixed
67
    {
68
        if (!$this->has($id) && $id !== 'container') {
69
            if (class_exists($id)) {
70
                $reflectionClass = new ReflectionClass($id);
71
                if ($reflectionClass->isInstantiable()) {
72
                    return $this->make($id);
73
                }
74
            }
75
            throw new NotFoundException(
76
                "Dependency container has not found any definition for '$id'"
77
            );
78 52
        }
79
        return $this->resolve($id);
80 52
    }
81 2
82 2
    /**
83
     * Returns true if the container can return an entry for the given
84 2
     * identifier. Returns false otherwise.
85
     *
86
     * @param string $id Identifier of the entry to look for.
87 50
     *
88 50
     * @return boolean
89 50
     */
90
    public function has(string $id): bool
91 50
    {
92 46
        if (!array_key_exists($id, $this->definitions)) {
93 46
            return $this->parentHas($id);
94
        }
95 50
96
        return true;
97
    }
98
99
    /**
100
     * Adds a definition or a value to the container
101
     *
102
     * @param string $name
103
     * @param mixed|null $definition
104
     * @param string|Scope $scope      Resolving scope
105
     * @param array        $parameters Used if $value is a callable
106 56
     *
107
     * @return Container
108 56
     */
109 28
    public function register(
110
        string       $name,
111
        mixed        $definition = null,
112 56
        string|Scope $scope = Scope::SINGLETON,
113 18
        array        $parameters = []
114
    ): Container {
115 52
        if ($definition instanceof DefinitionInterface) {
0 ignored issues
show
The type Slick\Di\DefinitionInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
116
            return $this->add($name, $definition);
117
        }
118
119
        $definition = $this->createDefinition(
120
            $definition,
121
            $parameters
122
        );
123
        $scope = is_string($scope) ? new Scope($scope) : $scope;
124
        $definition->setScope($scope);
125
        return $this->add($name, $definition);
126
    }
127
128
    /**
129
     * Checks if parent has a provided key
130
     *
131
     * @param string $key
132
     *
133 74
     * @return bool
134
     */
135
    protected function parentHas(string $key): bool
136
    {
137 74
        if (!$this->parent) {
138 72
            return false;
139 32
        }
140 32
        return $this->parent->has($key);
141 32
    }
142 32
143 32
    /**
144 32
     * Resolves the definition that was saved under the provided name
145 32
     *
146
     * @param string $name
147 72
     *
148 72
     * @return mixed
149 72
     *
150 72
     * @throws NotFoundException
151
     */
152 72
    protected function resolve(string $name): mixed
153 72
    {
154
        if (array_key_exists($name, self::$instances)) {
155 72
            return self::$instances[$name];
156
        }
157 72
158 72
        if (array_key_exists($name, $this->definitions)) {
159
            $entry = $this->definitions[$name];
160
            return $this->registerEntry($name, $entry);
161
        }
162
163
        return $this->parent->get($name);
164
    }
165
166
    /**
167
     * Checks the definition scope to register resolution result
168
     *
169
     * If scope is set to prototype the resolution result is not
170
     * stores in the container instances.
171
     *
172
     * @param string $name
173
     * @param DefinitionInterface $definition
174
     * @return mixed
175 10
     */
176
    protected function registerEntry(string $name, DefinitionInterface $definition): mixed
177
    {
178 10
        $value = $definition
179 2
            ->setContainer($this->container())
180
            ->resolve();
181 2
        if ((string) $definition->getScope() !== Scope::PROTOTYPE) {
182
            self::$instances[$name] = $value;
183
        }
184 8
        return $value;
185 4
    }
186 4
187
    /**
188 8
     * Adds a definition to the definitions list
189
     *
190
     * This method does not override an existing entry if the same name exists
191
     * in the definitions or in any definitions of its parents.
192
     * This way it is possible to change entries defined by other packages
193
     * as those are build after the main application container is build.
194
     * The main application container should be the first to be created and
195
     * therefore set any entry that will override the latest containers build.
196
     *
197
     * @param string $name
198 46
     * @param DefinitionInterface $definition
199
     *
200 46
     * @return Container
201 46
     */
202 46
    protected function add(string $name, DefinitionInterface $definition): static
203 46
    {
204 46
        if ($this->has($name)) {
205 46
            return $this;
206 46
        }
207
208
        $this->definitions[$name] = $definition;
209
        $definition->setContainer($this->container());
210
        return $this;
211
    }
212
213
    /**
214
     * Creates an instance of provided class injecting its dependencies
215
     *
216 70
     * @param string $className
217
     * @param array ...$arguments
218 70
     *
219 70
     * @return mixed
220
     * @throws ReflectionException
221
     */
222
    public function make(string $className, ...$arguments): mixed
223
    {
224
        if (is_a($className, ContainerInjectionInterface::class, true)) {
225
            return call_user_func_array([$className, 'create'], [$this]);
226
        }
227
228
        return $this->createFromClass($className, $this->container(), ...$arguments)->resolve();
229
    }
230 72
231
    /**
232 72
     * Gets the parent container if it exists
233
     *
234 72
     * @return null|ContainerInterface
235
     */
236 72
    public function parent(): ?ContainerInterface
237 72
    {
238
        return $this->parent;
239
    }
240
241
    /**
242
     * Get the top container
243
     *
244
     * @return container
245
     */
246
    private function container(): Container
247
    {
248
        return self::$instances['container'];
249
    }
250
}
251