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