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

CallProxy::setState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
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
     * @var object
19
     */
20
    protected object $instance;
21
22
    /**
23
     * @var object
24
     */
25
    protected object $previous;
26
27
    /**
28
     * @var array
29
     */
30
    protected array $state = [];
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 11
        } catch (\ReflectionException) {
73 3
            return $this->forwardCallTo($service, $method, $parameters);
74
        }
75
    }
76
77
    /**
78
     * @return void
79
     */
80 15
    protected function findForwardingInstance(): void
81
    {
82 15
        $clue = $this->instance::class . Forwarding::CONTAINER_KEY;
83
84 15
        if (app()->bound($clue)) {
85 12
            $newInstance = rescue(fn () => app($clue), report: false);
86
87 12
            if (! is_null($newInstance)) {
88 12
                $this->previous = $this->instance;
89 12
                $this->instance = $newInstance;
90
            }
91
        }
92
    }
93
94
    /**
95
     * @param  string  $name
96
     * @param  string  $type
97
     *
98
     * @return void
99
     */
100 59
    protected function setState(string $name, string $type): void
101
    {
102 59
        $this->state[$name] = $type;
103
    }
104
105
    /**
106
     * @param  string  $name
107
     *
108
     * @return bool
109
     */
110 15
    protected function hasPreviousState(string $name): bool
111
    {
112 15
        return isset($this->state[$name]) && isset($this->previous);
113
    }
114
115
    /**
116
     * Handle the missing by error message.
117
     *
118
     * @param  \Closure  $callback
119
     * @param  string  $by
120
     *
121
     * @return mixed
122
     */
123 62
    protected function handleMissing(\Closure $callback, string $by): mixed
124
    {
125
        try {
126 62
            return $callback();
127 13
        } catch (\Error|\ErrorException $e) {
128 8
            if (Str::contains($e->getMessage(), $by)) {
129 5
                $this->findForwardingInstance();
130
131 5
                return $callback();
132
            }
133
134 3
            throw $e;
135
        }
136
    }
137
138
    /**
139
     * Pass the call through container.
140
     *
141
     * @param  string  $method
142
     * @param  array  $parameters
143
     *
144
     * @return mixed
145
     */
146 53
    public function __call(string $method, array $parameters): mixed
147
    {
148 53
        if (! method_exists($this->instance, $method)) {
149 13
            if ($this->hasPreviousState($method)) {
150 1
                throw new InstanceInteractionException;
151
            }
152
153 13
            $this->findForwardingInstance();
154
        }
155
156 53
        $this->setState($method, Call::METHOD);
157
158 53
        return $this->handleMissing(
159 53
            fn () => $this->containerCall($this->instance, $method, $parameters),
160
            by: 'Call to undefined method'
161
        );
162
    }
163
164
    /**
165
     * Get the instance's property.
166
     *
167
     * @param  string  $name
168
     *
169
     * @return mixed
170
     */
171 10
    public function __get(string $name): mixed
172
    {
173 10
        if (! property_exists($this->instance, $name)) {
174 3
            if ($this->hasPreviousState($name)) {
175 1
                throw new InstanceInteractionException;
176
            }
177
178 2
            $this->findForwardingInstance();
179
180 2
            $this->setState($name, Call::GET);
181
        }
182
183 10
        return $this->handleMissing(
184 10
            fn () => $this->instance->{$name},
185
            by: 'Undefined property'
186
        );
187
    }
188
189
    /**
190
     * Set the instance's property.
191
     *
192
     * @param  string  $name
193
     * @param  mixed  $value
194
     */
195 6
    public function __set(string $name, mixed $value): void
196
    {
197 6
        $this->setState($name, Call::SET);
198
199 6
        $this->instance->{$name} = $value;
200
    }
201
202
    /**
203
     * Check the property is set.
204
     *
205
     * @param  string  $name
206
     *
207
     * @return bool
208
     */
209 1
    public function __isset(string $name): bool
210
    {
211 1
        return isset($this->instance->{$name});
212
    }
213
214
    /**
215
     * Unset the property.
216
     *
217
     * @param  string  $name
218
     *
219
     * @return void
220
     */
221 1
    public function __unset(string $name): void
222
    {
223 1
        unset($this->instance->{$name});
224
    }
225
}
226