Bind::__construct()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 14
c 0
b 0
f 0
rs 9.9666
cc 4
nc 6
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Di;
6
7
use Ray\Aop\MethodInterceptor;
8
use Ray\Aop\ReflectionClass;
9
use Ray\Di\Exception\InvalidToConstructorNameParameter;
10
use ReflectionException;
11
use ReflectionMethod;
12
13
use function array_keys;
14
use function array_reduce;
15
use function assert;
16
use function class_exists;
17
use function implode;
18
use function interface_exists;
19
use function is_array;
20
use function is_string;
21
22
final class Bind
23
{
24
    /** @var Container */
25
    private $container;
26
27
    /**
28
     * @var string|class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment string|class-string at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in string|class-string.
Loading history...
29
     * @phpstan-var class-string<MethodInterceptor>|string
30
     */
31
    private $interface;
32
33
    /** @var string */
34
    private $name = Name::ANY;
35
36
    /** @var DependencyInterface */
37
    private $bound;
38
39
    /** @var BindValidator */
40
    private $validate;
41
42
    /** @var ?Untarget */
43
    private $untarget;
44
45
    /**
46
     * @param Container                              $container dependency container
47
     * @param class-string<MethodInterceptor>|string $interface interface or concrete class name
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<MethodInterceptor>|string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<MethodInterceptor>|string.
Loading history...
48
     */
49
    public function __construct(Container $container, string $interface)
50
    {
51
        $this->container = $container;
52
        $this->interface = $interface;
53
        $this->validate = new BindValidator();
54
        $bindUntarget = class_exists($interface) && ! (new \ReflectionClass($interface))->isAbstract() && ! $this->isRegistered($interface);
55
        $this->bound = new NullDependency();
56
        if ($bindUntarget) {
57
            $this->untarget = new Untarget($interface);
58
59
            return;
60
        }
61
62
        $this->validate->constructor($interface);
63
    }
64
65
    public function __destruct()
66
    {
67
        if ($this->untarget) {
68
            ($this->untarget)($this->container, $this);
69
            $this->untarget = null;
70
        }
71
    }
72
73
    public function __toString(): string
74
    {
75
        return $this->interface . '-' . $this->name;
76
    }
77
78
    /**
79
     * Set dependency name
80
     */
81
    public function annotatedWith(string $name): self
82
    {
83
        $this->name = $name;
84
85
        return $this;
86
    }
87
88
    /**
89
     * Bind to class
90
     *
91
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
92
     */
93
    public function to(string $class): self
94
    {
95
        $this->untarget = null;
96
        $refClass = $this->validate->to($this->interface, $class);
97
        $this->bound = (new DependencyFactory())->newAnnotatedDependency($refClass);
98
        $this->container->add($this);
99
100
        return $this;
101
    }
102
103
    /**
104
     * Bind to constructor
105
     *
106
     * @param class-string<T>              $class class name
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
107
     * @param array<string, string>|string $name  "varName=bindName,..." or [$varName => $bindName, $varName => $bindName...]
108
     *
109
     * @throws ReflectionException
110
     *
111
     * @template T of object
112
     */
113
    public function toConstructor(string $class, $name, ?InjectionPoints $injectionPoints = null, ?string $postConstruct = null): self
114
    {
115
        if (is_array($name)) {
116
            $name = $this->getStringName($name);
117
        }
118
119
        $this->untarget = null;
120
        $postConstructRef = $postConstruct ? new ReflectionMethod($class, $postConstruct) : null;
121
        /** @var ReflectionClass<object> $reflection */
122
        $reflection = new ReflectionClass($class);
123
        $this->bound = (new DependencyFactory())->newToConstructor($reflection, $name, $injectionPoints, $postConstructRef);
124
        $this->container->add($this);
125
126
        return $this;
127
    }
128
129
    /**
130
     * Bind to provider
131
     *
132
     * @phpstan-param class-string $provider
133
     */
134
    public function toProvider(string $provider, string $context = ''): self
135
    {
136
        $this->untarget = null;
137
        $refClass = $this->validate->toProvider($provider);
138
        $this->bound = (new DependencyFactory())->newProvider($refClass, $context);
139
        $this->container->add($this);
140
141
        return $this;
142
    }
143
144
    /**
145
     * Bind to instance
146
     *
147
     * @param mixed $instance
148
     */
149
    public function toInstance($instance): self
150
    {
151
        $this->untarget = null;
152
        $this->bound = new Instance($instance);
153
        $this->container->add($this);
154
155
        return $this;
156
    }
157
158
    /**
159
     * Bind to NullObject
160
     */
161
    public function toNull(): self
162
    {
163
        $this->untarget = null;
164
        assert(interface_exists($this->interface));
165
        $this->bound = new NullObjectDependency($this->interface);
166
        $this->container->add($this);
167
168
        return $this;
169
    }
170
171
    /**
172
     * Set scope
173
     */
174
    public function in(string $scope): self
175
    {
176
        if ($this->bound instanceof Dependency || $this->bound instanceof DependencyProvider || $this->bound instanceof NullDependency) {
177
            $this->bound->setScope($scope);
178
        }
179
180
        if ($this->untarget) {
181
            $this->untarget->setScope($scope);
182
        }
183
184
        return $this;
185
    }
186
187
    public function getBound(): DependencyInterface
188
    {
189
        return $this->bound;
190
    }
191
192
    public function setBound(DependencyInterface $bound): void
193
    {
194
        $this->bound = $bound;
195
    }
196
197
    private function isRegistered(string $interface): bool
198
    {
199
        return isset($this->container->getContainer()[$interface . '-' . Name::ANY]);
200
    }
201
202
    /**
203
     * Return string
204
     *
205
     * input: ['varA' => 'nameA', 'varB' => 'nameB']
206
     * output: "varA=nameA,varB=nameB"
207
     *
208
     * @param array<string, string> $name
209
     */
210
    private function getStringName(array $name): string
211
    {
212
        $keys = array_keys($name);
213
214
        $names = array_reduce(
215
            $keys,
216
            /**
217
             * @param list<string> $carry
218
             * @param array-key $key
0 ignored issues
show
Documentation Bug introduced by
The doc comment array-key at position 0 could not be parsed: Unknown type name 'array-key' at position 0 in array-key.
Loading history...
219
             */
220
            static function (array $carry, $key) use ($name): array {
221
                if (! is_string($key)) {
222
                    throw new InvalidToConstructorNameParameter((string) $key);
223
                }
224
225
                $varName = $name[$key];
226
                $carry[] = $key . '=' . $varName;
227
228
                return $carry;
229
            },
230
            []
231
        );
232
233
        return implode(',', $names);
234
    }
235
}
236