Completed
Push — refactor ( 1ac315 )
by Akihito
05:56
created

NodeFactory::getDefault()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
rs 9.9332
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Compiler;
6
7
use LogicException;
8
use PhpParser\Node;
9
use PhpParser\Node\Expr;
10
use PhpParser\Node\Scalar;
11
use Ray\Compiler\Exception\NotCompiled;
12
use Ray\Di\Argument;
13
use Ray\Di\Exception\Unbound;
14
use Ray\Di\InjectorInterface;
15
use Ray\Di\SetterMethod;
16
use ReflectionClass;
17
18
final class NodeFactory
19
{
20
    /** @var InjectorInterface|null */
21
    private $injector;
22
23
    /** @var Normalizer */
24
    private $normalizer;
25
26
    /** @var FactoryCode */
27
    private $factoryCompiler;
28
29
    /** @var PrivateProperty */
30
    private $privateProperty;
31
32
    public function __construct(
33
        Normalizer $normalizer,
34
        FactoryCode $factoryCompiler,
35
        ?InjectorInterface $injector = null
36
    ) {
37
        $this->injector = $injector;
38
        $this->normalizer = $normalizer;
39
        $this->factoryCompiler = $factoryCompiler;
40
        $this->privateProperty = new PrivateProperty();
41
    }
42
43
    /**
44
     * Return on-demand dependency pull code for not compiled
45
     *
46
     * @return Expr|Expr\FuncCall
47
     */
48
    public function getNode(Argument $argument): Expr
49
    {
50
        $dependencyIndex = (string) $argument;
51
        if (! $this->injector instanceof ScriptInjector) {
52
            return $this->getDefault($argument);
53
        }
54
55
        try {
56
            $isSingleton = $this->injector->isSingleton($dependencyIndex);
57
        } catch (NotCompiled $e) {
58
            return $this->getDefault($argument);
59
        }
60
61
        $func = $isSingleton ? 'singleton' : 'prototype';
62
        $args = $this->getInjectionProviderParams($argument);
63
64
        /** @var array<Node\Arg> $args */
65
        return new Expr\FuncCall(new Expr\Variable($func), $args);
66
    }
67
68
    /**
69
     * @param SetterMethod[] $setterMethods
70
     *
71
     * @return Expr\MethodCall[]
72
     */
73
    public function getSetterInjection(Expr\Variable $instance, array $setterMethods): array
74
    {
75
        $setters = [];
76
        foreach ($setterMethods as $setterMethod) {
77
            $isOptional = ($this->privateProperty)($setterMethod, 'isOptional');
78
            $method = ($this->privateProperty)($setterMethod, 'method');
79
            $argumentsObject = ($this->privateProperty)($setterMethod, 'arguments');
80
            $arguments = ($this->privateProperty)($argumentsObject, 'arguments');
81
            /** @var array<Node\Arg> $args */
82
            $args = $this->getSetterParams($arguments, $isOptional);
83
            if (! $args) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type PhpParser\Node\Arg[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
84
                continue;
85
            }
86
87
            $setters[] = new Expr\MethodCall($instance, $method, $args);
88
        }
89
90
        return $setters;
91
    }
92
93
    public function getPostConstruct(Expr\Variable $instance, string $postConstruct): Expr\MethodCall
94
    {
95
        return new Expr\MethodCall($instance, $postConstruct);
96
    }
97
98
    /**
99
     * Return default argument value
100
     */
101
    private function getDefault(Argument $argument): Expr
102
    {
103
        if ($argument->isDefaultAvailable()) {
104
            $default = $argument->getDefaultValue();
105
106
            return ($this->normalizer)($default);
107
        }
108
109
        throw new Unbound($argument->getMeta());
110
    }
111
112
    /**
113
     * Return code for provider
114
     *
115
     * "$provider" needs [class, method, parameter] for InjectionPoint (Contextual Dependency Injection)
116
     *
117
     * @return array<Expr\Array_|Node\Arg>
118
     */
119
    private function getInjectionProviderParams(Argument $argument)
120
    {
121
        $param = $argument->get();
122
        $class = $param->getDeclaringClass();
123
        if (! $class instanceof ReflectionClass) {
124
            throw new LogicException(); // @codeCoverageIgnore
125
        }
126
127
        return [
128
            new Node\Arg(new Scalar\String_((string) $argument)),
129
            new Expr\Array_([
130
                new Node\Expr\ArrayItem(new Scalar\String_($class->name)),
131
                new Node\Expr\ArrayItem(new Scalar\String_($param->getDeclaringFunction()->name)),
132
                new Node\Expr\ArrayItem(new Scalar\String_($param->name)),
133
            ]),
134
        ];
135
    }
136
137
    /**
138
     * Return setter method parameters
139
     *
140
     * Return false when no dependency given and @ Inject(optional=true) annotated to setter method.
141
     *
142
     * @param Argument[] $arguments
143
     *
144
     * @return false|Node\Expr[]
145
     */
146
    private function getSetterParams(array $arguments, bool $isOptional)
147
    {
148
        $args = [];
149
        foreach ($arguments as $argument) {
150
            try {
151
                $args[] = $this->factoryCompiler->getArgStmt($argument);
152
            } catch (Unbound $e) {
153
                if ($isOptional) {
154
                    return false;
155
                }
156
            }
157
        }
158
159
        return $args;
160
    }
161
}
162