Completed
Push — master ( cf3632...f41966 )
by Vitaly
09:23
created

Container   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 182
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 95.77%

Importance

Changes 35
Bugs 1 Features 17
Metric Value
wmc 23
c 35
b 1
f 17
lcom 1
cbo 4
dl 0
loc 182
ccs 68
cts 71
cp 0.9577
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getClassName() 0 7 3
B buildDependenciesTree() 0 36 6
A service() 0 6 1
A set() 0 14 1
A generateInitialDeclaration() 0 14 2
D generateCondition() 0 39 10
1
<?php
2
/**
3
 * Created by Vitaly Iegorov <[email protected]>.
4
 * on 26.01.16 at 15:11
5
 */
6
namespace samsonframework\di;
7
8
use samsonframework\di\exception\ClassNotFoundException;
9
use samsonframework\di\exception\ConstructorParameterNotSetException;
10
11
class Container extends AbstractContainer
12
{
13
    /** Variable name for storing services */
14
    const STATIC_COLLECTION_VARIABLE = '$services';
15
16
    /** @var array[string] Collection of loaded services */
17
    protected $services = array();
18
19
    /**
20
     * Get reflection parameter class name type hint if present without
21
     * auto loading and throwing exceptions.
22
     * TODO: Add resolving configured classes
23
     *
24
     * @param \ReflectionParameter $param Parameter for parsing
25
     *
26
     * @return string|null Class name typehint or null
27
     */
28 2
    protected function getClassName(\ReflectionParameter $param)
29
    {
30 2
        preg_match('/\[\s\<\w+?>\s(?<class>[\w\\\\]+)/', (string)$param, $matches);
31 2
        return array_key_exists('class', $matches) && $matches['class'] !== 'array'
32 2
            ? '\\' . ltrim($matches[1], '\\')
33 2
            : null;
34
    }
35
36
    /**
37
     * Recursively build class constructor dependencies tree.
38
     * TODO: Analyze recurrent dependencies and throw an exception
39
     *
40
     * @param string $className    Current class name for analyzing
41
     * @param array  $dependencies Reference to tree for filling up
42
     *
43
     * @return array [string] Multidimensional array as dependency tree
44
     * @throws ClassNotFoundException
45
     */
46 2
    protected function buildDependenciesTree($className, array &$dependencies)
47
    {
48
        // We need this class to exists to use reflections, it will try to autoload it also
49 2
        if (class_exists($className)) {
50 2
            $class = new \ReflectionClass($className);
51
            // We can build dependency tree only from constructor dependencies
52 2
            $constructor = $class->getConstructor();
53 2
            if (null !== $constructor) {
54
                // Iterate all dependencies
55 2
                foreach ($constructor->getParameters() as $parameter) {
56
                    // Ignore optional parameters
57 2
                    if (!$parameter->isOptional()) {
58
                        // Read dependency class name
59 2
                        $dependencyClass = $this->getClassName($parameter);
60
61
                        // Set pointer to parameter as it can be set before
62 2
                        $parameterPointer = &$dependencies[$className][$parameter->getName()];
63
64
                        // If we have found dependency class
65 2
                        if ($dependencyClass !== null) {
66
                            // Point dependency class name
67 2
                            $parameterPointer = $dependencyClass;
68
                            // Go deeper in recursion and pass new branch there
69 2
                            $this->buildDependenciesTree($dependencyClass, $dependencies);
70 2
                        }
71 2
                    } else { // Stop iterating as first optional parameter is met
72 2
                        break;
73
                    }
74 2
                }
75 2
            }
76 2
        } else { // Something went wrong and class is not auto loaded and missing
77
            throw new ClassNotFoundException($className);
78
        }
79
80 2
        return $dependencies;
81
    }
82
83
    /**
84
     * Set service dependency. Upon first creation of this class instance
85
     * it would be used everywhere where this dependency is needed.
86
     *
87
     * @param string $className  Fully qualified class name
88
     * @param string $alias      Dependency name
89
     * @param array  $parameters Collection of parameters needed for dependency creation
90
     *
91
     * @return Container Chaining
92
     */
93 2
    public function service($className, $alias = null, array $parameters = array())
94
    {
95 2
        $this->services[$className] = $className;
96
97 2
        return $this->set($className, $alias, $parameters);
98
    }
99
100
    /**
101
     * Set dependency.
102
     *
103
     * @param string $className  Fully qualified class name
104
     * @param string $alias      Dependency name
105
     * @param array  $parameters Collection of parameters needed for dependency creation
106
     *
107
     * @return Container Chaining
108
     */
109 2
    public function set($className, $alias = null, array $parameters = array())
110
    {
111
        // Add this class dependencies to dependency tree
112 2
        $this->dependencies = array_merge(
113 2
            $this->dependencies,
114 2
            $this->buildDependenciesTree($className, $this->dependencies)
115 2
        );
116
117
        // Merge other class constructor parameters
118 2
        $this->dependencies[$className] = array_merge($this->dependencies[$className], $parameters);
119
120
        // Store alias for this class name
121 2
        $this->aliases[$alias] = $className;
122 2
    }
123
124
    /**
125
     * Generate initial class instance declaration
126
     * @param string $className Entity class name
127
     */
128 2
    protected function generateInitialDeclaration($className)
129
    {
130 2
        if (array_key_exists($className, $this->services)) {
131
            // Start service search or creation
132 2
            $this->generator
133 2
                ->text('isset('.self::STATIC_COLLECTION_VARIABLE.'[\''.$className.'\'])')
134 2
                ->newLine('? '.self::STATIC_COLLECTION_VARIABLE.'[\''.$className.'\']')
135 2
                ->newLine(': '.self::STATIC_COLLECTION_VARIABLE.'[\''.$className.'\'] = new '.$className.'(')
136
                ->tabs++;
137 2
        } else {
138
            // Start object instance creation
139 2
            $this->generator->text('new ' . $className . '(')->tabs++;
140
        }
141 2
    }
142
143
    /**
144
     * Generate container dependency condition code.
145
     *
146
     * @param string $className
147
     * @param mixed  $dependencies
148
     * @param int    $level
149
     *
150
     * @throws ConstructorParameterNotSetException
151
     *
152
     */
153 2
    protected function generateCondition($className, $dependencies, $level = 0)
154
    {
155 2
        $this->generator->newLine(($level === 0) ? 'return ' : '');
156
157 2
        $this->generateInitialDeclaration($className);
158
159
        // Get last dependency variable name
160 2
        end($dependencies);
161 2
        $last = key($dependencies);
162
163
        // Iterate all dependencies for this class
164 2
        foreach ($dependencies as $variable => $dependency) {
165
            // If dependency value is a string
166 2
            if (is_string($dependency)) {
167
                // Define if we have this dependency described in dependency tree
168 2
                $dependencyPointer = &$this->dependencies[$dependency];
169 2
                if (null !== $dependencyPointer) {
170
                    // We have dependencies tree for this entity
171 2
                    $this->generateCondition($dependency, $dependencyPointer, $level + 1);
172 2
                } elseif (class_exists($dependency, false)) {
173
                    // There are no dependencies for this entity
174 2
                    $this->generator->newLine('new ' . $dependency . '()');
175 2
                } else { // String variable
176 2
                    $this->generator->newLine()->stringValue($dependency);
177
                }
178 2
            } elseif (is_array($dependency)) { // Dependency value is array
179 2
                $this->generator->newLine()->arrayValue($dependency);
180 2
            } elseif ($dependency === null) { // Parameter is not set
181
                throw new ConstructorParameterNotSetException($className . '::' . $variable);
182
            }
183
184
            // Add comma if this is not last dependency
185 2
            if ($variable !== $last) {
186 2
                $this->generator->text(',');
187 2
            }
188 2
        }
189 2
        $this->generator->tabs--;
190 2
        $this->generator->newLine(')' . ($level === 0 ? ';' : ''));
191 2
    }
192
}
193