CallProxy::__set()   A
last analyzed

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 7
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 7
b 0
f 0
nc 1
nop 2
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\Container\BoundMethod;
8
use Illuminate\Container\Container;
9
use Illuminate\Support\Str;
10
use Illuminate\Support\Traits\ForwardsCalls;
11
use MichaelRubel\EnhancedContainer\Call;
12
use MichaelRubel\EnhancedContainer\Exceptions\InstanceInteractionException;
13
use MichaelRubel\EnhancedContainer\Traits\InteractsWithContainer;
14
15
class CallProxy implements Call
16
{
17
    use ForwardsCalls, InteractsWithContainer;
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...
18
19
    /**
20
     * Current proxy instance.
21
     */
22
    protected object $instance;
23
24
    /**
25
     * Previous proxy instance.
26
     */
27
    protected ?object $previous = null;
28
29
    /**
30
     * Determines if the forwarding is enabled in this proxy.
31
     */
32
    protected bool $forwarding = true;
33
34
    /**
35
     * Saves proxy interactions (method calls, property assignments, etc).
36
     */
37
    protected array $interactions = [];
38
39
    /**
40
     * Initialize a new CallProxy.
41
     */
42 60
    public function __construct(object|string $class, array $dependencies = [], ?string $context = null)
43
    {
44 60
        $this->instance = $this->getInstance($class, $dependencies, $context);
45
    }
46
47
    /**
48
     * Gets the internal property by name.
49
     */
50 14
    public function getInternal(string $property): mixed
51
    {
52 14
        return $this->{$property};
53
    }
54
55
    /**
56
     * Sets the internal instance to previous one.
57
     */
58 1
    public function setPrevious(): static
59
    {
60 1
        if ($this->previous) {
61 1
            $oldInstance = $this->instance;
62
63 1
            $this->instance = $this->previous;
64
65 1
            $this->previous = $oldInstance;
66
        }
67
68 1
        return $this;
69
    }
70
71
    /**
72
     * Disables the forwarding on the proxy level.
73
     */
74 2
    public function disableForwarding(): static
75
    {
76 2
        $this->forwarding = false;
77
78 2
        return $this;
79
    }
80
81
    /**
82
     * Enables the forwarding on the proxy level.
83
     */
84 1
    public function enableForwarding(): static
85
    {
86 1
        $this->forwarding = true;
87
88 1
        return $this;
89
    }
90
91
    /**
92
     * Perform the container call.
93
     */
94 46
    protected function containerCall(object $service, string $method, array $parameters): mixed
95
    {
96
        try {
97 46
            return BoundMethod::call(
98 46
                Container::getInstance(), [$service, $method], $this->getParameters($service, $method, $parameters)
99 46
            );
100 11
        } catch (\ReflectionException) {
101 2
            return $this->forwardCallTo($service, $method, $parameters);
102
        }
103
    }
104
105
    /**
106
     * Find the forwarding instance if bound.
107
     */
108 20
    protected function findForwardingInstance(): void
109
    {
110 20
        $clue = $this->instance::class . Forwarding::CONTAINER_KEY;
111
112 20
        if ($this->forwarding && app()->bound($clue)) {
113 16
            $newInstance = app($clue);
114
115 16
            $this->previous = $this->instance;
116 16
            $this->instance = $newInstance;
117
        }
118
    }
119
120
    /**
121
     * Save the interaction with proxy.
122
     */
123 57
    protected function interact(string $name, string $type): void
124
    {
125 57
        $this->interactions[$name] = $type;
126
    }
127
128
    /**
129
     * Check the proxy has previous interaction
130
     * with the same method or property.
131
     */
132 20
    protected function hasPreviousInteraction(string $name): bool
133
    {
134 20
        return $this->previous && isset($this->interactions[$name]);
135
    }
136
137
    /**
138
     * Handle the missing by error message.
139
     */
140 56
    protected function handleMissing(\Closure $callback, string $by): mixed
141
    {
142
        try {
143 56
            return $callback();
144 13
        } catch (\Error|\ErrorException $e) {
145 9
            if (Str::contains($e->getMessage(), $by)) {
146 6
                $this->findForwardingInstance();
147
148 6
                return $callback();
149
            }
150
151 3
            throw $e;
152
        }
153
    }
154
155
    /**
156
     * Pass the call through container.
157
     */
158 45
    public function __call(string $method, array $parameters): mixed
159
    {
160 45
        if (! method_exists($this->instance, $method)) {
161 15
            if ($this->hasPreviousInteraction($method)) {
162 1
                throw new InstanceInteractionException;
163
            }
164
165 15
            $this->findForwardingInstance();
166
        }
167
168 45
        $this->interact($method, Call::METHOD);
169
170 45
        return $this->handleMissing(
171 45
            fn () => $this->containerCall($this->instance, $method, $parameters),
172 45
            by: 'Call to undefined method'
173 45
        );
174
    }
175
176
    /**
177
     * Get the instance's property.
178
     */
179 12
    public function __get(string $name): mixed
180
    {
181 12
        if (! property_exists($this->instance, $name)) {
182 5
            if ($this->hasPreviousInteraction($name)) {
183 3
                throw new InstanceInteractionException;
184
            }
185
186 4
            $this->findForwardingInstance();
187
        }
188
189 12
        $this->interact($name, Call::GET);
190
191 12
        return $this->handleMissing(
192 12
            fn () => $this->instance->{$name},
193 12
            by: 'Undefined property'
194 12
        );
195
    }
196
197
    /**
198
     * Set the instance's property.
199
     */
200 6
    public function __set(string $name, mixed $value): void
201
    {
202 6
        $this->interact($name, Call::SET);
203
204 6
        $this->instance->{$name} = $value;
205
    }
206
207
    /**
208
     * Check the property is set.
209
     */
210 2
    public function __isset(string $name): bool
211
    {
212 2
        $this->interact($name, Call::ISSET);
213
214 2
        return isset($this->instance->{$name});
215
    }
216
217
    /**
218
     * Unset the property.
219
     */
220 2
    public function __unset(string $name): void
221
    {
222 2
        $this->interact($name, Call::UNSET);
223
224 2
        unset($this->instance->{$name});
225
    }
226
}
227