Builder   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 5
dl 0
loc 164
ccs 72
cts 72
cp 1
rs 10
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getInstance() 0 9 2
A buildInstance() 0 7 1
A buildFromFactory() 0 17 3
A buildDependencies() 0 14 4
A prepareDependencies() 0 10 2
A getParameterArgument() 0 16 4
A isDependencyInGivenArguments() 0 4 1
A getDependencyFromArguments() 0 4 1
A isDependencyClassOrInterface() 0 5 3
A getDependencyRecursively() 0 12 1
A isDependencyNameInContainer() 0 4 1
A getDependencyFromParameterInContainer() 0 4 1
A pushDependentClass() 0 4 1
A popDependentClass() 0 4 1
A pushArgumentName() 0 4 1
A popArgumentName() 0 4 1
1
<?php
2
3
namespace Hgraca\MicroDI;
4
5
use Hgraca\Helper\InstanceHelper;
6
use Hgraca\MicroDI\DependencyResolver\DependencyResolverInterface;
7
use Hgraca\MicroDI\Exception\CanNotInstantiateDependenciesException;
8
use Hgraca\MicroDI\Port\ContainerInterface;
9
use InvalidArgumentException;
10
11
final class Builder implements BuilderInterface
12
{
13
    /** @var ContainerInterface */
14
    private $container;
15
16
    /** @var string[] */
17
    private $dependentStack = [];
18
19
    /** @var string[] */
20
    private $argumentNameStack = [];
21
22
    /** @var DependencyResolverInterface */
23
    private $dependencyResolver;
24
25 26
    public function __construct(ContainerInterface $container, DependencyResolverInterface $dependencyResolver)
26
    {
27 26
        $this->container = $container;
28 26
        $this->dependencyResolver = $dependencyResolver;
29 26
    }
30
31 5
    public function getInstance(string $class, array $arguments = [])
32
    {
33 5
        if (!$this->container->hasInstance($class)) {
34 4
            $instance = $this->buildInstance($class, $arguments);
35 4
            $this->container->addInstance($instance);
36
        }
37
38 5
        return $this->container->getInstance($class);
39
    }
40
41 5
    public function buildInstance(string $class, array $arguments = [])
42
    {
43 5
        return InstanceHelper::createInstance(
44
            $class,
45 5
            $this->buildDependencies([$class, '__construct'], $arguments)
46
        );
47
    }
48
49 3
    public function buildFromFactory(string $factoryClass, array $arguments = [])
50
    {
51 3
        if (!is_a($factoryClass, FactoryInterface::class, $allowString = true)) {
52 1
            throw new InvalidArgumentException(
53 1
                "The given factory class $factoryClass must implement " . FactoryInterface::class
54
            );
55
        }
56
57
        /** @var FactoryInterface $factory */
58 2
        $factory = $this->getInstance($factoryClass, $arguments);
59
60 2
        $context = $this->container->hasFactoryContext($factoryClass)
61 1
            ? $this->container->getFactoryContext($factoryClass)
62 2
            : [];
63
64 2
        return $factory->create($context);
65
    }
66
67
    /**
68
     * $callable is not type hinted as callable because we also accept an array without method specified
69
     * By default, the method is '__construct'
70
     *
71
     * @throws \InvalidArgumentException
72
     */
73 6
    public function buildDependencies($callable, array $arguments = []): array
74
    {
75 6
        if (is_array($callable)) {
76 4
            $dependentClass  = is_string($callable[0]) ? $callable[0] : get_class($callable[0]);
77 3
        } elseif (is_object($callable)) {
78 2
            $dependentClass  = get_class($callable);
79
        } else {
80 1
            throw new InvalidArgumentException("The \$callable must be a callable(ish).");
81
        }
82
83 5
        $dependencies = $this->dependencyResolver->resolveDependencies($callable);
84
85 5
        return $this->prepareDependencies($dependentClass, $dependencies, $arguments);
86
    }
87
88 5
    private function prepareDependencies(string $dependentClass, array $parameters, array $arguments): array
89
    {
90 5
        $dependencies = [];
91
92 5
        foreach ($parameters as $parameter) {
93 4
            $dependencies[$parameter['name']] = $this->getParameterArgument($dependentClass, $parameter, $arguments);
94
        }
95
96 5
        return $dependencies;
97
    }
98
99 4
    private function getParameterArgument(string $dependentClass, array $parameter, array $arguments)
100
    {
101 4
        $parameterName = $parameter['name'];
102
        switch (true) {
103 4
            case $this->isDependencyInGivenArguments($parameterName, $arguments):
104 2
                return $this->getDependencyFromArguments($parameterName, $arguments);
105 4
            case $this->isDependencyClassOrInterface($parameter):
106 3
                return $this->getDependencyRecursively($dependentClass, $parameter);
107 4
            case $this->isDependencyNameInContainer($parameterName):
108 3
                return $this->getDependencyFromParameterInContainer($parameterName);
109
            default:
110 1
                throw new CanNotInstantiateDependenciesException(
111 1
                    "Could not get dependency for class '$dependentClass', parameter '$parameterName'."
112
                );
113
        }
114
    }
115
116 4
    private function isDependencyInGivenArguments(string $parameterName, array $arguments): bool
117
    {
118 4
        return array_key_exists($parameterName, $arguments);
119
    }
120
121 2
    private function getDependencyFromArguments(string $parameterName, array $arguments)
122
    {
123 2
        return $arguments[$parameterName];
124
    }
125
126 4
    private function isDependencyClassOrInterface(array $parameter): bool
127
    {
128 4
        return isset($parameter['class']) &&
129 4
            (class_exists($parameter['class']) || interface_exists($parameter['class']));
130
    }
131
132 3
    private function getDependencyRecursively(
133
        string $dependentClass,
134
        array $parameter
135
    ) {
136 3
        $this->pushDependentClass($dependentClass);
137 3
        $this->pushArgumentName($parameter['name']);
138 3
        $dependency = $this->getInstance($parameter['class']);
139 3
        $this->popDependentClass();
140 3
        $this->popArgumentName();
141
142 3
        return $dependency;
143
    }
144
145 4
    private function isDependencyNameInContainer(string $parameterName): bool
146
    {
147 4
        return $this->container->hasArgument($parameterName);
148
    }
149
150 3
    private function getDependencyFromParameterInContainer(string $parameterName)
151
    {
152 3
        return $this->container->getArgument($parameterName);
153
    }
154
155 3
    private function pushDependentClass(string $dependentClass)
156
    {
157 3
        $this->dependentStack[] = $dependentClass;
158 3
    }
159
160 3
    private function popDependentClass(): string
161
    {
162 3
        return array_pop($this->dependentStack);
163
    }
164
165 3
    private function pushArgumentName(string $argumentName)
166
    {
167 3
        $this->argumentNameStack[] = $argumentName;
168 3
    }
169
170 3
    private function popArgumentName(): string
171
    {
172 3
        return array_pop($this->argumentNameStack);
173
    }
174
}
175