Test Failed
Pull Request — master (#37)
by Divine Niiquaye
03:00
created

BindingTrait   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 0
Metric Value
eloc 47
c 8
b 1
f 0
dl 0
loc 161
rs 10
wmc 30

9 Methods

Rating   Name   Duplication   Size   Complexity  
A binds() 0 7 2
A call() 0 5 1
A bind() 0 9 4
B resolveBinding() 0 25 9
A getBindings() 0 3 1
A hasBinding() 0 3 3
A getExtras() 0 3 1
A getParameters() 0 3 1
B unbind() 0 27 8
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
    /**
46
     * Set/Replace a method/property binding to service definition.
47
     *
48
     * @param string $nameOrMethod A property name prefixed with a $, or a method name
49
     * @param mixed  $valueOrRef   The value, reference or statement to bind
50
     *
51
     * @return $this
52
     */
53
    public function bind(string $nameOrMethod, $valueOrRef = null)
54
    {
55
        if ('$' === $nameOrMethod[0]) {
56
            $this->parameters[\substr($nameOrMethod, 1)] = $valueOrRef;
57
        } elseif (2 === \func_num_args()) {
58
            $this->calls[] = [$nameOrMethod, \is_array($valueOrRef) ? $valueOrRef : [$valueOrRef]];
59
        }
60
61
        return $this;
62
    }
63
64
    /**
65
     * Sets a configurator to call after the service is fully initialized.
66
     *
67
     * @param mixed $configurator A PHP function, reference or an array containing a class/Reference and a method to call
68
     * @param bool  $extend       If true, this service is passed as first argument into $configurator
69
     *
70
     * @return $this
71
     */
72
    public function call($configurator, bool $extend = false)
73
    {
74
        $this->extras[] = [$extend, $configurator];
75
76
        return $this;
77
    }
78
79
    /**
80
     * Set/Replace a method binding or php code binding to service definition.
81
     *
82
     * @see Rade\DI\Definitions\Traits\BindingTrait::bind()
83
     *
84
     * @param array<string,mixed> $bindings
85
     *
86
     * @return $this
87
     */
88
    public function binds(array $bindings)
89
    {
90
        foreach ($bindings as $nameOrMethod => $valueOrRef) {
91
            $this->bind($nameOrMethod, $valueOrRef);
92
        }
93
94
        return $this;
95
    }
96
97
    /**
98
     * Removes a method/parameter binding from service definition.
99
     *
100
     * @return $this
101
     */
102
    public function unbind(string $parameterOrMethod)
103
    {
104
        if ('$' === $parameterOrMethod[0]) {
105
            if (\array_key_exists($parameterOrMethod = \substr($parameterOrMethod, 1), $this->parameters)) {
106
                unset($this->parameters[$parameterOrMethod]);
107
            }
108
            goto get_instance;
109
        }
110
111
        foreach ($this->calls as $offset => [$method, $mCall]) {
112
            if (\str_contains($parameterOrMethod, '.')) {
113
                [$nName, $name] = \explode('.', $parameterOrMethod);
114
115
                if ($method === $name && $offset === $nName) {
116
                    unset($this->calls[$offset][$name]);
117
118
                    break;
119
                }
120
            } elseif ($method === $parameterOrMethod) {
121
                unset($this->calls[$offset]);
122
123
                break;
124
            }
125
        }
126
127
        get_instance:
128
        return $this;
129
    }
130
131
    /**
132
     * Whether this definition has parameters and/or methods.
133
     */
134
    public function hasBinding(): bool
135
    {
136
        return !empty($this->parameters) || !empty($this->calls) || !empty($this->extras);
137
    }
138
139
    /**
140
     * Get the definition's bindings calls available.
141
     *
142
     * @return array<int|string,mixed>
143
     */
144
    public function getBindings(): array
145
    {
146
        return $this->calls;
147
    }
148
149
    /**
150
     * Get the definition's parameters.
151
     *
152
     * @return array<int|string,mixed>
153
     */
154
    public function getParameters(): array
155
    {
156
        return $this->parameters;
157
    }
158
159
    /**
160
     * Get the list of extra php code bindings.
161
     */
162
    public function getExtras(): array
163
    {
164
        return $this->extras;
165
    }
166
167
    /**
168
     * Resolves bindings for container builder class.
169
     */
170
    protected function resolveBinding(Method $defNode, Assign $createdDef, Resolver $resolver, BuilderFactory $builder): void
171
    {
172
        foreach ($this->parameters as $parameter => $pValue) {
173
            $pValue = $resolver->resolve($pValue);
174
175
            if ($pValue instanceof \PhpParser\Node\Stmt) {
176
                throw new ContainerResolutionException(\sprintf('Constructing property "%s" for service "%s" failed, expression not supported.', $parameter, $this->innerId));
177
            }
178
179
            $defNode->addStmt(new Assign($builder->propertyFetch($createdDef->var, $parameter), $pValue));
180
        }
181
182
        foreach ($this->calls as [$method, $mCall]) {
183
            if (\is_string($this->entity) && \method_exists($this->entity, $method)) {
184
                $mCall = $resolver->autowireArguments(Callback::toReflection([$this->entity, $method]), $mCall);
185
            } else {
186
                $mCall = $resolver->resolveArguments($mCall);
187
            }
188
189
            $defNode->addStmt($builder->methodCall($createdDef->var, $method, $mCall));
190
        }
191
192
        if ([] !== $this->extras) {
193
            foreach ($this->extras as [$extend, $code]) {
194
                $defNode->addStmt($resolver->resolve($code, $extend ? [$createdDef->var] : []));
195
            }
196
        }
197
    }
198
}
199