Completed
Push — master ( c89067...3b0f09 )
by Hong
04:25
created

FactoryTrait::aroundConstruct()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 3
nc 2
nop 3
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 something goes wrong
34
     * @throws UnresolvedClassException if parameter unresolved
35
     */
36
    protected function fabricate(array $definition): object
37
    {
38
        // beforehand
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
        // aftermath
49
        return $this->aroundConstruct($definition, 'after', $obj);
50
    }
51
52
    /**
53
     * fix object definition
54
     *
55
     * @param  string|object|callable $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 service object
73
     *
74
     * @param  string $class      class name
75
     * @param  array  $arguments  constructor arguments
76
     * @return object
77
     * @throws UnresolvedClassException
78
     * @throws LogicException
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 something goes 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)
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
139
     * @throws LogicException
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((array) $line, $object);
147
                $this->executeCallable($callable, $arguments);
148
            }
149
        }
150
        return $object;
151
    }
152
153
    /**
154
     * fix methods in the 'after'|'before' part of definition
155
     *
156
     * @param  array       $line
157
     * @param  object|null $object
158
     * @return array  [Callable, arguments]
159
     * @throws LogicException   if goes wrong
160
     */
161
    protected function fixMethod(array $line, ?object $object = NULL): array
162
    {
163
        if (is_callable($line[0])) {
164
            $callable = $line[0];
165
        } elseif (is_string($line[0]) && is_object($object) && method_exists($object, $line[0])) {
166
            $callable = [$object, $line[0]];
167
        } else {
168
            throw new LogicException("Bad method definition: $line");
169
        }
170
        return [$callable, (array) ($line[1] ?? [])];
171
    }
172
}