Completed
Push — sam-rework ( 1696e6 )
by Andrii
38:13 queued 23:11
created

Definition::normalize()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 30
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 30
rs 8.0555
c 0
b 0
f 0
cc 9
nc 7
nop 1
1
<?php
2
3
4
namespace yii\di;
5
6
use Psr\Container\ContainerInterface;
7
use yii\di\contracts\DependencyInterface;
8
use yii\di\dependencies\NamedDependency;
9
use yii\di\dependencies\ValueDependency;
10
use yii\di\exceptions\InvalidConfigException;
11
use yii\di\exceptions\NotInstantiableException;
12
use yii\di\resolvers\ClassNameResolver;
13
14
/**
15
 * Class Definition represents a definition in a container
16
 * @package yii\di
17
 */
18
class Definition
19
{
20
    private static $dependencies = [];
21
22
    private const TYPE_CALLABLE = 'callable';
23
    private const TYPE_ARRAY = 'array';
24
    private const TYPE_RESOLVABLE = 'resolvable';
25
    private const TYPE_VALUE = 'value';
26
    private $type;
27
28
    /**
29
     * @var array|DependencyInterface
30
     */
31
    private $config;
32
33
    private function __construct($config, string $type)
34
    {
35
        $this->type = $type;
36
        $this->config = $config;
37
    }
38
39
    public static function normalize($config): self
40
    {
41
        if ($config instanceof self) {
42
            return $config;
43
        }
44
45
        if (is_string($config)) {
46
            return new self(['__class' => $config], self::TYPE_ARRAY);
47
        }
48
49
        if (is_array($config)
50
            && !isset($config[0], $config[1])
51
            && isset($config['__class'])
52
        ) {
53
            return new self($config, self::TYPE_ARRAY);
54
        }
55
56
        if (\is_callable($config)) {
57
            return new self($config, self::TYPE_CALLABLE);
58
        }
59
60
        if ($config instanceof DependencyInterface) {
61
            return new self($config, self::TYPE_RESOLVABLE);
62
        }
63
64
        if (is_object($config)) {
65
            return new self($config, self::TYPE_VALUE);
66
        }
67
68
        throw new InvalidConfigException('Invalid definition:' . var_export($config, true));
69
    }
70
71
    /**
72
     * @param array $definition
73
     * @param ContainerInterface $rootContainer
74
     */
75
    private function resolveArray(array $config, ContainerInterface $rootContainer)
76
    {
77
        if (empty($config['__class'])) {
78
            throw new NotInstantiableException(var_export($config, true));
79
        }
80
        $class = $config['__class'];
81
        unset($config['__class']);
82
83
        $dependencies = $this->getDependencies($class);
84
85
        if (isset($config['__construct()'])) {
86
            foreach (array_values($config['__construct()']) as $index => $param) {
87
                if (!$param instanceof Reference) {
88
                    $dependencies[$index] = new ValueDependency($param);
89
                } else {
90
                    $dependencies[$index] = new NamedDependency($param->getId(), false);
91
                }
92
            }
93
            unset($config['__construct()']);
94
        }
95
96
        $resolved = [];
97
        /** @var DependencyInterface $dependency */
98
        foreach ($dependencies as $dependency) {
99
            $resolved[] = $dependency->resolve($rootContainer);
100
        }
101
        $object = new $class(...$resolved);
102
103
        $this->configure($object, $config, $rootContainer);
0 ignored issues
show
Deprecated Code introduced by
The function yii\di\Definition::configure() has been deprecated: Not recommended for explicit use. Added only to support Yii 2.0 behavior. ( Ignorable by Annotation )

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

103
        /** @scrutinizer ignore-deprecated */ $this->configure($object, $config, $rootContainer);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
104
        return $object;
105
    }
106
107
    /**
108
     * @param Container $container
109
     * @return mixed|object
110
     * @throws NotInstantiableException
111
     */
112
    public function resolve(ContainerInterface $rootContainer)
113
    {
114
        switch ($this->type) {
115
            case self::TYPE_CALLABLE:
116
                return call_user_func($this->config, $rootContainer);
0 ignored issues
show
Bug introduced by
It seems like $this->config can also be of type yii\di\contracts\DependencyInterface; however, parameter $function of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

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

116
                return call_user_func(/** @scrutinizer ignore-type */ $this->config, $rootContainer);
Loading history...
117
            case self::TYPE_ARRAY:
118
                return $this->resolveArray($this->config, $rootContainer);
0 ignored issues
show
Bug introduced by
It seems like $this->config can also be of type yii\di\contracts\DependencyInterface; however, parameter $config of yii\di\Definition::resolveArray() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

118
                return $this->resolveArray(/** @scrutinizer ignore-type */ $this->config, $rootContainer);
Loading history...
119
            case self::TYPE_VALUE:
120
                return $this->config;
121
            case self::TYPE_RESOLVABLE:
122
                return $this->config->resolve($rootContainer);
123
        }
124
125
        throw new \RuntimeException('Attempted to resolve invalid definition of type: ' . $this->type);
126
    }
127
128
    /**
129
     * Configures an object with the given configuration.
130
     * @deprecated Not recommended for explicit use. Added only to support Yii 2.0 behavior.
131
     * @param object $object the object to be configured
132
     * @param iterable $config property values and methods to call
133
     * @param ContainerInterface $container
134
     * @return object the object itself
135
     */
136
    private function configure($object, iterable $config, ContainerInterface $container)
137
    {
138
        foreach ($config as $action => $arguments) {
139
            if (substr($action, -2) === '()') {
140
                // method call
141
                \call_user_func_array([$object, substr($action, 0, -2)], $arguments);
142
            } else {
143
                // property
144
                if ($arguments instanceof DependencyInterface) {
145
                    $arguments = $arguments->resolve($container);
146
                }
147
                $object->$action = $arguments;
148
            }
149
        }
150
151
        return $object;
152
    }
153
154
    /**
155
     * Returns the dependencies of the specified class.
156
     * @param string $class class name, interface name or alias name
157
     * @return DependencyInterface[] the dependencies of the specified class.
158
     * @throws InvalidConfigException
159
     * @throws NotInstantiableException
160
     * @internal
161
     */
162
    private function getDependencies(string $class): array
163
    {
164
        if (!isset($this->dependencies[$class])) {
165
            // For now use hard coded resolver.
166
            $resolver = new ClassNameResolver();
167
168
            self::$dependencies[$class] = $resolver->resolveConstructor($class);
169
        }
170
171
        return self::$dependencies[$class];
172
    }
173
}
174