Passed
Push — develop ( 473af2...e1b286 )
by nguereza
01:55
created

ConstructorResolver::resolveParameter()   C

Complexity

Conditions 14
Paths 100

Size

Total Lines 59
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 30
nc 100
nop 3
dl 0
loc 59
rs 6.2666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\ParameterCollection;
53
use ReflectionClass;
54
use ReflectionException;
55
use ReflectionNamedType;
56
use ReflectionParameter;
57
use ReflectionUnionType;
58
59
/**
60
 * @class ConstructorResolver
61
 * @package Platine\Container\Resolver
62
 */
63
class ConstructorResolver implements ResolverInterface
64
{
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function resolve(
69
        ContainerInterface $container,
70
        string $type,
71
        ?ParameterCollection $parameters = null
72
    ): mixed {
73
        try {
74
            $class = new ReflectionClass($type);
75
        } catch (ReflectionException $e) {
76
            throw new ContainerException($e->getMessage());
77
        }
78
79
        if ($class->isInstantiable() === false) {
80
            throw new ContainerException(sprintf('Type/class [%s] is not instantiable!', $type));
81
        }
82
83
        $constructor = $class->getConstructor();
84
        if ($constructor === null) {
85
            try {
86
                return $class->newInstanceWithoutConstructor();
87
            } catch (ReflectionException $e) {
88
                throw new ContainerException($e->getMessage());
89
            }
90
        }
91
92
        $callback = function (ReflectionParameter $parameter) use ($container, $parameters) {
93
            return $this->resolveParameter(
94
                $container,
95
                $parameter,
96
                $parameters
97
            );
98
        };
99
100
        $arguments = array_map($callback, $constructor->getParameters());
101
        try {
102
            return $class->newInstanceArgs($arguments);
103
        } catch (ReflectionException $e) {
104
            throw new ContainerException($e->getMessage());
105
        }
106
    }
107
108
    /**
109
     * Resolve the parameter
110
     * @param  ContainerInterface       $container
111
     * @param  ReflectionParameter     $parameter           the reflection parameter
112
     * @param  ParameterCollection|null $parameters
113
     * @return mixed
114
     */
115
    protected function resolveParameter(
116
        ContainerInterface $container,
117
        ReflectionParameter $parameter,
118
        ?ParameterCollection $parameters = null
119
    ): mixed {
120
        $class = null;
121
        $className = null;
122
        $types = $this->getTypes($parameter);
123
124
        // TODO: handle for union types
125
        if (count($types) > 1) {
126
            foreach ($types as /** @var ReflectionNamedType $type */ $type) {
127
                $name = $type->getName();
128
                if ($type->isBuiltin() === false && $container->has($name)) {
129
                    $className = $name;
130
                    break;
131
                }
132
            }
133
        } else {
134
            if ($types[0]->isBuiltin() === false) {
135
                $className = $types[0]->getName();
136
            }
137
        }
138
139
        if ($className !== null) {
140
            $class = new ReflectionClass($className);
141
        }
142
143
        //If the parameter is not a class
144
        if ($class === null) {
145
            if ($parameters !== null) {
146
                if (
147
                        $parameters->has($parameter->name) &&
148
                        $parameters->get($parameter->name) !== null
149
                ) {
150
                    return $parameters->get($parameter->name)
151
                                       ->getValue($container);
152
                }
153
            }
154
155
            if ($parameter->isDefaultValueAvailable()) {
156
                try {
157
                    return $parameter->getDefaultValue();
158
                } catch (ReflectionException $e) {
159
                    throw new ContainerException($e->getMessage());
160
                }
161
            }
162
163
            if ($parameter->isOptional()) {
164
                // This branch is required to work around PHP bugs where a parameter is optional
165
                // but has no default value available through reflection. Specifically, PDO exhibits
166
                // this behavior.
167
                return null;
168
            }
169
170
            throw new ContainerException(sprintf('Parameter [%s] is not bound!', $parameter->name));
171
        }
172
173
        return $container->get($class->name);
174
    }
175
176
    /**
177
     * Return the types of the given parameter
178
     * @param ReflectionParameter $parameter
179
     * @return ReflectionNamedType[]
180
     */
181
    protected function getTypes(ReflectionParameter $parameter): array
182
    {
183
        if ($parameter->getType() === null) {
184
            return [];
185
        }
186
187
        $type = $parameter->getType();
188
        $types = [];
189
        if ($type instanceof ReflectionNamedType) {
190
            $types = [$type];
191
        }
192
193
        if ($type instanceof ReflectionUnionType) {
194
            /** @var ReflectionNamedType[] $namedTypes */
195
            $namedTypes = $type->getTypes();
196
            $types = $namedTypes;
197
        }
198
199
        return $types;
200
    }
201
}
202