Passed
Push — master ( 04570c...82e63e )
by Vee
01:40
created

Container::__set()   A

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
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace veejay\container;
6
7
use Closure;
8
use Psr\Container\ContainerInterface;
9
use ReflectionClass;
10
use ReflectionException;
11
use ReflectionParameter;
12
13
class Container implements ContainerInterface
14
{
15
    /**
16
     * Экземпляры сервисов.
17
     * @var array
18
     */
19
    protected array $instances = [];
20
21
    /**
22
     * Конструкторы сервисов.
23
     * @var array
24
     */
25
    protected array $definitions = [];
26
27
    /**
28
     * @param array $definitions
29
     */
30
    public function __construct(array $definitions = [])
31
    {
32
        $this->setMultiple($definitions);
33
    }
34
35
    /**
36
     * Получение сервиса.
37
     * @param string $id
38
     * @return object
39
     * @throws NotFoundException|ContainerException
40
     */
41
    public function get(string $id): object
42
    {
43
        if (array_key_exists($id, $this->instances)) {
44
            return $this->instances[$id];
45
        }
46
47
        return $this->instances[$id] = $this->getNew($id);
48
    }
49
50
    /**
51
     * Создание нового объекта, по названию сервиса.
52
     * @param string $id
53
     * @return object
54
     * @throws NotFoundException|ContainerException
55
     */
56
    public function getNew(string $id): object
57
    {
58
        if (!$this->has($id)) {
59
            if ($this->isInstantiable($id)) {
60
                return $this->createInstance($id);
61
            }
62
63
            throw new NotFoundException(sprintf('Definition not found: %s', $id));
64
        }
65
66
        $definition = $this->definitions[$id];
67
        $type = gettype($definition);
68
69
        if ($definition instanceof Closure) {
70
            $object = $definition($this);
71
72
            if (is_object($object)) {
73
                return $object;
74
            }
75
76
            throw new ContainerException(sprintf('Closure must returns an object: %s', $id));
77
        } elseif ($type == 'object') {
78
            return $definition;
79
        } elseif ($this->isInstantiable($definition)) { // $type == 'string'
80
            return $this->createInstance($definition);
81
        } else { // $type == 'string'
82
            throw new ContainerException(sprintf('Class cannot be instantiated: %s', $definition));
83
        }
84
    }
85
86
    /**
87
     * Регистрация сервиса.
88
     * @param string $id
89
     * @param object|callable|string $definition
90
     * @return void
91
     */
92
    public function set(string $id, object|callable|string $definition): void
93
    {
94
        if (array_key_exists($id, $this->instances)) {
95
            unset($this->instances[$id]);
96
        }
97
98
        $this->definitions[$id] = $definition;
99
    }
100
101
    /**
102
     * Регистрация сервисов.
103
     * @param array $definitions
104
     * @return void
105
     */
106
    public function setMultiple(array $definitions): void
107
    {
108
        foreach ($definitions as $id => $definition) {
109
            $this->set($id, $definition);
110
        }
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function has(string $id): bool
117
    {
118
        return array_key_exists($id, $this->definitions);
119
    }
120
121
    /**
122
     * Создать объект по названию класса.
123
     * @param string $class
124
     * @return object
125
     * @throws ContainerException
126
     */
127
    protected function createInstance(string $class): object
128
    {
129
        try {
130
            $reflection = new ReflectionClass($class);
131
        } catch (ReflectionException $e) {
132
            throw new ContainerException(sprintf('Class does not exists: %s', $class));
133
        }
134
135
        $constructor = $reflection->getConstructor();
136
137
        if (!$constructor) {
138
            return $reflection->newInstance();
139
        }
140
141
        $args = [];
142
143
        foreach ($constructor->getParameters() as $param) {
144
            $args[] = $this->getParamValue($param);
145
        }
146
147
        return $reflection->newInstanceArgs($args);
148
    }
149
150
    /**
151
     * Вернуть значение указанного параметра конструктора.
152
     * @param ReflectionParameter $param
153
     * @return mixed
154
     */
155
    private function getParamValue(ReflectionParameter $param): mixed
156
    {
157
        $type = $param->getType();
158
159
        if (!is_null($type)) {
160
            $type = (string)$type;
161
        }
162
163
        if (!is_null($type) && (class_exists($type) || interface_exists($type))) {
164
            return $this->get($type);
165
        }
166
167
        if ($param->isDefaultValueAvailable()) {
168
            return $param->getDefaultValue();
169
        }
170
171
        return null;
172
    }
173
174
    /**
175
     * Является ли класс инстанцируемым.
176
     * @param string $class
177
     * @return bool
178
     */
179
    private function isInstantiable(string $class): bool
180
    {
181
        if (!class_exists($class)) {
182
            return false;
183
        }
184
185
        $reflectionClass = new ReflectionClass($class);
186
        return $reflectionClass->isInstantiable();
187
    }
188
}
189