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

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