ConstructorResolver   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 140
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 64
c 1
b 0
f 0
dl 0
loc 140
rs 10
wmc 25

3 Methods

Rating   Name   Duplication   Size   Complexity  
B resolve() 0 37 6
C resolveParameter() 0 62 15
A getTypes() 0 19 4
1
<?php
2
3
/**
4
 * Platine Container
5
 *
6
 * Platine Container is the implementation of PSR 11
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Container
11
 * Copyright (c) 2019 Dion Chaika
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file ConstructorResolver.php
34
 *
35
 *  This class use constructor to resolve the instance
36
 *
37
 *  @package    Platine\Container
38
 *  @author Platine Developers Team
39
 *  @copyright  Copyright (c) 2020
40
 *  @license    http://opensource.org/licenses/MIT  MIT License
41
 *  @link   https://www.platine-php.com
42
 *  @version 1.0.0
43
 *  @filesource
44
 */
45
46
declare(strict_types=1);
47
48
namespace Platine\Container\Resolver;
49
50
use Platine\Container\ContainerInterface;
51
use Platine\Container\Exception\ContainerException;
52
use Platine\Container\Exception\NotFoundException;
53
use Platine\Container\ParameterCollection;
54
use ReflectionClass;
55
use ReflectionException;
56
use ReflectionNamedType;
57
use ReflectionParameter;
58
use ReflectionUnionType;
59
60
/**
61
 * @class ConstructorResolver
62
 * @package Platine\Container\Resolver
63
 */
64
class ConstructorResolver implements ResolverInterface
65
{
66
    /**
67
     * {@inheritdoc}
68
     */
69
    public function resolve(
70
        ContainerInterface $container,
71
        string $type,
72
        ?ParameterCollection $parameters = null
73
    ): mixed {
74
        try {
75
            $class = new ReflectionClass($type);
76
        } catch (ReflectionException $e) {
77
            throw new ContainerException($e->getMessage());
78
        }
79
80
        if ($class->isInstantiable() === false) {
81
            throw new ContainerException(sprintf('Type/class [%s] is not instantiable!', $type));
82
        }
83
84
        $constructor = $class->getConstructor();
85
        if ($constructor === null) {
86
            try {
87
                return $class->newInstanceWithoutConstructor();
88
            } catch (ReflectionException $e) {
89
                throw new ContainerException($e->getMessage());
90
            }
91
        }
92
93
        $callback = function (ReflectionParameter $parameter) use ($container, $parameters) {
94
            return $this->resolveParameter(
95
                $container,
96
                $parameter,
97
                $parameters
98
            );
99
        };
100
101
        $arguments = array_map($callback, $constructor->getParameters());
102
        try {
103
            return $class->newInstanceArgs($arguments);
104
        } catch (ReflectionException $e) {
105
            throw new ContainerException($e->getMessage());
106
        }
107
    }
108
109
    /**
110
     * Resolve the parameter
111
     * @param  ContainerInterface       $container
112
     * @param  ReflectionParameter     $parameter           the reflection parameter
113
     * @param  ParameterCollection|null $parameters
114
     * @return mixed
115
     */
116
    protected function resolveParameter(
117
        ContainerInterface $container,
118
        ReflectionParameter $parameter,
119
        ?ParameterCollection $parameters = null
120
    ): mixed {
121
        $class = null;
122
        $className = null;
123
        $types = $this->getTypes($parameter);
124
125
        // TODO: handle for union types
126
        if (count($types) > 0) {
127
            foreach ($types as /** @var ReflectionNamedType $type */ $type) {
128
                $name = $type->getName();
129
                if ($type->isBuiltin() === false && $container->has($name)) {
130
                    $className = $name;
131
                    break;
132
                }
133
            }
134
        }
135
136
        if ($className !== null) {
137
            $class = new ReflectionClass($className);
138
        }
139
140
        //If the parameter is not a class
141
        if ($class === null) {
142
            if ($parameters !== null) {
143
                if (
144
                        $parameters->has($parameter->name) &&
145
                        $parameters->get($parameter->name) !== null
146
                ) {
147
                    return $parameters->get($parameter->name)
148
                                       ->getValue($container);
149
                }
150
            }
151
152
            if ($parameter->isDefaultValueAvailable()) {
153
                try {
154
                    return $parameter->getDefaultValue();
155
                } catch (ReflectionException $e) {
156
                    throw new ContainerException($e->getMessage());
157
                }
158
            }
159
160
            if ($parameter->isOptional()) {
161
                // This branch is required to work around PHP bugs where a parameter is optional
162
                // but has no default value available through reflection. Specifically, PDO exhibits
163
                // this behavior.
164
                return null;
165
            }
166
167
            if (count($types) === 1 && $types[0]->isBuiltin() === false) {
168
                throw new NotFoundException(sprintf(
169
                    'The type/class [%s] does not exist in the container!',
170
                    $types[0]->getName()
171
                ));
172
            }
173
174
            throw new ContainerException(sprintf('Parameter [%s] is not bound!', $parameter->name));
175
        }
176
177
        return $container->get($class->name);
178
    }
179
180
    /**
181
     * Return the types of the given parameter
182
     * @param ReflectionParameter $parameter
183
     * @return ReflectionNamedType[]
184
     */
185
    protected function getTypes(ReflectionParameter $parameter): array
186
    {
187
        if ($parameter->getType() === null) {
188
            return [];
189
        }
190
191
        $type = $parameter->getType();
192
        $types = [];
193
        if ($type instanceof ReflectionNamedType) {
194
            $types = [$type];
195
        }
196
197
        if ($type instanceof ReflectionUnionType) {
198
            /** @var ReflectionNamedType[] $namedTypes */
199
            $namedTypes = $type->getTypes();
200
            $types = $namedTypes;
201
        }
202
203
        return $types;
204
    }
205
}
206