Passed
Pull Request — main (#4)
by Michael
03:29
created

CallProxy::handleMissing()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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