Test Failed
Pull Request — master (#37)
by Divine Niiquaye
12:23
created

BindingTrait::resolveBinding()   B

Complexity

Conditions 9
Paths 19

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 9
eloc 14
c 5
b 1
f 0
nc 19
nop 4
dl 0
loc 25
rs 8.0555
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2021 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade\DI\Definitions\Traits;
19
20
use Nette\Utils\Callback;
21
use PhpParser\Builder\Method;
22
use PhpParser\BuilderFactory;
23
use PhpParser\Node\Expr\Assign;
24
use Rade\DI\Definitions\Statement;
25
use Rade\DI\Exceptions\ContainerResolutionException;
26
use Rade\DI\Resolver;
27
28
/**
29
 * This trait adds method binding functionality to the service definition
30
 * after service initialization.
31
 *
32
 * @author Divine Niiquaye Ibok <[email protected]>
33
 */
34
trait BindingTrait
35
{
36
    /** @var array<string,mixed> */
37
    private array $parameters = [];
38
39
    /** @var array<int|string,mixed> */
40
    private array $calls = [];
41
42
    /** @var array<int,Statement|string> */
43
    private array $extras = [];
44
45
    private bool $hasBindings = false;
46
47
    /**
48
     * Set/Replace a method/property binding to service definition.
49
     *
50
     * @param string $nameOrMethod A property name prefixed with a $, or a method name
51
     * @param mixed  $valueOrRef   The value, reference or statement to bind
52
     *
53
     * @return $this
54
     */
55
    public function bind(string $nameOrMethod, $valueOrRef = null)
56
    {
57
        $this->hasBindings = true;
58
59
        if ('$' === $nameOrMethod[0]) {
60
            $this->parameters[\substr($nameOrMethod, 1)] = $valueOrRef;
61
        } else {
62
            if (2 === \func_num_args()) {
63
                $valueOrRef = \is_array($valueOrRef) ? $valueOrRef : [$valueOrRef];
64
            }
65
66
            $this->calls[] = [$nameOrMethod, $valueOrRef];
67
        }
68
69
        return $this;
70
    }
71
72
    /**
73
     * Sets a configurator to call after the service is fully initialized.
74
     *
75
     * @param mixed $configurator A PHP function, reference or an array containing a class/Reference and a method to call
76
     * @param bool  $extend       If true, this service is passed as first argument into $configurator
77
     *
78
     * @return $this
79
     */
80
    public function call($configurator, bool $extend = false)
81
    {
82
        $this->hasBindings = true;
83
        $this->extras[] = [$extend, $configurator];
84
85
        return $this;
86
    }
87
88
    /**
89
     * Set/Replace a method binding or php code binding to service definition.
90
     *
91
     * @see Rade\DI\Definitions\Traits\BindingTrait::bind()
92
     *
93
     * @param array<string,mixed> $bindings
94
     *
95
     * @return $this
96
     */
97
    public function binds(array $bindings)
98
    {
99
        foreach ($bindings as $nameOrMethod => $valueOrRef) {
100
            $this->bind($nameOrMethod, $valueOrRef);
101
        }
102
103
        return $this;
104
    }
105
106
    /**
107
     * Removes a method/parameter binding from service definition.
108
     *
109
     * @return $this
110
     */
111
    public function unbind(string $parameterOrMethod)
112
    {
113
        if ('$' === $parameterOrMethod[0]) {
114
            $parameterOrMethod = \substr($parameterOrMethod, 1);
115
116
            if (\array_key_exists($parameterOrMethod, $this->parameters)) {
117
                unset($this->parameters[$parameterOrMethod]);
118
            }
119
        } elseif (\str_contains($parameterOrMethod, '.')) {
120
            [$offset, $method] = \explode('.', $parameterOrMethod);
121
122
            if (isset($this->calls[$offset]) && $method === $this->calls[$offset][0]) {
123
                unset($this->calls[$offset]);
124
            }
125
        } else {
126
            foreach ($this->calls as $offset => [$method, $mCall]) {
127
                if ($method === $parameterOrMethod) {
128
                    unset($this->calls[$offset]);
129
                }
130
            }
131
        }
132
133
        return $this;
134
    }
135
136
    /**
137
     * Whether this definition has parameters and/or methods.
138
     */
139
    public function hasBinding(): bool
140
    {
141
        return $this->hasBindings;
142
    }
143
144
    /**
145
     * Get the definition's bindings calls available.
146
     *
147
     * @return array<int|string,mixed>
148
     */
149
    public function getBindings(): array
150
    {
151
        return $this->calls;
152
    }
153
154
    /**
155
     * Get the definition's parameters.
156
     *
157
     * @return array<int|string,mixed>
158
     */
159
    public function getParameters(): array
160
    {
161
        return $this->parameters;
162
    }
163
164
    /**
165
     * Get the list of extra php code bindings.
166
     */
167
    public function getExtras(): array
168
    {
169
        return $this->extras;
170
    }
171
172
    /**
173
     * Resolves bindings for container builder class.
174
     */
175
    protected function resolveBinding(Method $defNode, Assign $createdDef, Resolver $resolver, BuilderFactory $builder): void
176
    {
177
        foreach ($this->parameters as $parameter => $pValue) {
178
            $pValue = $resolver->resolve($pValue);
179
180
            if ($pValue instanceof \PhpParser\Node\Stmt) {
181
                throw new ContainerResolutionException(\sprintf('Constructing property "%s" for service "%s" failed, expression not supported.', $parameter, $this->innerId));
182
            }
183
184
            $defNode->addStmt(new Assign($builder->propertyFetch($createdDef->var, $parameter), $pValue));
185
        }
186
187
        foreach ($this->calls as [$method, $mCall]) {
188
            if (\is_string($this->entity) && \method_exists($this->entity, $method)) {
189
                $mCall = $resolver->autowireArguments(Callback::toReflection([$this->entity, $method]), $mCall ?? []);
190
            } else {
191
                $mCall = $resolver->resolveArguments($mCall ?? []);
192
            }
193
194
            $defNode->addStmt($builder->methodCall($createdDef->var, $method, $mCall));
195
        }
196
197
        if ([] !== $this->extras) {
198
            foreach ($this->extras as [$extend, $code]) {
199
                $defNode->addStmt($resolver->resolve($code, $extend ? [$createdDef->var] : []));
200
            }
201
        }
202
    }
203
}
204