Completed
Push — master ( e28001...e6d5b1 )
by Vitaly
08:19 queued 22s
created

Container::logic()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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