Completed
Pull Request — 2.x (#216)
by Akihito
09:22
created

NodeFactory::getNode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
c 0
b 0
f 0
rs 9.7
cc 4
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Compiler;
6
7
use PhpParser\Node;
8
use PhpParser\Node\Expr;
9
use PhpParser\Node\Scalar;
10
use Ray\Compiler\Exception\NotCompiled;
11
use Ray\Di\Argument;
12
use Ray\Di\Exception\Unbound;
13
use Ray\Di\InjectorInterface;
14
use Ray\Di\SetterMethod;
15
16
final class NodeFactory
17
{
18
    /**
19
     * @var null|InjectorInterface
20
     */
21
    private $injector;
22
23
    /**
24
     * @var Normalizer
25
     */
26
    private $normalizer;
27
28
    /**
29
     * @var FactoryCode
30
     */
31
    private $factoryCompiler;
32
33
    /**
34
     * @var PrivateProperty
35
     */
36
    private $privateProperty;
37
38
    public function __construct(
39
        Normalizer $normalizer,
40
        FactoryCode $factoryCompiler,
41
        InjectorInterface $injector = null
42
    ) {
43
        $this->injector = $injector;
44
        $this->normalizer = $normalizer;
45
        $this->factoryCompiler = $factoryCompiler;
46
        $this->privateProperty = new PrivateProperty;
47
    }
48
49
    /**
50
     * Return on-demand dependency pull code for not compiled
51
     *
52
     * @return Expr|Expr\FuncCall
53
     */
54
    public function getNode(Argument $argument) : Expr
55
    {
56
        $dependencyIndex = (string) $argument;
57
        if (! $this->injector instanceof ScriptInjector) {
58
            return $this->getDefault($argument);
59
        }
60
        try {
61
            $isSingleton = $this->injector->isSingleton($dependencyIndex);
62
        } catch (NotCompiled $e) {
63
            return $this->getDefault($argument);
64
        }
65
        $func = $isSingleton ? 'singleton' : 'prototype';
66
        $args = $this->getInjectionProviderParams($argument);
67
68
        /** @var array<Node\Arg> $args */
69
        return new Expr\FuncCall(new Expr\Variable($func), $args);
70
    }
71
72
    /**
73
     * @param SetterMethod[] $setterMethods
74
     *
75
     * @return Expr\MethodCall[]
76
     */
77
    public function getSetterInjection(Expr\Variable $instance, array $setterMethods) : array
78
    {
79
        $setters = [];
80
        foreach ($setterMethods as $setterMethod) {
81
            $isOptional = ($this->privateProperty)($setterMethod, 'isOptional');
82
            $method = ($this->privateProperty)($setterMethod, 'method');
83
            $argumentsObject = ($this->privateProperty)($setterMethod, 'arguments');
84
            $arguments = ($this->privateProperty)($argumentsObject, 'arguments');
85
            $args = $this->getSetterParams($arguments, $isOptional);
86
            if (! $args) {
87
                continue;
88
            }
89
            /** @var array<Node\Arg> $args */
90
            $setters[] = new Expr\MethodCall($instance, $method, $args); // @phpstan-ignore-line
91
        }
92
93
        return $setters;
94
    }
95
96
    public function getPostConstruct(Expr\Variable $instance, string $postConstruct) : Expr\MethodCall
97
    {
98
        return new Expr\MethodCall($instance, $postConstruct);
99
    }
100
101
    /**
102
     * Return default argument value
103
     */
104
    private function getDefault(Argument $argument) : Expr
105
    {
106
        if ($argument->isDefaultAvailable()) {
107
            $default = $argument->getDefaultValue();
108
109
            return ($this->normalizer)($default);
110
        }
111
112
        throw new Unbound($argument->getMeta());
113
    }
114
115
    /**
116
     * Return code for provider
117
     *
118
     * "$provider" needs [class, method, parameter] for InjectionPoint (Contextual Dependency Injection)
119
     *
120
     * @return array<Expr\Array_|Node\Arg>
121
     */
122
    private function getInjectionProviderParams(Argument $argument)
123
    {
124
        $param = $argument->get();
125
        $class = $param->getDeclaringClass();
126
        if (! $class instanceof \ReflectionClass) {
127
            throw new \LogicException; // @codeCoverageIgnore
128
        }
129
130
        return [
131
            new Node\Arg(new Scalar\String_((string) $argument)),
132
            new Expr\Array_([
133
                new Node\Expr\ArrayItem(new Scalar\String_($class->name)),
134
                new Node\Expr\ArrayItem(new Scalar\String_($param->getDeclaringFunction()->name)),
135
                new Node\Expr\ArrayItem(new Scalar\String_($param->name))
136
            ])
137
        ];
138
    }
139
140
    /**
141
     * Return setter method parameters
142
     *
143
     * Return false when no dependency given and @ Inject(optional=true) annotated to setter method.
144
     *
145
     * @param Argument[] $arguments
146
     *
147
     * @return false|Node\Expr[]
148
     */
149
    private function getSetterParams(array $arguments, bool $isOptional)
150
    {
151
        $args = [];
152
        foreach ($arguments as $argument) {
153
            try {
154
                $args[] = $this->factoryCompiler->getArgStmt($argument);
155
            } catch (Unbound $e) {
156
                if ($isOptional) {
157
                    return false;
158
                }
159
            }
160
        }
161
162
        return $args;
163
    }
164
}
165