Config::testInstance()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 10
c 1
b 0
f 0
cc 3
nc 2
nop 2
crap 3
1
<?php
2
3
namespace PhpWinTools\WmiScripting\Configuration;
4
5
use Illuminate\Support\Arr;
6
use PhpWinTools\Support\COM\ComWrapper;
7
use PhpWinTools\Support\COM\VariantWrapper;
8
use PhpWinTools\Support\COM\ComVariantWrapper;
9
use PhpWinTools\WmiScripting\Containers\Connections;
10
use PhpWinTools\WmiScripting\Connections\ComConnection;
11
use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
12
use PhpWinTools\WmiScripting\Exceptions\UnresolvableClassException;
13
14
class Config
15
{
16
    /** @var Config|null */
17
    protected static $instance = null;
18
19
    protected static $test_mode = false;
20
21
    protected $config;
22
23
    protected $resolver;
24
25
    protected $resolve_stack = [];
26
27 33
    public function __construct(array $config = [], Resolver $resolver = null)
28
    {
29 33
        $this->resolver = $resolver ?? new Resolver($this);
30 33
        $this->boot($config);
31
32 33
        static::$instance = $this;
33 33
    }
34
35
    /**
36
     * Returns current instance if available and merges any available configuration.
37
     * This is the method used throughout the library to retrieve configuration.
38
     *
39
     * @param array         $items
40
     * @param Resolver|null $resolver
41
     *
42
     * @return Config
43
     */
44 25
    public static function instance(array $items = [], Resolver $resolver = null)
45
    {
46 25
        if (static::$instance && !empty($items)) {
47 3
            return static::newInstance(array_merge(static::$instance->config, $items), $resolver);
48
        }
49
50 25
        if (static::$instance && $resolver) {
51
            static::$instance->resolver = $resolver;
52
        }
53
54 25
        return static::$instance ?? static::newInstance($items, $resolver);
55
    }
56
57
    /**
58
     * Always returns a new instance. Used primary for testing. If used be aware that any previous changes
59
     * to the instance will be lost. If it's in test mode it will remain until you explicitly remove it.
60
     * You should never need to reference this directly except inside of tests.
61
     *
62
     * @param array         $items
63
     * @param Resolver|null $resolver
64
     *
65
     * @return Config
66
     */
67 32
    public static function newInstance(array $items = [], Resolver $resolver = null)
68
    {
69 32
        return new static($items, $resolver);
70
    }
71
72
    /**
73
     * This merges in a testing configuration. Any instance from this point will use that configuration.
74
     *
75
     * @param array         $items
76
     * @param Resolver|null $resolver
77
     *
78
     * @return Config
79
     */
80 5
    public static function testInstance(array $items = [], Resolver $resolver = null)
81
    {
82 5
        if (static::$test_mode === false && !is_null(static::$instance)) {
83 4
            static::$instance = null;
84
        }
85
86 5
        static::$test_mode = true;
87
88 5
        return static::instance($items, $resolver);
89
    }
90
91
    /**
92
     * Same as endTest, but also returns a fresh instance.
93
     *
94
     * @param array         $items
95
     * @param Resolver|null $resolver
96
     *
97
     * @return Config
98
     */
99 20
    public static function killTestInstance(array $items = [], Resolver $resolver = null)
100
    {
101 20
        (new static())->endTest();
102
103 20
        return static::instance($items, $resolver);
104
    }
105
106
    /**
107
     * This removes the testing flag and allow the normal configuration to return. This must be called to have
108
     * the library behave normally when testing.
109
     *
110
     * @return Config
111
     */
112 21
    public function endTest()
113
    {
114 21
        static::$test_mode = false;
115 21
        static::$instance = null;
116
117 21
        return $this;
118
    }
119
120
    /**
121
     * Returns the Resolver if no class is specified otherwise it attempts to resolve the given class.
122
     *
123
     * @param string|null $class
124
     * @param mixed       ...$parameters
125
     *
126
     * @return Resolver|mixed|null
127
     */
128 8
    public function __invoke(string $class = null, ...$parameters)
129
    {
130 8
        if (is_null($class)) {
131 3
            return $this->resolve();
132
        }
133
134 7
        if ($this->hasResolvable($class)) {
135 3
            return $this->resolveFromStack($class);
136
        }
137
138 5
        if (array_key_exists($class, $this->apiObjects())) {
139 4
            return $this->resolve()->make($this->getApiObject($class), ...$parameters);
140
        }
141
142 2
        if (array_key_exists($class, $this->com())) {
143 1
            return $this->resolve()->make($this->com()[$class], ...$parameters);
144
        }
145
146 1
        throw UnresolvableClassException::default($class);
147
    }
148
149
    /**
150
     * @return Resolver
151
     */
152 8
    public function resolve()
153
    {
154 8
        return $this->resolver;
155
    }
156
157
    /**
158
     * Returns the current resolve stack.
159
     *
160
     * @return array
161
     */
162
    public function resolveStack()
163
    {
164
        return $this->resolve_stack;
165
    }
166
167
    /**
168
     * This attempts to get a resolvable item from the stack. Items on the stack are FIFO (First In First Out).
169
     * This is only ever utilized if using the Config classes' __invoke capability.
170
     *
171
     * @param $abstract
172
     *
173
     * @return mixed|null
174
     */
175 3
    public function resolveFromStack($abstract)
176
    {
177 3
        foreach ($this->resolve_stack as $key => $resolvable) {
178 3
            if (array_key_exists($abstract, $resolvable)) {
179 3
                $result = $resolvable[$abstract];
180 3
                unset($this->resolve_stack[$key]);
181
182 3
                return $result;
183
            }
184
        }
185
186
        return null;
187
    }
188
189
    /**
190
     * Add new resolvable to the end of the stack.
191
     *
192
     * @param $abstract
193
     * @param $concrete
194
     *
195
     * @return Config
196
     */
197 3
    public function addResolvable($abstract, $concrete)
198
    {
199 3
        $this->resolve_stack[] = [$abstract => $concrete];
200
201 3
        return $this;
202
    }
203
204
    /**
205
     * Check stack for resolvable. There may be a chance for caching pointers for resolvable abstracts.
206
     *
207
     * @param $abstract
208
     *
209
     * @return bool
210
     */
211 7
    public function hasResolvable($abstract): bool
212
    {
213 7
        foreach ($this->resolve_stack as $key => $resolvable) {
214 3
            if (array_key_exists($abstract, $resolvable)) {
215 3
                return true;
216
            }
217
        }
218
219 5
        return false;
220
    }
221
222
    /**
223
     * @return array
224
     */
225 5
    public function apiObjects()
226
    {
227 5
        return $this->get('wmi.api_objects', []);
228
    }
229
230
    /**
231
     * @param string                 $abstract_class
232
     * @param string|callable|object $concrete_class
233
     *
234
     * @return Config
235
     */
236 4
    public function addApiObject(string $abstract_class, $concrete_class)
237
    {
238 4
        $this->set("wmi.api_objects.{$abstract_class}", $concrete_class);
239
240 4
        return $this;
241
    }
242
243
    /**
244
     * @param string $class
245
     *
246
     * @return string
247
     */
248 6
    public function getApiObject(string $class)
249
    {
250 6
        return $this->get("wmi.api_objects.{$class}");
251
    }
252
253
    /**
254
     * @return array
255
     */
256 2
    public function com()
257
    {
258 2
        return $this->get('com', []);
259
    }
260
261
    /**
262
     * @param string                 $abstract_class
263
     * @param string|callable|object $concrete_class
264
     *
265
     * @return Config
266
     */
267 1
    public function addComObject(string $abstract_class, $concrete_class)
268
    {
269 1
        $this->set("com.{$abstract_class}", $concrete_class);
270
271 1
        return $this;
272
    }
273
274
    /**
275
     * @return string
276
     */
277 2
    public function getComClass()
278
    {
279 2
        return $this->get('com.com_class');
280
    }
281
282
    /**
283
     * @return string
284
     */
285
    public function getVariantClass()
286
    {
287
        return $this->get('com.variant_class');
288
    }
289
290
    /**
291
     * @return string
292
     */
293 1
    public function getComVariantWrapper()
294
    {
295 1
        return $this->get('com.' . ComVariantWrapper::class);
296
    }
297
298
    /**
299
     * @return string
300
     */
301 2
    public function getComWrapper()
302
    {
303 2
        return $this->get('com.' . ComWrapper::class);
304
    }
305
306
    /**
307
     * @return string
308
     */
309
    public function getVariantWrapper()
310
    {
311
        return $this->get('com.' . VariantWrapper::class);
312
    }
313
314
    /**
315
     * @return Connections
316
     */
317 15
    public function connections()
318
    {
319 15
        return $this->get('wmi.connections.servers');
320
    }
321
322
    /**
323
     * @param string $name
324
     *
325
     * @return ComConnection|null
326
     */
327
    public function getConnection(string $name = null)
328 6
    {
329
        if ($name === 'default' || is_null($name)) {
330 6
            $name = $this->get('wmi.connections.default');
331
        }
332 6
333
        return $this->connections()->get($name);
334
    }
335 1
336
    /**
337 1
     * @param string        $name
338
     * @param ComConnection $connection
339
     *
340 3
     * @return Config
341
     */
342 3
    public function addConnection(string $name, ComConnection $connection): self
343 1
    {
344
        $this->connections()->set($name, $connection);
345
346 2
        return $this;
347
    }
348 2
349
    public function getDefaultConnection()
350
    {
351
        return $this->getConnection($this->getDefaultConnectionName());
352
    }
353
354
    public function getDefaultConnectionName()
355
    {
356 13
        return $this->get('wmi.connections.default');
357
    }
358 13
359 6
    public function setDefaultConnection(string $name)
360
    {
361
        if (!$this->getConnection($name)) {
362 13
            throw InvalidConnectionException::new($name);
363
        }
364
365 34
        $this->set('wmi.connections.default', $name);
366
367 34
        return $this;
368
    }
369
370 6
    public function get($key, $default = null)
371
    {
372 6
        return Arr::get($this->config, $key, $default);
373
    }
374 6
375 6
    public function set($key, $value = null)
376
    {
377 6
        $keys = is_array($key) ? $key : [$key => $value];
378
379 33
        foreach ($keys as $key => $value) {
0 ignored issues
show
introduced by
$key is overwriting one of the parameters of this function.
Loading history...
380
            Arr::set($this->config, $key, $value);
381 33
        }
382 6
    }
383
384
    protected function boot(array $config)
385 33
    {
386 5
        if (static::$test_mode) {
387
            $this->merge(include(__DIR__ . '/../config/testing.php'));
388
        }
389 33
390
        if (!empty($config)) {
391 33
            $this->merge($config);
392 33
        }
393
394 33
        $this->merge(include(__DIR__ . '/../config/bootstrap.php'));
395
396 33
        $this->bootConnections();
397 33
    }
398
399 33
    protected function merge(array $config)
400
    {
401 33
        foreach (Arr::dot($config) as $key => $value) {
402
            $this->config = Arr::add($this->config, $key, $value);
403 33
        }
404 33
    }
405
406
    protected function bootConnections()
407
    {
408
        Arr::set($this->config, 'wmi.connections.servers', new Connections($this));
409
    }
410
}
411