Completed
Push — 2.x ( 8f6930...447f85 )
by Akihito
01:14
created

Bind::toNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Di;
6
7
use Koriym\NullObject\NullObject;
8
use Ray\Aop\MethodInterceptor;
9
use Ray\Di\Exception\InvalidToConstructorNameParameter;
10
use ReflectionClass;
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
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 introduced by
The doc-type class-string<MethodInterceptor>|string could not be parsed: Unknown type name "class-string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

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); // @phpstan-ignore-line
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
    public function to(string $class): self
92
    {
93
        $this->untarget = null;
94
        $refClass = $this->validate->to($this->interface, $class);
95
        $this->bound = (new DependencyFactory())->newAnnotatedDependency($refClass);
96
        $this->container->add($this);
97
98
        return $this;
99
    }
100
101
    /**
102
     * Bind to constructor
103
     *
104
     * @param class-string                 $class           class name
0 ignored issues
show
Documentation introduced by
The doc-type class-string could not be parsed: Unknown type name "class-string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
105
     * @param array<string, string>|string $name            "varName=bindName,..." or [$varName => $bindName, $varName => $bindName...]
106
     * @param InjectionPoints              $injectionPoints injection points
107
     * @param string                       $postConstruct   method name of initialization after all dependencies are injected*
108
     */
109
    public function toConstructor(string $class, $name, ?InjectionPoints $injectionPoints = null, ?string $postConstruct = null): self
110
    {
111
        if (is_array($name)) {
112
            $name = $this->getStringName($name);
113
        }
114
115
        $this->untarget = null;
116
        $postConstructRef = $postConstruct ? new ReflectionMethod($class, $postConstruct) : null;
117
        $this->bound = (new DependencyFactory())->newToConstructor(new ReflectionClass($class), $name, $injectionPoints, $postConstructRef);
118
        $this->container->add($this);
119
120
        return $this;
121
    }
122
123
    /**
124
     * Bind to provider
125
     *
126
     * @phpstan-param class-string $provider
127
     */
128
    public function toProvider(string $provider, string $context = ''): self
129
    {
130
        $this->untarget = null;
131
        $refClass = $this->validate->toProvider($provider);
132
        $this->bound = (new DependencyFactory())->newProvider($refClass, $context);
133
        $this->container->add($this);
134
135
        return $this;
136
    }
137
138
    /**
139
     * Bind to instance
140
     *
141
     * @param mixed $instance
142
     */
143
    public function toInstance($instance): self
144
    {
145
        $this->untarget = null;
146
        $this->bound = new Instance($instance);
147
        $this->container->add($this);
148
149
        return $this;
150
    }
151
152
    /**
153
     * Bind to NullObject
154
     */
155
    public function toNull(): self
156
    {
157
        $this->untarget = null;
158
        assert(interface_exists($this->interface));
159
        $this->bound = new NullObjectDependency($this->interface);
160
        $this->container->add($this);
161
162
        return $this;
163
    }
164
165
    /**
166
     * Set scope
167
     */
168
    public function in(string $scope): self
169
    {
170
        if ($this->bound instanceof Dependency || $this->bound instanceof DependencyProvider) {
171
            $this->bound->setScope($scope);
172
        }
173
174
        if ($this->untarget) {
175
            $this->untarget->setScope($scope);
176
        }
177
178
        return $this;
179
    }
180
181
    public function getBound(): DependencyInterface
182
    {
183
        return $this->bound;
184
    }
185
186
    public function setBound(DependencyInterface $bound): void
187
    {
188
        $this->bound = $bound;
189
    }
190
191
    private function isRegistered(string $interface): bool
192
    {
193
        return isset($this->container->getContainer()[$interface . '-' . Name::ANY]);
194
    }
195
196
    /**
197
     * Return string
198
     *
199
     * input: ['varA' => 'nameA', 'varB' => 'nameB']
200
     * output: "varA=nameA,varB=nameB"
201
     *
202
     * @param array<string, string> $name
203
     */
204
    private function getStringName(array $name): string
205
    {
206
        $keys = array_keys($name);
207
208
        $names = array_reduce(
209
            $keys,
210
            /**
211
             * @param array-key $key
0 ignored issues
show
Documentation introduced by
The doc-type array-key could not be parsed: Unknown type name "array-key" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
212
             */
213
            static function (array $carry, $key) use ($name): array {
214
                if (! is_string($key)) {
215
                    throw new InvalidToConstructorNameParameter((string) $key);
216
                }
217
218
                $varName = $name[$key];
219
                $carry[] = $key . '=' . $varName;
220
221
                return $carry;
222
            },
223
            []
224
        );
225
226
        return implode(',', $names);
227
    }
228
}
229