Completed
Push — drivers ( fe89be...446647 )
by Joe
01:59
created

Config::set()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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