Passed
Push — main ( 776d54...bb1c56 )
by Michael
01:01 queued 12s
created

CallProxy::enableForwarding()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MichaelRubel\EnhancedContainer\Core;
6
7
use Illuminate\Support\Str;
8
use Illuminate\Support\Traits\ForwardsCalls;
9
use MichaelRubel\EnhancedContainer\Call;
10
use MichaelRubel\EnhancedContainer\Exceptions\InstanceInteractionException;
11
use MichaelRubel\EnhancedContainer\Traits\InteractsWithContainer;
12
13
class CallProxy implements Call
14
{
15
    use InteractsWithContainer, ForwardsCalls;
0 ignored issues
show
introduced by
The trait MichaelRubel\EnhancedCon...\InteractsWithContainer requires some properties which are not provided by MichaelRubel\EnhancedContainer\Core\CallProxy: $contextual, $map
Loading history...
16
17
    /**
18
     * Current proxy instance.
19
     *
20
     * @var object
21
     */
22
    protected object $instance;
23
24
    /**
25
     * Previous proxy instance.
26
     *
27
     * @var object|null
28
     */
29
    protected ?object $previous = null;
30
31
    /**
32
     * Determines if the forwarding is enabled in this proxy.
33
     *
34
     * @var bool
35
     */
36
    protected bool $forwarding = true;
37
38
    /**
39
     * Saves proxy interactions (method calls, property assignments, etc).
40
     *
41
     * @var array
42
     */
43
    protected array $interactions = [];
44
45
    /**
46
     * Initialize a new CallProxy.
47
     *
48
     * @param  object|string  $class
49
     * @param  array  $dependencies
50
     * @param  string|null  $context
51
     */
52 67
    public function __construct(object|string $class, array $dependencies = [], ?string $context = null)
53
    {
54 67
        $this->instance = $this->getInstance($class, $dependencies, $context);
55
    }
56
57
    /**
58
     * Gets the internal property by name.
59
     *
60
     * @param  string  $property
61
     *
62
     * @return mixed
63
     */
64 15
    public function getInternal(string $property): mixed
65
    {
66 15
        return $this->{$property};
67
    }
68
69
    /**
70
     * Sets the internal instance to previous one.
71
     *
72
     * @return static
73
     */
74 1
    public function setPrevious(): static
75
    {
76 1
        if ($this->previous) {
77 1
            $oldInstance = $this->instance;
78
79 1
            $this->instance = $this->previous;
80
81 1
            $this->previous = $oldInstance;
82
        }
83
84 1
        return $this;
85
    }
86
87
    /**
88
     * Disables the forwarding on the proxy level.
89
     *
90
     * @return static
91
     */
92 2
    public function disableForwarding(): static
93
    {
94 2
        $this->forwarding = false;
95
96 2
        return $this;
97
    }
98
99
    /**
100
     * Enables the forwarding on the proxy level.
101
     *
102
     * @return static
103
     */
104 1
    public function enableForwarding(): static
105
    {
106 1
        $this->forwarding = true;
107
108 1
        return $this;
109
    }
110
111
    /**
112
     * Perform the container call.
113
     *
114
     * @param  object  $service
115
     * @param  string  $method
116
     * @param  array  $parameters
117
     *
118
     * @return mixed
119
     */
120 55
    protected function containerCall(object $service, string $method, array $parameters): mixed
121
    {
122
        try {
123 55
            return app()->call(
124 55
                [$service, $method],
125 55
                $this->getParameters($service, $method, $parameters)
126
            );
127 11
        } catch (\ReflectionException) {
128 2
            return $this->forwardCallTo($service, $method, $parameters);
129
        }
130
    }
131
132
    /**
133
     * Find the forwarding instance if bound.
134
     *
135
     * @return void
136
     */
137 17
    protected function findForwardingInstance(): void
138
    {
139 17
        $clue = $this->instance::class . Forwarding::CONTAINER_KEY;
140
141 17
        if ($this->forwarding && app()->bound($clue)) {
142 13
            $newInstance = rescue(fn () => app($clue), report: false);
143
144 13
            if (! is_null($newInstance)) {
145 13
                $this->previous = $this->instance;
146 13
                $this->instance = $newInstance;
147
            }
148
        }
149
    }
150
151
    /**
152
     * Save the interaction with proxy.
153
     *
154
     * @param  string  $name
155
     * @param  string  $type
156
     *
157
     * @return void
158
     */
159 65
    protected function interact(string $name, string $type): void
160
    {
161 65
        $this->interactions[$name] = $type;
162
    }
163
164
    /**
165
     * Check the proxy has previous interaction
166
     * with the same method or property.
167
     *
168
     * @param  string  $name
169
     *
170
     * @return bool
171
     */
172 17
    protected function hasPreviousInteraction(string $name): bool
173
    {
174 17
        return $this->forwarding && $this->previous && isset($this->interactions[$name]);
175
    }
176
177
    /**
178
     * Handle the missing by error message.
179
     *
180
     * @param  \Closure  $callback
181
     * @param  string  $by
182
     *
183
     * @return mixed
184
     */
185 64
    protected function handleMissing(\Closure $callback, string $by): mixed
186
    {
187
        try {
188 64
            return $callback();
189 13
        } catch (\Error|\ErrorException $e) {
190 9
            if (Str::contains($e->getMessage(), $by)) {
191 6
                $this->findForwardingInstance();
192
193 6
                return $callback();
194
            }
195
196 3
            throw $e;
197
        }
198
    }
199
200
    /**
201
     * Pass the call through container.
202
     *
203
     * @param  string  $method
204
     * @param  array  $parameters
205
     *
206
     * @return mixed
207
     */
208 55
    public function __call(string $method, array $parameters): mixed
209
    {
210 55
        if (! method_exists($this->instance, $method)) {
211 15
            if ($this->hasPreviousInteraction($method)) {
212 1
                throw new InstanceInteractionException;
213
            }
214
215 15
            $this->findForwardingInstance();
216
        }
217
218 55
        $this->interact($method, Call::METHOD);
219
220 55
        return $this->handleMissing(
221 55
            fn () => $this->containerCall($this->instance, $method, $parameters),
222
            by: 'Call to undefined method'
223
        );
224
    }
225
226
    /**
227
     * Get the instance's property.
228
     *
229
     * @param  string  $name
230
     *
231
     * @return mixed
232
     */
233 11
    public function __get(string $name): mixed
234
    {
235 11
        if (! property_exists($this->instance, $name)) {
236 3
            if ($this->hasPreviousInteraction($name)) {
237 1
                throw new InstanceInteractionException;
238
            }
239
240 2
            $this->findForwardingInstance();
241
        }
242
243 11
        $this->interact($name, Call::GET);
244
245 11
        return $this->handleMissing(
246 11
            fn () => $this->instance->{$name},
247
            by: 'Undefined property'
248
        );
249
    }
250
251
    /**
252
     * Set the instance's property.
253
     *
254
     * @param  string  $name
255
     * @param  mixed  $value
256
     */
257 6
    public function __set(string $name, mixed $value): void
258
    {
259 6
        $this->interact($name, Call::SET);
260
261 6
        $this->instance->{$name} = $value;
262
    }
263
264
    /**
265
     * Check the property is set.
266
     *
267
     * @param  string  $name
268
     *
269
     * @return bool
270
     */
271 1
    public function __isset(string $name): bool
272
    {
273 1
        $this->interact($name, Call::ISSET);
274
275 1
        return isset($this->instance->{$name});
276
    }
277
278
    /**
279
     * Unset the property.
280
     *
281
     * @param  string  $name
282
     *
283
     * @return void
284
     */
285 1
    public function __unset(string $name): void
286
    {
287 1
        $this->interact($name, Call::UNSET);
288
289 1
        unset($this->instance->{$name});
290
    }
291
}
292