FactoryTrait   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 53
dl 0
loc 154
rs 10
c 3
b 0
f 1
wmc 23

6 Methods

Rating   Name   Duplication   Size   Complexity  
A executeCallable() 0 20 5
A fabricate() 0 14 2
A fixMethod() 0 15 5
A fixDefinition() 0 11 4
A aroundConstruct() 0 12 3
A constructObject() 0 17 4
1
<?php
2
3
/**
4
 * Phoole (PHP7.2+)
5
 *
6
 * @category  Library
7
 * @package   Phoole\Di
8
 * @copyright Copyright (c) 2019 Hong Zhang
9
 */
10
declare(strict_types=1);
11
12
namespace Phoole\Di\Util;
13
14
use Phoole\Di\Exception\LogicException;
15
use Phoole\Di\Exception\UnresolvedClassException;
16
17
/**
18
 * FactoryTrait
19
 *
20
 * Fabricate object by its definition
21
 *
22
 * @package Phoole\Di
23
 */
24
trait FactoryTrait
25
{
26
    use AutowiringTrait;
27
28
    /**
29
     * fabricate the object using the definition
30
     *
31
     * @param  array $definition
32
     * @return object
33
     * @throws LogicException   if 'before|after' methods defined wrong
34
     * @throws UnresolvedClassException if parameter unresolved
35
     */
36
    protected function fabricate(array $definition): object
37
    {
38
        // execute its own beforehand methods
39
        $this->aroundConstruct($definition, 'before');
40
41
        // construct it
42
        if (is_string($definition['class'])) { // class name provided
43
            $obj = $this->constructObject($definition['class'], $definition['args']);
44
        } else { // callable stored in $def['class']
45
            $obj = $this->executeCallable($definition['class'], $definition['args']);
46
        }
47
48
        // execute its own aftermath methods
49
        return $this->aroundConstruct($definition, 'after', $obj);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->aroundCons...inition, 'after', $obj) could return the type null which is incompatible with the type-hinted return object. Consider adding an additional type-check to rule them out.
Loading history...
50
    }
51
52
    /**
53
     * fix object definition
54
     *
55
     * @param  mixed $definition
56
     * @return array
57
     */
58
    protected function fixDefinition($definition): array
59
    {
60
        if (!is_array($definition) || !isset($definition['class'])) {
61
            $definition = ['class' => $definition];
62
        }
63
64
        if (!isset($definition['args'])) {
65
            $definition['args'] = [];
66
        }
67
68
        return (array) $definition;
69
    }
70
71
    /**
72
     * Instantiate the object
73
     *
74
     * @param  string $class      class name
75
     * @param  array  $arguments  constructor arguments
76
     * @return object
77
     * @throws UnresolvedClassException if parameters unresolved
78
     * @throws LogicException if definition went wrong
79
     */
80
    protected function constructObject(string $class, array $arguments): object
81
    {
82
        try {
83
            $reflector = new \ReflectionClass($class);
84
            $constructor = $reflector->getConstructor();
85
            if (is_null($constructor)) {
86
                return $reflector->newInstanceWithoutConstructor();
87
            } else {
88
                $arguments = $this->matchArguments(
89
                    $arguments, $constructor->getParameters()
90
                );
91
                return $reflector->newInstanceArgs($arguments);
92
            }
93
        } catch (UnresolvedClassException $e) {
94
            throw $e;
95
        } catch (\Throwable $e) {
96
            throw new LogicException($e->getMessage());
97
        }
98
    }
99
100
    /**
101
     * execute callable
102
     *
103
     * @param  callable|object $callable   callable
104
     * @param  array           $arguments  constructor arguments
105
     * @return mixed
106
     * @throws LogicException       if definition went wrong
107
     * @throws UnresolvedClassException if parameter unresolved
108
     */
109
    protected function executeCallable($callable, array $arguments)
110
    {
111
        if (is_callable($callable)) {
112
            try {
113
                $arguments = $this->matchArguments(
114
                    $arguments, $this->getCallableParameters($callable)
0 ignored issues
show
Bug introduced by
It seems like $callable can also be of type object; however, parameter $callable of Phoole\Di\Util\FactoryTr...getCallableParameters() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

114
                    $arguments, $this->getCallableParameters(/** @scrutinizer ignore-type */ $callable)
Loading history...
115
                );
116
                return call_user_func_array($callable, $arguments);
117
            } catch (UnresolvedClassException $e) {
118
                throw $e;
119
            } catch (\Throwable $e) {
120
                throw new LogicException($e->getMessage());
121
            }
122
        }
123
124
        if (is_object($callable)) {
125
            return $callable;
126
        }
127
128
        throw new LogicException((string) $callable . " not a callable");
129
    }
130
131
    /**
132
     * Processing service beforehand / aftermath
133
     *
134
     * @param  array  $definition  service definition
135
     * @param  string $stage       'before' or 'after'
136
     * @param  object $object      the created object
137
     * @return object|null
138
     * @throws UnresolvedClassException if arguments not resolved
139
     * @throws LogicException if method definitions went wrong
140
     */
141
    protected function aroundConstruct(
142
        array $definition, string $stage, ?object $object = NULL
143
    ): ?object {
144
        if (isset($definition[$stage])) {
145
            foreach ($definition[$stage] as $line) {
146
                list($callable, $arguments) = $this->fixMethod(
147
                    (array) $line, $object ?? $definition
148
                );
149
                $this->executeCallable($callable, $arguments);
150
            }
151
        }
152
        return $object;
153
    }
154
155
    /**
156
     * fix methods in the 'after'|'before' part of definition
157
     *
158
     * @param  array        $line
159
     * @param  object|array $object  or object definition
160
     * @return array  [Callable, arguments]
161
     * @throws LogicException   if definition went wrong
162
     */
163
    protected function fixMethod(array $line, $object): array
164
    {
165
        // callable found
166
        if (is_callable($line[0])) {
167
            $callable = $line[0];
168
            $arguments = (array) ($line[1] ?? [$object]);
169
            // object method found [$object, 'method']
170
        } elseif (is_string($line[0]) && is_object($object) && method_exists($object, $line[0])) {
171
            $callable = [$object, $line[0]];
172
            $arguments = (array) ($line[1] ?? []);
173
            // nothing right
174
        } else {
175
            throw new LogicException("Bad method definition: $line");
176
        }
177
        return [$callable, $arguments];
178
    }
179
}