Passed
Pull Request — main (#5)
by Michael
03:33
created

CallProxy::__unset()   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 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 2
b 0
f 0
nc 1
nop 1
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
        $oldInstance = $this->instance;
77
78 1
        $this->instance = $this->previous;
79
80 1
        $this->previous = $oldInstance;
81
82 1
        return $this;
83
    }
84
85
    /**
86
     * Disables the forwarding on the proxy level.
87
     *
88
     * @return CallProxy
89
     */
90 2
    public function disableForwarding(): static
91
    {
92 2
        $this->forwarding = false;
93
94 2
        return $this;
95
    }
96
97
    /**
98
     * Enables the forwarding on the proxy level.
99
     *
100
     * @return CallProxy
101
     */
102 1
    public function enableForwarding(): static
103
    {
104 1
        $this->forwarding = true;
105
106 1
        return $this;
107
    }
108
109
    /**
110
     * Perform the container call.
111
     *
112
     * @param  object  $service
113
     * @param  string  $method
114
     * @param  array  $parameters
115
     *
116
     * @return mixed
117
     */
118 55
    protected function containerCall(object $service, string $method, array $parameters): mixed
119
    {
120
        try {
121 55
            return app()->call(
122 55
                [$service, $method],
123 55
                $this->getParameters($service, $method, $parameters)
124
            );
125 11
        } catch (\ReflectionException) {
126 2
            return $this->forwardCallTo($service, $method, $parameters);
127
        }
128
    }
129
130
    /**
131
     * Find the forwarding instance if bound.
132
     *
133
     * @return void
134
     */
135 17
    protected function findForwardingInstance(): void
136
    {
137 17
        $clue = $this->instance::class . Forwarding::CONTAINER_KEY;
138
139 17
        if ($this->forwarding && app()->bound($clue)) {
140 13
            $newInstance = rescue(fn () => app($clue), report: false);
141
142 13
            if (! is_null($newInstance)) {
143 13
                $this->previous = $this->instance;
144 13
                $this->instance = $newInstance;
145
            }
146
        }
147
    }
148
149
    /**
150
     * Save the interaction with proxy.
151
     *
152
     * @param  string  $name
153
     * @param  string  $type
154
     *
155
     * @return void
156
     */
157 65
    protected function interact(string $name, string $type): void
158
    {
159 65
        $this->interactions[$name] = $type;
160
    }
161
162
    /**
163
     * Check the proxy has previous interaction
164
     * with the same method or property.
165
     *
166
     * @param  string  $name
167
     *
168
     * @return bool
169
     */
170 17
    protected function hasPreviousInteraction(string $name): bool
171
    {
172 17
        return $this->forwarding
173 17
            && isset($this->interactions[$name])
174 17
            && isset($this->previous);
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