Completed
Push — master ( a7b775...284f02 )
by Max
02:04
created

AbstractContainer::tryAddParentConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 6
rs 10
1
<?php
2
/*
3
 * @copyright (c) 2019 Mendel <[email protected]>
4
 * @license see license.txt
5
 */
6
namespace drycart\di;
7
8
/**
9
 * Main code for instantiate our object, magic call etc
10
 * Abstract, need configuration logic
11
 * @author Mendel <[email protected]>
12
 */
13
abstract class AbstractContainer extends AbstractParametersContainer implements DiInterface
14
{
15
    /**
16
     * Contain our configuration data
17
     * @var array 
18
     */
19
    protected $config = [];
20
    
21
    /**
22
     * Contain stored object (for services)
23
     * @var array
24
     */
25
    private $storage = [];
26
    
27
    private $cachedConfig = [];
28
        
29
    /**
30
     * Get prepared config for selected class
31
     * Add default value for system fields if empty
32
     * @param string $id 
33
     * @param bool $required need check if exist, and throw if not
34
     * @return array
35
     */
36
    protected function classConfig(string $id, bool $required = false) : ?array
37
    {
38
        if (!isset($this->cachedConfig[$id])) {
39
            $config = $this->internalConfig($id);
40
            $this->initRequired($config['#required'] ?? []);
41
            unset($config['#required']);
42
            $this->cachedConfig[$id] = $config;
43
        }
44
        if($required and is_null($this->cachedConfig[$id])) {
45
            throw new NotFoundException('Not exist class: '.$id);
46
        }
47
        return $this->cachedConfig[$id];
48
    }
49
    
50
    protected function internalConfig(string $id) : ?array
51
    {
52
        if (!isset($this->config[$id]) and !class_exists($id)) {
53
            return null;
54
        }
55
        $config = [];
56
        if (!$this->isAlias($id)) {
57
            $config = $this->tryAddParentConfig($id, $config);
58
        } elseif (isset($this->config[$id]['#class'])) {
59
            $config = $this->tryAddParentConfig($this->config[$id]['#class'], $config);
60
        }
61
        $config = array_merge($config, $this->config[$id] ?? []);
62
        //
63
        if (empty($config['#class'])) {
64
            $config['#class'] = $id;
65
        }
66
        //
67
        if (!isset($config['#singleton'])) {
68
            $config['#singleton'] = false;
69
        }
70
        return $config;
71
    }
72
    
73
    protected function tryAddParentConfig(string $id, array $config) : array 
74
    {
75
        foreach (array_reverse(class_parents($id)) as $parent) {
76
            $config = array_merge($config, $this->config[$parent] ?? []);
77
        }
78
        return $config;
79
    }
80
81
    /**
82
     * Singleton wrapper for internal use
83
     * @param string $id
84
     * @param array $config
85
     * @return mixed
86
     */
87
    protected function internalSingleton($id, array $config)
88
    {
89
        if (!isset($this->storage[$id])) {
90
            $this->storage[$id] = $this->internalMake($id, $config);
91
        } 
92
        return $this->storage[$id];
93
    }
94
    
95
    /**
96
     * Create new object using our core
97
     * @param string $id class name
98
     * @param array $config
99
     * @param array $parameters parameters from request
100
     * @return mixed
101
     * @throws ContainerException
102
     */
103
    protected function internalMake($id, array $config, array $parameters = [])
104
    {
105
        $fullParameters = array_merge($parameters, $config);
106
        $obj = $this->getObject($fullParameters);
107
        //
108
        if (!$this->isAlias($id) and !is_a($obj, $id)) {
109
            throw new ContainerException('Wrong class, will be '.$id);
110
        }
111
        return $obj;
112
    }
113
114
    protected function isAlias(string $id) : bool
115
    {
116
        return (substr($id, 0, 1) == '#');
117
    }
118
    
119
    protected function initRequired(array $requirement) : void
120
    {
121
        foreach ($requirement as $className) {
122
            if (!isset($this->storage[$className])) {
123
                $this->singleton($className);
124
            }
125
        }
126
    }
127
    
128
    /**
129
     * Get reflector for method/function... i.e. callable
130
     * @2do: check if it need, maybe it can be done whithout this?
131
     * @param callable $callable
132
     * @return array
133
     */
134
    protected function getCallableDependency(callable $callable) : array
135
    {
136
        if (is_array($callable)) {
137
            $className = get_class($callable[0]);
138
            $classReflector = new \ReflectionClass($className);
139
            return $classReflector->getMethod($callable[1])->getParameters();
140
        } else {
141
            return (new \ReflectionFunction($callable))->getParameters();
142
        }
143
    }
144
    
145
    /**
146
     * Prepare parameters for callable (add dependency)
147
     * @param callable $callable
148
     * @param array $parameters assoc array of parameters
149
     * @return array prepared parameters
150
     */
151
    protected function callableParameters(callable $callable, array $parameters) : array
152
    {
153
        $dependency = $this->getCallableDependency($callable);
154
        return $this->prepareParameters($dependency, $parameters);
155
    }
156
    
157
    private $reflectionCache = [];
158
    private function getClassDependency(string $className) : array
159
    {
160
        if (!class_exists($className)) {
161
            // Not NotFoundException => it is missconfiguration, i.e. wrong class at config
162
            throw new ContainerException('Class need to instantiate not exist '.$className);
163
        }
164
        if (!isset($this->reflectionCache[$className])) {
165
            $reflector = new \ReflectionClass($className);
166
            $constructor = $reflector->getConstructor();
167
            if (!is_null($constructor)) {
168
                $this->reflectionCache[$className] = $constructor->getParameters();
169
            } else {
170
                $this->reflectionCache[$className] = [];
171
            }
172
        }
173
        return $this->reflectionCache[$className];
174
    }
175
    
176
    /**
177
     * Instantiate object using parameters array
178
     * @param array $parameters
179
     * @return mixed
180
     */
181
    private function getObject(array $parameters = [])
182
    {
183
        if (isset($parameters['#factory'])) {
184
            return call_user_func_array($parameters['#factory'], [$parameters, $this]);
185
        }
186
        $className = $parameters['#class'];
187
        //
188
        $dependency = $this->getClassDependency($className);
189
        if (!empty($dependency)) {
190
            $preparedParameters = $this->prepareParameters($dependency, $parameters);
191
        } else {
192
            $preparedParameters = [];
193
        }
194
        return new $className(...$preparedParameters);
195
    }
196
}
197