Completed
Push — master ( a5f802...e17b66 )
by Vitaly
07:43 queued 11s
created

Container::generateLogicConditions()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 9.0036

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 37
ccs 27
cts 28
cp 0.9643
rs 4.909
cc 9
eloc 24
nc 11
nop 2
crap 9.0036
1
<?php
2
/**
3
 * Created by Vitaly Iegorov <[email protected]>.
4
 * on 22.01.16 at 23:53
5
 */
6
namespace samsonframework\di;
7
8
use samsonframework\di\exception\ClassNotFoundException;
9
use samsonframework\di\exception\ConstructorParameterNotSetException;
10
use samsonframework\di\exception\ContainerException;
11
use samsonframework\di\exception\NotFoundException;
12
use samsonphp\generator\Generator;
13
14
//TODO: caching
15
//TODO: Interface & abstract class resolving
16
//TODO: Other parameter types(not hintable) resolving
17
//TODO: Lazy creation by default
18
19
/**
20
 * Class Container
21
 * @package samsonframework\di
22
 */
23
class Container implements ContainerInterface
24
{
25
    /** @var array[string] Collection of loaded services */
26
    protected $services = array();
27
28
    /** @var array[string] Collection of alias => class name for alias resolving*/
29
    protected $aliases = array();
30
31
    /** @var array[string] Collection of class name dependencies trees */
32
    protected $dependencies = array();
33
34
    /** @var Generator */
35
    protected $generator;
36
37 1
    public function __construct(Generator $generator)
38
    {
39 1
        $this->generator = $generator;
40 1
    }
41
42
    /**
43
     * Get reflection paramater class name type hint if present without
44
     * autoloading and throwing exceptions.
45
     *
46
     * @param \ReflectionParameter $param Parameter for parsing
47
     *
48
     * @return string|null Class name typehint or null
49
     */
50 1
    protected function getClassName(\ReflectionParameter $param)
51
    {
52 1
        preg_match('/\[\s\<\w+?>\s(?<class>[\w\\\\]+)/', (string)$param, $matches);
53 1
        return array_key_exists('class', $matches) && $matches['class'] !== 'array'
54 1
            ? '\\' . ltrim($matches[1], '\\')
55 1
            : null;
56
    }
57
58
    /**
59
     * Recursively build class constructor dependencies tree.
60
     * TODO: Analyze recurrent dependencies and throw an exception
61
     *
62
     * @param string $className    Current class name for analyzing
63
     * @param array  $dependencies Reference to tree for filling up
64
     *
65
     * @return array [string] Multidimensional array as dependency tree
66
     * @throws ClassNotFoundException
67
     */
68 1
    protected function buildDependenciesTree($className, array &$dependencies)
69
    {
70
        // We need this class to exists to use reflections, it will try to autoload it also
71 1
        if (class_exists($className)) {
72 1
            $class = new \ReflectionClass($className);
73
            // We can build dependency tree only from constructor dependencies
74 1
            $constructor = $class->getConstructor();
75 1
            if (null !== $constructor) {
76
                // Iterate all dependencies
77 1
                foreach ($constructor->getParameters() as $parameter) {
78
                    // Ignore optional parameters
79 1
                    if (!$parameter->isOptional()) {
80
                        // Read dependency class name
81 1
                        $dependencyClass = $this->getClassName($parameter);
82
83
                        // Set pointer to parameter as it can be set before
84 1
                        $parameterPointer = &$dependencies[$className][$parameter->getName()];
85
86
                        // If we have found dependency class
87 1
                        if ($dependencyClass !== null) {
88
                            // Point dependency class name
89 1
                            $parameterPointer = $dependencyClass;
90
                            // Go deeper in recursion and pass new branch there
91 1
                            $this->buildDependenciesTree($dependencyClass, $dependencies);
92 1
                        } elseif (!isset($parameterPointer)) { // Set null parameter value
93 1
                            $parameterPointer = null;
94 1
                        }
95
96 1
                    } else { // Stop iterating as first optional parameter is met
97 1
                        break;
98
                    }
99 1
                }
100 1
            }
101 1
        } else { // Something went wrong and class is not auto loaded and missing
102
            throw new ClassNotFoundException($className);
103
        }
104
105 1
        return $dependencies;
106
    }
107
108 1
    public function generateLogicConditions(array $dependencies, $class)
109
    {
110 1
        $this->generator->tabs++;
111
112 1
        $variables = array_keys($dependencies);
113 1
        for ($i = 0, $s = count($dependencies); $i < $s; $i++) {
114 1
            $className = $dependencies[$variables[$i]];
115 1
            if (is_string($className)) {
116
                // Define if we have this dependency described in dependency tree
117 1
                $dependencyPointer = &$this->dependencies[$className];
118 1
                if (isset($dependencyPointer)) {
119 1
                    $this->generator->newLine('new ' . $className . '(');
120 1
                    $this->generateLogicConditions($dependencyPointer, $className);
121 1
                    $this->generator->newLine(')');
122 1
                } elseif (class_exists($className, false)) {
123 1
                    $this->generator->newLine('new ' . $className . '()');
124 1
                } else { // String variable
125 1
                    $this->generator->newLine()->stringValue($className);
126
                }
127
128 1
                if ($i !== $s - 1) {
129 1
                    $this->generator->text(',');
130 1
                }
131 1
            } elseif (is_array($className)){ // Regular constructor parameter
132 1
                $this->generator->newLine()->arrayValue($className);
133
134 1
                if ($i !== $s - 1) {
135 1
                    $this->generator->text(',');
136 1
                }
137 1
            } elseif ($className === null) { // Parameter is not set
138
                throw new ConstructorParameterNotSetException($class.'::'.$variables[$i]);
139
            }
140
141
142 1
        }
143 1
        $this->generator->tabs--;
144 1
    }
145
146 1
    public function generateLogicFunction($functionName = 'diContainer')
147
    {
148 1
        $inputVariable = '$aliasOrClassName';
149 1
        $this->generator
150 1
            ->defFunction($functionName, array($inputVariable))
151 1
            ->defVar('static $services')
152
        ;
153
154 1
        $started = false;
155 1
        foreach ($this->dependencies as $alias => $dependency) {
156
            // Get class name from alias
157 1
            $className = array_key_exists($alias, $this->aliases) ? $this->aliases[$alias] : $alias;
158
159
            // Generate condition statement to define if this class is needed
160 1
            if (!$started) {
161 1
                $started = true;
162 1
                $this->generator->defIfCondition($inputVariable . ' === \'' . $alias . '\'');
163 1
            } else {
164 1
                $this->generator->defElseIfCondition($inputVariable . ' === \'' . $alias . '\'');
165
            }
166
167 1
            $this->generator->newLine('return new '.$className.'(');
168
169 1
            $this->generateLogicConditions($dependency, $className);
170
171 1
            $this->generator->newLine(');');
172 1
        }
173
174
        // Add method not found
175 1
        $this->generator->endIfCondition()->newLine('return null;')->endFunction();
176
177 1
        return $this->generator->flush();
178
    }
179
180
    /**
181
     * Finds an entry of the container by its identifier and returns it.
182
     *
183
     * @param string $alias Identifier of the entry to look for.
184
     *
185
     * @throws NotFoundException  No entry was found for this identifier.
186
     * @throws ContainerException Error while retrieving the entry.
187
     *
188
     * @return mixed Entry.
189
     */
190
    public function get($alias)
191
    {
192
        // Set pointer to module
193
        $module = &$this->services[$alias];
194
195
        if (null === $module) {
196
            throw new NotFoundException($alias);
197
        } else {
198
            if (!is_object($module)) {
199
                throw new ContainerException($alias);
200
            } else {
201
                return $module;
202
            }
203
        }
204
    }
205
206
    /**
207
     * Returns true if the container can return an entry for the given identifier.
208
     * Returns false otherwise.
209
     *
210
     * @param string $alias Identifier of the entry to look for.
211
     *
212
     * @return boolean
213
     */
214
    public function has($alias)
215
    {
216
        return array_key_exists($alias, $this->services)
217
        || array_key_exists($alias, $this->aliases);
218
    }
219
220
    /**
221
     * Set dependency alias with callback function.
222
     *
223
     * @param callable $callable Callable to return dependency
224
     * @param string   $alias    Dependency name
225
     *
226
     * @return self Chaining
227
     */
228
    public function callback($callable, $alias = null)
229
    {
230
        // TODO: Implement callback() method.
231
    }
232
233
    /**
234
     * Set service dependency. Upon first creation of this class instance
235
     * it would be used everywhere where this dependency is needed.
236
     *
237
     * @param string $className  Fully qualified class name
238
     * @param string $alias      Dependency name
239
     * @param array  $parameters Collection of parameters needed for dependency creation
240
     *
241
     * @return self Chaining
242
     */
243
    public function service($className, $alias = null, array $parameters = array())
244
    {
245
        // TODO: Implement service() method.
246
    }
247
248
    /**
249
     * Set service dependency by passing object instance.
250
     *
251
     * @param mixed  $instance   Instance that needs to be return by this dependency
252
     * @param string $alias      Dependency name
253
     * @param array  $parameters Collection of parameters needed for dependency creation
254
     *
255
     * @return self Chaining
256
     */
257
    public function instance(&$instance, $alias = null, array $parameters = array())
258
    {
259
260
        // TODO: Implement instance() method.
261
    }
262
263
    /**
264
     * Set dependency.
265
     *
266
     * @param string $className  Fully qualified class name
267
     * @param string $alias      Dependency name
268
     * @param array  $parameters Collection of parameters needed for dependency creation
269
     *
270
     * @return ContainerInterface Chaining
271
     */
272 1
    public function set($className, $alias = null, array $parameters = array())
273
    {
274
        // Add this class dependencies to dependency tree
275 1
        $this->dependencies = array_merge(
276 1
            $this->dependencies,
277 1
            $this->buildDependenciesTree($className, $this->dependencies)
278 1
        );
279
280
        // Merge other class constructor parameters
281 1
        $this->dependencies[$className] = array_merge($this->dependencies[$className], $parameters);
282
283
        // Store alias for this class name
284 1
        $this->aliases[$alias] = $className;
285 1
    }
286
}
287