FactoryHelperTrait::mergeMethods()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 3
nop 1
1
<?php
2
/**
3
 * Phossa Project
4
 *
5
 * PHP version 5.4
6
 *
7
 * @category  Library
8
 * @package   Phossa2\Di
9
 * @copyright Copyright (c) 2016 phossa.com
10
 * @license   http://mit-license.org/ MIT License
11
 * @link      http://www.phossa.com/
12
 */
13
/*# declare(strict_types=1); */
14
15
namespace Phossa2\Di\Factory;
16
17
use Phossa2\Di\Message\Message;
18
use Phossa2\Di\Resolver\ObjectResolver;
19
use Phossa2\Di\Exception\LogicException;
20
use Phossa2\Di\Traits\ResolverAwareTrait;
21
22
/**
23
 * FactoryHelperTrait
24
 *
25
 * Create service instance here
26
 *
27
 * @package Phossa2\Di
28
 * @author  Hong Zhang <[email protected]>
29
 * @version 2.0.0
30
 * @since   2.0.0 added
31
 */
32
trait FactoryHelperTrait
33
{
34
    use ResolverAwareTrait;
35
36
    /**
37
     * Get service definition
38
     *
39
     * @param  string $rawId
40
     * @param  array $args
41
     * @return array
42
     * @access protected
43
     */
44
    protected function getDefinition(
45
        /*# string */ $rawId,
46
        array $args
47
    )/*# : array */ {
48
        // get the definition
49
        $def = $this->getResolver()->getService($rawId);
50
51
        // fix class
52
        if (!is_array($def) || !isset($def['class'])) {
53
            $def = ['class' => $def];
54
        }
55
56
        // add arguments
57
        if (!empty($args)) {
58
            $def['args'] = $args;
59
        }
60
61
        return (array) $def;
62
    }
63
64
    /**
65
     * Instantiate service object from classname
66
     *
67
     * @param  string $class
68
     * @param  array $args
69
     * @return object
70
     * @throws LogicException if something goes wrong
71
     * @access protected
72
     */
73
    protected function constructObject(/*# string */ $class, array $args)
74
    {
75
        $reflector = new \ReflectionClass($class);
76
        $constructor = $reflector->getConstructor();
77
78
        // not constructor defined
79
        if (is_null($constructor)) {
80
            $obj = $reflector->newInstanceWithoutConstructor();
81
82
        // normal class with constructor
83
        } else {
84
            $args = $this->matchArguments($constructor->getParameters(), $args);
85
            $obj = $reflector->newInstanceArgs($args);
86
        }
87
88
        return $obj;
89
    }
90
91
    /**
92
     * Match provided arguments with a method/function's reflection parameters
93
     *
94
     * @param  \ReflectionParameter[] $reflectionParameters
95
     * @param  array $providedArguments
96
     * @return array the resolved arguments
97
     * @throws LogicException
98
     * @access protected
99
     */
100
    protected function matchArguments(
101
        array $reflectionParameters,
102
        array $providedArguments
103
    )/*# : array */ {
104
        // result
105
        $resolvedArguments = [];
106
        foreach ($reflectionParameters as $i => $param) {
107
            $class = $param->getClass();
108
109
            if ($this->isTypeMatched($class, $providedArguments)) {
110
                $resolvedArguments[$i] = array_shift($providedArguments);
111
            } elseif ($this->isRequiredClass($param, $providedArguments)) {
112
                $resolvedArguments[$i] = $this->getObjectByClass($class->getName());
113
            }
114
        }
115
        return array_merge($resolvedArguments, $providedArguments);
116
    }
117
118
    /**
119
     * Try best to guess parameter and argument are the same type
120
     *
121
     * @param  null|\ReflectionClass $class
122
     * @param  array $arguments
123
     * @return bool
124
     * @access protected
125
     */
126
    protected function isTypeMatched($class, array $arguments)/*# : bool */
127
    {
128
        if (empty($arguments)) {
129
            return false;
130
        } elseif (null !== $class) {
131
            return is_a($arguments[0], $class->getName());
132
        } else {
133
            return true;
134
        }
135
    }
136
137
    /**
138
     * Is $param required and is a class/interface
139
     *
140
     * @param  \ReflectionParameter $param
141
     * @param  array $arguments
142
     * @return bool
143
     * @throws LogicException if mismatched arguments
144
     * @access protected
145
     */
146
    protected function isRequiredClass(
147
        \ReflectionParameter $param,
148
        array $arguments
149
    )/*# : bool */ {
150
        $optional = $param->isOptional();
151
        if ($param->getClass()) {
152
            return !$optional || !empty($arguments);
153
        } else {
154
            return false;
155
        }
156
    }
157
158
    /**
159
     * Get callable parameters
160
     *
161
     * @param  callable $callable
162
     * @return \ReflectionParameter[]
163
     * @throws LogicException if something goes wrong
164
     * @access protected
165
     */
166
    protected function getCallableParameters(callable $callable)/*# : array */
167
    {
168
        // array type
169
        if (is_array($callable)) {
170
            $reflector = new \ReflectionClass($callable[0]);
171
            $method = $reflector->getMethod($callable[1]);
172
173
        // object with __invoke() defined
174
        } elseif ($this->isInvocable($callable)) {
175
            $reflector = new \ReflectionClass($callable);
176
            $method = $reflector->getMethod('__invoke');
177
178
        // simple function
179
        } else {
180
            $method = new \ReflectionFunction($callable);
181
        }
182
183
        return $method->getParameters();
184
    }
185
186
    /**
187
     * Is $var a non-closure object with '__invoke()' defined ?
188
     *
189
     * @param  mixed $var
190
     * @return bool
191
     * @access protected
192
     */
193
    protected function isInvocable($var)/*# : bool */
194
    {
195
        return is_object($var) &&
196
            !$var instanceof \Closure &&
197
            method_exists($var, '__invoke');
198
    }
199
200
    /**
201
     * Get an object base on provided classname or interface name
202
     *
203
     * @param  string $classname class or interface name
204
     * @return object
205
     * @throws \Exception if something goes wrong
206
     * @access protected
207
     */
208
    protected function getObjectByClass(/*# string */ $classname)
209
    {
210
        if ($this->getResolver()->hasService($classname)) {
211
            $serviceId = ObjectResolver::getServiceId($classname);
212
            return $this->getResolver()->get($serviceId);
213
        }
214
        throw new LogicException(
215
            Message::get(Message::DI_CLASS_UNKNOWN, $classname),
216
            Message::DI_CLASS_UNKNOWN
217
        );
218
    }
219
220
    /**
221
     * Returns [$object, $method] if it is a callable, otherwise returns $method
222
     *
223
     * @param  mixed $object
224
     * @param  mixed $method
225
     * @return bool
226
     * @access protected
227
     */
228
    protected function getObjectMethod($object, $method)/*# : bool */
229
    {
230
        if (is_string($method) && method_exists($object, $method)) {
231
            return [$object, $method];
232
        } elseif (is_callable($method)) {
233
            return $method;
234
        } else {
235
            throw new LogicException(
236
                Message::get(Message::DI_CALLABLE_BAD, $method),
237
                Message::DI_CALLABLE_BAD
238
            );
239
        }
240
    }
241
242
    /**
243
     * Merge different sections of a node
244
     *
245
     * convert
246
     *   `['section1' => [[1], [2]], 'section2' => [[3], [4]]]`
247
     *
248
     * to
249
     *   `[[1], [2], [3], [4]]`
250
     *
251
     * @param  array|null $nodeData
252
     * @return array
253
     * @access protected
254
     */
255
    protected function mergeMethods($nodeData)/*# : array */
256
    {
257
        // no merge
258
        if (empty($nodeData) || isset($nodeData[0])) {
259
            return (array) $nodeData;
260
        }
261
262
        // in sections
263
        $result = [];
264
        foreach ($nodeData as $data) {
265
            $result = array_merge($result, $data);
266
        }
267
        return $result;
268
    }
269
270
    /**
271
     * Get common methods
272
     *
273
     * @return array
274
     * @access protected
275
     */
276
    protected function getCommonMethods()/*# : array */
277
    {
278
        // di.common node
279
        $commNode = $this->getResolver()->getSectionId('', 'common');
280
281
        return $this->mergeMethods(
282
            $this->getResolver()->get($commNode)
283
        );
284
    }
285
}
286