Completed
Push — drivers ( c650c9...b4fa87 )
by Joe
02:26
created

Config::make()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

382
        return $this->bindings($alias, /** @scrutinizer ignore-type */ $default);
Loading history...
383
    }
384
385
    /**
386
     * @param string              $alias
387
     * @param array|mixed|Closure ...$constructor
388
     *
389
     * @return mixed
390
     */
391
    public function makeProvider(string $alias, ...$constructor)
392
    {
393
        if (($provider = $this->providers($alias, false)) === false) {
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type array expected by parameter $default of PhpWinTools\WmiScripting...ion\Config::providers(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

393
        if (($provider = $this->providers($alias, /** @scrutinizer ignore-type */ false)) === false) {
Loading history...
394
            throw new InvalidArgumentException("{$alias} is not a valid provider.");
395
        }
396
397
        return $this->make($provider, ...$constructor);
398
    }
399
400
    public function getCacheDriver($provider = null)
401
    {
402
        $provider = is_null($provider) ? $provider : "{$provider}.";
403
404
        $driver = $this->get("{$provider}cache.driver");
405
406
        return new $driver($this);
407
    }
408
409
    public function shouldTrackEvents()
410
    {
411
        return $this->get('event.track', false);
412
    }
413
414
    public function trackEvents()
415
    {
416
        $this->set('event.track', true);
417
418
        return $this;
419
    }
420
421
    public function doNotTrackEvents()
422
    {
423
        $this->set('event.track', false);
424
425
        return $this;
426
    }
427
428
    /**
429
     * @return string
430
     */
431
    public function getComClass()
432
    {
433
        return $this->get('com.com_class');
434
    }
435
436
    /**
437
     * @return string
438
     */
439
    public function getVariantClass()
440
    {
441
        return $this->get('com.variant_class');
442
    }
443
444
    /**
445
     * @return string
446
     */
447
    public function getComVariantWrapper()
448
    {
449
        return $this->get('com.' . ComVariantWrapper::class);
450
    }
451
452
    /**
453
     * @return string
454
     */
455
    public function getComWrapper()
456
    {
457
        return $this->get('com.' . ComWrapper::class);
458
    }
459
460
    /**
461
     * @return string
462
     */
463
    public function getVariantWrapper()
464
    {
465
        return $this->get('com.' . VariantWrapper::class);
466
    }
467
468
    /**
469
     * @return Connections
470
     */
471
    public function connections()
472
    {
473
        return $this->get('wmi.connections.servers');
474
    }
475
476
    /**
477
     * @param string $name
478
     *
479
     * @return ComConnection|null
480
     */
481
    public function getConnection(string $name = null)
482
    {
483
        if ($name === 'default' || is_null($name)) {
484
            $name = $this->get('wmi.connections.default');
485
        }
486
487
        return $this->connections()->get($name);
488
    }
489
490
    /**
491
     * @param string        $name
492
     * @param ComConnection $connection
493
     *
494
     * @return Config
495
     */
496
    public function addConnection(string $name, ComConnection $connection): self
497
    {
498
        $this->connections()->set($name, $connection);
499
500
        return $this;
501
    }
502
503
    public function getDefaultConnection()
504
    {
505
        return $this->getConnection($this->getDefaultConnectionName());
506
    }
507
508
    public function getDefaultConnectionName()
509
    {
510
        return $this->get('wmi.connections.default');
511
    }
512
513
    public function setDefaultConnection(string $name)
514
    {
515
        if (!$this->getConnection($name)) {
516
            throw InvalidConnectionException::new($name);
517
        }
518
519
        return $this->set('wmi.connections.default', $name);
520
    }
521
522
    public function registerProvider($alias, $instance = null)
523
    {
524
        if (!is_object($instance)) {
525
            $instance = $this->makeProvider($alias);
526
        }
527
528
        $this->bind("{$alias}", $instance);
529
530
        return $instance;
531
    }
532
533
    protected function boot(array $config)
534
    {
535
        if (static::$test_mode) {
536
            $this->merge(include(__DIR__ . '/../config/testing.php'));
537
        }
538
539
        if (!empty($config)) {
540
            $this->merge($config);
541
        }
542
543
        $this->merge(include(__DIR__ . '/../config/bootstrap.php'))
544
            ->set('bindings', [])
545
            ->registerProviders()
546
            ->bootConnections();
547
    }
548
549
    protected function registerProviders()
550
    {
551
        array_map(function ($alias) {
552
            $this->registerProvider($alias);
553
        }, array_keys($this->providers()));
554
555
        return $this;
556
    }
557
558
    protected function bootConnections()
559
    {
560
        Arr::set($this->container, 'wmi.connections.servers', new Connections($this));
561
562
        return $this;
563
    }
564
}
565