From 1398d744924fdeeccc0f450fc647368f1c8fe0d4 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Sun, 15 Sep 2019 10:14:41 -0400
Subject: [PATCH 01/33] Prep for drivers

---
 src/WmiScripting/Connections/Connection.php        |  1 +
 src/WmiScripting/config/connections.php            |  3 +++
 .../Support/Connections/ConnectionTest.php         | 14 ++++++++++++++
 3 files changed, 18 insertions(+)
 create mode 100644 tests/WmiScripting/Support/Connections/ConnectionTest.php

diff --git a/src/WmiScripting/Connections/Connection.php b/src/WmiScripting/Connections/Connection.php
index 616a122..45138fa 100644
--- a/src/WmiScripting/Connections/Connection.php
+++ b/src/WmiScripting/Connections/Connection.php
@@ -4,4 +4,5 @@
 
 interface Connection
 {
+    public static function instantiate(... $constructor_vars): self;
 }
diff --git a/src/WmiScripting/config/connections.php b/src/WmiScripting/config/connections.php
index 136479c..7f45a4c 100644
--- a/src/WmiScripting/config/connections.php
+++ b/src/WmiScripting/config/connections.php
@@ -1,10 +1,13 @@
 <?php
 
+use PhpWinTools\WmiScripting\Connections\ComConnection;
+
 return [
     'default' => 'local',
 
     'servers' => [
         'local' => [
+            'type'            => ComConnection::class,
             'server'          => '.',
             'namespace'       => 'Root\CIMv2',
             'user'            => null,
diff --git a/tests/WmiScripting/Support/Connections/ConnectionTest.php b/tests/WmiScripting/Support/Connections/ConnectionTest.php
new file mode 100644
index 0000000..fc6dbde
--- /dev/null
+++ b/tests/WmiScripting/Support/Connections/ConnectionTest.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Connections;
+
+use Tests\TestCase;
+
+class ConnectionTest extends TestCase
+{
+    /** @test */
+    public function it_can_instantiate_a_com_connection_from_configuration()
+    {
+        $this->assertTrue(true);
+    }
+}

From 8489225cf83b76f8c63eca204aefc08ce6398a62 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Sun, 15 Sep 2019 11:15:35 -0400
Subject: [PATCH 02/33] WIP

---
 src/WmiScripting/Connections/Connection.php        |  2 +-
 src/WmiScripting/Connections/ConnectionManager.php | 13 +++++++++++++
 2 files changed, 14 insertions(+), 1 deletion(-)
 create mode 100644 src/WmiScripting/Connections/ConnectionManager.php

diff --git a/src/WmiScripting/Connections/Connection.php b/src/WmiScripting/Connections/Connection.php
index 45138fa..586d8d2 100644
--- a/src/WmiScripting/Connections/Connection.php
+++ b/src/WmiScripting/Connections/Connection.php
@@ -4,5 +4,5 @@
 
 interface Connection
 {
-    public static function instantiate(... $constructor_vars): self;
+    //public static function instantiate(... $constructor_vars): self;
 }
diff --git a/src/WmiScripting/Connections/ConnectionManager.php b/src/WmiScripting/Connections/ConnectionManager.php
new file mode 100644
index 0000000..8cae972
--- /dev/null
+++ b/src/WmiScripting/Connections/ConnectionManager.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Connections;
+
+class ConnectionManager
+{
+    protected $connection;
+
+    public function __construct(Connection $connection)
+    {
+        $this->connection = $connection;
+    }
+}

From 04ec8891cc99067dc1a352e47826bf6837a771e9 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Sun, 15 Sep 2019 19:10:38 -0400
Subject: [PATCH 03/33] Work on bus

---
 src/WmiScripting/Support/Bus/CommandBus.php   |   9 ++
 .../Support/Bus/CommandHandler.php            |  10 ++
 .../Support/Bus/Commands/Command.php          |   7 +
 src/WmiScripting/Support/Bus/Context.php      |  34 ++++
 src/WmiScripting/Support/Bus/CoreBus.php      | 149 ++++++++++++++++++
 .../Support/Bus/Events/BusStartedHandling.php |   7 +
 .../Support/Bus/Events/CommandBusEvent.php    |  42 +++++
 src/WmiScripting/Support/Bus/Events/Event.php |  20 +++
 .../Support/Bus/Events/EventHandler.php       |  59 +++++++
 .../Support/Bus/Events/FiredEvent.php         |  54 +++++++
 .../Support/Bus/Events/Listener.php           |   8 +
 .../Bus/Middleware/CommandMiddleware.php      |  22 +++
 .../Middleware/FailureCommandMiddleware.php   |   7 +
 .../Bus/Middleware/FiredMiddleware.php        |  39 +++++
 .../Bus/Middleware/MiddlewareProcessor.php    |  44 ++++++
 .../Bus/Middleware/PostCommandMiddleware.php  |   7 +
 .../Bus/Middleware/PreCommandMiddleware.php   |   7 +
 .../Middleware/SuccessCommandMiddleware.php   |   8 +
 .../WmiScripting/Support/Bus/CoreBusTest.php  |  51 ++++++
 19 files changed, 584 insertions(+)
 create mode 100644 src/WmiScripting/Support/Bus/CommandBus.php
 create mode 100644 src/WmiScripting/Support/Bus/CommandHandler.php
 create mode 100644 src/WmiScripting/Support/Bus/Commands/Command.php
 create mode 100644 src/WmiScripting/Support/Bus/Context.php
 create mode 100644 src/WmiScripting/Support/Bus/CoreBus.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/BusStartedHandling.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/Event.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/EventHandler.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/FiredEvent.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/Listener.php
 create mode 100644 src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
 create mode 100644 src/WmiScripting/Support/Bus/Middleware/FailureCommandMiddleware.php
 create mode 100644 src/WmiScripting/Support/Bus/Middleware/FiredMiddleware.php
 create mode 100644 src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
 create mode 100644 src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php
 create mode 100644 src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php
 create mode 100644 src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php
 create mode 100644 tests/WmiScripting/Support/Bus/CoreBusTest.php

diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
new file mode 100644
index 0000000..3617844
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus;
+
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+abstract class CommandBus
+{
+}
diff --git a/src/WmiScripting/Support/Bus/CommandHandler.php b/src/WmiScripting/Support/Bus/CommandHandler.php
new file mode 100644
index 0000000..ce7742e
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/CommandHandler.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus;
+
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+abstract class CommandHandler
+{
+    abstract public function handle(Command $command);
+}
diff --git a/src/WmiScripting/Support/Bus/Commands/Command.php b/src/WmiScripting/Support/Bus/Commands/Command.php
new file mode 100644
index 0000000..b1f4e7e
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Commands/Command.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Commands;
+
+abstract class Command
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Context.php b/src/WmiScripting/Support/Bus/Context.php
new file mode 100644
index 0000000..29e70f1
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Context.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus;
+
+class Context
+{
+    protected $context = [];
+
+    public function add($key, $value)
+    {
+        $this->context[$key] = $value;
+
+        return $this;
+    }
+
+    public function get($key, $default = null)
+    {
+        if ($this->doesNotExist($key)) {
+            return $default;
+        }
+
+        return $this->context[$key] ?? $default;
+    }
+
+    public function exists($key)
+    {
+        return array_key_exists($key, $this->context);
+    }
+
+    public function doesNotExist($key)
+    {
+        return $this->exists($key) === false;
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/CoreBus.php b/src/WmiScripting/Support/Bus/CoreBus.php
new file mode 100644
index 0000000..ee63413
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/CoreBus.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus;
+
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareProcessor;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
+
+class CoreBus extends CommandBus
+{
+    /** @var self|null */
+    protected static $instance = null;
+
+    protected $config;
+
+    /** @var array|CommandHandler[][] */
+    protected $bus;
+
+    protected $pre_middleware = [];
+
+    protected $post_middleware = [];
+
+    protected $success_middleware = [];
+
+    protected $failure_middleware = [];
+
+    protected $parent = null;
+
+    protected $children = [];
+
+    public function __construct(Config $config = null, CommandBus $parent = null)
+    {
+        $this->config = $config ?? Config::instance();
+        $this->parent = $parent;
+
+        static::$instance = $this;
+    }
+
+    public static function instance(Config $config = null)
+    {
+        return static::$instance ?? new static($config ?? Config::instance());
+    }
+
+    public function getConfig()
+    {
+        return $this->config;
+    }
+
+    public function assignHandler($command, CommandHandler $commandHandler, CommandBus $bus = null)
+    {
+        if ($this->doesNotExtendCommand($command)) {
+            throw new InvalidArgumentException("{$command} must extend " . Command::class);
+        }
+
+        if (($bus = $bus ?? $this) !== $this) {
+            $this->children[get_class($bus)] = $bus;
+            return $bus->assignHandler($command, $commandHandler);
+        }
+
+        $this->bus[$command] = $commandHandler;
+
+        return $this;
+    }
+
+    public function registerMiddleware($middleware, $command, CommandBus $bus = null)
+    {
+        if ($this->doesNotExtendCommand($command)) {
+            throw new InvalidArgumentException("{$command} must extend " . Command::class);
+        }
+
+        if ($this->doesNotExtendMiddleware($middleware)) {
+            throw new InvalidArgumentException("{$middleware} must extend " . CommandMiddleware::class);
+        }
+
+        if (($bus = $bus ?? $this) !== $this) {
+            $this->children[get_class($bus)] = $bus;
+            return $bus->registerMiddleware($command, $middleware);
+        }
+
+        $parents = class_parents($middleware);
+
+        if (array_key_exists(PreCommandMiddleware::class, $parents)) {
+            $this->pre_middleware[$command] = $middleware;
+
+            return $this;
+        }
+
+        if (array_key_exists(SuccessCommandMiddleware::class, $parents)) {
+            $this->success_middleware[$command] = $middleware;
+
+            return $this;
+        }
+
+        if (array_key_exists(FailureCommandMiddleware::class, $parents)) {
+            $this->failure_middleware[$command] = $middleware;
+
+            return $this;
+        }
+
+        $this->post_middleware[$command] = $middleware;
+
+        return $this;
+    }
+
+    public function handle(Command $command)
+    {
+        // Pre-Middleware -> Post-Middleware (Any, Success, Failure)
+        // Core: Middleware -> (Inner Bus -> Inner Middleware) -> CommandHandler - (Inner Bus -> CommandHandler) -> Middleware
+
+        MiddlewareProcessor::process($this->pre_middleware, $command);
+
+        return $this;
+    }
+
+    protected function process($stack, $method, Command $command)
+    {
+
+    }
+
+    protected function extendsCommand($command)
+    {
+        return $this->classExtends($command, Command::class);
+    }
+
+    protected function doesNotExtendCommand($command)
+    {
+        return $this->extendsCommand($command) === false;
+    }
+
+    protected function extendsMiddleware($middleware)
+    {
+        return $this->classExtends($middleware, CommandMiddleware::class);
+    }
+
+    protected function doesNotExtendMiddleware($middleware)
+    {
+        return $this->extendsMiddleware($middleware) === false;
+    }
+
+    protected function classExtends($class, $parent)
+    {
+        return array_key_exists($parent, class_parents($class));
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/BusStartedHandling.php b/src/WmiScripting/Support/Bus/Events/BusStartedHandling.php
new file mode 100644
index 0000000..c1db084
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/BusStartedHandling.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class BusStartedHandling extends CommandBusEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
new file mode 100644
index 0000000..f8b0a46
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Support\Bus\Context;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
+
+abstract class CommandBusEvent extends Event
+{
+    protected $bus;
+
+    protected $command;
+
+    protected $middleware;
+
+    public function __construct(CommandBus $bus, Command $command = null, CommandMiddleware $middleware = null)
+    {
+        $this->bus = $bus;
+        $this->command = $command;
+        $this->middleware = $middleware;
+
+        parent::__construct((new Context())
+                ->add('bus', $this->bus)->add('command', $this->command)->add('middleware', $this->middleware));
+    }
+
+    public function bus()
+    {
+        return $this->bus;
+    }
+
+    public function command()
+    {
+        return $this->command;
+    }
+
+    public function middleware()
+    {
+        return $this->middleware;
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/Event.php b/src/WmiScripting/Support/Bus/Events/Event.php
new file mode 100644
index 0000000..f717619
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/Event.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+use PhpWinTools\WmiScripting\Support\Bus\Context;
+
+abstract class Event
+{
+    protected $context;
+
+    public function __construct(Context $context)
+    {
+        $this->context = $context;
+    }
+
+    public function context(): Context
+    {
+        return $this->context;
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/EventHandler.php b/src/WmiScripting/Support/Bus/Events/EventHandler.php
new file mode 100644
index 0000000..241fa60
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/EventHandler.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+use PhpWinTools\WmiScripting\Configuration\Config;
+
+class EventHandler
+{
+    /** @var self|null */
+    protected static $instance;
+
+    protected $config;
+
+    public function __construct(Config $config = null)
+    {
+        $this->config = $config ?? Config::instance();
+
+        static::$instance = $this;
+    }
+
+    public static function instance(Config $config = null)
+    {
+        if (static::$instance && $config) {
+            static::$instance->config = $config;
+
+            return static::$instance;
+        }
+
+        return new static($config);
+    }
+
+    /** @var array|Listener[][] */
+    protected $listeners = [];
+
+    protected $fired = [];
+
+    public function subscribe($event, Listener $listener)
+    {
+        $this->listeners[$event][] = $listener;
+
+        return $this;
+    }
+
+    public function fire(Event $event): void
+    {
+        $listeners = $this->listeners[$event_name = get_class($event)] ?? [];
+
+        $this->fired[][$event] = new FiredEvent($event, $event->context(), $listeners);
+
+        array_map(function (Listener $listener) use ($event) {
+            $listener->react($event);
+        }, $listeners);
+    }
+
+    public function fired(string $event_name = null)
+    {
+        return $event_name ? $this->fired[$event_name] ?? [] : $this->fired;
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/FiredEvent.php b/src/WmiScripting/Support/Bus/Events/FiredEvent.php
new file mode 100644
index 0000000..4720d5a
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/FiredEvent.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+use PhpWinTools\WmiScripting\Support\Bus\Context;
+
+class FiredEvent
+{
+    protected $event;
+
+    protected $context;
+
+    /** @var array|Listener[] */
+    protected $listeners = [];
+
+    public function __construct(Event $event, Context $context, array $listeners = [])
+    {
+        $this->event = $event;
+        $this->context = $context;
+        $this->setListeners($listeners);
+    }
+
+    public function event()
+    {
+        return $this->event;
+    }
+
+    public function eventType()
+    {
+        return get_class($this->event());
+    }
+
+    public function context()
+    {
+        return $this->context;
+    }
+
+    public function listeners()
+    {
+        return $this->listeners;
+    }
+
+    public function listenerTypes()
+    {
+        return array_keys($this->listeners());
+    }
+
+    protected function setListeners(array $listeners = [])
+    {
+        array_map(function (Listener $listener) {
+            $this->listeners[get_class($listener)] = $listener;
+        }, $listeners);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/Listener.php b/src/WmiScripting/Support/Bus/Events/Listener.php
new file mode 100644
index 0000000..6ce0823
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/Listener.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+abstract class Listener
+{
+    abstract public function react(Event $event);
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
new file mode 100644
index 0000000..5852050
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+use Closure;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+class CommandMiddleware
+{
+    protected $originalCommand;
+
+    protected $command;
+
+    protected $results;
+
+    protected $next;
+
+    public function resolve(Command $command, Closure $next)
+    {
+        return $next($command);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/FailureCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/FailureCommandMiddleware.php
new file mode 100644
index 0000000..604c1ff
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/FailureCommandMiddleware.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+class FailureCommandMiddleware extends PostCommandMiddleware
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/FiredMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/FiredMiddleware.php
new file mode 100644
index 0000000..25261e8
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/FiredMiddleware.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+class FiredMiddleware
+{
+    protected $middleware;
+
+    protected $command;
+
+    public function __construct(CommandMiddleware $middleware, Command $command)
+    {
+        $this->command = $command;
+
+        $this->middleware = $middleware;
+    }
+
+    public function command()
+    {
+        return $this->command;
+    }
+
+    public function commandType()
+    {
+        return get_class($this->command());
+    }
+
+    public function middleware()
+    {
+        return $this->middleware;
+    }
+
+    public function middlewareType()
+    {
+        return $this->middleware();
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
new file mode 100644
index 0000000..282e757
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+class MiddlewareProcessor
+{
+    protected static $instance;
+
+    protected $fired = [];
+
+    public function __construct()
+    {
+        static::$instance = $this;
+    }
+
+    public static function instance()
+    {
+        return static::$instance ?? static::$instance = new static();
+    }
+
+    /**
+     * @param array|CommandMiddleware[] $middleware_array
+     * @param Command                   $command
+     *
+     * @return Command
+     */
+    public static function process(array $middleware_array, Command $command)
+    {
+        $processor = static::instance();
+
+        array_map(function ($middleware) use (&$command, $processor) {
+            /** @var string|CommandMiddleware $middleware */
+            $middleware = new $middleware();
+            $processor->fired[get_class($middleware)][] = new FiredMiddleware($middleware, $command);
+            $command = $middleware->resolve($command, function () {
+                //
+            });
+        }, $middleware_array);
+
+        return $command;
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php
new file mode 100644
index 0000000..1da7b6c
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+class PostCommandMiddleware extends CommandMiddleware
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php
new file mode 100644
index 0000000..8e4338b
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+class PreCommandMiddleware extends CommandMiddleware
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php
new file mode 100644
index 0000000..1b4371a
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+class SuccessCommandMiddleware extends PostCommandMiddleware
+{
+
+}
diff --git a/tests/WmiScripting/Support/Bus/CoreBusTest.php b/tests/WmiScripting/Support/Bus/CoreBusTest.php
new file mode 100644
index 0000000..4ae1a3a
--- /dev/null
+++ b/tests/WmiScripting/Support/Bus/CoreBusTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Bus;
+
+use Closure;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Support\Bus\CoreBus;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareProcessor;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use Tests\TestCase;
+
+class CoreBusTest extends TestCase
+{
+    /** @test */
+    public function it_can_instantiate()
+    {
+        $config = new Config();
+        $bus = new CoreBus();
+
+        $this->assertInstanceOf(CoreBus::class, $bus);
+        $this->assertSame($config, $bus->getConfig());
+        $this->assertSame(CoreBus::instance(), $bus);
+    }
+
+    /** @test */
+    public function it_runs_middleware_before_command()
+    {
+        $command = new class extends Command {};
+
+        $middleware = new class extends PreCommandMiddleware {
+            public static $has_run = false;
+
+            public $has_run_instance;
+
+            public function resolve(Command $command, Closure $next)
+            {
+                self::$has_run = true;
+
+                $this->has_run_instance = self::$has_run;
+                return $next($command);
+            }
+        };
+
+        $bus = CoreBus::instance()->registerMiddleware(get_class($middleware), get_class($command));
+
+        dump($bus->handle($command));
+        dd(MiddlewareProcessor::instance());
+    }
+}

From 4994e864fcc1204b9e42351d4856150ee9a4a8f6 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Sun, 15 Sep 2019 20:04:03 -0400
Subject: [PATCH 04/33] WIP

---
 tests/WmiScripting/Support/Bus/CoreBusTest.php | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/tests/WmiScripting/Support/Bus/CoreBusTest.php b/tests/WmiScripting/Support/Bus/CoreBusTest.php
index 4ae1a3a..da29693 100644
--- a/tests/WmiScripting/Support/Bus/CoreBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CoreBusTest.php
@@ -30,15 +30,23 @@ public function it_runs_middleware_before_command()
         $command = new class extends Command {};
 
         $middleware = new class extends PreCommandMiddleware {
-            public static $has_run = false;
+            public static $instance;
 
-            public $has_run_instance;
+            public $has_run_instance = false;
 
-            public function resolve(Command $command, Closure $next)
+            public function __construct()
             {
-                self::$has_run = true;
+                self::$instance = $this;
+            }
 
-                $this->has_run_instance = self::$has_run;
+            public static function instance()
+            {
+                return self::$instance ?? self::$instance = new self();
+            }
+
+            public function resolve(Command $command, Closure $next)
+            {
+                $this->has_run_instance = true;
                 return $next($command);
             }
         };

From a1aba599a9381b86e60d1982bfefcc1209bb4e98 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Sun, 15 Sep 2019 20:15:45 -0400
Subject: [PATCH 05/33] WIP

---
 .../Support/Bus/Middleware/MiddlewareProcessor.php           | 5 +++++
 tests/WmiScripting/Support/Bus/CoreBusTest.php               | 5 ++---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
index 282e757..aed2f2b 100644
--- a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
@@ -41,4 +41,9 @@ public static function process(array $middleware_array, Command $command)
 
         return $command;
     }
+
+    public function fired()
+    {
+        return $this->fired;
+    }
 }
diff --git a/tests/WmiScripting/Support/Bus/CoreBusTest.php b/tests/WmiScripting/Support/Bus/CoreBusTest.php
index da29693..0911cc4 100644
--- a/tests/WmiScripting/Support/Bus/CoreBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CoreBusTest.php
@@ -52,8 +52,7 @@ public function resolve(Command $command, Closure $next)
         };
 
         $bus = CoreBus::instance()->registerMiddleware(get_class($middleware), get_class($command));
-
-        dump($bus->handle($command));
-        dd(MiddlewareProcessor::instance());
+        $bus->handle($command);
+        dd(MiddlewareProcessor::instance()->fired());
     }
 }

From 82ca74047328a682a39d36f45e46e76a7704b138 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Mon, 16 Sep 2019 12:03:46 -0400
Subject: [PATCH 06/33] WIP

---
 src/WmiScripting/Support/Bus/CommandBus.php   | 34 ++++++++++++-
 src/WmiScripting/Support/Bus/CoreBus.php      | 48 ++++---------------
 .../Bus/Middleware/CommandMiddleware.php      |  2 +-
 .../Bus/Middleware/MiddlewareProcessor.php    | 27 +++++++----
 .../WmiScripting/Support/Bus/CoreBusTest.php  | 33 ++++++-------
 5 files changed, 75 insertions(+), 69 deletions(-)

diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
index 3617844..1f6918d 100644
--- a/src/WmiScripting/Support/Bus/CommandBus.php
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -2,8 +2,40 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus;
 
-use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Configuration\Config;
 
 abstract class CommandBus
 {
+    /** @var self|null */
+    protected static $instance = null;
+
+    /** @var Config */
+    protected $config;
+
+    protected $parent = null;
+
+    protected $children = [];
+
+    public function __construct(Config $config = null, CommandBus $parent = null)
+    {
+        $this->config = $config ?? Config::instance();
+        $this->parent = $parent;
+
+        static::$instance = $this;
+    }
+
+    public static function instance(Config $config = null)
+    {
+        return static::$instance ?? new static($config ?? Config::instance());
+    }
+
+    public function getConfig()
+    {
+        return $this->config;
+    }
+
+    protected function classExtends($class, $parent)
+    {
+        return array_key_exists($parent, class_parents($class));
+    }
 }
diff --git a/src/WmiScripting/Support/Bus/CoreBus.php b/src/WmiScripting/Support/Bus/CoreBus.php
index ee63413..9a476ac 100644
--- a/src/WmiScripting/Support/Bus/CoreBus.php
+++ b/src/WmiScripting/Support/Bus/CoreBus.php
@@ -2,22 +2,16 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus;
 
-use PhpWinTools\WmiScripting\Configuration\Config;
-use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareProcessor;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
 
 class CoreBus extends CommandBus
 {
-    /** @var self|null */
-    protected static $instance = null;
-
-    protected $config;
-
     /** @var array|CommandHandler[][] */
     protected $bus;
 
@@ -29,28 +23,6 @@ class CoreBus extends CommandBus
 
     protected $failure_middleware = [];
 
-    protected $parent = null;
-
-    protected $children = [];
-
-    public function __construct(Config $config = null, CommandBus $parent = null)
-    {
-        $this->config = $config ?? Config::instance();
-        $this->parent = $parent;
-
-        static::$instance = $this;
-    }
-
-    public static function instance(Config $config = null)
-    {
-        return static::$instance ?? new static($config ?? Config::instance());
-    }
-
-    public function getConfig()
-    {
-        return $this->config;
-    }
-
     public function assignHandler($command, CommandHandler $commandHandler, CommandBus $bus = null)
     {
         if ($this->doesNotExtendCommand($command)) {
@@ -85,24 +57,24 @@ public function registerMiddleware($middleware, $command, CommandBus $bus = null
         $parents = class_parents($middleware);
 
         if (array_key_exists(PreCommandMiddleware::class, $parents)) {
-            $this->pre_middleware[$command] = $middleware;
+            $this->pre_middleware[$command][] = $middleware;
 
             return $this;
         }
 
         if (array_key_exists(SuccessCommandMiddleware::class, $parents)) {
-            $this->success_middleware[$command] = $middleware;
+            $this->success_middleware[$command][] = $middleware;
 
             return $this;
         }
 
         if (array_key_exists(FailureCommandMiddleware::class, $parents)) {
-            $this->failure_middleware[$command] = $middleware;
+            $this->failure_middleware[$command][] = $middleware;
 
             return $this;
         }
 
-        $this->post_middleware[$command] = $middleware;
+        $this->post_middleware[$command][] = $middleware;
 
         return $this;
     }
@@ -112,7 +84,8 @@ public function handle(Command $command)
         // Pre-Middleware -> Post-Middleware (Any, Success, Failure)
         // Core: Middleware -> (Inner Bus -> Inner Middleware) -> CommandHandler - (Inner Bus -> CommandHandler) -> Middleware
 
-        MiddlewareProcessor::process($this->pre_middleware, $command);
+        $result = MiddlewareProcessor::process($this->pre_middleware[get_class($command)], $command);
+        dump($result);
 
         return $this;
     }
@@ -141,9 +114,4 @@ protected function doesNotExtendMiddleware($middleware)
     {
         return $this->extendsMiddleware($middleware) === false;
     }
-
-    protected function classExtends($class, $parent)
-    {
-        return array_key_exists($parent, class_parents($class));
-    }
 }
diff --git a/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
index 5852050..f8c3d70 100644
--- a/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
+++ b/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
@@ -15,7 +15,7 @@ class CommandMiddleware
 
     protected $next;
 
-    public function resolve(Command $command, Closure $next)
+    public function handle(Command $command, Closure $next)
     {
         return $next($command);
     }
diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
index aed2f2b..9ae0574 100644
--- a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
@@ -2,6 +2,7 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
 
+use Closure;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
 
 class MiddlewareProcessor
@@ -23,23 +24,31 @@ public static function instance()
     /**
      * @param array|CommandMiddleware[] $middleware_array
      * @param Command                   $command
+     * @param Closure|null              $core
      *
      * @return Command
      */
-    public static function process(array $middleware_array, Command $command)
+    public static function process(array $middleware_array, Command $command, Closure $core = null)
     {
         $processor = static::instance();
+        $core = $core ?? function (Command $command) {
+            dump('core');
+            return $command;
+        };
 
-        array_map(function ($middleware) use (&$command, $processor) {
-            /** @var string|CommandMiddleware $middleware */
+        $middleware_array = array_reverse($middleware_array);
+
+        $stack = array_reduce($middleware_array, function ($next, $middleware) use ($command, $processor) {
+            /** @var CommandMiddleware $middleware */
             $middleware = new $middleware();
-            $processor->fired[get_class($middleware)][] = new FiredMiddleware($middleware, $command);
-            $command = $middleware->resolve($command, function () {
-                //
-            });
-        }, $middleware_array);
 
-        return $command;
+            return function ($command) use ($next, $middleware, $processor) {
+                $processor->fired[] = new FiredMiddleware($middleware, $command);
+                return $middleware->handle($command, $next);
+            };
+        }, $core);
+
+        return $stack($command);
     }
 
     public function fired()
diff --git a/tests/WmiScripting/Support/Bus/CoreBusTest.php b/tests/WmiScripting/Support/Bus/CoreBusTest.php
index 0911cc4..0c196a1 100644
--- a/tests/WmiScripting/Support/Bus/CoreBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CoreBusTest.php
@@ -29,30 +29,27 @@ public function it_runs_middleware_before_command()
     {
         $command = new class extends Command {};
 
-        $middleware = new class extends PreCommandMiddleware {
-            public static $instance;
-
-            public $has_run_instance = false;
-
-            public function __construct()
+        $first_middleware = new class extends PreCommandMiddleware {
+            public $name = 'first';
+            public function handle(Command $command, Closure $next)
             {
-                self::$instance = $this;
+                dump($this->name);
+                return parent::handle($command, $next); // TODO: Change the autogenerated stub
             }
-
-            public static function instance()
-            {
-                return self::$instance ?? self::$instance = new self();
-            }
-
-            public function resolve(Command $command, Closure $next)
+        };
+        $second_middleware = new class extends PreCommandMiddleware {
+            public $name = 'second';
+            public function handle(Command $command, Closure $next)
             {
-                $this->has_run_instance = true;
-                return $next($command);
+                dump($this->name);
+                return parent::handle($command, $next); // TODO: Change the autogenerated stub
             }
         };
 
-        $bus = CoreBus::instance()->registerMiddleware(get_class($middleware), get_class($command));
-        $bus->handle($command);
+        $bus = CoreBus::instance()
+            ->registerMiddleware(get_class($first_middleware), get_class($command))
+            ->registerMiddleware(get_class($second_middleware), get_class($command));
+        dump($bus->handle($command));
         dd(MiddlewareProcessor::instance()->fired());
     }
 }

From dd9e8285487656a618edf0a1a874b5040d5605b5 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Mon, 16 Sep 2019 14:34:39 -0400
Subject: [PATCH 07/33] WIP

---
 src/WmiScripting/Support/Bus/CommandBus.php   | 125 +++++++++++++++++-
 .../Support/Bus/CommandHandler.php            |   7 +
 src/WmiScripting/Support/Bus/CoreBus.php      | 117 ----------------
 ...andBusEvent.php => CommandBusPreEvent.php} |   4 +-
 ...ling.php => CommandBusStartedHandling.php} |   2 +-
 .../Bus/Middleware/CommandMiddleware.php      |  13 +-
 .../Bus/Middleware/MiddlewareProcessor.php    |  41 ++----
 .../Bus/Middleware/PostCommandMiddleware.php  |   6 +
 .../Bus/Middleware/PreCommandMiddleware.php   |   6 +
 .../Middleware/SuccessCommandMiddleware.php   |   1 -
 .../Support/Bus/CommandBusTest.php            | 106 +++++++++++++++
 .../WmiScripting/Support/Bus/CoreBusTest.php  |  55 --------
 12 files changed, 258 insertions(+), 225 deletions(-)
 delete mode 100644 src/WmiScripting/Support/Bus/CoreBus.php
 rename src/WmiScripting/Support/Bus/Events/{CommandBusEvent.php => CommandBusPreEvent.php} (95%)
 rename src/WmiScripting/Support/Bus/Events/{BusStartedHandling.php => CommandBusStartedHandling.php} (53%)
 create mode 100644 tests/WmiScripting/Support/Bus/CommandBusTest.php
 delete mode 100644 tests/WmiScripting/Support/Bus/CoreBusTest.php

diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
index 1f6918d..5a42974 100644
--- a/src/WmiScripting/Support/Bus/CommandBus.php
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -2,9 +2,18 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus;
 
+use Closure;
 use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareProcessor;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
 
-abstract class CommandBus
+class CommandBus
 {
     /** @var self|null */
     protected static $instance = null;
@@ -12,14 +21,24 @@ abstract class CommandBus
     /** @var Config */
     protected $config;
 
-    protected $parent = null;
+    /** @var array|CommandHandler[] */
+    protected $handlers = [];
 
-    protected $children = [];
+    /** @var array|PreCommandMiddleware[] */
+    protected $pre_middleware = [];
 
-    public function __construct(Config $config = null, CommandBus $parent = null)
+    /** @var array|PostCommandMiddleware[] */
+    protected $post_middleware = [];
+
+    /** @var array|SuccessCommandMiddleware[] */
+    protected $success_middleware = [];
+
+    /** @var array|FailureCommandMiddleware[] */
+    protected $failure_middleware = [];
+
+    public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
-        $this->parent = $parent;
 
         static::$instance = $this;
     }
@@ -34,6 +53,102 @@ public function getConfig()
         return $this->config;
     }
 
+    public function assignHandler($command, CommandHandler $commandHandler)
+    {
+        if ($this->doesNotExtendCommand($command)) {
+            throw new InvalidArgumentException("{$command} must extend " . Command::class);
+        }
+
+        $this->handlers[$command] = $commandHandler;
+
+        return $this;
+    }
+
+    public function registerMiddleware($middleware, $command)
+    {
+        if ($this->doesNotExtendCommand($command)) {
+            throw new InvalidArgumentException("{$command} must extend " . Command::class);
+        }
+
+        if ($this->doesNotExtendMiddleware($middleware)) {
+            throw new InvalidArgumentException("{$middleware} must extend " . CommandMiddleware::class);
+        }
+
+        $parents = class_parents($middleware);
+
+        if (array_key_exists(PreCommandMiddleware::class, $parents)) {
+            $this->pre_middleware[$command][] = $middleware;
+
+            return $this;
+        }
+
+        if (array_key_exists(SuccessCommandMiddleware::class, $parents)) {
+            $this->success_middleware[$command][] = $middleware;
+
+            return $this;
+        }
+
+        if (array_key_exists(FailureCommandMiddleware::class, $parents)) {
+            $this->failure_middleware[$command][] = $middleware;
+
+            return $this;
+        }
+
+        $this->post_middleware[$command][] = $middleware;
+
+        return $this;
+    }
+
+    public function handle(Command $command, Closure $core_callback = null)
+    {
+        $command_name = get_class($command);
+
+        $pre = $this->processMiddleware($this->pre_middleware[$command_name] ?? [], $command, $core_callback);
+        $command_failure = true;
+        $result = null;
+
+        if (array_key_exists($command_name, $this->handlers)) {
+            $handler = $this->handlers[$command_name];
+            $result = $handler->handle($pre);
+            $command_failure = $handler->getFailureResult() === $result;
+        }
+
+        $result = $this->processMiddleware($this->post_middleware[$command_name] ?? [], $result, $core_callback);
+
+        if ($command_failure) {
+            $result = $this->processMiddleware($this->failure_middleware[$command_name] ?? [], $result, $core_callback);
+        } else {
+            $result = $this->processMiddleware($this->success_middleware[$command_name] ?? [], $result, $core_callback);
+        }
+
+        return $result;
+    }
+
+    protected function extendsCommand($command)
+    {
+        return $this->classExtends($command, Command::class);
+    }
+
+    protected function doesNotExtendCommand($command)
+    {
+        return $this->extendsCommand($command) === false;
+    }
+
+    protected function extendsMiddleware($middleware)
+    {
+        return $this->classExtends($middleware, CommandMiddleware::class);
+    }
+
+    protected function doesNotExtendMiddleware($middleware)
+    {
+        return $this->extendsMiddleware($middleware) === false;
+    }
+
+    protected function processMiddleware(array $stack, $subject, Closure $core_callback = null)
+    {
+        return MiddlewareProcessor::process($stack, $subject, $core_callback);
+    }
+
     protected function classExtends($class, $parent)
     {
         return array_key_exists($parent, class_parents($class));
diff --git a/src/WmiScripting/Support/Bus/CommandHandler.php b/src/WmiScripting/Support/Bus/CommandHandler.php
index ce7742e..e619428 100644
--- a/src/WmiScripting/Support/Bus/CommandHandler.php
+++ b/src/WmiScripting/Support/Bus/CommandHandler.php
@@ -6,5 +6,12 @@
 
 abstract class CommandHandler
 {
+    protected $failure_result = null;
+
     abstract public function handle(Command $command);
+
+    public function getFailureResult()
+    {
+        return $this->failure_result;
+    }
 }
diff --git a/src/WmiScripting/Support/Bus/CoreBus.php b/src/WmiScripting/Support/Bus/CoreBus.php
deleted file mode 100644
index 9a476ac..0000000
--- a/src/WmiScripting/Support/Bus/CoreBus.php
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-
-namespace PhpWinTools\WmiScripting\Support\Bus;
-
-use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
-use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareProcessor;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
-
-class CoreBus extends CommandBus
-{
-    /** @var array|CommandHandler[][] */
-    protected $bus;
-
-    protected $pre_middleware = [];
-
-    protected $post_middleware = [];
-
-    protected $success_middleware = [];
-
-    protected $failure_middleware = [];
-
-    public function assignHandler($command, CommandHandler $commandHandler, CommandBus $bus = null)
-    {
-        if ($this->doesNotExtendCommand($command)) {
-            throw new InvalidArgumentException("{$command} must extend " . Command::class);
-        }
-
-        if (($bus = $bus ?? $this) !== $this) {
-            $this->children[get_class($bus)] = $bus;
-            return $bus->assignHandler($command, $commandHandler);
-        }
-
-        $this->bus[$command] = $commandHandler;
-
-        return $this;
-    }
-
-    public function registerMiddleware($middleware, $command, CommandBus $bus = null)
-    {
-        if ($this->doesNotExtendCommand($command)) {
-            throw new InvalidArgumentException("{$command} must extend " . Command::class);
-        }
-
-        if ($this->doesNotExtendMiddleware($middleware)) {
-            throw new InvalidArgumentException("{$middleware} must extend " . CommandMiddleware::class);
-        }
-
-        if (($bus = $bus ?? $this) !== $this) {
-            $this->children[get_class($bus)] = $bus;
-            return $bus->registerMiddleware($command, $middleware);
-        }
-
-        $parents = class_parents($middleware);
-
-        if (array_key_exists(PreCommandMiddleware::class, $parents)) {
-            $this->pre_middleware[$command][] = $middleware;
-
-            return $this;
-        }
-
-        if (array_key_exists(SuccessCommandMiddleware::class, $parents)) {
-            $this->success_middleware[$command][] = $middleware;
-
-            return $this;
-        }
-
-        if (array_key_exists(FailureCommandMiddleware::class, $parents)) {
-            $this->failure_middleware[$command][] = $middleware;
-
-            return $this;
-        }
-
-        $this->post_middleware[$command][] = $middleware;
-
-        return $this;
-    }
-
-    public function handle(Command $command)
-    {
-        // Pre-Middleware -> Post-Middleware (Any, Success, Failure)
-        // Core: Middleware -> (Inner Bus -> Inner Middleware) -> CommandHandler - (Inner Bus -> CommandHandler) -> Middleware
-
-        $result = MiddlewareProcessor::process($this->pre_middleware[get_class($command)], $command);
-        dump($result);
-
-        return $this;
-    }
-
-    protected function process($stack, $method, Command $command)
-    {
-
-    }
-
-    protected function extendsCommand($command)
-    {
-        return $this->classExtends($command, Command::class);
-    }
-
-    protected function doesNotExtendCommand($command)
-    {
-        return $this->extendsCommand($command) === false;
-    }
-
-    protected function extendsMiddleware($middleware)
-    {
-        return $this->classExtends($middleware, CommandMiddleware::class);
-    }
-
-    protected function doesNotExtendMiddleware($middleware)
-    {
-        return $this->extendsMiddleware($middleware) === false;
-    }
-}
diff --git a/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php b/src/WmiScripting/Support/Bus/Events/CommandBusPreEvent.php
similarity index 95%
rename from src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
rename to src/WmiScripting/Support/Bus/Events/CommandBusPreEvent.php
index f8b0a46..42a6ed4 100644
--- a/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
+++ b/src/WmiScripting/Support/Bus/Events/CommandBusPreEvent.php
@@ -2,12 +2,12 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Events;
 
+use PhpWinTools\WmiScripting\Support\Bus\Context;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
-use PhpWinTools\WmiScripting\Support\Bus\Context;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
 
-abstract class CommandBusEvent extends Event
+abstract class CommandBusPreEvent extends Event
 {
     protected $bus;
 
diff --git a/src/WmiScripting/Support/Bus/Events/BusStartedHandling.php b/src/WmiScripting/Support/Bus/Events/CommandBusStartedHandling.php
similarity index 53%
rename from src/WmiScripting/Support/Bus/Events/BusStartedHandling.php
rename to src/WmiScripting/Support/Bus/Events/CommandBusStartedHandling.php
index c1db084..2fb30a5 100644
--- a/src/WmiScripting/Support/Bus/Events/BusStartedHandling.php
+++ b/src/WmiScripting/Support/Bus/Events/CommandBusStartedHandling.php
@@ -2,6 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Events;
 
-class BusStartedHandling extends CommandBusEvent
+class CommandBusStartedHandling extends CommandBusPreEvent
 {
 }
diff --git a/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
index f8c3d70..16c6e2c 100644
--- a/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
+++ b/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
@@ -3,20 +3,11 @@
 namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
 
 use Closure;
-use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
 
 class CommandMiddleware
 {
-    protected $originalCommand;
-
-    protected $command;
-
-    protected $results;
-
-    protected $next;
-
-    public function handle(Command $command, Closure $next)
+    public function handle($subject, Closure $next)
     {
-        return $next($command);
+        return $next($subject);
     }
 }
diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
index 9ae0574..38b5b18 100644
--- a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
@@ -3,56 +3,31 @@
 namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
 
 use Closure;
-use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
 
 class MiddlewareProcessor
 {
-    protected static $instance;
-
-    protected $fired = [];
-
-    public function __construct()
-    {
-        static::$instance = $this;
-    }
-
-    public static function instance()
-    {
-        return static::$instance ?? static::$instance = new static();
-    }
-
     /**
      * @param array|CommandMiddleware[] $middleware_array
-     * @param Command                   $command
+     * @param mixed                     $subject
      * @param Closure|null              $core
      *
-     * @return Command
+     * @return mixed
      */
-    public static function process(array $middleware_array, Command $command, Closure $core = null)
+    public static function process(array $middleware_array, $subject, Closure $core = null)
     {
-        $processor = static::instance();
-        $core = $core ?? function (Command $command) {
-            dump('core');
-            return $command;
+        $core = $core ?? function ($subject) {
+            return $subject;
         };
 
-        $middleware_array = array_reverse($middleware_array);
-
-        $stack = array_reduce($middleware_array, function ($next, $middleware) use ($command, $processor) {
+        $stack = array_reduce(array_reverse($middleware_array), function ($next, $middleware) use ($subject) {
             /** @var CommandMiddleware $middleware */
             $middleware = new $middleware();
 
-            return function ($command) use ($next, $middleware, $processor) {
-                $processor->fired[] = new FiredMiddleware($middleware, $command);
+            return function ($command) use ($next, $middleware) {
                 return $middleware->handle($command, $next);
             };
         }, $core);
 
-        return $stack($command);
-    }
-
-    public function fired()
-    {
-        return $this->fired;
+        return $stack($subject);
     }
 }
diff --git a/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php
index 1da7b6c..ac8f642 100644
--- a/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php
+++ b/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php
@@ -2,6 +2,12 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
 
+use Closure;
+
 class PostCommandMiddleware extends CommandMiddleware
 {
+    public function handle($handled_result, Closure $next)
+    {
+        return parent::handle($handled_result, $next);
+    }
 }
diff --git a/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php
index 8e4338b..b732dc5 100644
--- a/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php
+++ b/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php
@@ -2,6 +2,12 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
 
+use Closure;
+
 class PreCommandMiddleware extends CommandMiddleware
 {
+    public function handle($command, Closure $next)
+    {
+        return parent::handle($command, $next);
+    }
 }
diff --git a/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php
index 1b4371a..49ea6df 100644
--- a/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php
+++ b/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php
@@ -4,5 +4,4 @@
 
 class SuccessCommandMiddleware extends PostCommandMiddleware
 {
-
 }
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
new file mode 100644
index 0000000..ba37aa6
--- /dev/null
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Bus;
+
+use Closure;
+use Tests\TestCase;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Bus\CommandHandler;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+
+class CommandBusTest extends TestCase
+{
+    /** @test */
+    public function it_can_instantiate()
+    {
+        $config = new Config();
+        $bus = new CommandBus();
+
+        $this->assertInstanceOf(CommandBus::class, $bus);
+        $this->assertSame($config, $bus->getConfig());
+        $this->assertSame(CommandBus::instance(), $bus);
+    }
+
+    /** @test */
+    public function it_sends_commands_to_assigned_handler()
+    {
+        $command = new class extends Command {
+            public $processed = [];
+        };
+
+        $handler = new class extends CommandHandler {
+            public function handle(Command $command)
+            {
+                $command->processed[] = 'command';
+                return $command;
+            }
+        };
+
+        $bus = (new CommandBus())->assignHandler(get_class($command), $handler);
+
+        $this->assertEmpty($command->processed);
+        $this->assertSame(['command'], $bus->handle($command)->processed);
+    }
+
+    /** @test */
+    public function it_runs_pre_middleware_before_the_handler()
+    {
+        $command = new class extends Command {
+            public $processed = [];
+        };
+
+        $handler = new class extends CommandHandler {
+            public function handle(Command $command)
+            {
+                $command->processed[] = 'command';
+                return $command;
+            }
+        };
+
+        $first_middleware = new class extends PreCommandMiddleware {
+            public $name = 'first';
+            public function handle($subject, Closure $next)
+            {
+                $subject->processed[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $second_middleware = new class extends PreCommandMiddleware {
+            public $name = 'second';
+            public function handle($subject, Closure $next)
+            {
+                $subject->processed[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $after_middleware = new class extends PreCommandMiddleware {
+            public $name = 'first after';
+            public function handle($subject, Closure $next)
+            {
+                $result = parent::handle($subject, $next);
+                $subject->processed[] = $this->name;
+                return $result;
+            }
+        };
+
+
+        $bus = (new CommandBus())
+            ->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($after_middleware), get_class($command))
+            ->registerMiddleware(get_class($first_middleware), get_class($command))
+            ->registerMiddleware(get_class($second_middleware), get_class($command));
+
+        $expected = [
+            'first',
+            'second',
+            'first after',
+            'command',
+        ];
+
+        $this->assertEquals($expected, $bus->handle($command)->processed);
+    }
+}
diff --git a/tests/WmiScripting/Support/Bus/CoreBusTest.php b/tests/WmiScripting/Support/Bus/CoreBusTest.php
deleted file mode 100644
index 0c196a1..0000000
--- a/tests/WmiScripting/Support/Bus/CoreBusTest.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-namespace Tests\WmiScripting\Support\Bus;
-
-use Closure;
-use PhpWinTools\WmiScripting\Configuration\Config;
-use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
-use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
-use PhpWinTools\WmiScripting\Support\Bus\CoreBus;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareProcessor;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
-use Tests\TestCase;
-
-class CoreBusTest extends TestCase
-{
-    /** @test */
-    public function it_can_instantiate()
-    {
-        $config = new Config();
-        $bus = new CoreBus();
-
-        $this->assertInstanceOf(CoreBus::class, $bus);
-        $this->assertSame($config, $bus->getConfig());
-        $this->assertSame(CoreBus::instance(), $bus);
-    }
-
-    /** @test */
-    public function it_runs_middleware_before_command()
-    {
-        $command = new class extends Command {};
-
-        $first_middleware = new class extends PreCommandMiddleware {
-            public $name = 'first';
-            public function handle(Command $command, Closure $next)
-            {
-                dump($this->name);
-                return parent::handle($command, $next); // TODO: Change the autogenerated stub
-            }
-        };
-        $second_middleware = new class extends PreCommandMiddleware {
-            public $name = 'second';
-            public function handle(Command $command, Closure $next)
-            {
-                dump($this->name);
-                return parent::handle($command, $next); // TODO: Change the autogenerated stub
-            }
-        };
-
-        $bus = CoreBus::instance()
-            ->registerMiddleware(get_class($first_middleware), get_class($command))
-            ->registerMiddleware(get_class($second_middleware), get_class($command));
-        dump($bus->handle($command));
-        dd(MiddlewareProcessor::instance()->fired());
-    }
-}

From 809557b0f63c5945b8108a6dfbb6f1c7b383736b Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Mon, 16 Sep 2019 16:15:24 -0400
Subject: [PATCH 08/33] WIP

---
 src/WmiScripting/Support/Bus/CommandBus.php   |  65 +++++-----
 .../Bus/Middleware/MiddlewareProcessor.php    |  33 -----
 .../Bus/Middleware/MiddlewareStack.php        |  65 ++++++++++
 .../Support/Bus/CommandBusTest.php            | 113 ++++++++++++++++++
 4 files changed, 212 insertions(+), 64 deletions(-)
 delete mode 100644 src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
 create mode 100644 src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php

diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
index 5a42974..27a74f0 100644
--- a/src/WmiScripting/Support/Bus/CommandBus.php
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -6,10 +6,9 @@
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
 use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareStack;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareProcessor;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
 
@@ -24,21 +23,22 @@ class CommandBus
     /** @var array|CommandHandler[] */
     protected $handlers = [];
 
-    /** @var array|PreCommandMiddleware[] */
-    protected $pre_middleware = [];
+    /** @var MiddlewareStack */
+    protected $preMiddleware;
 
-    /** @var array|PostCommandMiddleware[] */
-    protected $post_middleware = [];
+    /** @var MiddlewareStack */
+    protected $postMiddleware = [];
 
-    /** @var array|SuccessCommandMiddleware[] */
-    protected $success_middleware = [];
+    /** @var MiddlewareStack */
+    protected $failureMiddleware = [];
 
-    /** @var array|FailureCommandMiddleware[] */
-    protected $failure_middleware = [];
+    /** @var MiddlewareStack */
+    protected $successMiddleware = [];
 
     public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
+        $this->boot();
 
         static::$instance = $this;
     }
@@ -55,7 +55,7 @@ public function getConfig()
 
     public function assignHandler($command, CommandHandler $commandHandler)
     {
-        if ($this->doesNotExtendCommand($command)) {
+        if ($this->invalidCommand($command)) {
             throw new InvalidArgumentException("{$command} must extend " . Command::class);
         }
 
@@ -66,35 +66,35 @@ public function assignHandler($command, CommandHandler $commandHandler)
 
     public function registerMiddleware($middleware, $command)
     {
-        if ($this->doesNotExtendCommand($command)) {
+        if ($this->invalidCommand($command)) {
             throw new InvalidArgumentException("{$command} must extend " . Command::class);
         }
 
-        if ($this->doesNotExtendMiddleware($middleware)) {
+        if ($this->invalidMiddleware($middleware)) {
             throw new InvalidArgumentException("{$middleware} must extend " . CommandMiddleware::class);
         }
 
         $parents = class_parents($middleware);
 
         if (array_key_exists(PreCommandMiddleware::class, $parents)) {
-            $this->pre_middleware[$command][] = $middleware;
+            $this->preMiddleware->add($middleware, $command);
 
             return $this;
         }
 
         if (array_key_exists(SuccessCommandMiddleware::class, $parents)) {
-            $this->success_middleware[$command][] = $middleware;
+            $this->successMiddleware->add($middleware, $command);
 
             return $this;
         }
 
         if (array_key_exists(FailureCommandMiddleware::class, $parents)) {
-            $this->failure_middleware[$command][] = $middleware;
+            $this->failureMiddleware->add($middleware, $command);
 
             return $this;
         }
 
-        $this->post_middleware[$command][] = $middleware;
+        $this->postMiddleware->add($middleware, $command);
 
         return $this;
     }
@@ -103,7 +103,7 @@ public function handle(Command $command, Closure $core_callback = null)
     {
         $command_name = get_class($command);
 
-        $pre = $this->processMiddleware($this->pre_middleware[$command_name] ?? [], $command, $core_callback);
+        $pre = $this->preMiddleware->process($command, $core_callback);
         $command_failure = true;
         $result = null;
 
@@ -113,40 +113,43 @@ public function handle(Command $command, Closure $core_callback = null)
             $command_failure = $handler->getFailureResult() === $result;
         }
 
-        $result = $this->processMiddleware($this->post_middleware[$command_name] ?? [], $result, $core_callback);
+        $result = $this->postMiddleware->process($command, $core_callback);
 
         if ($command_failure) {
-            $result = $this->processMiddleware($this->failure_middleware[$command_name] ?? [], $result, $core_callback);
+            $result = $this->failureMiddleware->process($result, $core_callback);
         } else {
-            $result = $this->processMiddleware($this->success_middleware[$command_name] ?? [], $result, $core_callback);
+            $result = $this->successMiddleware->process($result, $core_callback);
         }
 
         return $result;
     }
 
-    protected function extendsCommand($command)
+    protected function boot()
     {
-        return $this->classExtends($command, Command::class);
+        $this->preMiddleware = new MiddlewareStack();
+        $this->postMiddleware = new MiddlewareStack();
+        $this->failureMiddleware = new MiddlewareStack();
+        $this->successMiddleware = new MiddlewareStack();
     }
 
-    protected function doesNotExtendCommand($command)
+    protected function validCommand($command)
     {
-        return $this->extendsCommand($command) === false;
+        return $command = '*' || $this->classExtends($command, Command::class);
     }
 
-    protected function extendsMiddleware($middleware)
+    protected function invalidCommand($command)
     {
-        return $this->classExtends($middleware, CommandMiddleware::class);
+        return $this->validCommand($command) === false;
     }
 
-    protected function doesNotExtendMiddleware($middleware)
+    protected function validMiddleware($middleware)
     {
-        return $this->extendsMiddleware($middleware) === false;
+        return $middleware instanceof Closure || $this->classExtends($middleware, CommandMiddleware::class);
     }
 
-    protected function processMiddleware(array $stack, $subject, Closure $core_callback = null)
+    protected function invalidMiddleware($middleware)
     {
-        return MiddlewareProcessor::process($stack, $subject, $core_callback);
+        return $this->validMiddleware($middleware) === false;
     }
 
     protected function classExtends($class, $parent)
diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
deleted file mode 100644
index 38b5b18..0000000
--- a/src/WmiScripting/Support/Bus/Middleware/MiddlewareProcessor.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
-
-use Closure;
-
-class MiddlewareProcessor
-{
-    /**
-     * @param array|CommandMiddleware[] $middleware_array
-     * @param mixed                     $subject
-     * @param Closure|null              $core
-     *
-     * @return mixed
-     */
-    public static function process(array $middleware_array, $subject, Closure $core = null)
-    {
-        $core = $core ?? function ($subject) {
-            return $subject;
-        };
-
-        $stack = array_reduce(array_reverse($middleware_array), function ($next, $middleware) use ($subject) {
-            /** @var CommandMiddleware $middleware */
-            $middleware = new $middleware();
-
-            return function ($command) use ($next, $middleware) {
-                return $middleware->handle($command, $next);
-            };
-        }, $core);
-
-        return $stack($subject);
-    }
-}
diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
new file mode 100644
index 0000000..9b1d4d0
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+use Closure;
+
+class MiddlewareStack
+{
+    protected $stack = [];
+
+    public function add($middleware, string $subject)
+    {
+        $this->stack[$subject][] = $middleware;
+
+        return $this;
+    }
+
+    public function all()
+    {
+        return $this->prep($this->stack);
+    }
+
+    public function get($subject)
+    {
+        $subject = is_object($subject) ? get_class($subject) : $subject;
+
+        $wildcard = $this->prep($this->stack['*'] ?? []);
+
+        return array_merge($this->prep($this->stack[$subject] ?? []), $wildcard);
+    }
+
+    public function has($subject)
+    {
+        return empty($this->get($subject));
+    }
+
+    public function process($subject, Closure $core = null)
+    {
+        $core = $core ?? function ($subject) {
+                return $subject;
+        };
+
+        $stack = array_reduce($this->get($subject), function ($next, $middleware) use ($subject) {
+            if ($middleware instanceof Closure) {
+                return function ($command) use ($next, $middleware) {
+                    return $middleware($command, $next);
+                };
+            }
+
+            /** @var CommandMiddleware $middleware */
+            $middleware = new $middleware();
+
+            return function ($command) use ($next, $middleware) {
+                return $middleware->handle($command, $next);
+            };
+        }, $core);
+
+        return $stack($subject);
+    }
+
+    protected function prep(array $stack)
+    {
+        return array_reverse($stack);
+    }
+}
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
index ba37aa6..bcf1496 100644
--- a/tests/WmiScripting/Support/Bus/CommandBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -6,8 +6,12 @@
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Bus\Events\Event;
 use PhpWinTools\WmiScripting\Support\Bus\CommandHandler;
+use PhpWinTools\WmiScripting\Support\Bus\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Support\Bus\Events\EventHandler;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusPreEvent;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
 
 class CommandBusTest extends TestCase
@@ -103,4 +107,113 @@ public function handle($subject, Closure $next)
 
         $this->assertEquals($expected, $bus->handle($command)->processed);
     }
+
+    /** @test */
+    public function it_fires_wildcard_pre_middleware_first()
+    {
+        $command = new class extends Command {
+            public $processed = [];
+        };
+
+        $handler = new class extends CommandHandler {
+            public function handle(Command $command)
+            {
+                $command->processed[] = 'command';
+                return $command;
+            }
+        };
+
+
+
+        $middleware = new class extends PreCommandMiddleware {
+            public $name = 'middleware';
+            public function handle($subject, Closure $next)
+            {
+                $subject->processed[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $closure = function ($subject, Closure $next) {
+            $subject->processed[] = 'closure';
+            return $next($subject);
+        };
+
+        $bus = (new CommandBus())
+            ->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($middleware), get_class($command))
+            ->registerMiddleware($closure, get_class($command));
+
+        $expected = [
+            'middleware',
+            'closure',
+            'command',
+        ];
+
+        $this->assertSame(
+            $expected, $bus->handle($command)->processed, 'Failed to fire wildcard middleware before named middleware'
+        );
+    }
+
+    /** @test */
+    public function it_takes_a_closure_as_valid_middleware()
+    {
+
+    }
+
+    /** @test */
+    public function it_fires_an_event_before_any_pre_middleware()
+    {
+        $eventHandler = new EventHandler();
+
+        $listener = new class extends Listener {
+            public $reacted = false;
+
+            public function react(Event $event)
+            {
+                $this->reacted = true;
+            }
+        };
+
+        $eventHandler->subscribe(CommandBusPreEvent::class, $listener);
+
+        $command = new class extends Command {
+            public $processed = [];
+        };
+
+        $handler = new class extends CommandHandler {
+            public function handle(Command $command)
+            {
+                $command->processed[] = 'command';
+                return $command;
+            }
+        };
+
+        $first_middleware = new class extends PreCommandMiddleware {
+            public $name = '*';
+            public function handle($subject, Closure $next)
+            {
+                $subject->processed[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $second_middleware = new class extends PreCommandMiddleware {
+            public $name = 'named';
+            public function handle($subject, Closure $next)
+            {
+                $subject->processed[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $bus = (new CommandBus())
+            ->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($first_middleware), '*')
+            ->registerMiddleware(get_class($second_middleware), get_class($command));
+
+        $bus->handle($command);
+
+        $this->assertTrue($listener->reacted, 'Listener did not react to command event');
+    }
 }

From 38151426e5441f94ff65993353908cdca162a816 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 17 Sep 2019 10:54:48 -0400
Subject: [PATCH 09/33] Start shoring up event sytem

---
 src/WmiScripting/Configuration/Config.php     | 18 ++++++
 src/WmiScripting/Support/Bus/CommandBus.php   | 61 ++++++++++++++++++-
 ...andBusPreEvent.php => CommandBusEvent.php} | 16 ++---
 ...edHandling.php => CommandHandlerEnded.php} |  2 +-
 .../Bus/Events/CommandHandlerEvent.php        | 17 ++++++
 .../Bus/Events/CommandHandlerStarted.php      |  7 +++
 .../Support/Bus/Events/FiredEvent.php         | 54 ----------------
 .../Support/Bus/Events/PreMiddlewareEnded.php |  7 +++
 .../Bus/Events/PreMiddlewareStarted.php       |  7 +++
 .../Support/{Bus => }/Events/Event.php        | 12 +++-
 .../Support/{Bus => }/Events/EventHandler.php | 40 +++++++++---
 .../Support/Events/FiredEvent.php             | 39 ++++++++++++
 .../Support/Events/FiredEvents.php            | 44 +++++++++++++
 .../Support/{Bus => }/Events/Listener.php     |  2 +-
 src/WmiScripting/config/bootstrap.php         |  5 ++
 .../Support/Bus/CommandBusTest.php            | 47 +++++++-------
 16 files changed, 272 insertions(+), 106 deletions(-)
 rename src/WmiScripting/Support/Bus/Events/{CommandBusPreEvent.php => CommandBusEvent.php} (64%)
 rename src/WmiScripting/Support/Bus/Events/{CommandBusStartedHandling.php => CommandHandlerEnded.php} (53%)
 create mode 100644 src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/CommandHandlerStarted.php
 delete mode 100644 src/WmiScripting/Support/Bus/Events/FiredEvent.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php
 rename src/WmiScripting/Support/{Bus => }/Events/Event.php (54%)
 rename src/WmiScripting/Support/{Bus => }/Events/EventHandler.php (54%)
 create mode 100644 src/WmiScripting/Support/Events/FiredEvent.php
 create mode 100644 src/WmiScripting/Support/Events/FiredEvents.php
 rename src/WmiScripting/Support/{Bus => }/Events/Listener.php (60%)

diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index ffae3e4..00ec9fd 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -10,6 +10,7 @@
 use PhpWinTools\WmiScripting\Connections\ComConnection;
 use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
 use PhpWinTools\WmiScripting\Exceptions\UnresolvableClassException;
+use PhpWinTools\WmiScripting\Support\Events\EventHandler;
 
 class Config
 {
@@ -22,6 +23,8 @@ class Config
 
     protected $resolver;
 
+    protected $eventHandler;
+
     protected $resolve_stack = [];
 
     public function __construct(array $config = [], Resolver $resolver = null)
@@ -271,6 +274,14 @@ public function addComObject(string $abstract_class, $concrete_class)
         return $this;
     }
 
+    /**
+     * @return EventHandler|null
+     */
+    public function events()
+    {
+        return $this->eventHandler ?? $this->eventHandler = EventHandler::instance($this);
+    }
+
     /**
      * @return string
      */
@@ -394,6 +405,7 @@ protected function boot(array $config)
         $this->merge(include(__DIR__ . '/../config/bootstrap.php'));
 
         $this->bootConnections();
+        $this->bootEventSystem();
     }
 
     protected function merge(array $config)
@@ -407,4 +419,10 @@ protected function bootConnections()
     {
         Arr::set($this->config, 'wmi.connections.servers', new Connections($this));
     }
+
+    protected function bootEventSystem()
+    {
+        $handler = $this->get('events.handler');
+        $this->eventHandler = new $handler($this);
+    }
 }
diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
index 27a74f0..b80c053 100644
--- a/src/WmiScripting/Support/Bus/CommandBus.php
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -6,8 +6,15 @@
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
 use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusEvent;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Events\Event;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareStack;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
@@ -64,7 +71,7 @@ public function assignHandler($command, CommandHandler $commandHandler)
         return $this;
     }
 
-    public function registerMiddleware($middleware, $command)
+    public function registerMiddleware($middleware, $command, string $stack = PostCommandMiddleware::class)
     {
         if ($this->invalidCommand($command)) {
             throw new InvalidArgumentException("{$command} must extend " . Command::class);
@@ -76,6 +83,10 @@ public function registerMiddleware($middleware, $command)
 
         $parents = class_parents($middleware);
 
+        if ($middleware instanceof Closure) {
+            $parents = [$stack => $stack];
+        }
+
         if (array_key_exists(PreCommandMiddleware::class, $parents)) {
             $this->preMiddleware->add($middleware, $command);
 
@@ -103,14 +114,23 @@ public function handle(Command $command, Closure $core_callback = null)
     {
         $command_name = get_class($command);
 
+        $this->firePreMiddlewareStartEvent($command);
+
         $pre = $this->preMiddleware->process($command, $core_callback);
         $command_failure = true;
         $result = null;
 
+        $this->firePreMiddleEndEvent($command);
+
         if (array_key_exists($command_name, $this->handlers)) {
             $handler = $this->handlers[$command_name];
+
+            $this->fireCommandHandlerStartEvent($command, $handler);
+
             $result = $handler->handle($pre);
             $command_failure = $handler->getFailureResult() === $result;
+
+            $this->fireCommandHandlerEndEvent($command, $handler);
         }
 
         $result = $this->postMiddleware->process($command, $core_callback);
@@ -124,6 +144,45 @@ public function handle(Command $command, Closure $core_callback = null)
         return $result;
     }
 
+    protected function firePreMiddlewareStartEvent(Command $command)
+    {
+        $this->fire(new PreMiddlewareStarted($this, $command));
+
+        return $this;
+    }
+
+    protected function firePreMiddleEndEvent(Command $command)
+    {
+        $this->fire(new PreMiddlewareEnded($this, $command));
+
+        return $this;
+    }
+
+    protected function fireCommandHandlerStartEvent(Command $command, CommandHandler $handler)
+    {
+        $this->fire(new CommandHandlerStarted($this, $command, $handler));
+
+        return $this;
+    }
+
+    protected function fireCommandHandlerEndEvent(Command $command, CommandHandler $handler)
+    {
+        $this->fire(new CommandHandlerEnded($this, $command, $handler));
+
+        return $this;
+    }
+
+
+    protected function fire(Event $event)
+    {
+        $this->events()->fire($event);
+    }
+
+    protected function events()
+    {
+        return $this->getConfig()->events();
+    }
+
     protected function boot()
     {
         $this->preMiddleware = new MiddlewareStack();
diff --git a/src/WmiScripting/Support/Bus/Events/CommandBusPreEvent.php b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
similarity index 64%
rename from src/WmiScripting/Support/Bus/Events/CommandBusPreEvent.php
rename to src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
index 42a6ed4..3894945 100644
--- a/src/WmiScripting/Support/Bus/Events/CommandBusPreEvent.php
+++ b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
@@ -3,26 +3,23 @@
 namespace PhpWinTools\WmiScripting\Support\Bus\Events;
 
 use PhpWinTools\WmiScripting\Support\Bus\Context;
+use PhpWinTools\WmiScripting\Support\Events\Event;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
 
-abstract class CommandBusPreEvent extends Event
+abstract class CommandBusEvent extends Event
 {
     protected $bus;
 
     protected $command;
 
-    protected $middleware;
-
-    public function __construct(CommandBus $bus, Command $command = null, CommandMiddleware $middleware = null)
+    public function __construct(CommandBus $bus, Command $command = null)
     {
         $this->bus = $bus;
         $this->command = $command;
-        $this->middleware = $middleware;
 
         parent::__construct((new Context())
-                ->add('bus', $this->bus)->add('command', $this->command)->add('middleware', $this->middleware));
+                ->add('bus', $this->bus)->add('command', $this->command));
     }
 
     public function bus()
@@ -34,9 +31,4 @@ public function command()
     {
         return $this->command;
     }
-
-    public function middleware()
-    {
-        return $this->middleware;
-    }
 }
diff --git a/src/WmiScripting/Support/Bus/Events/CommandBusStartedHandling.php b/src/WmiScripting/Support/Bus/Events/CommandHandlerEnded.php
similarity index 53%
rename from src/WmiScripting/Support/Bus/Events/CommandBusStartedHandling.php
rename to src/WmiScripting/Support/Bus/Events/CommandHandlerEnded.php
index 2fb30a5..137740e 100644
--- a/src/WmiScripting/Support/Bus/Events/CommandBusStartedHandling.php
+++ b/src/WmiScripting/Support/Bus/Events/CommandHandlerEnded.php
@@ -2,6 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Events;
 
-class CommandBusStartedHandling extends CommandBusPreEvent
+class CommandHandlerEnded extends CommandHandlerEvent
 {
 }
diff --git a/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php b/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php
new file mode 100644
index 0000000..a993e3c
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Bus\CommandHandler;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+class CommandHandlerEvent extends CommandBusEvent
+{
+    public function __construct(CommandBus $bus, Command $command, CommandHandler $commandHandler)
+    {
+        parent::__construct($bus, $command);
+
+        $this->context->add('handler', $commandHandler);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/CommandHandlerStarted.php b/src/WmiScripting/Support/Bus/Events/CommandHandlerStarted.php
new file mode 100644
index 0000000..5bbbea9
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandHandlerStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class CommandHandlerStarted extends CommandHandlerEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/FiredEvent.php b/src/WmiScripting/Support/Bus/Events/FiredEvent.php
deleted file mode 100644
index 4720d5a..0000000
--- a/src/WmiScripting/Support/Bus/Events/FiredEvent.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-namespace PhpWinTools\WmiScripting\Support\Bus\Events;
-
-use PhpWinTools\WmiScripting\Support\Bus\Context;
-
-class FiredEvent
-{
-    protected $event;
-
-    protected $context;
-
-    /** @var array|Listener[] */
-    protected $listeners = [];
-
-    public function __construct(Event $event, Context $context, array $listeners = [])
-    {
-        $this->event = $event;
-        $this->context = $context;
-        $this->setListeners($listeners);
-    }
-
-    public function event()
-    {
-        return $this->event;
-    }
-
-    public function eventType()
-    {
-        return get_class($this->event());
-    }
-
-    public function context()
-    {
-        return $this->context;
-    }
-
-    public function listeners()
-    {
-        return $this->listeners;
-    }
-
-    public function listenerTypes()
-    {
-        return array_keys($this->listeners());
-    }
-
-    protected function setListeners(array $listeners = [])
-    {
-        array_map(function (Listener $listener) {
-            $this->listeners[get_class($listener)] = $listener;
-        }, $listeners);
-    }
-}
diff --git a/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php
new file mode 100644
index 0000000..82f22cb
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PreMiddlewareEnded extends CommandBusEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php
new file mode 100644
index 0000000..18063fc
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PreMiddlewareStarted extends CommandBusEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/Event.php b/src/WmiScripting/Support/Events/Event.php
similarity index 54%
rename from src/WmiScripting/Support/Bus/Events/Event.php
rename to src/WmiScripting/Support/Events/Event.php
index f717619..14b282d 100644
--- a/src/WmiScripting/Support/Bus/Events/Event.php
+++ b/src/WmiScripting/Support/Events/Event.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+namespace PhpWinTools\WmiScripting\Support\Events;
 
 use PhpWinTools\WmiScripting\Support\Bus\Context;
 
@@ -13,8 +13,18 @@ public function __construct(Context $context)
         $this->context = $context;
     }
 
+    public static function fire(Context $context)
+    {
+        return new static($context);
+    }
+
     public function context(): Context
     {
         return $this->context;
     }
+
+    public function ancestry()
+    {
+        return class_parents(get_called_class());
+    }
 }
diff --git a/src/WmiScripting/Support/Bus/Events/EventHandler.php b/src/WmiScripting/Support/Events/EventHandler.php
similarity index 54%
rename from src/WmiScripting/Support/Bus/Events/EventHandler.php
rename to src/WmiScripting/Support/Events/EventHandler.php
index 241fa60..994f67f 100644
--- a/src/WmiScripting/Support/Bus/Events/EventHandler.php
+++ b/src/WmiScripting/Support/Events/EventHandler.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+namespace PhpWinTools\WmiScripting\Support\Events;
 
 use PhpWinTools\WmiScripting\Configuration\Config;
 
@@ -9,11 +9,19 @@ class EventHandler
     /** @var self|null */
     protected static $instance;
 
+    /** @var Config */
     protected $config;
 
+    /** @var array|Listener[][] */
+    protected $listeners = [];
+
+    /** @var FiredEvents */
+    protected $fired;
+
     public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
+        $this->fired = new FiredEvents();
 
         static::$instance = $this;
     }
@@ -29,11 +37,6 @@ public static function instance(Config $config = null)
         return new static($config);
     }
 
-    /** @var array|Listener[][] */
-    protected $listeners = [];
-
-    protected $fired = [];
-
     public function subscribe($event, Listener $listener)
     {
         $this->listeners[$event][] = $listener;
@@ -43,17 +46,34 @@ public function subscribe($event, Listener $listener)
 
     public function fire(Event $event): void
     {
-        $listeners = $this->listeners[$event_name = get_class($event)] ?? [];
+        $listeners = array_merge(
+            $this->listeners[$event_name = get_class($event)] ?? [],
+            $this->fireFromAncestry($event)
+        );
 
-        $this->fired[][$event] = new FiredEvent($event, $event->context(), $listeners);
+        $this->fired->add($event, $listeners);
 
         array_map(function (Listener $listener) use ($event) {
             $listener->react($event);
         }, $listeners);
     }
 
-    public function fired(string $event_name = null)
+    public function history()
+    {
+        return $this->fired;
+    }
+
+    protected function fireFromAncestry(Event $event)
     {
-        return $event_name ? $this->fired[$event_name] ?? [] : $this->fired;
+        $ancestors = class_parents($event);
+        $listeners = [];
+
+        foreach ($ancestors as $ancestor) {
+            if (array_key_exists($ancestor, $this->listeners)) {
+                $listeners = array_merge($listeners, $this->listeners[$ancestor]);
+            }
+        }
+
+        return $listeners;
     }
 }
diff --git a/src/WmiScripting/Support/Events/FiredEvent.php b/src/WmiScripting/Support/Events/FiredEvent.php
new file mode 100644
index 0000000..5964954
--- /dev/null
+++ b/src/WmiScripting/Support/Events/FiredEvent.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+class FiredEvent
+{
+    protected $event;
+
+    /** @var array|Listener[] */
+    protected $listeners = [];
+
+    public function __construct(Event $event, array $listeners = [])
+    {
+        $this->event = $event;
+        $this->listeners = $listeners;
+    }
+
+    public function event()
+    {
+        return $this->event;
+    }
+
+    public function eventType()
+    {
+        return get_class($this->event());
+    }
+
+    public function listeners()
+    {
+        return $this->listeners;
+    }
+
+    public function listenerTypes()
+    {
+        return array_map(function (Listener $listener) {
+            return get_class($listener);
+        }, $this->listeners());
+    }
+}
diff --git a/src/WmiScripting/Support/Events/FiredEvents.php b/src/WmiScripting/Support/Events/FiredEvents.php
new file mode 100644
index 0000000..3d6591f
--- /dev/null
+++ b/src/WmiScripting/Support/Events/FiredEvents.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+use PhpWinTools\WmiScripting\Support\Bus\Context;
+
+class FiredEvents
+{
+    protected $events = [];
+
+    protected $event_cache = [];
+
+    protected $event_ancestry_cache = [];
+
+    protected $listener_cache = [];
+
+    public function add(Event $event, $listeners = [])
+    {
+        $this->events[][get_class($event)] = new FiredEvent($event, $listeners);
+
+        $this->event_cache[get_class($event)][] = count($this->events) - 1;
+
+        array_map(function ($parent) {
+            $this->event_ancestry_cache[$parent][] = count($this->events) - 1;
+        }, class_parents($event));
+
+        array_map(function (Listener $listener) {
+            $this->listener_cache[get_class($listener)][] = count($this->events) - 1;
+        }, $listeners);
+    }
+
+    public function happened(string $event)
+    {
+        return array_key_exists($event, $this->event_cache)
+            || array_key_exists($event, $this->event_ancestry_cache);
+    }
+
+    public function toStringArray()
+    {
+        return array_map(function ($event) {
+            return [key($event) => $event[key($event)]->listenerTypes()];
+        }, $this->events);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/Listener.php b/src/WmiScripting/Support/Events/Listener.php
similarity index 60%
rename from src/WmiScripting/Support/Bus/Events/Listener.php
rename to src/WmiScripting/Support/Events/Listener.php
index 6ce0823..407ce5a 100644
--- a/src/WmiScripting/Support/Bus/Events/Listener.php
+++ b/src/WmiScripting/Support/Events/Listener.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+namespace PhpWinTools\WmiScripting\Support\Events;
 
 abstract class Listener
 {
diff --git a/src/WmiScripting/config/bootstrap.php b/src/WmiScripting/config/bootstrap.php
index e1517c0..f6f6255 100644
--- a/src/WmiScripting/config/bootstrap.php
+++ b/src/WmiScripting/config/bootstrap.php
@@ -3,6 +3,7 @@
 use PhpWinTools\Support\COM\ComWrapper;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
+use PhpWinTools\WmiScripting\Support\Events\EventHandler;
 
 return [
     'com' => [
@@ -13,5 +14,9 @@
         VariantWrapper::class       => VariantWrapper::class,
     ],
 
+    'events' => [
+        'handler' => EventHandler::class,
+    ],
+
     'wmi' => include('wmi.php'),
 ];
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
index bcf1496..b46d454 100644
--- a/tests/WmiScripting/Support/Bus/CommandBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -5,13 +5,12 @@
 use Closure;
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
-use PhpWinTools\WmiScripting\Support\Bus\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Bus\CommandHandler;
-use PhpWinTools\WmiScripting\Support\Bus\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
-use PhpWinTools\WmiScripting\Support\Bus\Events\EventHandler;
-use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusPreEvent;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusEvent;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
 
 class CommandBusTest extends TestCase
@@ -142,7 +141,7 @@ public function handle($subject, Closure $next)
         $bus = (new CommandBus())
             ->assignHandler(get_class($command), $handler)
             ->registerMiddleware(get_class($middleware), get_class($command))
-            ->registerMiddleware($closure, get_class($command));
+            ->registerMiddleware($closure, get_class($command), PreCommandMiddleware::class);
 
         $expected = [
             'middleware',
@@ -155,28 +154,9 @@ public function handle($subject, Closure $next)
         );
     }
 
-    /** @test */
-    public function it_takes_a_closure_as_valid_middleware()
-    {
-
-    }
-
     /** @test */
     public function it_fires_an_event_before_any_pre_middleware()
     {
-        $eventHandler = new EventHandler();
-
-        $listener = new class extends Listener {
-            public $reacted = false;
-
-            public function react(Event $event)
-            {
-                $this->reacted = true;
-            }
-        };
-
-        $eventHandler->subscribe(CommandBusPreEvent::class, $listener);
-
         $command = new class extends Command {
             public $processed = [];
         };
@@ -207,13 +187,28 @@ public function handle($subject, Closure $next)
             }
         };
 
-        $bus = (new CommandBus())
+        $bus = (new CommandBus(Config::newInstance()))
             ->assignHandler(get_class($command), $handler)
             ->registerMiddleware(get_class($first_middleware), '*')
             ->registerMiddleware(get_class($second_middleware), get_class($command));
 
+        $listener = new class extends Listener {
+            public $reacted = false;
+
+            public function react(Event $event)
+            {
+                $this->reacted = true;
+            }
+        };
+
+        $bus->getConfig()->events()->subscribe(CommandBusEvent::class, $listener);
+
+        $this->assertFalse($listener->reacted);
+        $this->assertFalse($bus->getConfig()->events()->history()->happened(CommandBusEvent::class));
+
         $bus->handle($command);
 
-        $this->assertTrue($listener->reacted, 'Listener did not react to command event');
+        $this->assertTrue($listener->reacted);
+        $this->assertTrue($bus->getConfig()->events()->history()->happened(CommandBusEvent::class));
     }
 }

From 378782a54b3406c39bf1ee8781e6931aed0988c1 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 17 Sep 2019 10:56:33 -0400
Subject: [PATCH 10/33] Fix some use statements

---
 src/WmiScripting/Support/Bus/CommandBus.php     | 11 +++++------
 src/WmiScripting/Support/Events/FiredEvents.php |  2 --
 2 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
index b80c053..c096da2 100644
--- a/src/WmiScripting/Support/Bus/CommandBus.php
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -3,19 +3,18 @@
 namespace PhpWinTools\WmiScripting\Support\Bus;
 
 use Closure;
+use PhpWinTools\WmiScripting\Support\Events\Event;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
 use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
-use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerEnded;
-use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerStarted;
 use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareEnded;
-use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusEvent;
-use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
-use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerEnded;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareStack;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerStarted;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
 
diff --git a/src/WmiScripting/Support/Events/FiredEvents.php b/src/WmiScripting/Support/Events/FiredEvents.php
index 3d6591f..36f67e3 100644
--- a/src/WmiScripting/Support/Events/FiredEvents.php
+++ b/src/WmiScripting/Support/Events/FiredEvents.php
@@ -2,8 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Events;
 
-use PhpWinTools\WmiScripting\Support\Bus\Context;
-
 class FiredEvents
 {
     protected $events = [];

From 15c73f6c1de8204f8471d9cef8ad73d5ad2ddf73 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 17 Sep 2019 11:49:31 -0400
Subject: [PATCH 11/33] Allow event tracking to be optional

---
 src/WmiScripting/Configuration/Config.php     | 19 ++++++++++++++++++
 .../Support/Events/EventHandler.php           |  2 +-
 .../Support/Events/FiredEvents.php            | 20 +++++++++++++++++++
 src/WmiScripting/config/bootstrap.php         |  1 +
 .../Support/Bus/CommandBusTest.php            |  3 ++-
 5 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index 00ec9fd..e78b06b 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -282,6 +282,25 @@ public function events()
         return $this->eventHandler ?? $this->eventHandler = EventHandler::instance($this);
     }
 
+    public function shouldTrackEvents()
+    {
+        return $this->get('events.track', false);
+    }
+
+    public function trackEvents()
+    {
+        $this->set('events.track', true);
+
+        return $this;
+    }
+
+    public function doNotTrackEvents()
+    {
+        $this->set('events.track', false);
+
+        return $this;
+    }
+
     /**
      * @return string
      */
diff --git a/src/WmiScripting/Support/Events/EventHandler.php b/src/WmiScripting/Support/Events/EventHandler.php
index 994f67f..4176f1b 100644
--- a/src/WmiScripting/Support/Events/EventHandler.php
+++ b/src/WmiScripting/Support/Events/EventHandler.php
@@ -21,7 +21,7 @@ class EventHandler
     public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
-        $this->fired = new FiredEvents();
+        $this->fired = new FiredEvents($config);
 
         static::$instance = $this;
     }
diff --git a/src/WmiScripting/Support/Events/FiredEvents.php b/src/WmiScripting/Support/Events/FiredEvents.php
index 36f67e3..ad85f35 100644
--- a/src/WmiScripting/Support/Events/FiredEvents.php
+++ b/src/WmiScripting/Support/Events/FiredEvents.php
@@ -2,6 +2,8 @@
 
 namespace PhpWinTools\WmiScripting\Support\Events;
 
+use PhpWinTools\WmiScripting\Configuration\Config;
+
 class FiredEvents
 {
     protected $events = [];
@@ -12,8 +14,19 @@ class FiredEvents
 
     protected $listener_cache = [];
 
+    protected $config;
+
+    public function __construct(Config $config = null)
+    {
+        $this->config = $config ?? Config::instance();
+    }
+
     public function add(Event $event, $listeners = [])
     {
+        if (!$this->track()) {
+            return $this;
+        }
+
         $this->events[][get_class($event)] = new FiredEvent($event, $listeners);
 
         $this->event_cache[get_class($event)][] = count($this->events) - 1;
@@ -25,6 +38,8 @@ public function add(Event $event, $listeners = [])
         array_map(function (Listener $listener) {
             $this->listener_cache[get_class($listener)][] = count($this->events) - 1;
         }, $listeners);
+        
+        return $this;
     }
 
     public function happened(string $event)
@@ -39,4 +54,9 @@ public function toStringArray()
             return [key($event) => $event[key($event)]->listenerTypes()];
         }, $this->events);
     }
+
+    protected function track()
+    {
+        return $this->config->shouldTrackEvents();
+    }
 }
diff --git a/src/WmiScripting/config/bootstrap.php b/src/WmiScripting/config/bootstrap.php
index f6f6255..61114c4 100644
--- a/src/WmiScripting/config/bootstrap.php
+++ b/src/WmiScripting/config/bootstrap.php
@@ -16,6 +16,7 @@
 
     'events' => [
         'handler' => EventHandler::class,
+        'track'   => false,
     ],
 
     'wmi' => include('wmi.php'),
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
index b46d454..363e97e 100644
--- a/tests/WmiScripting/Support/Bus/CommandBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -201,7 +201,8 @@ public function react(Event $event)
             }
         };
 
-        $bus->getConfig()->events()->subscribe(CommandBusEvent::class, $listener);
+        Config::instance()->trackEvents();
+        Config::instance()->events()->subscribe(CommandBusEvent::class, $listener);
 
         $this->assertFalse($listener->reacted);
         $this->assertFalse($bus->getConfig()->events()->history()->happened(CommandBusEvent::class));

From 9ecd3661fa0ba5293ecfbc5c9a0679b4ffd6669a Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 17 Sep 2019 11:51:23 -0400
Subject: [PATCH 12/33] Pass configuration down to event tracker

---
 src/WmiScripting/Support/Events/EventHandler.php | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/WmiScripting/Support/Events/EventHandler.php b/src/WmiScripting/Support/Events/EventHandler.php
index 4176f1b..f006ee9 100644
--- a/src/WmiScripting/Support/Events/EventHandler.php
+++ b/src/WmiScripting/Support/Events/EventHandler.php
@@ -20,8 +20,7 @@ class EventHandler
 
     public function __construct(Config $config = null)
     {
-        $this->config = $config ?? Config::instance();
-        $this->fired = new FiredEvents($config);
+        $this->fired = new FiredEvents($this->config = $config ?? Config::instance());
 
         static::$instance = $this;
     }

From 279c9a32cf08bee1f9be97b5054409a3f45b5d6a Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 17 Sep 2019 12:02:58 -0400
Subject: [PATCH 13/33] WIP

---
 .../Support/Events/EventHandler.php           |  4 ++--
 .../Support/Bus/CommandBusTest.php            | 24 +++++++++++++------
 2 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/WmiScripting/Support/Events/EventHandler.php b/src/WmiScripting/Support/Events/EventHandler.php
index f006ee9..25e9fec 100644
--- a/src/WmiScripting/Support/Events/EventHandler.php
+++ b/src/WmiScripting/Support/Events/EventHandler.php
@@ -47,7 +47,7 @@ public function fire(Event $event): void
     {
         $listeners = array_merge(
             $this->listeners[$event_name = get_class($event)] ?? [],
-            $this->fireFromAncestry($event)
+            $this->getAncestorListeners($event)
         );
 
         $this->fired->add($event, $listeners);
@@ -62,7 +62,7 @@ public function history()
         return $this->fired;
     }
 
-    protected function fireFromAncestry(Event $event)
+    protected function getAncestorListeners(Event $event)
     {
         $ancestors = class_parents($event);
         $listeners = [];
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
index 363e97e..db1421b 100644
--- a/tests/WmiScripting/Support/Bus/CommandBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -3,6 +3,7 @@
 namespace Tests\WmiScripting\Support\Bus;
 
 use Closure;
+use PhpWinTools\WmiScripting\Support\Events\EventHandler;
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Events\Event;
@@ -15,6 +16,16 @@
 
 class CommandBusTest extends TestCase
 {
+    /** @var Config */
+    protected $config;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->config = Config::newInstance();
+    }
+
     /** @test */
     public function it_can_instantiate()
     {
@@ -122,8 +133,6 @@ public function handle(Command $command)
             }
         };
 
-
-
         $middleware = new class extends PreCommandMiddleware {
             public $name = 'middleware';
             public function handle($subject, Closure $next)
@@ -187,7 +196,7 @@ public function handle($subject, Closure $next)
             }
         };
 
-        $bus = (new CommandBus(Config::newInstance()))
+        $bus = (new CommandBus($this->config))
             ->assignHandler(get_class($command), $handler)
             ->registerMiddleware(get_class($first_middleware), '*')
             ->registerMiddleware(get_class($second_middleware), get_class($command));
@@ -201,15 +210,16 @@ public function react(Event $event)
             }
         };
 
-        Config::instance()->trackEvents();
-        Config::instance()->events()->subscribe(CommandBusEvent::class, $listener);
+        $this->config->trackEvents();
+        $events = $this->config->events();
+        $events->subscribe(CommandBusEvent::class, $listener);
 
         $this->assertFalse($listener->reacted);
-        $this->assertFalse($bus->getConfig()->events()->history()->happened(CommandBusEvent::class));
+        $this->assertFalse($events->history()->happened(CommandBusEvent::class));
 
         $bus->handle($command);
 
         $this->assertTrue($listener->reacted);
-        $this->assertTrue($bus->getConfig()->events()->history()->happened(CommandBusEvent::class));
+        $this->assertTrue($events->history()->happened(CommandBusEvent::class));
     }
 }

From 5cc0d43c45842d7ab1ed770f353d8a38e1c03355 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 17 Sep 2019 12:32:49 -0400
Subject: [PATCH 14/33] WIP

---
 .../Bus/Events/CommandMiddlewareEvent.php     |  7 ++++
 .../Support/Bus/Events/PreMiddlewareEnded.php |  2 +-
 .../Bus/Events/PreMiddlewareStarted.php       |  2 +-
 .../Bus/Middleware/FiredMiddleware.php        | 39 -------------------
 .../Support/Bus/CommandBusTest.php            | 35 ++++-------------
 5 files changed, 17 insertions(+), 68 deletions(-)
 create mode 100644 src/WmiScripting/Support/Bus/Events/CommandMiddlewareEvent.php
 delete mode 100644 src/WmiScripting/Support/Bus/Middleware/FiredMiddleware.php

diff --git a/src/WmiScripting/Support/Bus/Events/CommandMiddlewareEvent.php b/src/WmiScripting/Support/Bus/Events/CommandMiddlewareEvent.php
new file mode 100644
index 0000000..415682c
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandMiddlewareEvent.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class CommandMiddlewareEvent extends CommandBusEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php
index 82f22cb..e121044 100644
--- a/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php
+++ b/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php
@@ -2,6 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Events;
 
-class PreMiddlewareEnded extends CommandBusEvent
+class PreMiddlewareEnded extends CommandMiddlewareEvent
 {
 }
diff --git a/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php
index 18063fc..42371f6 100644
--- a/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php
+++ b/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php
@@ -2,6 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Events;
 
-class PreMiddlewareStarted extends CommandBusEvent
+class PreMiddlewareStarted extends CommandMiddlewareEvent
 {
 }
diff --git a/src/WmiScripting/Support/Bus/Middleware/FiredMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/FiredMiddleware.php
deleted file mode 100644
index 25261e8..0000000
--- a/src/WmiScripting/Support/Bus/Middleware/FiredMiddleware.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
-
-use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
-
-class FiredMiddleware
-{
-    protected $middleware;
-
-    protected $command;
-
-    public function __construct(CommandMiddleware $middleware, Command $command)
-    {
-        $this->command = $command;
-
-        $this->middleware = $middleware;
-    }
-
-    public function command()
-    {
-        return $this->command;
-    }
-
-    public function commandType()
-    {
-        return get_class($this->command());
-    }
-
-    public function middleware()
-    {
-        return $this->middleware;
-    }
-
-    public function middlewareType()
-    {
-        return $this->middleware();
-    }
-}
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
index db1421b..94cc20b 100644
--- a/tests/WmiScripting/Support/Bus/CommandBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -3,6 +3,7 @@
 namespace Tests\WmiScripting\Support\Bus;
 
 use Closure;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
 use PhpWinTools\WmiScripting\Support\Events\EventHandler;
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
@@ -158,48 +159,26 @@ public function handle($subject, Closure $next)
             'command',
         ];
 
-        $this->assertSame(
-            $expected, $bus->handle($command)->processed, 'Failed to fire wildcard middleware before named middleware'
-        );
+        $this->assertSame($expected, $bus->handle($command)->processed);
     }
 
     /** @test */
     public function it_fires_an_event_before_any_pre_middleware()
     {
-        $command = new class extends Command {
-            public $processed = [];
-        };
+        $command = new class extends Command {};
 
         $handler = new class extends CommandHandler {
             public function handle(Command $command)
             {
-                $command->processed[] = 'command';
                 return $command;
             }
         };
 
-        $first_middleware = new class extends PreCommandMiddleware {
-            public $name = '*';
-            public function handle($subject, Closure $next)
-            {
-                $subject->processed[] = $this->name;
-                return parent::handle($subject, $next);
-            }
-        };
-
-        $second_middleware = new class extends PreCommandMiddleware {
-            public $name = 'named';
-            public function handle($subject, Closure $next)
-            {
-                $subject->processed[] = $this->name;
-                return parent::handle($subject, $next);
-            }
-        };
+        $middleware = new class extends PreCommandMiddleware {};
 
         $bus = (new CommandBus($this->config))
             ->assignHandler(get_class($command), $handler)
-            ->registerMiddleware(get_class($first_middleware), '*')
-            ->registerMiddleware(get_class($second_middleware), get_class($command));
+            ->registerMiddleware(get_class($middleware), get_class($command));
 
         $listener = new class extends Listener {
             public $reacted = false;
@@ -211,8 +190,9 @@ public function react(Event $event)
         };
 
         $this->config->trackEvents();
+        /** TODO: All singletons need to register themselves with the Config */
         $events = $this->config->events();
-        $events->subscribe(CommandBusEvent::class, $listener);
+        $events->subscribe(PreMiddlewareStarted::class, $listener);
 
         $this->assertFalse($listener->reacted);
         $this->assertFalse($events->history()->happened(CommandBusEvent::class));
@@ -221,5 +201,6 @@ public function react(Event $event)
 
         $this->assertTrue($listener->reacted);
         $this->assertTrue($events->history()->happened(CommandBusEvent::class));
+        $this->assertTrue($events->history()->happened(PreMiddlewareStarted::class));
     }
 }

From ea5f0ef47fbac7e09ecb975007a4dcdb126f4160 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 17 Sep 2019 15:54:53 -0400
Subject: [PATCH 15/33] Refactor tests and WIP

---
 src/WmiScripting/Configuration/Config.php     |  79 +++++++--
 src/WmiScripting/Support/Bus/CommandBus.php   |  10 +-
 ...redEvents.php => EventHistoryProvider.php} |   9 +-
 .../{EventHandler.php => EventProvider.php}   |  30 +++-
 src/WmiScripting/config/bootstrap.php         |   6 +-
 src/WmiScripting/config/providers.php         |  11 ++
 .../Support/Bus/CommandBusTest.php            | 152 ++++++++----------
 7 files changed, 171 insertions(+), 126 deletions(-)
 rename src/WmiScripting/Support/Events/{FiredEvents.php => EventHistoryProvider.php} (86%)
 rename src/WmiScripting/Support/Events/{EventHandler.php => EventProvider.php} (72%)
 create mode 100644 src/WmiScripting/config/providers.php

diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index e78b06b..592bf9c 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -7,10 +7,12 @@
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
 use PhpWinTools\WmiScripting\Containers\Connections;
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
 use PhpWinTools\WmiScripting\Connections\ComConnection;
+use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
 use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
 use PhpWinTools\WmiScripting\Exceptions\UnresolvableClassException;
-use PhpWinTools\WmiScripting\Support\Events\EventHandler;
 
 class Config
 {
@@ -23,7 +25,7 @@ class Config
 
     protected $resolver;
 
-    protected $eventHandler;
+    protected $eventProvider;
 
     protected $resolve_stack = [];
 
@@ -275,11 +277,44 @@ public function addComObject(string $abstract_class, $concrete_class)
     }
 
     /**
-     * @return EventHandler|null
+     * @return EventProvider
      */
-    public function events()
+    public function eventProvider()
     {
-        return $this->eventHandler ?? $this->eventHandler = EventHandler::instance($this);
+        return $this->getProvider('event');
+    }
+
+    /**
+     * @return EventHistoryProvider
+     */
+    public function eventHistoryProvider()
+    {
+        return $this->getProvider('event_history');
+    }
+
+    /**
+     * @return CommandBus
+     */
+    public function commandBus()
+    {
+        return $this->getProvider('bus');
+    }
+
+    /**
+     * Return an already registered provider or instantiate it from configuration when $default is null.
+     *
+     * @param string     $alias
+     * @param null|mixed $default
+     *
+     * @return mixed
+     */
+    public function getProvider($alias, $default = null)
+    {
+        $default = $default ?? function () use ($alias) {
+            return $this->registerProvider($alias);
+        };
+
+        return $this->get("providers.registered.{$alias}", $default);
     }
 
     public function shouldTrackEvents()
@@ -392,9 +427,19 @@ public function setDefaultConnection(string $name)
             throw InvalidConnectionException::new($name);
         }
 
-        $this->set('wmi.connections.default', $name);
+        return $this->set('wmi.connections.default', $name);
+    }
 
-        return $this;
+    public function registerProvider($provider_alias, $instance = null)
+    {
+        if (!is_object($instance) && ($instance = $this->getProvider($provider_alias, false)) === false) {
+            $instance = $this->get("providers.{$provider_alias}");
+            $instance = new $instance($this);
+        }
+
+        $this->set("providers.registered.{$provider_alias}", $instance);
+
+        return $instance;
     }
 
     public function get($key, $default = null)
@@ -409,6 +454,8 @@ public function set($key, $value = null)
         foreach ($keys as $key => $value) {
             Arr::set($this->config, $key, $value);
         }
+
+        return $this;
     }
 
     protected function boot(array $config)
@@ -423,8 +470,18 @@ protected function boot(array $config)
 
         $this->merge(include(__DIR__ . '/../config/bootstrap.php'));
 
+        $this->registerProviders();
+
         $this->bootConnections();
-        $this->bootEventSystem();
+    }
+
+    protected function registerProviders()
+    {
+        array_map(function ($alias) {
+            if ($alias !== 'registered') {
+                $this->registerProvider($alias);
+            }
+        }, array_keys($this->get('providers', [])));
     }
 
     protected function merge(array $config)
@@ -438,10 +495,4 @@ protected function bootConnections()
     {
         Arr::set($this->config, 'wmi.connections.servers', new Connections($this));
     }
-
-    protected function bootEventSystem()
-    {
-        $handler = $this->get('events.handler');
-        $this->eventHandler = new $handler($this);
-    }
 }
diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
index c096da2..caeb7cd 100644
--- a/src/WmiScripting/Support/Bus/CommandBus.php
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -44,9 +44,10 @@ class CommandBus
     public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
+
         $this->boot();
 
-        static::$instance = $this;
+        static::$instance = $this->config->registerProvider('bus', $this);
     }
 
     public static function instance(Config $config = null)
@@ -174,12 +175,7 @@ protected function fireCommandHandlerEndEvent(Command $command, CommandHandler $
 
     protected function fire(Event $event)
     {
-        $this->events()->fire($event);
-    }
-
-    protected function events()
-    {
-        return $this->getConfig()->events();
+        $this->getConfig()->eventProvider()->fire($event);
     }
 
     protected function boot()
diff --git a/src/WmiScripting/Support/Events/FiredEvents.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
similarity index 86%
rename from src/WmiScripting/Support/Events/FiredEvents.php
rename to src/WmiScripting/Support/Events/EventHistoryProvider.php
index ad85f35..0a0b36c 100644
--- a/src/WmiScripting/Support/Events/FiredEvents.php
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -4,7 +4,7 @@
 
 use PhpWinTools\WmiScripting\Configuration\Config;
 
-class FiredEvents
+class EventHistoryProvider
 {
     protected $events = [];
 
@@ -48,13 +48,6 @@ public function happened(string $event)
             || array_key_exists($event, $this->event_ancestry_cache);
     }
 
-    public function toStringArray()
-    {
-        return array_map(function ($event) {
-            return [key($event) => $event[key($event)]->listenerTypes()];
-        }, $this->events);
-    }
-
     protected function track()
     {
         return $this->config->shouldTrackEvents();
diff --git a/src/WmiScripting/Support/Events/EventHandler.php b/src/WmiScripting/Support/Events/EventProvider.php
similarity index 72%
rename from src/WmiScripting/Support/Events/EventHandler.php
rename to src/WmiScripting/Support/Events/EventProvider.php
index 25e9fec..6a01e77 100644
--- a/src/WmiScripting/Support/Events/EventHandler.php
+++ b/src/WmiScripting/Support/Events/EventProvider.php
@@ -4,7 +4,7 @@
 
 use PhpWinTools\WmiScripting\Configuration\Config;
 
-class EventHandler
+class EventProvider
 {
     /** @var self|null */
     protected static $instance;
@@ -15,14 +15,16 @@ class EventHandler
     /** @var array|Listener[][] */
     protected $listeners = [];
 
-    /** @var FiredEvents */
-    protected $fired;
+    /** @var EventHistoryProvider */
+    protected $history;
 
     public function __construct(Config $config = null)
     {
-        $this->fired = new FiredEvents($this->config = $config ?? Config::instance());
+        $this->config = $config ?? Config::instance();
 
-        static::$instance = $this;
+        $this->history = $this->config->eventHistoryProvider();
+
+        static::$instance = $this->config->registerProvider('event', $this);
     }
 
     public static function instance(Config $config = null)
@@ -50,16 +52,30 @@ public function fire(Event $event): void
             $this->getAncestorListeners($event)
         );
 
-        $this->fired->add($event, $listeners);
+        $this->history->add($event, $listeners);
 
         array_map(function (Listener $listener) use ($event) {
             $listener->react($event);
         }, $listeners);
     }
 
+    public function trackEvents()
+    {
+        $this->config->trackEvents();
+
+        return $this;
+    }
+
+    public function doNotTrackEvents()
+    {
+        $this->config->doNotTrackEvents();
+
+        return $this;
+    }
+
     public function history()
     {
-        return $this->fired;
+        return $this->history;
     }
 
     protected function getAncestorListeners(Event $event)
diff --git a/src/WmiScripting/config/bootstrap.php b/src/WmiScripting/config/bootstrap.php
index 61114c4..0728740 100644
--- a/src/WmiScripting/config/bootstrap.php
+++ b/src/WmiScripting/config/bootstrap.php
@@ -3,7 +3,6 @@
 use PhpWinTools\Support\COM\ComWrapper;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
-use PhpWinTools\WmiScripting\Support\Events\EventHandler;
 
 return [
     'com' => [
@@ -15,9 +14,10 @@
     ],
 
     'events' => [
-        'handler' => EventHandler::class,
-        'track'   => false,
+        'track' => false,
     ],
 
+    'providers' => include('providers.php'),
+
     'wmi' => include('wmi.php'),
 ];
diff --git a/src/WmiScripting/config/providers.php b/src/WmiScripting/config/providers.php
new file mode 100644
index 0000000..844acb3
--- /dev/null
+++ b/src/WmiScripting/config/providers.php
@@ -0,0 +1,11 @@
+<?php
+
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
+
+return [
+    'bus'           => CommandBus::class,
+    'event'         => EventProvider::class,
+    'event_history' => EventHistoryProvider::class,
+];
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
index 94cc20b..2d172b0 100644
--- a/tests/WmiScripting/Support/Bus/CommandBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -3,8 +3,6 @@
 namespace Tests\WmiScripting\Support\Bus;
 
 use Closure;
-use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
-use PhpWinTools\WmiScripting\Support\Events\EventHandler;
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Events\Event;
@@ -12,7 +10,7 @@
 use PhpWinTools\WmiScripting\Support\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Bus\CommandHandler;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
-use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusEvent;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
 
 class CommandBusTest extends TestCase
@@ -20,11 +18,16 @@ class CommandBusTest extends TestCase
     /** @var Config */
     protected $config;
 
+    /** @var CommandBus */
+    protected $bus;
+
     protected function setUp()
     {
         parent::setUp();
 
         $this->config = Config::newInstance();
+
+        $this->bus = $this->config->commandBus();
     }
 
     /** @test */
@@ -41,44 +44,26 @@ public function it_can_instantiate()
     /** @test */
     public function it_sends_commands_to_assigned_handler()
     {
-        $command = new class extends Command {
-            public $processed = [];
-        };
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
 
-        $handler = new class extends CommandHandler {
-            public function handle(Command $command)
-            {
-                $command->processed[] = 'command';
-                return $command;
-            }
-        };
+        $this->bus->assignHandler(get_class($command), $handler);
 
-        $bus = (new CommandBus())->assignHandler(get_class($command), $handler);
-
-        $this->assertEmpty($command->processed);
-        $this->assertSame(['command'], $bus->handle($command)->processed);
+        $this->assertEmpty($command->order);
+        $this->assertSame(['command'], $this->bus->handle($command)->order);
     }
 
     /** @test */
     public function it_runs_pre_middleware_before_the_handler()
     {
-        $command = new class extends Command {
-            public $processed = [];
-        };
-
-        $handler = new class extends CommandHandler {
-            public function handle(Command $command)
-            {
-                $command->processed[] = 'command';
-                return $command;
-            }
-        };
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
 
         $first_middleware = new class extends PreCommandMiddleware {
             public $name = 'first';
             public function handle($subject, Closure $next)
             {
-                $subject->processed[] = $this->name;
+                $subject->order[] = $this->name;
                 return parent::handle($subject, $next);
             }
         };
@@ -87,7 +72,7 @@ public function handle($subject, Closure $next)
             public $name = 'second';
             public function handle($subject, Closure $next)
             {
-                $subject->processed[] = $this->name;
+                $subject->order[] = $this->name;
                 return parent::handle($subject, $next);
             }
         };
@@ -97,110 +82,103 @@ public function handle($subject, Closure $next)
             public function handle($subject, Closure $next)
             {
                 $result = parent::handle($subject, $next);
-                $subject->processed[] = $this->name;
+                $subject->order[] = $this->name;
                 return $result;
             }
         };
 
-
-        $bus = (new CommandBus())
-            ->assignHandler(get_class($command), $handler)
+        $this->bus->assignHandler(get_class($command), $handler)
             ->registerMiddleware(get_class($after_middleware), get_class($command))
             ->registerMiddleware(get_class($first_middleware), get_class($command))
             ->registerMiddleware(get_class($second_middleware), get_class($command));
 
-        $expected = [
-            'first',
-            'second',
-            'first after',
-            'command',
-        ];
-
-        $this->assertEquals($expected, $bus->handle($command)->processed);
+        $this->assertEquals(['first', 'second', 'first after', 'command'], $this->bus->handle($command)->order);
     }
 
     /** @test */
-    public function it_fires_wildcard_pre_middleware_first()
+    public function it_can_accept_a_closure_as_middleware()
     {
-        $command = new class extends Command {
-            public $processed = [];
-        };
-
-        $handler = new class extends CommandHandler {
-            public function handle(Command $command)
-            {
-                $command->processed[] = 'command';
-                return $command;
-            }
-        };
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
 
         $middleware = new class extends PreCommandMiddleware {
             public $name = 'middleware';
             public function handle($subject, Closure $next)
             {
-                $subject->processed[] = $this->name;
+                $subject->order[] = $this->name;
                 return parent::handle($subject, $next);
             }
         };
 
         $closure = function ($subject, Closure $next) {
-            $subject->processed[] = 'closure';
+            $subject->order[] = 'closure';
             return $next($subject);
         };
 
-        $bus = (new CommandBus())
-            ->assignHandler(get_class($command), $handler)
+        $this->bus->assignHandler(get_class($command), $handler)
             ->registerMiddleware(get_class($middleware), get_class($command))
             ->registerMiddleware($closure, get_class($command), PreCommandMiddleware::class);
 
-        $expected = [
-            'middleware',
-            'closure',
-            'command',
-        ];
-
-        $this->assertSame($expected, $bus->handle($command)->processed);
+        $this->assertSame(['middleware', 'closure', 'command'], $this->bus->handle($command)->order);
     }
 
     /** @test */
     public function it_fires_an_event_before_any_pre_middleware()
     {
-        $command = new class extends Command {};
+        $command = $this->makeCommand();
 
-        $handler = new class extends CommandHandler {
-            public function handle(Command $command)
+        $middleware = new class extends PreCommandMiddleware {
+            public function handle($command, Closure $next)
             {
-                return $command;
+                $command->order[] = 'middleware';
+                return parent::handle($command, $next);
             }
         };
 
-        $middleware = new class extends PreCommandMiddleware {};
-
-        $bus = (new CommandBus($this->config))
-            ->assignHandler(get_class($command), $handler)
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
             ->registerMiddleware(get_class($middleware), get_class($command));
 
-        $listener = new class extends Listener {
-            public $reacted = false;
+        $this->config->eventProvider()
+            ->subscribe(PreMiddlewareStarted::class, $this->makeListener('listener', $command));
 
-            public function react(Event $event)
+        $this->assertSame(['listener', 'middleware', 'command'], $this->bus->handle($command)->order);
+    }
+
+    protected function makeCommand()
+    {
+        return new class extends Command {
+            public $order = [];
+        };
+    }
+
+    protected function makeCommandHandler()
+    {
+        return new class extends CommandHandler {
+            public function handle(Command $command)
             {
-                $this->reacted = true;
+                $command->order[] = 'command';
+                return $command;
             }
         };
+    }
 
-        $this->config->trackEvents();
-        /** TODO: All singletons need to register themselves with the Config */
-        $events = $this->config->events();
-        $events->subscribe(PreMiddlewareStarted::class, $listener);
+    protected function makeListener($name, $command)
+    {
+        return new class($command, $name) extends Listener {
+            public $command;
 
-        $this->assertFalse($listener->reacted);
-        $this->assertFalse($events->history()->happened(CommandBusEvent::class));
+            public $name;
 
-        $bus->handle($command);
+            public function __construct($command, $name)
+            {
+                $this->command = $command;
+                $this->name = $name;
+            }
 
-        $this->assertTrue($listener->reacted);
-        $this->assertTrue($events->history()->happened(CommandBusEvent::class));
-        $this->assertTrue($events->history()->happened(PreMiddlewareStarted::class));
+            public function react(Event $event)
+            {
+                $this->command->order[] = $this->name;
+            }
+        };
     }
 }

From 3af7bd3acd4ffc746c60fea8ec6c02167ac6cfb9 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Wed, 18 Sep 2019 12:43:02 -0400
Subject: [PATCH 16/33] Clean up event system and add testing

---
 src/WmiScripting/Support/Bus/CommandBus.php   |  80 ++++---
 .../Support/Bus/Events/CommandBusEvent.php    |  19 +-
 .../Bus/Events/FailureMiddlewareEnded.php     |   7 +
 .../Bus/Events/FailureMiddlewareStarted.php   |   7 +
 .../Bus/Events/PostMiddlewareEnded.php        |   7 +
 .../Bus/Events/PostMiddlewareEvent.php        |   7 +
 .../Bus/Events/PostMiddlewareStarted.php      |   7 +
 .../Bus/Events/SuccessMiddlewareEnded.php     |   7 +
 .../Bus/Events/SuccessMiddlewareStarted.php   |   7 +
 .../Bus/Middleware/MiddlewareStack.php        |  52 ++++-
 .../Support/{Bus => Events}/Context.php       |   2 +-
 src/WmiScripting/Support/Events/Event.php     |   4 +-
 .../Support/Events/EventHistoryProvider.php   | 134 +++++++++++-
 .../Support/Bus/CommandBusEventTest.php       | 197 ++++++++++++++++++
 .../Support/Bus/CommandBusMiddlewareTest.php  | 188 +++++++++++++++++
 .../Support/Bus/CommandBusTest.php            | 109 ++--------
 .../Event/EventHistoryProviderTest.php        | 118 +++++++++++
 .../Support/Event/EventProviderTest.php       | 107 ++++++++++
 18 files changed, 880 insertions(+), 179 deletions(-)
 create mode 100644 src/WmiScripting/Support/Bus/Events/FailureMiddlewareEnded.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/FailureMiddlewareStarted.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/PostMiddlewareEnded.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/PostMiddlewareEvent.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/PostMiddlewareStarted.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/SuccessMiddlewareEnded.php
 create mode 100644 src/WmiScripting/Support/Bus/Events/SuccessMiddlewareStarted.php
 rename src/WmiScripting/Support/{Bus => Events}/Context.php (91%)
 create mode 100644 tests/WmiScripting/Support/Bus/CommandBusEventTest.php
 create mode 100644 tests/WmiScripting/Support/Bus/CommandBusMiddlewareTest.php
 create mode 100644 tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
 create mode 100644 tests/WmiScripting/Support/Event/EventProviderTest.php

diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
index caeb7cd..b633125 100644
--- a/src/WmiScripting/Support/Bus/CommandBus.php
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -3,16 +3,22 @@
 namespace PhpWinTools\WmiScripting\Support\Bus;
 
 use Closure;
-use PhpWinTools\WmiScripting\Support\Events\Event;
 use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
 use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareEnded;
 use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PostMiddlewareEnded;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareStack;
 use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
 use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PostMiddlewareStarted;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Events\FailureMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\SuccessMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\FailureMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\SuccessMiddlewareStarted;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
 use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
@@ -45,7 +51,7 @@ public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
 
-        $this->boot();
+        $this->bootMiddlewareStack();
 
         static::$instance = $this->config->registerProvider('bus', $this);
     }
@@ -112,25 +118,19 @@ public function registerMiddleware($middleware, $command, string $stack = PostCo
 
     public function handle(Command $command, Closure $core_callback = null)
     {
+        $result = null;
+        $command_failure = true;
         $command_name = get_class($command);
 
-        $this->firePreMiddlewareStartEvent($command);
-
         $pre = $this->preMiddleware->process($command, $core_callback);
-        $command_failure = true;
-        $result = null;
-
-        $this->firePreMiddleEndEvent($command);
 
         if (array_key_exists($command_name, $this->handlers)) {
             $handler = $this->handlers[$command_name];
 
-            $this->fireCommandHandlerStartEvent($command, $handler);
-
+            $this->fireEvent(new CommandHandlerStarted($this, $command, $handler));
             $result = $handler->handle($pre);
             $command_failure = $handler->getFailureResult() === $result;
-
-            $this->fireCommandHandlerEndEvent($command, $handler);
+            $this->fireEvent(new CommandHandlerEnded($this, $command, $handler));
         }
 
         $result = $this->postMiddleware->process($command, $core_callback);
@@ -144,46 +144,36 @@ public function handle(Command $command, Closure $core_callback = null)
         return $result;
     }
 
-    protected function firePreMiddlewareStartEvent(Command $command)
-    {
-        $this->fire(new PreMiddlewareStarted($this, $command));
-
-        return $this;
-    }
-
-    protected function firePreMiddleEndEvent(Command $command)
+    protected function fireEvent(Event $event)
     {
-        $this->fire(new PreMiddlewareEnded($this, $command));
-
-        return $this;
+        $this->getConfig()->eventProvider()->fire($event);
     }
 
-    protected function fireCommandHandlerStartEvent(Command $command, CommandHandler $handler)
+    protected function bootMiddlewareStack()
     {
-        $this->fire(new CommandHandlerStarted($this, $command, $handler));
+        $this->preMiddleware = new MiddlewareStack(
+            $this->getConfig(),
+            PreMiddlewareStarted::class,
+            PreMiddlewareEnded::class
+        );
 
-        return $this;
-    }
+        $this->postMiddleware = new MiddlewareStack(
+            $this->getConfig(),
+            PostMiddlewareStarted::class,
+            PostMiddlewareEnded::class
+        );
 
-    protected function fireCommandHandlerEndEvent(Command $command, CommandHandler $handler)
-    {
-        $this->fire(new CommandHandlerEnded($this, $command, $handler));
+        $this->failureMiddleware = new MiddlewareStack(
+            $this->getConfig(),
+            FailureMiddlewareStarted::class,
+            FailureMiddlewareEnded::class
+        );
 
-        return $this;
-    }
-
-
-    protected function fire(Event $event)
-    {
-        $this->getConfig()->eventProvider()->fire($event);
-    }
-
-    protected function boot()
-    {
-        $this->preMiddleware = new MiddlewareStack();
-        $this->postMiddleware = new MiddlewareStack();
-        $this->failureMiddleware = new MiddlewareStack();
-        $this->successMiddleware = new MiddlewareStack();
+        $this->successMiddleware = new MiddlewareStack(
+            $this->getConfig(),
+            SuccessMiddlewareStarted::class,
+            SuccessMiddlewareEnded::class
+        );
     }
 
     protected function validCommand($command)
diff --git a/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
index 3894945..abfd2f8 100644
--- a/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
+++ b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
@@ -2,33 +2,22 @@
 
 namespace PhpWinTools\WmiScripting\Support\Bus\Events;
 
-use PhpWinTools\WmiScripting\Support\Bus\Context;
 use PhpWinTools\WmiScripting\Support\Events\Event;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
-use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Support\Events\Context;
 
-abstract class CommandBusEvent extends Event
+class CommandBusEvent extends Event
 {
     protected $bus;
 
     protected $command;
 
-    public function __construct(CommandBus $bus, Command $command = null)
+    public function __construct(CommandBus $bus, $command = null)
     {
         $this->bus = $bus;
         $this->command = $command;
 
         parent::__construct((new Context())
-                ->add('bus', $this->bus)->add('command', $this->command));
-    }
-
-    public function bus()
-    {
-        return $this->bus;
-    }
-
-    public function command()
-    {
-        return $this->command;
+                ->add('bus', $this->bus)->add('subject', $this->command));
     }
 }
diff --git a/src/WmiScripting/Support/Bus/Events/FailureMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/FailureMiddlewareEnded.php
new file mode 100644
index 0000000..ebad9c9
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/FailureMiddlewareEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class FailureMiddlewareEnded extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/FailureMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/FailureMiddlewareStarted.php
new file mode 100644
index 0000000..f8ac1e1
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/FailureMiddlewareStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class FailureMiddlewareStarted extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PostMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/PostMiddlewareEnded.php
new file mode 100644
index 0000000..889f081
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PostMiddlewareEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PostMiddlewareEnded extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PostMiddlewareEvent.php b/src/WmiScripting/Support/Bus/Events/PostMiddlewareEvent.php
new file mode 100644
index 0000000..1d0064a
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PostMiddlewareEvent.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PostMiddlewareEvent extends CommandMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PostMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/PostMiddlewareStarted.php
new file mode 100644
index 0000000..ba1e713
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PostMiddlewareStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PostMiddlewareStarted extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareEnded.php
new file mode 100644
index 0000000..bd36a9d
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class SuccessMiddlewareEnded extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareStarted.php
new file mode 100644
index 0000000..59f7d93
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class SuccessMiddlewareStarted extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
index 9b1d4d0..3c72219 100644
--- a/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
@@ -3,11 +3,27 @@
 namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
 
 use Closure;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandMiddlewareEvent;
 
 class MiddlewareStack
 {
+    protected $config;
+
+    protected $pre_event_class;
+
+    protected $post_event_class;
+
     protected $stack = [];
 
+    public function __construct(Config $config = null, $pre_event_class = null, $post_event_class = null)
+    {
+        $this->config = $config ?? Config::instance();
+        $this->pre_event_class = $this->validateEventClass($pre_event_class);
+        $this->post_event_class = $this->validateEventClass($post_event_class);
+    }
+
     public function add($middleware, string $subject)
     {
         $this->stack[$subject][] = $middleware;
@@ -15,11 +31,6 @@ public function add($middleware, string $subject)
         return $this;
     }
 
-    public function all()
-    {
-        return $this->prep($this->stack);
-    }
-
     public function get($subject)
     {
         $subject = is_object($subject) ? get_class($subject) : $subject;
@@ -29,13 +40,12 @@ public function get($subject)
         return array_merge($this->prep($this->stack[$subject] ?? []), $wildcard);
     }
 
-    public function has($subject)
-    {
-        return empty($this->get($subject));
-    }
-
     public function process($subject, Closure $core = null)
     {
+        if ($this->pre_event_class) {
+            $this->fireEvent(new $this->pre_event_class($this->config->commandBus(), $subject));
+        }
+
         $core = $core ?? function ($subject) {
                 return $subject;
         };
@@ -55,7 +65,27 @@ public function process($subject, Closure $core = null)
             };
         }, $core);
 
-        return $stack($subject);
+        $result = $stack($subject);
+
+        if ($this->post_event_class) {
+            $this->fireEvent(new $this->post_event_class($this->config->commandBus(), $subject));
+        }
+
+        return $result;
+    }
+
+    protected function fireEvent(CommandMiddlewareEvent $event)
+    {
+        $this->config->eventProvider()->fire($event);
+    }
+
+    protected function validateEventClass($event_class)
+    {
+        if (!array_key_exists(CommandMiddlewareEvent::class, class_parents($event_class))) {
+            throw new InvalidArgumentException('Events must extend ' . CommandMiddlewareEvent::class);
+        }
+
+        return $event_class;
     }
 
     protected function prep(array $stack)
diff --git a/src/WmiScripting/Support/Bus/Context.php b/src/WmiScripting/Support/Events/Context.php
similarity index 91%
rename from src/WmiScripting/Support/Bus/Context.php
rename to src/WmiScripting/Support/Events/Context.php
index 29e70f1..f4184c6 100644
--- a/src/WmiScripting/Support/Bus/Context.php
+++ b/src/WmiScripting/Support/Events/Context.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace PhpWinTools\WmiScripting\Support\Bus;
+namespace PhpWinTools\WmiScripting\Support\Events;
 
 class Context
 {
diff --git a/src/WmiScripting/Support/Events/Event.php b/src/WmiScripting/Support/Events/Event.php
index 14b282d..2d3b5e8 100644
--- a/src/WmiScripting/Support/Events/Event.php
+++ b/src/WmiScripting/Support/Events/Event.php
@@ -2,9 +2,9 @@
 
 namespace PhpWinTools\WmiScripting\Support\Events;
 
-use PhpWinTools\WmiScripting\Support\Bus\Context;
+use PhpWinTools\WmiScripting\Support\Events\Context;
 
-abstract class Event
+class Event
 {
     protected $context;
 
diff --git a/src/WmiScripting/Support/Events/EventHistoryProvider.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
index 0a0b36c..648afc5 100644
--- a/src/WmiScripting/Support/Events/EventHistoryProvider.php
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -6,7 +6,8 @@
 
 class EventHistoryProvider
 {
-    protected $events = [];
+    /** @var array|FiredEvent[] */
+    protected $fired_events = [];
 
     protected $event_cache = [];
 
@@ -21,35 +22,146 @@ public function __construct(Config $config = null)
         $this->config = $config ?? Config::instance();
     }
 
-    public function add(Event $event, $listeners = [])
+    /**
+     * @param Event            $event
+     * @param array|Listener[] $listeners
+     *
+     * @return self
+     */
+    public function add(Event $event, $listeners = []): self
     {
-        if (!$this->track()) {
+        if ($this->shouldNotTrack()) {
             return $this;
         }
 
-        $this->events[][get_class($event)] = new FiredEvent($event, $listeners);
+        $this->fired_events[] = new FiredEvent($event, $listeners);
 
-        $this->event_cache[get_class($event)][] = count($this->events) - 1;
+        $this->event_cache[get_class($event)][] = count($this->fired_events) - 1;
 
         array_map(function ($parent) {
-            $this->event_ancestry_cache[$parent][] = count($this->events) - 1;
+            $this->event_ancestry_cache[$parent][] = count($this->fired_events) - 1;
         }, class_parents($event));
 
         array_map(function (Listener $listener) {
-            $this->listener_cache[get_class($listener)][] = count($this->events) - 1;
+            $this->listener_cache[get_class($listener)][] = count($this->fired_events) - 1;
         }, $listeners);
         
         return $this;
     }
 
-    public function happened(string $event)
+    /**
+     * @param string     $event
+     * @param null|mixed $default
+     *
+     * @return array|FiredEvent[]|mixed
+     */
+    public function get(string $event, $default = [])
     {
-        return array_key_exists($event, $this->event_cache)
-            || array_key_exists($event, $this->event_ancestry_cache);
+        $events = array_values(array_map(function ($event_key) {
+            return $this->fired_events[$event_key];
+        }, $this->getFiredEventKeys($event)));
+
+        if (empty($events)) {
+            return is_callable($default) ? $default() : $default;
+        }
+
+        return $events;
+    }
+
+    public function getFiredEventKeys(string $event)
+    {
+        $keys = [];
+
+        if ($this->wasFiredByName($event)) {
+            $keys = $this->event_cache[$event];
+        }
+
+        if ($this->wasFiredByDescendant($event)) {
+            $keys = array_merge($keys, $this->event_ancestry_cache[$event]);
+        }
+
+        return $keys;
     }
 
-    protected function track()
+    /**
+     * @return array|FiredEvent[]
+     */
+    public function all()
+    {
+        return $this->fired_events;
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return int
+     */
+    public function eventCount(string $event): int
+    {
+        $count = 0;
+
+        if ($this->doNotHappen($event)) {
+            return $count;
+        }
+
+        if ($this->wasFiredByName($event)) {
+            $count = count($this->event_cache[$event]);
+        }
+
+        if ($this->wasFiredByDescendant($event)) {
+            $count += count($this->event_ancestry_cache[$event]);
+        }
+
+        return $count;
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return bool
+     */
+    public function happened(string $event): bool
+    {
+        return $this->wasFiredByName($event) || $this->wasFiredByDescendant($event);
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return bool
+     */
+    public function doNotHappen(string $event): bool
+    {
+        return $this->happened($event) === false;
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return bool
+     */
+    public function wasFiredByName(string $event): bool
+    {
+        return array_key_exists($event, $this->event_cache);
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return bool
+     */
+    public function wasFiredByDescendant(string $event): bool
+    {
+        return array_key_exists($event, $this->event_ancestry_cache);
+    }
+
+    protected function shouldTrack(): bool
     {
         return $this->config->shouldTrackEvents();
     }
+
+    protected function shouldNotTrack(): bool
+    {
+        return $this->shouldTrack() === false;
+    }
 }
diff --git a/tests/WmiScripting/Support/Bus/CommandBusEventTest.php b/tests/WmiScripting/Support/Bus/CommandBusEventTest.php
new file mode 100644
index 0000000..37a5f8d
--- /dev/null
+++ b/tests/WmiScripting/Support/Bus/CommandBusEventTest.php
@@ -0,0 +1,197 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Bus;
+
+use Closure;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PostMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PostMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\FailureMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\SuccessMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\FailureMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\SuccessMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
+
+class CommandBusEventTest extends CommandBusTest
+{
+    /** @test */
+    public function it_fires_events_before_and_after_pre_middleware_but_before_command()
+    {
+        $command = $this->makeCommand();
+
+        $middleware = new class extends PreCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'middleware';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
+            ->registerMiddleware(get_class($middleware), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(PreMiddlewareStarted::class, $this->makeListener('pre_started', $command))
+            ->subscribe(PreMiddlewareEnded::class, $this->makeListener('pre_ended', $command));
+
+        $this->assertSame(['pre_started', 'middleware', 'pre_ended', 'command'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_command_handle_but_before_post_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $pre = new class extends PreCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'pre';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $post = new class extends PostCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'post';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware(get_class($pre), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(CommandHandlerStarted::class, $this->makeListener('command_started', $command))
+            ->subscribe(CommandHandlerEnded::class, $this->makeListener('command_ended', $command));
+
+        $this->assertSame(
+            ['pre', 'command_started', 'command', 'command_ended', 'post'], $this->bus->handle($command)->order
+        );
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_post_middleware_but_before_success_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $post = new class extends PostCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'post';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $success = new class extends SuccessCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'success';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware(get_class($success), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(PostMiddlewareStarted::class, $this->makeListener('post_started', $command))
+            ->subscribe(PostMiddlewareEnded::class, $this->makeListener('post_ended', $command));
+
+        $this->assertSame(
+            ['command', 'post_started', 'post', 'post_ended', 'success'], $this->bus->handle($command)->order
+        );
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_post_middleware_but_before_failure_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $post = new class extends PostCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'post';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $success = new class extends FailureCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'failure';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler('failure', true))
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware(get_class($success), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(PostMiddlewareStarted::class, $this->makeListener('post_started', $command))
+            ->subscribe(PostMiddlewareEnded::class, $this->makeListener('post_ended', $command));
+
+        $this->assertSame(
+            ['command', 'post_started', 'post', 'post_ended', 'failure'], $this->bus->handle($command)->order
+        );
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_failure_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $success = new class extends FailureCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'failure';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler('failure', true))
+            ->registerMiddleware(get_class($success), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(FailureMiddlewareStarted::class, $this->makeListener('failure_started', $command))
+            ->subscribe(FailureMiddlewareEnded::class, $this->makeListener('failure_ended', $command));
+
+        $this->assertSame(
+            ['command', 'failure_started', 'failure', 'failure_ended'], $this->bus->handle($command)->order
+        );
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_success_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $success = new class extends SuccessCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'success';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
+            ->registerMiddleware(get_class($success), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(SuccessMiddlewareStarted::class, $this->makeListener('success_started', $command))
+            ->subscribe(SuccessMiddlewareEnded::class, $this->makeListener('success_ended', $command));
+
+        $this->assertSame(
+            ['command', 'success_started', 'success', 'success_ended'], $this->bus->handle($command)->order
+        );
+    }
+}
diff --git a/tests/WmiScripting/Support/Bus/CommandBusMiddlewareTest.php b/tests/WmiScripting/Support/Bus/CommandBusMiddlewareTest.php
new file mode 100644
index 0000000..583c062
--- /dev/null
+++ b/tests/WmiScripting/Support/Bus/CommandBusMiddlewareTest.php
@@ -0,0 +1,188 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Bus;
+
+use Closure;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
+
+class CommandBusMiddlewareTest extends CommandBusTest
+{
+    /** @test */
+    public function it_can_accept_a_closure_as_pre_middleware()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $middleware = new class extends PreCommandMiddleware {
+            public $name = 'middleware';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $closure = function ($subject, Closure $next) {
+            $subject->order[] = 'closure';
+            return $next($subject);
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($middleware), get_class($command))
+            ->registerMiddleware($closure, get_class($command), PreCommandMiddleware::class);
+
+        $this->assertSame(['middleware', 'closure', 'command'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_can_accept_a_closure_as_post_middleware()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $middleware = new class extends PreCommandMiddleware {
+            public $name = 'middleware';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $closure = function ($subject, Closure $next) {
+            $subject->order[] = 'closure';
+            return $next($subject);
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($middleware), get_class($command))
+            ->registerMiddleware($closure, get_class($command), PostCommandMiddleware::class);
+
+        $this->assertSame(['middleware', 'command', 'closure'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_can_accept_a_closure_as_success_middleware()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $pre = new class extends PreCommandMiddleware {
+            public $name = 'pre';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $post = new class extends PostCommandMiddleware {
+            public $name = 'post';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $closure = function ($subject, Closure $next) {
+            $subject->order[] = 'closure';
+            return $next($subject);
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware($closure, get_class($command), SuccessCommandMiddleware::class)
+            ->registerMiddleware(get_class($pre), get_class($command));
+
+        $this->assertSame(['pre', 'command', 'post', 'closure'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_can_accept_a_closure_as_failure_middleware()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler('failed', true);
+
+        $pre = new class extends PreCommandMiddleware {
+            public $name = 'pre';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $post = new class extends PostCommandMiddleware {
+            public $name = 'post';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $success = function ($subject, Closure $next) {
+            $subject->order[] = 'success';
+            return $next($subject);
+        };
+
+        $failure = function ($subject, Closure $next) {
+            $subject->order[] = 'failure';
+            return $next($subject);
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware($failure, get_class($command), FailureCommandMiddleware::class)
+            ->registerMiddleware($success, get_class($command), SuccessCommandMiddleware::class)
+            ->registerMiddleware(get_class($pre), get_class($command));
+
+        $this->assertSame(['pre', 'command', 'post', 'failure'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_runs_pre_middleware_before_the_handler()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $first_middleware = new class extends PreCommandMiddleware {
+            public $name = 'first';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $second_middleware = new class extends PreCommandMiddleware {
+            public $name = 'second';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $after_middleware = new class extends PreCommandMiddleware {
+            public $name = 'first after';
+            public function handle($subject, Closure $next)
+            {
+                $result = parent::handle($subject, $next);
+                $subject->order[] = $this->name;
+                return $result;
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($after_middleware), get_class($command))
+            ->registerMiddleware(get_class($first_middleware), get_class($command))
+            ->registerMiddleware(get_class($second_middleware), get_class($command));
+
+        $this->assertEquals(['first', 'second', 'first after', 'command'], $this->bus->handle($command)->order);
+    }
+}
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
index 2d172b0..f3ed203 100644
--- a/tests/WmiScripting/Support/Bus/CommandBusTest.php
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -2,7 +2,6 @@
 
 namespace Tests\WmiScripting\Support\Bus;
 
-use Closure;
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Events\Event;
@@ -10,8 +9,6 @@
 use PhpWinTools\WmiScripting\Support\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Bus\CommandHandler;
 use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
-use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
-use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
 
 class CommandBusTest extends TestCase
 {
@@ -53,110 +50,34 @@ public function it_sends_commands_to_assigned_handler()
         $this->assertSame(['command'], $this->bus->handle($command)->order);
     }
 
-    /** @test */
-    public function it_runs_pre_middleware_before_the_handler()
+    protected function makeCommand()
     {
-        $command = $this->makeCommand();
-        $handler = $this->makeCommandHandler();
-
-        $first_middleware = new class extends PreCommandMiddleware {
-            public $name = 'first';
-            public function handle($subject, Closure $next)
-            {
-                $subject->order[] = $this->name;
-                return parent::handle($subject, $next);
-            }
-        };
-
-        $second_middleware = new class extends PreCommandMiddleware {
-            public $name = 'second';
-            public function handle($subject, Closure $next)
-            {
-                $subject->order[] = $this->name;
-                return parent::handle($subject, $next);
-            }
-        };
-
-        $after_middleware = new class extends PreCommandMiddleware {
-            public $name = 'first after';
-            public function handle($subject, Closure $next)
-            {
-                $result = parent::handle($subject, $next);
-                $subject->order[] = $this->name;
-                return $result;
-            }
+        return new class extends Command {
+            public $order = [];
         };
-
-        $this->bus->assignHandler(get_class($command), $handler)
-            ->registerMiddleware(get_class($after_middleware), get_class($command))
-            ->registerMiddleware(get_class($first_middleware), get_class($command))
-            ->registerMiddleware(get_class($second_middleware), get_class($command));
-
-        $this->assertEquals(['first', 'second', 'first after', 'command'], $this->bus->handle($command)->order);
     }
 
-    /** @test */
-    public function it_can_accept_a_closure_as_middleware()
+    protected function makeCommandHandler($failure_result = null, bool $trigger_failure = false)
     {
-        $command = $this->makeCommand();
-        $handler = $this->makeCommandHandler();
+        return new class($failure_result, $trigger_failure) extends CommandHandler {
+            protected $failure_result;
 
-        $middleware = new class extends PreCommandMiddleware {
-            public $name = 'middleware';
-            public function handle($subject, Closure $next)
-            {
-                $subject->order[] = $this->name;
-                return parent::handle($subject, $next);
-            }
-        };
+            protected $trigger_failure = false;
 
-        $closure = function ($subject, Closure $next) {
-            $subject->order[] = 'closure';
-            return $next($subject);
-        };
-
-        $this->bus->assignHandler(get_class($command), $handler)
-            ->registerMiddleware(get_class($middleware), get_class($command))
-            ->registerMiddleware($closure, get_class($command), PreCommandMiddleware::class);
-
-        $this->assertSame(['middleware', 'closure', 'command'], $this->bus->handle($command)->order);
-    }
-
-    /** @test */
-    public function it_fires_an_event_before_any_pre_middleware()
-    {
-        $command = $this->makeCommand();
-
-        $middleware = new class extends PreCommandMiddleware {
-            public function handle($command, Closure $next)
+            public function __construct($failure_result, $trigger_failure)
             {
-                $command->order[] = 'middleware';
-                return parent::handle($command, $next);
+                $this->failure_result = $failure_result;
+                $this->trigger_failure = $trigger_failure;
             }
-        };
-
-        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
-            ->registerMiddleware(get_class($middleware), get_class($command));
-
-        $this->config->eventProvider()
-            ->subscribe(PreMiddlewareStarted::class, $this->makeListener('listener', $command));
 
-        $this->assertSame(['listener', 'middleware', 'command'], $this->bus->handle($command)->order);
-    }
-
-    protected function makeCommand()
-    {
-        return new class extends Command {
-            public $order = [];
-        };
-    }
-
-    protected function makeCommandHandler()
-    {
-        return new class extends CommandHandler {
             public function handle(Command $command)
             {
                 $command->order[] = 'command';
+
+                if ($this->trigger_failure) {
+                    return $this->failure_result;
+                }
+
                 return $command;
             }
         };
diff --git a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
new file mode 100644
index 0000000..6f162af
--- /dev/null
+++ b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Event;
+
+use PhpWinTools\WmiScripting\Support\Events\Context;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusEvent;
+
+class EventHistoryProviderTest extends EventProviderTest
+{
+    /** @test */
+    public function it_does_not_track_events_by_default()
+    {
+        $this->event->fire(new Event(new Context()));
+
+        $this->assertEmpty($this->history->all());
+    }
+
+    /** @test */
+    public function it_can_track_events()
+    {
+        $this->event->trackEvents()->fire(new Event(new Context()));
+
+        $this->assertTrue($this->history->happened(Event::class));
+    }
+
+    /** @test */
+    public function it_can_count_events()
+    {
+        $this->event->trackEvents();
+        $this->event->fire(new Event(new Context()));
+        $this->event->fire(new Event(new Context()));
+
+        $this->assertEquals(2, $this->history->eventCount(Event::class));
+    }
+
+    /** @test */
+    public function it_can_count_events_through_ancestry()
+    {
+        $this->event->trackEvents();
+
+        $child = new class(new Context()) extends Event {};
+
+        $this->event->fire($child);
+        $this->event->fire(Event::fire(new Context()));
+
+        $this->assertEquals(1, $this->history->eventCount(get_class($child)));
+        $this->assertEquals(2, $this->history->eventCount(Event::class));
+    }
+
+    /** @test */
+    public function it_can_count_events_through_ancestry_without_over_counting()
+    {
+        $this->event->trackEvents();
+
+        $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
+        $notFired = new class($bus = Config::instance()->commandBus(), 'nothing') extends CommandBusEvent {};
+
+        $this->event->fire(Event::fire(new Context()));
+        $this->event->fire($child);
+        $this->event->fire(new CommandBusEvent($bus, 'new test'));
+
+        $this->assertEquals(3, $this->history->eventCount(Event::class));
+        $this->assertEquals(2, $this->history->eventCount(CommandBusEvent::class));
+        $this->assertEquals(1, $this->history->eventCount(get_class($child)));
+        $this->assertEquals(0, $this->history->eventCount(get_class($notFired)));
+    }
+
+    /** @test */
+    public function it_returns_events_in_the_order_that_they_were_fired()
+    {
+        $this->event->trackEvents();
+
+        $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
+
+        $this->event->fire($first = Event::fire(new Context()));
+        $this->event->fire($second = Event::fire(new Context()));
+        $this->event->fire($child);
+        $this->event->fire($forth = new CommandBusEvent($bus, 'new test'));
+
+
+        $this->assertSame($first, $this->history->all()[0]->event());
+        $this->assertSame($second, $this->history->all()[1]->event());
+        $this->assertSame($child, $this->history->all()[2]->event());
+        $this->assertSame($forth, $this->history->all()[3]->event());
+    }
+
+    /** @test */
+    public function it_can_return_all_fired_events_by_name()
+    {
+        $this->event->trackEvents();
+
+        $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
+
+        $this->event->fire($first = Event::fire(new Context()));
+        $this->event->fire($second = Event::fire(new Context()));
+        $this->event->fire($child);
+
+        $this->assertCount(1, $firedEvents = $this->history->get(get_class($child)));
+        $this->assertSame($child, $firedEvents[0]->event());
+
+        $this->assertCount(3, $firedEvents = $this->history->get(Event::class));
+        $this->assertSame($first, $firedEvents[0]->event());
+        $this->assertSame($second, $firedEvents[1]->event());
+        $this->assertSame($child, $firedEvents[2]->event());
+    }
+
+    /** @test */
+    public function it_returns_an_empty_array_if_no_events_are_found_or_calls_default_value_if_callable_or_returns_it()
+    {
+        $this->assertEmpty($this->history->get(Event::class));
+        $this->assertSame('nope', $this->history->get(Event::class, 'nope'));
+        $this->assertSame('i returned', $this->history->get(Event::class, function () {
+            return 'i returned';
+        }));
+    }
+}
diff --git a/tests/WmiScripting/Support/Event/EventProviderTest.php b/tests/WmiScripting/Support/Event/EventProviderTest.php
new file mode 100644
index 0000000..bf5bfaa
--- /dev/null
+++ b/tests/WmiScripting/Support/Event/EventProviderTest.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Event;
+
+use PhpWinTools\WmiScripting\Support\Events\Context;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\Listener;
+use Tests\TestCase;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
+
+class EventProviderTest extends TestCase
+{
+    /** @var EventProvider */
+    protected $event;
+
+    /** @var EventHistoryProvider */
+    protected $history;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->event = new EventProvider();
+        $this->history = $this->event->history();
+    }
+
+    protected function tearDown()
+    {
+        parent::tearDown();
+
+        Config::newInstance();
+    }
+
+    /** @test */
+    public function it_can_instantiate()
+    {
+        $this->assertInstanceOf(EventProvider::class, new EventProvider(new Config()));
+        $this->assertInstanceOf(EventProvider::class, EventProvider::instance());
+    }
+
+    /** @test */
+    public function it_registers_itself_with_the_config_on_instantiation()
+    {
+        $config = new Config();
+        $configEvent = $config->getProvider('event');
+
+        $this->assertNotSame($configEvent, $newEvent = new EventProvider());
+        $this->assertSame($newEvent, $config->getProvider('event'));
+    }
+
+    /** @test */
+    public function it_runs_listeners_when_an_applicable_event_is_fired()
+    {
+        $subject = new class {
+            public $order = [];
+        };
+
+        $child = new class(new Context()) extends Event {};
+
+        $this->event->subscribe(get_class($child), $this->makeListener('listener', $subject));
+
+        $this->event->fire(Event::fire(new Context()));
+        $this->assertEmpty($subject->order);
+
+        $this->event->fire($child);
+        $this->assertEquals(['listener'], $subject->order);
+    }
+
+    /** @test */
+    public function it_runs_ancestor_listeners_when_a_child_event_is_fired()
+    {
+        $subject = new class { public $order = []; };
+        $descendant = new class(new Context()) extends Event {};
+        $listener = $this->makeListener('listener_fired', $subject);
+
+        $this->event->trackEvents()
+            ->subscribe(Event::class, $listener)
+            ->fire($descendant);
+
+        $this->assertEquals(['listener_fired'], $subject->order);
+
+        $this->assertFalse($this->history->wasFiredByName(Event::class));
+        $this->assertTrue($this->history->wasFiredByDescendant(Event::class));
+    }
+
+    protected function makeListener($name, $subject)
+    {
+        return new class($subject, $name) extends Listener {
+            public $subject;
+
+            public $name;
+
+            public function __construct($subject, $name)
+            {
+                $this->subject = $subject;
+                $this->name = $name;
+            }
+
+            public function react(Event $event)
+            {
+                $this->subject->order[] = $this->name;
+            }
+        };
+    }
+}

From 636bebade1ae35f7c62954ad1788609bea0e2b54 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Wed, 18 Sep 2019 12:44:57 -0400
Subject: [PATCH 17/33] Style fixes

---
 src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php | 2 +-
 src/WmiScripting/Support/Events/Event.php                   | 2 --
 src/WmiScripting/Support/Events/EventHistoryProvider.php    | 2 +-
 3 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
index 3c72219..52c782b 100644
--- a/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
@@ -47,7 +47,7 @@ public function process($subject, Closure $core = null)
         }
 
         $core = $core ?? function ($subject) {
-                return $subject;
+            return $subject;
         };
 
         $stack = array_reduce($this->get($subject), function ($next, $middleware) use ($subject) {
diff --git a/src/WmiScripting/Support/Events/Event.php b/src/WmiScripting/Support/Events/Event.php
index 2d3b5e8..169e766 100644
--- a/src/WmiScripting/Support/Events/Event.php
+++ b/src/WmiScripting/Support/Events/Event.php
@@ -2,8 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Events;
 
-use PhpWinTools\WmiScripting\Support\Events\Context;
-
 class Event
 {
     protected $context;
diff --git a/src/WmiScripting/Support/Events/EventHistoryProvider.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
index 648afc5..7d20a7a 100644
--- a/src/WmiScripting/Support/Events/EventHistoryProvider.php
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -45,7 +45,7 @@ public function add(Event $event, $listeners = []): self
         array_map(function (Listener $listener) {
             $this->listener_cache[get_class($listener)][] = count($this->fired_events) - 1;
         }, $listeners);
-        
+
         return $this;
     }
 

From fe89bef50abd8c0c42150a101981ec1ef3bc7019 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Wed, 18 Sep 2019 12:59:06 -0400
Subject: [PATCH 18/33] Clean up dead code

---
 src/WmiScripting/Support/Bus/CommandBus.php                 | 2 +-
 src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php | 2 +-
 src/WmiScripting/Support/Events/EventProvider.php           | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
index b633125..41afeb1 100644
--- a/src/WmiScripting/Support/Bus/CommandBus.php
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -178,7 +178,7 @@ protected function bootMiddlewareStack()
 
     protected function validCommand($command)
     {
-        return $command = '*' || $this->classExtends($command, Command::class);
+        return $command === '*' || $this->classExtends($command, Command::class);
     }
 
     protected function invalidCommand($command)
diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
index 52c782b..9f11974 100644
--- a/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
@@ -50,7 +50,7 @@ public function process($subject, Closure $core = null)
             return $subject;
         };
 
-        $stack = array_reduce($this->get($subject), function ($next, $middleware) use ($subject) {
+        $stack = array_reduce($this->get($subject), function ($next, $middleware) {
             if ($middleware instanceof Closure) {
                 return function ($command) use ($next, $middleware) {
                     return $middleware($command, $next);
diff --git a/src/WmiScripting/Support/Events/EventProvider.php b/src/WmiScripting/Support/Events/EventProvider.php
index 6a01e77..d2022de 100644
--- a/src/WmiScripting/Support/Events/EventProvider.php
+++ b/src/WmiScripting/Support/Events/EventProvider.php
@@ -48,7 +48,7 @@ public function subscribe($event, Listener $listener)
     public function fire(Event $event): void
     {
         $listeners = array_merge(
-            $this->listeners[$event_name = get_class($event)] ?? [],
+            $this->listeners[get_class($event)] ?? [],
             $this->getAncestorListeners($event)
         );
 

From 446647b25651be0deaebc06f90fcb73a6640f622 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Wed, 18 Sep 2019 16:02:43 -0400
Subject: [PATCH 19/33] WIP

---
 src/WmiScripting/Configuration/Config.php     |  42 +----
 src/WmiScripting/Containers/Collection.php    |   9 +
 src/WmiScripting/Containers/Connections.php   |   2 +-
 src/WmiScripting/Containers/Container.php     |  37 +++-
 src/WmiScripting/Support/Events/Event.php     |   7 +-
 .../Support/Events/EventCacheContainer.php    |  13 ++
 .../Support/Events/EventHistoryProvider.php   | 174 ++++++++++++------
 .../Event/EventHistoryProviderTest.php        |  55 +++++-
 .../Support/Event/EventProviderTest.php       |   8 +-
 9 files changed, 238 insertions(+), 109 deletions(-)
 create mode 100644 src/WmiScripting/Containers/Collection.php
 create mode 100644 src/WmiScripting/Support/Events/EventCacheContainer.php

diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index 592bf9c..11bb69e 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -7,6 +7,7 @@
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
 use PhpWinTools\WmiScripting\Containers\Connections;
+use PhpWinTools\WmiScripting\Containers\Container;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
 use PhpWinTools\WmiScripting\Connections\ComConnection;
 use PhpWinTools\WmiScripting\Support\Events\EventProvider;
@@ -14,15 +15,13 @@
 use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
 use PhpWinTools\WmiScripting\Exceptions\UnresolvableClassException;
 
-class Config
+class Config extends Container
 {
     /** @var Config|null */
     protected static $instance = null;
 
     protected static $test_mode = false;
 
-    protected $config;
-
     protected $resolver;
 
     protected $eventProvider;
@@ -49,7 +48,7 @@ public function __construct(array $config = [], Resolver $resolver = null)
     public static function instance(array $items = [], Resolver $resolver = null)
     {
         if (static::$instance && !empty($items)) {
-            return static::newInstance(array_merge(static::$instance->config, $items), $resolver);
+            return static::newInstance(array_merge(static::$instance->container, $items), $resolver);
         }
 
         if (static::$instance && $resolver) {
@@ -442,22 +441,6 @@ public function registerProvider($provider_alias, $instance = null)
         return $instance;
     }
 
-    public function get($key, $default = null)
-    {
-        return Arr::get($this->config, $key, $default);
-    }
-
-    public function set($key, $value = null)
-    {
-        $keys = is_array($key) ? $key : [$key => $value];
-
-        foreach ($keys as $key => $value) {
-            Arr::set($this->config, $key, $value);
-        }
-
-        return $this;
-    }
-
     protected function boot(array $config)
     {
         if (static::$test_mode) {
@@ -468,11 +451,9 @@ protected function boot(array $config)
             $this->merge($config);
         }
 
-        $this->merge(include(__DIR__ . '/../config/bootstrap.php'));
-
-        $this->registerProviders();
-
-        $this->bootConnections();
+        $this->merge(include(__DIR__ . '/../config/bootstrap.php'))
+            ->registerProviders()
+            ->bootConnections();
     }
 
     protected function registerProviders()
@@ -482,17 +463,14 @@ protected function registerProviders()
                 $this->registerProvider($alias);
             }
         }, array_keys($this->get('providers', [])));
-    }
 
-    protected function merge(array $config)
-    {
-        foreach (Arr::dot($config) as $key => $value) {
-            $this->config = Arr::add($this->config, $key, $value);
-        }
+        return $this;
     }
 
     protected function bootConnections()
     {
-        Arr::set($this->config, 'wmi.connections.servers', new Connections($this));
+        Arr::set($this->container, 'wmi.connections.servers', new Connections($this));
+
+        return $this;
     }
 }
diff --git a/src/WmiScripting/Containers/Collection.php b/src/WmiScripting/Containers/Collection.php
new file mode 100644
index 0000000..98189b3
--- /dev/null
+++ b/src/WmiScripting/Containers/Collection.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Containers;
+
+use Illuminate\Support\Collection as BaseCollection;
+
+class Collection extends BaseCollection
+{
+}
diff --git a/src/WmiScripting/Containers/Connections.php b/src/WmiScripting/Containers/Connections.php
index b12927b..c2fab5f 100644
--- a/src/WmiScripting/Containers/Connections.php
+++ b/src/WmiScripting/Containers/Connections.php
@@ -5,7 +5,7 @@
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Connections\ComConnection;
 
-class Connections extends Container
+class Connections extends Collection
 {
     public function __construct(Config $config)
     {
diff --git a/src/WmiScripting/Containers/Container.php b/src/WmiScripting/Containers/Container.php
index 549c483..61ea802 100644
--- a/src/WmiScripting/Containers/Container.php
+++ b/src/WmiScripting/Containers/Container.php
@@ -2,8 +2,41 @@
 
 namespace PhpWinTools\WmiScripting\Containers;
 
-use Illuminate\Support\Collection;
+use Illuminate\Support\Arr;
 
-class Container extends Collection
+class Container
 {
+    protected $container;
+
+    public function get($key, $default = null)
+    {
+        return Arr::get($this->container, $key, $default);
+    }
+
+    public function append($key, $value)
+    {
+        if (is_null($this->get($key))) {
+            //
+        }
+    }
+
+    public function set($key, $value = null)
+    {
+        $keys = is_array($key) ? $key : [$key => $value];
+
+        foreach ($keys as $key => $value) {
+            Arr::set($this->container, $key, $value);
+        }
+
+        return $this;
+    }
+
+    public function merge(array $array)
+    {
+        foreach (Arr::dot($array) as $key => $value) {
+            $this->container = Arr::add($this->container, $key, $value);
+        }
+
+        return $this;
+    }
 }
diff --git a/src/WmiScripting/Support/Events/Event.php b/src/WmiScripting/Support/Events/Event.php
index 169e766..06467a2 100644
--- a/src/WmiScripting/Support/Events/Event.php
+++ b/src/WmiScripting/Support/Events/Event.php
@@ -11,7 +11,7 @@ public function __construct(Context $context)
         $this->context = $context;
     }
 
-    public static function fire(Context $context)
+    public static function new(Context $context)
     {
         return new static($context);
     }
@@ -20,9 +20,4 @@ public function context(): Context
     {
         return $this->context;
     }
-
-    public function ancestry()
-    {
-        return class_parents(get_called_class());
-    }
 }
diff --git a/src/WmiScripting/Support/Events/EventCacheContainer.php b/src/WmiScripting/Support/Events/EventCacheContainer.php
new file mode 100644
index 0000000..d31f4f4
--- /dev/null
+++ b/src/WmiScripting/Support/Events/EventCacheContainer.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+use PhpWinTools\WmiScripting\Containers\Container;
+
+class EventCacheContainer extends Container
+{
+    public function addActual(Event $event)
+    {
+//        $this->set('actual', )
+    }
+}
diff --git a/src/WmiScripting/Support/Events/EventHistoryProvider.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
index 7d20a7a..dd90642 100644
--- a/src/WmiScripting/Support/Events/EventHistoryProvider.php
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -2,24 +2,37 @@
 
 namespace PhpWinTools\WmiScripting\Support\Events;
 
+use Closure;
+use Countable;
 use PhpWinTools\WmiScripting\Configuration\Config;
 
-class EventHistoryProvider
+class EventHistoryProvider implements Countable
 {
+    /** @var Config */
+    protected $config;
+
     /** @var array|FiredEvent[] */
     protected $fired_events = [];
 
-    protected $event_cache = [];
-
-    protected $event_ancestry_cache = [];
+    /** @var array */
+    protected $event_cache = [
+        'actual' => [],
+        'ancestors' => [],
+        'listeners' => [],
+    ];
 
-    protected $listener_cache = [];
-
-    protected $config;
+    protected $eventContainer;
 
     public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
+
+        $this->eventContainer = new EventCacheContainer();
+    }
+
+    public function container()
+    {
+        return $this->eventContainer;
     }
 
     /**
@@ -28,7 +41,7 @@ public function __construct(Config $config = null)
      *
      * @return self
      */
-    public function add(Event $event, $listeners = []): self
+    public function add(Event $event, array $listeners = []): self
     {
         if ($this->shouldNotTrack()) {
             return $this;
@@ -36,83 +49,83 @@ public function add(Event $event, $listeners = []): self
 
         $this->fired_events[] = new FiredEvent($event, $listeners);
 
-        $this->event_cache[get_class($event)][] = count($this->fired_events) - 1;
-
-        array_map(function ($parent) {
-            $this->event_ancestry_cache[$parent][] = count($this->fired_events) - 1;
-        }, class_parents($event));
-
-        array_map(function (Listener $listener) {
-            $this->listener_cache[get_class($listener)][] = count($this->fired_events) - 1;
-        }, $listeners);
+        return $this->cacheEvent($event, $listeners);
+    }
 
-        return $this;
+    /**
+     * @return array|FiredEvent[]
+     */
+    public function all()
+    {
+        return $this->fired_events;
     }
 
     /**
-     * @param string     $event
-     * @param null|mixed $default
+     * @param string              $event
+     * @param array|Closure|mixed $default
      *
      * @return array|FiredEvent[]|mixed
      */
     public function get(string $event, $default = [])
     {
-        $events = array_values(array_map(function ($event_key) {
-            return $this->fired_events[$event_key];
-        }, $this->getFiredEventKeys($event)));
-
-        if (empty($events)) {
-            return is_callable($default) ? $default() : $default;
-        }
+        return $this->findEvents($this->getFiredEventKeys($event), $default);
+    }
 
-        return $events;
+    /**
+     * @param string              $listener
+     * @param array|Closure|mixed $default
+     *
+     * @return array|FiredEvent[]|mixed
+     */
+    public function getFromListener(string $listener, $default = [])
+    {
+        return $this->findEvents($this->event_cache['listeners'][$listener] ?? [], $default);
     }
 
+    /**
+     * @param string $event
+     *
+     * @return array|mixed
+     */
     public function getFiredEventKeys(string $event)
     {
         $keys = [];
 
         if ($this->wasFiredByName($event)) {
-            $keys = $this->event_cache[$event];
+            $keys = $this->event_cache['actual'][$event];
         }
 
         if ($this->wasFiredByDescendant($event)) {
-            $keys = array_merge($keys, $this->event_ancestry_cache[$event]);
+            $keys = array_merge($keys, $this->event_cache['ancestors'][$event]);
         }
 
         return $keys;
     }
 
     /**
-     * @return array|FiredEvent[]
+     * @return int
      */
-    public function all()
+    public function count(): int
     {
-        return $this->fired_events;
+        return count($this->fired_events);
     }
 
     /**
-     * @param string $event
+     * @param string|null $event
      *
      * @return int
      */
-    public function eventCount(string $event): int
+    public function eventCount(string $event = null): int
     {
-        $count = 0;
-
-        if ($this->doNotHappen($event)) {
-            return $count;
-        }
-
-        if ($this->wasFiredByName($event)) {
-            $count = count($this->event_cache[$event]);
-        }
-
-        if ($this->wasFiredByDescendant($event)) {
-            $count += count($this->event_ancestry_cache[$event]);
-        }
+        return is_null($event) ? $this->count() : count($this->get($event));
+    }
 
-        return $count;
+    /**
+     * @return int
+     */
+    public function lastIndex(): int
+    {
+        return $this->count() - 1;
     }
 
     /**
@@ -120,7 +133,7 @@ public function eventCount(string $event): int
      *
      * @return bool
      */
-    public function happened(string $event): bool
+    public function hasFired(string $event): bool
     {
         return $this->wasFiredByName($event) || $this->wasFiredByDescendant($event);
     }
@@ -130,9 +143,9 @@ public function happened(string $event): bool
      *
      * @return bool
      */
-    public function doNotHappen(string $event): bool
+    public function hasNotFired(string $event): bool
     {
-        return $this->happened($event) === false;
+        return $this->hasFired($event) === false;
     }
 
     /**
@@ -142,7 +155,7 @@ public function doNotHappen(string $event): bool
      */
     public function wasFiredByName(string $event): bool
     {
-        return array_key_exists($event, $this->event_cache);
+        return array_key_exists($event, $this->event_cache['actual']);
     }
 
     /**
@@ -152,14 +165,67 @@ public function wasFiredByName(string $event): bool
      */
     public function wasFiredByDescendant(string $event): bool
     {
-        return array_key_exists($event, $this->event_ancestry_cache);
+        return array_key_exists($event, $this->event_cache['ancestors']);
+    }
+
+    /**
+     * @param array               $event_keys
+     * @param array|Closure|mixed $default
+     *
+     * @return array|FiredEvent[]|mixed
+     */
+    protected function findEvents(array $event_keys = [], $default = [])
+    {
+        $events = array_values(array_map(function ($event_key) {
+            return $this->fired_events[$event_key];
+        }, $event_keys));
+
+        if (empty($events)) {
+            return is_callable($default) ? $default() : $default;
+        }
+
+        return $events;
     }
 
+    /**
+     * @param Event            $event
+     * @param array|Listener[] $listeners
+     *
+     * @return self
+     */
+    protected function cacheEvent(Event $event, array $listeners = []): self
+    {
+        $this->event_cache['actual'][get_class($event)][] = $this->lastIndex();
+
+        $this->cacheEventSet($event, 'ancestors', class_parents($event), function ($item) {
+            return $item;
+        });
+
+        $this->cacheEventSet($event, 'listeners', $listeners, function (Listener $listener) {
+            return get_class($listener);
+        });
+
+        return $this;
+    }
+
+    protected function cacheEventSet(Event $event, string $set_name, array $set, Closure $getItemKey)
+    {
+        array_map(function ($item) use ($set_name, $getItemKey) {
+            $this->event_cache[$set_name][$getItemKey($item)][] = $this->lastIndex();
+        }, $set);
+    }
+
+    /**
+     * @return bool
+     */
     protected function shouldTrack(): bool
     {
         return $this->config->shouldTrackEvents();
     }
 
+    /**
+     * @return bool
+     */
     protected function shouldNotTrack(): bool
     {
         return $this->shouldTrack() === false;
diff --git a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
index 6f162af..929c812 100644
--- a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
@@ -2,9 +2,10 @@
 
 namespace Tests\WmiScripting\Support\Event;
 
-use PhpWinTools\WmiScripting\Support\Events\Context;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\Context;
+use PhpWinTools\WmiScripting\Support\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusEvent;
 
 class EventHistoryProviderTest extends EventProviderTest
@@ -20,19 +21,29 @@ public function it_does_not_track_events_by_default()
     /** @test */
     public function it_can_track_events()
     {
+        $notFired = new class(new Context()) extends Event {};
         $this->event->trackEvents()->fire(new Event(new Context()));
 
-        $this->assertTrue($this->history->happened(Event::class));
+        $this->assertTrue($this->history->hasFired(Event::class));
+        $this->assertTrue($this->history->hasNotFired(get_class($notFired)));
     }
 
     /** @test */
     public function it_can_count_events()
     {
         $this->event->trackEvents();
+        $child = new class(new Context()) extends Event {};
+
+        $this->event->fire($child);
         $this->event->fire(new Event(new Context()));
         $this->event->fire(new Event(new Context()));
 
-        $this->assertEquals(2, $this->history->eventCount(Event::class));
+//        dd($this->history->container());
+
+        $this->assertEquals(3, $this->history->count());
+        $this->assertEquals(3, $this->history->eventCount());
+        $this->assertEquals(3, $this->history->eventCount(Event::class));
+        $this->assertEquals(1, $this->history->eventCount(get_class($child)));
     }
 
     /** @test */
@@ -43,7 +54,7 @@ public function it_can_count_events_through_ancestry()
         $child = new class(new Context()) extends Event {};
 
         $this->event->fire($child);
-        $this->event->fire(Event::fire(new Context()));
+        $this->event->fire(Event::new(new Context()));
 
         $this->assertEquals(1, $this->history->eventCount(get_class($child)));
         $this->assertEquals(2, $this->history->eventCount(Event::class));
@@ -57,7 +68,7 @@ public function it_can_count_events_through_ancestry_without_over_counting()
         $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
         $notFired = new class($bus = Config::instance()->commandBus(), 'nothing') extends CommandBusEvent {};
 
-        $this->event->fire(Event::fire(new Context()));
+        $this->event->fire(Event::new(new Context()));
         $this->event->fire($child);
         $this->event->fire(new CommandBusEvent($bus, 'new test'));
 
@@ -74,8 +85,8 @@ public function it_returns_events_in_the_order_that_they_were_fired()
 
         $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
 
-        $this->event->fire($first = Event::fire(new Context()));
-        $this->event->fire($second = Event::fire(new Context()));
+        $this->event->fire($first = Event::new(new Context()));
+        $this->event->fire($second = Event::new(new Context()));
         $this->event->fire($child);
         $this->event->fire($forth = new CommandBusEvent($bus, 'new test'));
 
@@ -93,8 +104,8 @@ public function it_can_return_all_fired_events_by_name()
 
         $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
 
-        $this->event->fire($first = Event::fire(new Context()));
-        $this->event->fire($second = Event::fire(new Context()));
+        $this->event->fire($first = Event::new(new Context()));
+        $this->event->fire($second = Event::new(new Context()));
         $this->event->fire($child);
 
         $this->assertCount(1, $firedEvents = $this->history->get(get_class($child)));
@@ -107,7 +118,7 @@ public function it_can_return_all_fired_events_by_name()
     }
 
     /** @test */
-    public function it_returns_an_empty_array_if_no_events_are_found_or_calls_default_value_if_callable_or_returns_it()
+    public function it_returns_an_empty_array_if_no_events_are_found_calls_default_value_if_callable_or_returns_it()
     {
         $this->assertEmpty($this->history->get(Event::class));
         $this->assertSame('nope', $this->history->get(Event::class, 'nope'));
@@ -115,4 +126,28 @@ public function it_returns_an_empty_array_if_no_events_are_found_or_calls_defaul
             return 'i returned';
         }));
     }
+
+    /** @test */
+    public function it_can_return_an_array_of_fired_events_by_the_listener_name_triggered()
+    {
+        $listener = $this->makeListener('listener', new class {
+            public $order = [];
+        });
+
+        $this->event->trackEvents()
+            ->subscribe(Event::class, $listener)
+            ->fire($event = Event::new(new Context()));
+
+        $this->assertSame($event, $this->history->getFromListener(get_class($listener))[0]->event());
+    }
+
+    /** @test */
+    public function it_returns_an_empty_array_if_no_listeners_are_found_calls_default_value_if_callable_or_returns_it()
+    {
+        $this->assertEmpty($this->history->getFromListener(Listener::class));
+        $this->assertSame('nope', $this->history->getFromListener(Listener::class, 'nope'));
+        $this->assertSame('i returned', $this->history->getFromListener(Listener::class, function () {
+            return 'i returned';
+        }));
+    }
 }
diff --git a/tests/WmiScripting/Support/Event/EventProviderTest.php b/tests/WmiScripting/Support/Event/EventProviderTest.php
index bf5bfaa..58e65a0 100644
--- a/tests/WmiScripting/Support/Event/EventProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventProviderTest.php
@@ -2,11 +2,11 @@
 
 namespace Tests\WmiScripting\Support\Event;
 
-use PhpWinTools\WmiScripting\Support\Events\Context;
-use PhpWinTools\WmiScripting\Support\Events\Event;
-use PhpWinTools\WmiScripting\Support\Events\Listener;
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\Context;
+use PhpWinTools\WmiScripting\Support\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Events\EventProvider;
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
 
@@ -61,7 +61,7 @@ public function it_runs_listeners_when_an_applicable_event_is_fired()
 
         $this->event->subscribe(get_class($child), $this->makeListener('listener', $subject));
 
-        $this->event->fire(Event::fire(new Context()));
+        $this->event->fire(Event::new(new Context()));
         $this->assertEmpty($subject->order);
 
         $this->event->fire($child);

From 4bb5a447fb1c6e511073cb20b7521e972ad1bf24 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Wed, 18 Sep 2019 19:20:29 -0400
Subject: [PATCH 20/33] WIP

---
 src/WmiScripting/Containers/Container.php     | 19 ++++++++++++++++--
 .../Support/Events/EventCacheContainer.php    | 20 +++++++++++++++++--
 .../Support/Events/EventHistoryProvider.php   |  6 ++++++
 .../Event/EventHistoryProviderTest.php        |  3 ++-
 4 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/src/WmiScripting/Containers/Container.php b/src/WmiScripting/Containers/Container.php
index 61ea802..780ada0 100644
--- a/src/WmiScripting/Containers/Container.php
+++ b/src/WmiScripting/Containers/Container.php
@@ -3,6 +3,7 @@
 namespace PhpWinTools\WmiScripting\Containers;
 
 use Illuminate\Support\Arr;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 
 class Container
 {
@@ -15,9 +16,23 @@ public function get($key, $default = null)
 
     public function append($key, $value)
     {
-        if (is_null($this->get($key))) {
-            //
+        $index = null;
+
+        if (is_null($array = $this->get($key))) {
+            $index = 0;
+        }
+
+        if ($array && Arr::isAssoc($array)) {
+            throw new InvalidArgumentException("Cannot append to {$key} because it is associative.");
+        }
+
+        if ($array) {
+            $index = count($array) - 1;
         }
+
+        $this->set("{$key}.{$index}", $value);
+
+        return $this;
     }
 
     public function set($key, $value = null)
diff --git a/src/WmiScripting/Support/Events/EventCacheContainer.php b/src/WmiScripting/Support/Events/EventCacheContainer.php
index d31f4f4..6c0b8ee 100644
--- a/src/WmiScripting/Support/Events/EventCacheContainer.php
+++ b/src/WmiScripting/Support/Events/EventCacheContainer.php
@@ -6,8 +6,24 @@
 
 class EventCacheContainer extends Container
 {
-    public function addActual(Event $event)
+    protected $fired_event_key = 'actual';
+
+    protected $event_ancestor_key = 'ancestors';
+
+    protected $event_listeners_key = 'listeners';
+
+    /** TODO: Implement PSR Cache for this */
+    public function __construct()
     {
-//        $this->set('actual', )
+        $this->set($this->fired_event_key);
+        $this->set($this->event_ancestor_key);
+        $this->set($this->event_listeners_key);
+    }
+
+    public function addActual(string $event_name, int $index)
+    {
+        $this->append("{$this->fired_event_key}.{$event_name}", $index);
+
+        return $this;
     }
 }
diff --git a/src/WmiScripting/Support/Events/EventHistoryProvider.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
index dd90642..5d46f2b 100644
--- a/src/WmiScripting/Support/Events/EventHistoryProvider.php
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -35,6 +35,11 @@ public function container()
         return $this->eventContainer;
     }
 
+    public function cache()
+    {
+        return $this->event_cache;
+    }
+
     /**
      * @param Event            $event
      * @param array|Listener[] $listeners
@@ -196,6 +201,7 @@ protected function findEvents(array $event_keys = [], $default = [])
     protected function cacheEvent(Event $event, array $listeners = []): self
     {
         $this->event_cache['actual'][get_class($event)][] = $this->lastIndex();
+        $this->eventContainer->addActual(get_class($event), $this->lastIndex());
 
         $this->cacheEventSet($event, 'ancestors', class_parents($event), function ($item) {
             return $item;
diff --git a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
index 929c812..2fa28e8 100644
--- a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
@@ -38,7 +38,8 @@ public function it_can_count_events()
         $this->event->fire(new Event(new Context()));
         $this->event->fire(new Event(new Context()));
 
-//        dd($this->history->container());
+        dump($this->history->cache());
+        dd($this->history->container());
 
         $this->assertEquals(3, $this->history->count());
         $this->assertEquals(3, $this->history->eventCount());

From c650c9cc55a30349152d37cd02f284c6c226f6a5 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Thu, 19 Sep 2019 14:28:13 -0400
Subject: [PATCH 21/33] WIP

---
 composer.json                                 |   3 +-
 src/WmiScripting/Configuration/Config.php     |  39 ++++---
 .../CacheInvalidArgumentException.php         |  10 ++
 .../Support/Cache/ArrayDriver.php             | 101 ++++++++++++++++++
 .../Support/Cache/CacheDriver.php             |  18 ++++
 .../Support/Cache/CacheProvider.php           |  61 +++++++++++
 .../Support/Events/EventCache.php             |  10 ++
 .../Support/Events/EventCacheContainer.php    |  29 -----
 .../Support/Events/EventHistoryProvider.php   |  11 +-
 src/WmiScripting/config/bootstrap.php         |  13 ++-
 src/WmiScripting/config/providers.php         |  10 +-
 11 files changed, 248 insertions(+), 57 deletions(-)
 create mode 100644 src/WmiScripting/Exceptions/CacheInvalidArgumentException.php
 create mode 100644 src/WmiScripting/Support/Cache/ArrayDriver.php
 create mode 100644 src/WmiScripting/Support/Cache/CacheDriver.php
 create mode 100644 src/WmiScripting/Support/Cache/CacheProvider.php
 create mode 100644 src/WmiScripting/Support/Events/EventCache.php
 delete mode 100644 src/WmiScripting/Support/Events/EventCacheContainer.php

diff --git a/composer.json b/composer.json
index c2fe30c..70c5d1f 100644
--- a/composer.json
+++ b/composer.json
@@ -29,7 +29,8 @@
         "ext-com_dotnet": "*",
         "illuminate/contracts": "^5.8",
         "symfony/process": "^4.2",
-        "tightenco/collect": "^5.8"
+        "tightenco/collect": "^5.8",
+        "psr/simple-cache": "^1.0"
     },
     "require-dev": {
         "fzaninotto/faker": "^1.4",
diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index 11bb69e..55d304d 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -40,7 +40,7 @@ public function __construct(array $config = [], Resolver $resolver = null)
      * Returns current instance if available and merges any available configuration.
      * This is the method used throughout the library to retrieve configuration.
      *
-     * @param array         $items
+     * @param array $items
      * @param Resolver|null $resolver
      *
      * @return Config
@@ -63,7 +63,7 @@ public static function instance(array $items = [], Resolver $resolver = null)
      * to the instance will be lost. If it's in test mode it will remain until you explicitly remove it.
      * You should never need to reference this directly except inside of tests.
      *
-     * @param array         $items
+     * @param array $items
      * @param Resolver|null $resolver
      *
      * @return Config
@@ -76,7 +76,7 @@ public static function newInstance(array $items = [], Resolver $resolver = null)
     /**
      * This merges in a testing configuration. Any instance from this point will use that configuration.
      *
-     * @param array         $items
+     * @param array $items
      * @param Resolver|null $resolver
      *
      * @return Config
@@ -95,7 +95,7 @@ public static function testInstance(array $items = [], Resolver $resolver = null
     /**
      * Same as endTest, but also returns a fresh instance.
      *
-     * @param array         $items
+     * @param array $items
      * @param Resolver|null $resolver
      *
      * @return Config
@@ -125,7 +125,7 @@ public function endTest()
      * Returns the Resolver if no class is specified otherwise it attempts to resolve the given class.
      *
      * @param string|null $class
-     * @param mixed       ...$parameters
+     * @param mixed ...$parameters
      *
      * @return Resolver|mixed|null
      */
@@ -232,7 +232,7 @@ public function apiObjects()
     }
 
     /**
-     * @param string                 $abstract_class
+     * @param string $abstract_class
      * @param string|callable|object $concrete_class
      *
      * @return Config
@@ -263,7 +263,7 @@ public function com()
     }
 
     /**
-     * @param string                 $abstract_class
+     * @param string $abstract_class
      * @param string|callable|object $concrete_class
      *
      * @return Config
@@ -302,7 +302,7 @@ public function commandBus()
     /**
      * Return an already registered provider or instantiate it from configuration when $default is null.
      *
-     * @param string     $alias
+     * @param string $alias
      * @param null|mixed $default
      *
      * @return mixed
@@ -316,21 +316,30 @@ public function getProvider($alias, $default = null)
         return $this->get("providers.registered.{$alias}", $default);
     }
 
+    public function getCacheDriver($provider = null)
+    {
+        $provider = is_null($provider) ? $provider : "{$provider}.";
+
+        $driver = $this->get("{$provider}cache.driver");
+
+        return new $driver($this);
+    }
+
     public function shouldTrackEvents()
     {
-        return $this->get('events.track', false);
+        return $this->get('event.track', false);
     }
 
     public function trackEvents()
     {
-        $this->set('events.track', true);
+        $this->set('event.track', true);
 
         return $this;
     }
 
     public function doNotTrackEvents()
     {
-        $this->set('events.track', false);
+        $this->set('event.track', false);
 
         return $this;
     }
@@ -432,7 +441,7 @@ public function setDefaultConnection(string $name)
     public function registerProvider($provider_alias, $instance = null)
     {
         if (!is_object($instance) && ($instance = $this->getProvider($provider_alias, false)) === false) {
-            $instance = $this->get("providers.{$provider_alias}");
+            $instance = $this->get("providers.bootstrap.{$provider_alias}");
             $instance = new $instance($this);
         }
 
@@ -459,10 +468,8 @@ protected function boot(array $config)
     protected function registerProviders()
     {
         array_map(function ($alias) {
-            if ($alias !== 'registered') {
-                $this->registerProvider($alias);
-            }
-        }, array_keys($this->get('providers', [])));
+            $this->registerProvider($alias);
+        }, array_keys($this->get('providers.bootstrap', [])));
 
         return $this;
     }
diff --git a/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php b/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php
new file mode 100644
index 0000000..224e21a
--- /dev/null
+++ b/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Exceptions;
+
+use Psr\SimpleCache\InvalidArgumentException as BaseException;
+
+class CacheInvalidArgumentException extends InvalidArgumentException implements BaseException
+{
+
+}
diff --git a/src/WmiScripting/Support/Cache/ArrayDriver.php b/src/WmiScripting/Support/Cache/ArrayDriver.php
new file mode 100644
index 0000000..242d5c9
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/ArrayDriver.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache;
+
+use Psr\SimpleCache\CacheInterface;
+use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
+
+class ArrayDriver extends CacheDriver implements CacheInterface
+{
+    protected $store = [];
+
+    public function get($key, $default = null)
+    {
+        if ($this->has($key)) {
+            return $this->store[$key];
+        }
+
+        return is_callable($default) ? $default() : $default;
+    }
+
+    public function set($key, $value, $ttl = null)
+    {
+        $this->store[$this->validateKey($key)] = $value;
+
+        return true;
+    }
+
+    public function delete($key)
+    {
+        if ($this->has($key)) {
+            unset($this->store[$key]);
+            return true;
+        }
+
+        return false;
+    }
+
+    public function clear()
+    {
+        $this->store = [];
+
+        return true;
+    }
+
+    public function getMultiple($keys, $default = null)
+    {
+        $result = [];
+
+        foreach ($keys as $key) {
+            $result[$key] = $this->get($key, $default);
+        }
+
+        return $result;
+    }
+
+    public function setMultiple($values, $ttl = null)
+    {
+        foreach ($values as $key => $value) {
+            $this->set($key, $value);
+        }
+
+        return true;
+    }
+
+    public function deleteMultiple($keys)
+    {
+        foreach ($keys as $key) {
+            if ($this->delete($key)) {
+                continue;
+            } else {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public function has($key)
+    {
+        return array_key_exists($this->validateKey($key), $this->store);
+    }
+
+    public function exists($key)
+    {
+        return $this->has($key);
+    }
+
+    public function doesNotExist($key)
+    {
+        return $this->exists($key) === false;
+    }
+
+    protected function validateKey($key)
+    {
+        if (is_string($key)) {
+            return $key;
+        }
+
+        throw new CacheInvalidArgumentException("{$key} is not a valid key");
+    }
+}
diff --git a/src/WmiScripting/Support/Cache/CacheDriver.php b/src/WmiScripting/Support/Cache/CacheDriver.php
new file mode 100644
index 0000000..c32f28a
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/CacheDriver.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache;
+
+use Psr\SimpleCache\CacheInterface;
+use PhpWinTools\WmiScripting\Configuration\Config;
+
+abstract class CacheDriver implements CacheInterface
+{
+    protected $config;
+
+    protected $store;
+
+    public function __construct(Config $config = null)
+    {
+        $this->config = $config ?? Config::instance();
+    }
+}
diff --git a/src/WmiScripting/Support/Cache/CacheProvider.php b/src/WmiScripting/Support/Cache/CacheProvider.php
new file mode 100644
index 0000000..9f218e7
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/CacheProvider.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache;
+
+use Psr\SimpleCache\CacheInterface;
+use PhpWinTools\WmiScripting\Configuration\Config;
+
+class CacheProvider implements CacheInterface
+{
+    protected $config;
+
+    /** @var CacheDriver */
+    protected $cacheDriver;
+
+    public function __construct(Config $config = null, CacheDriver $cacheDriver = null)
+    {
+        $this->config = $config ?? Config::instance();
+
+        $this->cacheDriver = $cacheDriver ?? $this->config->getCacheDriver();
+    }
+
+    public function get($key, $default = null)
+    {
+        return $this->cacheDriver->get($key, $default);
+    }
+
+    public function set($key, $value, $ttl = null)
+    {
+        return $this->cacheDriver->set($key, $value, $ttl);
+    }
+
+    public function delete($key)
+    {
+        return $this->cacheDriver->delete($key);
+    }
+
+    public function clear()
+    {
+        return $this->cacheDriver->clear();
+    }
+
+    public function getMultiple($keys, $default = null)
+    {
+        return $this->cacheDriver->getMultiple($keys, $default);
+    }
+
+    public function setMultiple($values, $ttl = null)
+    {
+        return $this->cacheDriver->setMultiple($values, $ttl);
+    }
+
+    public function deleteMultiple($keys)
+    {
+        return $this->cacheDriver->deleteMultiple($keys);
+    }
+
+    public function has($key)
+    {
+        return $this->cacheDriver->has($key);
+    }
+}
diff --git a/src/WmiScripting/Support/Events/EventCache.php b/src/WmiScripting/Support/Events/EventCache.php
new file mode 100644
index 0000000..eb714e1
--- /dev/null
+++ b/src/WmiScripting/Support/Events/EventCache.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
+
+class EventCache extends ArrayDriver
+{
+
+}
diff --git a/src/WmiScripting/Support/Events/EventCacheContainer.php b/src/WmiScripting/Support/Events/EventCacheContainer.php
deleted file mode 100644
index 6c0b8ee..0000000
--- a/src/WmiScripting/Support/Events/EventCacheContainer.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-namespace PhpWinTools\WmiScripting\Support\Events;
-
-use PhpWinTools\WmiScripting\Containers\Container;
-
-class EventCacheContainer extends Container
-{
-    protected $fired_event_key = 'actual';
-
-    protected $event_ancestor_key = 'ancestors';
-
-    protected $event_listeners_key = 'listeners';
-
-    /** TODO: Implement PSR Cache for this */
-    public function __construct()
-    {
-        $this->set($this->fired_event_key);
-        $this->set($this->event_ancestor_key);
-        $this->set($this->event_listeners_key);
-    }
-
-    public function addActual(string $event_name, int $index)
-    {
-        $this->append("{$this->fired_event_key}.{$event_name}", $index);
-
-        return $this;
-    }
-}
diff --git a/src/WmiScripting/Support/Events/EventHistoryProvider.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
index 5d46f2b..730d77e 100644
--- a/src/WmiScripting/Support/Events/EventHistoryProvider.php
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -5,6 +5,7 @@
 use Closure;
 use Countable;
 use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Cache\CacheDriver;
 
 class EventHistoryProvider implements Countable
 {
@@ -21,18 +22,17 @@ class EventHistoryProvider implements Countable
         'listeners' => [],
     ];
 
-    protected $eventContainer;
+    protected $eventCache;
 
-    public function __construct(Config $config = null)
+    public function __construct(Config $config = null, CacheDriver $cacheDriver = null)
     {
         $this->config = $config ?? Config::instance();
-
-        $this->eventContainer = new EventCacheContainer();
+        $this->eventCache = $cacheDriver ?? $this->config->getCacheDriver('event');
     }
 
     public function container()
     {
-        return $this->eventContainer;
+        return $this->eventCache;
     }
 
     public function cache()
@@ -201,7 +201,6 @@ protected function findEvents(array $event_keys = [], $default = [])
     protected function cacheEvent(Event $event, array $listeners = []): self
     {
         $this->event_cache['actual'][get_class($event)][] = $this->lastIndex();
-        $this->eventContainer->addActual(get_class($event), $this->lastIndex());
 
         $this->cacheEventSet($event, 'ancestors', class_parents($event), function ($item) {
             return $item;
diff --git a/src/WmiScripting/config/bootstrap.php b/src/WmiScripting/config/bootstrap.php
index 0728740..c571d7c 100644
--- a/src/WmiScripting/config/bootstrap.php
+++ b/src/WmiScripting/config/bootstrap.php
@@ -3,8 +3,14 @@
 use PhpWinTools\Support\COM\ComWrapper;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
+use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
+use PhpWinTools\WmiScripting\Support\Events\EventCache;
 
 return [
+    'cache' => [
+        'driver' => ArrayDriver::class,
+    ],
+
     'com' => [
         'com_class'                 => COM::class,
         'variant_class'             => VARIANT::class,
@@ -13,8 +19,11 @@
         VariantWrapper::class       => VariantWrapper::class,
     ],
 
-    'events' => [
-        'track' => false,
+    'event' => [
+        'track'  => false,
+        'cache'  => [
+            'driver' => EventCache::class,
+        ]
     ],
 
     'providers' => include('providers.php'),
diff --git a/src/WmiScripting/config/providers.php b/src/WmiScripting/config/providers.php
index 844acb3..a36b8ca 100644
--- a/src/WmiScripting/config/providers.php
+++ b/src/WmiScripting/config/providers.php
@@ -1,11 +1,15 @@
 <?php
 
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Cache\CacheProvider;
 use PhpWinTools\WmiScripting\Support\Events\EventProvider;
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
 
 return [
-    'bus'           => CommandBus::class,
-    'event'         => EventProvider::class,
-    'event_history' => EventHistoryProvider::class,
+    'bootstrap' => [
+        'bus'           => CommandBus::class,
+//        'cache'         => CacheProvider::class,
+        'event'         => EventProvider::class,
+        'event_history' => EventHistoryProvider::class,
+    ],
 ];

From b4fa87bdec6c6ed2612a5438b1fa994d362a287b Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Fri, 20 Sep 2019 10:49:30 -0400
Subject: [PATCH 22/33] Further complicate Config but added some refactorings
 that can be abstracted

---
 src/WmiScripting/Configuration/Config.php     | 133 ++++++++++++++----
 .../Support/Cache/ArrayDriver.php             |  15 +-
 .../Support/Events/EventCache.php             |  10 --
 .../Support/Events/EventHistoryProvider.php   |  30 ++--
 src/WmiScripting/config/bootstrap.php         |   4 -
 src/WmiScripting/config/providers.php         |  10 +-
 .../Event/EventHistoryProviderTest.php        |   3 +-
 .../Support/Event/EventProviderTest.php       |   4 +-
 8 files changed, 127 insertions(+), 82 deletions(-)
 delete mode 100644 src/WmiScripting/Support/Events/EventCache.php

diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index 55d304d..a91a5db 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -2,15 +2,17 @@
 
 namespace PhpWinTools\WmiScripting\Configuration;
 
+use Closure;
 use Illuminate\Support\Arr;
 use PhpWinTools\Support\COM\ComWrapper;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
-use PhpWinTools\WmiScripting\Containers\Connections;
 use PhpWinTools\WmiScripting\Containers\Container;
+use PhpWinTools\WmiScripting\Containers\Connections;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
 use PhpWinTools\WmiScripting\Connections\ComConnection;
 use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
 use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
 use PhpWinTools\WmiScripting\Exceptions\UnresolvableClassException;
@@ -40,7 +42,7 @@ public function __construct(array $config = [], Resolver $resolver = null)
      * Returns current instance if available and merges any available configuration.
      * This is the method used throughout the library to retrieve configuration.
      *
-     * @param array $items
+     * @param array         $items
      * @param Resolver|null $resolver
      *
      * @return Config
@@ -63,7 +65,7 @@ public static function instance(array $items = [], Resolver $resolver = null)
      * to the instance will be lost. If it's in test mode it will remain until you explicitly remove it.
      * You should never need to reference this directly except inside of tests.
      *
-     * @param array $items
+     * @param array         $items
      * @param Resolver|null $resolver
      *
      * @return Config
@@ -76,7 +78,7 @@ public static function newInstance(array $items = [], Resolver $resolver = null)
     /**
      * This merges in a testing configuration. Any instance from this point will use that configuration.
      *
-     * @param array $items
+     * @param array         $items
      * @param Resolver|null $resolver
      *
      * @return Config
@@ -95,7 +97,7 @@ public static function testInstance(array $items = [], Resolver $resolver = null
     /**
      * Same as endTest, but also returns a fresh instance.
      *
-     * @param array $items
+     * @param array         $items
      * @param Resolver|null $resolver
      *
      * @return Config
@@ -125,7 +127,7 @@ public function endTest()
      * Returns the Resolver if no class is specified otherwise it attempts to resolve the given class.
      *
      * @param string|null $class
-     * @param mixed ...$parameters
+     * @param mixed       ...$parameters
      *
      * @return Resolver|mixed|null
      */
@@ -193,12 +195,12 @@ public function resolveFromStack($abstract)
     /**
      * Add new resolvable to the end of the stack.
      *
-     * @param $abstract
-     * @param $concrete
+     * @param string $abstract
+     * @param        $concrete
      *
      * @return Config
      */
-    public function addResolvable($abstract, $concrete)
+    public function addResolvable(string $abstract, $concrete)
     {
         $this->resolve_stack[] = [$abstract => $concrete];
 
@@ -208,11 +210,11 @@ public function addResolvable($abstract, $concrete)
     /**
      * Check stack for resolvable. There may be a chance for caching pointers for resolvable abstracts.
      *
-     * @param $abstract
+     * @param string $abstract
      *
      * @return bool
      */
-    public function hasResolvable($abstract): bool
+    public function hasResolvable(string $abstract): bool
     {
         foreach ($this->resolve_stack as $key => $resolvable) {
             if (array_key_exists($abstract, $resolvable)) {
@@ -232,7 +234,7 @@ public function apiObjects()
     }
 
     /**
-     * @param string $abstract_class
+     * @param string                 $abstract_class
      * @param string|callable|object $concrete_class
      *
      * @return Config
@@ -263,7 +265,7 @@ public function com()
     }
 
     /**
-     * @param string $abstract_class
+     * @param string                 $abstract_class
      * @param string|callable|object $concrete_class
      *
      * @return Config
@@ -280,7 +282,7 @@ public function addComObject(string $abstract_class, $concrete_class)
      */
     public function eventProvider()
     {
-        return $this->getProvider('event');
+        return $this->getBoundProvider('event');
     }
 
     /**
@@ -288,7 +290,7 @@ public function eventProvider()
      */
     public function eventHistoryProvider()
     {
-        return $this->getProvider('event_history');
+        return $this->getBoundProvider('event_history');
     }
 
     /**
@@ -296,24 +298,103 @@ public function eventHistoryProvider()
      */
     public function commandBus()
     {
-        return $this->getProvider('bus');
+        return $this->getBoundProvider('bus');
+    }
+
+    /**
+     * Returns the given binding if exists and provided, or all bindings if not given. If the value cannot be found
+     * it will return $default which can be a Closure that can be used to resolve the binding.
+     *
+     * @param string|null $abstract
+     * @param array       $default
+     *
+     * @return mixed
+     */
+    public function bindings(string $abstract = null, $default = [])
+    {
+        $abstract = is_null($abstract) ? $abstract : ".{$abstract}";
+
+        return $this->get("bindings{$abstract}", $default);
+    }
+
+    /**
+     * Stores the given abstract with a reference to the given instance.
+     *
+     * @param string $abstract
+     * @param        $concrete
+     *
+     * @return Config
+     */
+    public function bind(string $abstract, $concrete)
+    {
+        return $this->set("bindings.{$abstract}", $concrete);
+    }
+
+    /**
+     * Resolves the given concrete with either the given constructor arguments. If the first argument is a Closure
+     * it will be used to resolve the given concrete.
+     *
+     * @param          $class
+     * @param mixed ...$constructor
+     *
+     * @return mixed
+     */
+    public function make(string $class, ...$constructor)
+    {
+        if (isset($constructor[0]) && $constructor[0] instanceof Closure) {
+            $make = Arr::pull($constructor, 0);
+        }
+
+        $make = $make ?? function ($class) use ($constructor) {
+            return new $class($this, ...$constructor);
+        };
+
+        return $make($class, $this);
     }
 
     /**
-     * Return an already registered provider or instantiate it from configuration when $default is null.
+     * @param string|null $alias
+     * @param array       $default
      *
-     * @param string $alias
+     * @return mixed
+     */
+    public function providers(string $alias = null, $default = [])
+    {
+        $alias = is_null($alias) ? $alias : ".{$alias}";
+
+        return $this->get("providers{$alias}", $default);
+    }
+
+    /**
+     * Return an already registered provider or instantiates it from configuration when $default is null.
+     *
+     * @param string     $alias
      * @param null|mixed $default
      *
      * @return mixed
      */
-    public function getProvider($alias, $default = null)
+    public function getBoundProvider($alias, $default = null)
     {
         $default = $default ?? function () use ($alias) {
             return $this->registerProvider($alias);
         };
 
-        return $this->get("providers.registered.{$alias}", $default);
+        return $this->bindings($alias, $default);
+    }
+
+    /**
+     * @param string              $alias
+     * @param array|mixed|Closure ...$constructor
+     *
+     * @return mixed
+     */
+    public function makeProvider(string $alias, ...$constructor)
+    {
+        if (($provider = $this->providers($alias, false)) === false) {
+            throw new InvalidArgumentException("{$alias} is not a valid provider.");
+        }
+
+        return $this->make($provider, ...$constructor);
     }
 
     public function getCacheDriver($provider = null)
@@ -438,14 +519,13 @@ public function setDefaultConnection(string $name)
         return $this->set('wmi.connections.default', $name);
     }
 
-    public function registerProvider($provider_alias, $instance = null)
+    public function registerProvider($alias, $instance = null)
     {
-        if (!is_object($instance) && ($instance = $this->getProvider($provider_alias, false)) === false) {
-            $instance = $this->get("providers.bootstrap.{$provider_alias}");
-            $instance = new $instance($this);
+        if (!is_object($instance)) {
+            $instance = $this->makeProvider($alias);
         }
 
-        $this->set("providers.registered.{$provider_alias}", $instance);
+        $this->bind("{$alias}", $instance);
 
         return $instance;
     }
@@ -461,6 +541,7 @@ protected function boot(array $config)
         }
 
         $this->merge(include(__DIR__ . '/../config/bootstrap.php'))
+            ->set('bindings', [])
             ->registerProviders()
             ->bootConnections();
     }
@@ -469,7 +550,7 @@ protected function registerProviders()
     {
         array_map(function ($alias) {
             $this->registerProvider($alias);
-        }, array_keys($this->get('providers.bootstrap', [])));
+        }, array_keys($this->providers()));
 
         return $this;
     }
diff --git a/src/WmiScripting/Support/Cache/ArrayDriver.php b/src/WmiScripting/Support/Cache/ArrayDriver.php
index 242d5c9..5e93688 100644
--- a/src/WmiScripting/Support/Cache/ArrayDriver.php
+++ b/src/WmiScripting/Support/Cache/ArrayDriver.php
@@ -2,6 +2,7 @@
 
 namespace PhpWinTools\WmiScripting\Support\Cache;
 
+use Illuminate\Support\Arr;
 use Psr\SimpleCache\CacheInterface;
 use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
 
@@ -11,24 +12,18 @@ class ArrayDriver extends CacheDriver implements CacheInterface
 
     public function get($key, $default = null)
     {
-        if ($this->has($key)) {
-            return $this->store[$key];
-        }
-
-        return is_callable($default) ? $default() : $default;
+        return Arr::get($this->store, $this->validateKey($key), $default);
     }
 
     public function set($key, $value, $ttl = null)
     {
-        $this->store[$this->validateKey($key)] = $value;
-
-        return true;
+        return is_array(Arr::set($this->store, $this->validateKey($key), $value));
     }
 
     public function delete($key)
     {
         if ($this->has($key)) {
-            unset($this->store[$key]);
+            Arr::forget($this->store, $key);
             return true;
         }
 
@@ -77,7 +72,7 @@ public function deleteMultiple($keys)
 
     public function has($key)
     {
-        return array_key_exists($this->validateKey($key), $this->store);
+        return Arr::has($this->store, $this->validateKey($key));
     }
 
     public function exists($key)
diff --git a/src/WmiScripting/Support/Events/EventCache.php b/src/WmiScripting/Support/Events/EventCache.php
deleted file mode 100644
index eb714e1..0000000
--- a/src/WmiScripting/Support/Events/EventCache.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace PhpWinTools\WmiScripting\Support\Events;
-
-use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
-
-class EventCache extends ArrayDriver
-{
-
-}
diff --git a/src/WmiScripting/Support/Events/EventHistoryProvider.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
index 730d77e..0e63da5 100644
--- a/src/WmiScripting/Support/Events/EventHistoryProvider.php
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -5,7 +5,6 @@
 use Closure;
 use Countable;
 use PhpWinTools\WmiScripting\Configuration\Config;
-use PhpWinTools\WmiScripting\Support\Cache\CacheDriver;
 
 class EventHistoryProvider implements Countable
 {
@@ -22,17 +21,9 @@ class EventHistoryProvider implements Countable
         'listeners' => [],
     ];
 
-    protected $eventCache;
-
-    public function __construct(Config $config = null, CacheDriver $cacheDriver = null)
+    public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
-        $this->eventCache = $cacheDriver ?? $this->config->getCacheDriver('event');
-    }
-
-    public function container()
-    {
-        return $this->eventCache;
     }
 
     public function cache()
@@ -202,22 +193,17 @@ protected function cacheEvent(Event $event, array $listeners = []): self
     {
         $this->event_cache['actual'][get_class($event)][] = $this->lastIndex();
 
-        $this->cacheEventSet($event, 'ancestors', class_parents($event), function ($item) {
-            return $item;
-        });
-
-        $this->cacheEventSet($event, 'listeners', $listeners, function (Listener $listener) {
-            return get_class($listener);
-        });
-
-        return $this;
+        return $this->cacheSet($event, 'ancestors', class_parents($event))->cacheSet($event, 'listeners', $listeners);
     }
 
-    protected function cacheEventSet(Event $event, string $set_name, array $set, Closure $getItemKey)
+    protected function cacheSet(Event $event, string $set_name, array $set)
     {
-        array_map(function ($item) use ($set_name, $getItemKey) {
-            $this->event_cache[$set_name][$getItemKey($item)][] = $this->lastIndex();
+        array_map(function ($item) use ($set_name) {
+            $item = is_object($item) ? get_class($item) : $item;
+            $this->event_cache[$set_name][$item][] = $this->lastIndex();
         }, $set);
+
+        return $this;
     }
 
     /**
diff --git a/src/WmiScripting/config/bootstrap.php b/src/WmiScripting/config/bootstrap.php
index c571d7c..bf33e9f 100644
--- a/src/WmiScripting/config/bootstrap.php
+++ b/src/WmiScripting/config/bootstrap.php
@@ -4,7 +4,6 @@
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
 use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
-use PhpWinTools\WmiScripting\Support\Events\EventCache;
 
 return [
     'cache' => [
@@ -21,9 +20,6 @@
 
     'event' => [
         'track'  => false,
-        'cache'  => [
-            'driver' => EventCache::class,
-        ]
     ],
 
     'providers' => include('providers.php'),
diff --git a/src/WmiScripting/config/providers.php b/src/WmiScripting/config/providers.php
index a36b8ca..bf18e4a 100644
--- a/src/WmiScripting/config/providers.php
+++ b/src/WmiScripting/config/providers.php
@@ -6,10 +6,8 @@
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
 
 return [
-    'bootstrap' => [
-        'bus'           => CommandBus::class,
-//        'cache'         => CacheProvider::class,
-        'event'         => EventProvider::class,
-        'event_history' => EventHistoryProvider::class,
-    ],
+    'bus'           => CommandBus::class,
+    'cache'         => CacheProvider::class,
+    'event'         => EventProvider::class,
+    'event_history' => EventHistoryProvider::class,
 ];
diff --git a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
index 2fa28e8..3b095de 100644
--- a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
@@ -38,8 +38,7 @@ public function it_can_count_events()
         $this->event->fire(new Event(new Context()));
         $this->event->fire(new Event(new Context()));
 
-        dump($this->history->cache());
-        dd($this->history->container());
+//        dd(Config::instance());
 
         $this->assertEquals(3, $this->history->count());
         $this->assertEquals(3, $this->history->eventCount());
diff --git a/tests/WmiScripting/Support/Event/EventProviderTest.php b/tests/WmiScripting/Support/Event/EventProviderTest.php
index 58e65a0..43b3781 100644
--- a/tests/WmiScripting/Support/Event/EventProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventProviderTest.php
@@ -44,10 +44,10 @@ public function it_can_instantiate()
     public function it_registers_itself_with_the_config_on_instantiation()
     {
         $config = new Config();
-        $configEvent = $config->getProvider('event');
+        $configEvent = $config->getBoundProvider('event');
 
         $this->assertNotSame($configEvent, $newEvent = new EventProvider());
-        $this->assertSame($newEvent, $config->getProvider('event'));
+        $this->assertSame($newEvent, $config->getBoundProvider('event'));
     }
 
     /** @test */

From a5e7267f79fc5e44d53a1986efddccfaa9d62882 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Fri, 20 Sep 2019 10:51:38 -0400
Subject: [PATCH 23/33] Styling

---
 src/WmiScripting/Support/Events/EventHistoryProvider.php | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/WmiScripting/Support/Events/EventHistoryProvider.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
index 0e63da5..b1092b0 100644
--- a/src/WmiScripting/Support/Events/EventHistoryProvider.php
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -16,7 +16,7 @@ class EventHistoryProvider implements Countable
 
     /** @var array */
     protected $event_cache = [
-        'actual' => [],
+        'actual'    => [],
         'ancestors' => [],
         'listeners' => [],
     ];
@@ -26,10 +26,6 @@ public function __construct(Config $config = null)
         $this->config = $config ?? Config::instance();
     }
 
-    public function cache()
-    {
-        return $this->event_cache;
-    }
 
     /**
      * @param Event            $event

From 03304c5537bc918a460482d71a4d1093d823b943 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Fri, 20 Sep 2019 16:11:24 -0400
Subject: [PATCH 24/33] WIP

---
 src/WmiScripting/Configuration/Config.php     |  76 +++++++--
 .../Support/Events/EventProvider.php          |   5 +-
 src/WmiScripting/Support/functions.php        | 158 ++++++++++++------
 .../Event/EventHistoryProviderTest.php        |   2 -
 .../Support/Event/EventProviderTest.php       |  12 +-
 5 files changed, 171 insertions(+), 82 deletions(-)

diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index a91a5db..917b5b2 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -11,7 +11,10 @@
 use PhpWinTools\WmiScripting\Containers\Connections;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
 use PhpWinTools\WmiScripting\Connections\ComConnection;
+use function PhpWinTools\WmiScripting\Support\is_closure;
+use function PhpWinTools\WmiScripting\Support\if_not_null;
 use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use function PhpWinTools\WmiScripting\Support\is_not_closure;
 use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
 use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
@@ -22,20 +25,21 @@ class Config extends Container
     /** @var Config|null */
     protected static $instance = null;
 
+    /** @var bool */
     protected static $test_mode = false;
 
+    /** @var Resolver */
     protected $resolver;
 
-    protected $eventProvider;
-
+    /** @var array */
     protected $resolve_stack = [];
 
     public function __construct(array $config = [], Resolver $resolver = null)
     {
+        static::$instance = $this;
+
         $this->resolver = $resolver ?? new Resolver($this);
         $this->boot($config);
-
-        static::$instance = $this;
     }
 
     /**
@@ -141,6 +145,14 @@ public function __invoke(string $class = null, ...$parameters)
             return $this->resolveFromStack($class);
         }
 
+        if ($this->isProviderClass($class)) {
+            return $this->getBoundProvider($class);
+        }
+
+        if (($bound = $this->bindings($class, false)) !== false) {
+            return $bound;
+        }
+
         if (array_key_exists($class, $this->apiObjects())) {
             return $this->resolve()->make($this->getApiObject($class), ...$parameters);
         }
@@ -312,9 +324,7 @@ public function commandBus()
      */
     public function bindings(string $abstract = null, $default = [])
     {
-        $abstract = is_null($abstract) ? $abstract : ".{$abstract}";
-
-        return $this->get("bindings{$abstract}", $default);
+        return $this->get('bindings' . if_not_null($abstract, ".{$abstract}"), $default);
     }
 
     /**
@@ -327,6 +337,14 @@ public function bindings(string $abstract = null, $default = [])
      */
     public function bind(string $abstract, $concrete)
     {
+        if (is_object($concrete) && class_exists($abstract) === false && is_not_closure($concrete)) {
+            $this->set('bindings.' . get_class($concrete), $concrete);
+        }
+
+        if ($this->isProviderClass($abstract)) {
+            $this->set('bindings.' . $this->getProviderAlias($abstract), $concrete);
+        }
+
         return $this->set("bindings.{$abstract}", $concrete);
     }
 
@@ -341,7 +359,7 @@ public function bind(string $abstract, $concrete)
      */
     public function make(string $class, ...$constructor)
     {
-        if (isset($constructor[0]) && $constructor[0] instanceof Closure) {
+        if (isset($constructor[0]) && is_closure($constructor[0])) {
             $make = Arr::pull($constructor, 0);
         }
 
@@ -360,13 +378,11 @@ public function make(string $class, ...$constructor)
      */
     public function providers(string $alias = null, $default = [])
     {
-        $alias = is_null($alias) ? $alias : ".{$alias}";
-
-        return $this->get("providers{$alias}", $default);
+        return $this->get('providers' . if_not_null($alias, ".{$alias}"), $default);
     }
 
     /**
-     * Return an already registered provider or instantiates it from configuration when $default is null.
+     * Returns an already registered provider or instantiates it from configuration when $default is null.
      *
      * @param string     $alias
      * @param null|mixed $default
@@ -399,9 +415,7 @@ public function makeProvider(string $alias, ...$constructor)
 
     public function getCacheDriver($provider = null)
     {
-        $provider = is_null($provider) ? $provider : "{$provider}.";
-
-        $driver = $this->get("{$provider}cache.driver");
+        $driver = $this->get(if_not_null($provider, "{$provider}.") . 'cache.driver');
 
         return new $driver($this);
     }
@@ -521,6 +535,10 @@ public function setDefaultConnection(string $name)
 
     public function registerProvider($alias, $instance = null)
     {
+        if (is_null($instance) && ($bound = $this->getBoundProvider($alias, false)) !== false) {
+            return $bound;
+        }
+
         if (!is_object($instance)) {
             $instance = $this->makeProvider($alias);
         }
@@ -530,6 +548,33 @@ public function registerProvider($alias, $instance = null)
         return $instance;
     }
 
+    /**
+     * @param string $abstract
+     *
+     * @return bool
+     */
+    protected function isProvider(string $abstract): bool
+    {
+        return array_key_exists($abstract, $this->providers())
+            || $this->providers($abstract, false)
+            || $this->getProviderAlias($abstract) !== false;
+    }
+
+    protected function getProviderAlias($abstract)
+    {
+        return class_exists($abstract) ? array_search($abstract, $this->providers(), true) : false;
+    }
+
+    /**
+     * @param string $abstract
+     *
+     * @return bool
+     */
+    protected function isProviderClass(string $abstract): bool
+    {
+        return class_exists($abstract) && $this->isProvider($abstract);
+    }
+
     protected function boot(array $config)
     {
         if (static::$test_mode) {
@@ -541,7 +586,6 @@ protected function boot(array $config)
         }
 
         $this->merge(include(__DIR__ . '/../config/bootstrap.php'))
-            ->set('bindings', [])
             ->registerProviders()
             ->bootConnections();
     }
diff --git a/src/WmiScripting/Support/Events/EventProvider.php b/src/WmiScripting/Support/Events/EventProvider.php
index d2022de..8b744ae 100644
--- a/src/WmiScripting/Support/Events/EventProvider.php
+++ b/src/WmiScripting/Support/Events/EventProvider.php
@@ -22,9 +22,10 @@ public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
 
-        $this->history = $this->config->eventHistoryProvider();
+//        $this->history = $this->config->eventHistoryProvider();
+        $this->history = \PhpWinTools\WmiScripting\Support\resolve(EventHistoryProvider::class);
 
-        static::$instance = $this->config->registerProvider('event', $this);
+        static::$instance = $this->config->registerProvider(EventProvider::class, $this);
     }
 
     public static function instance(Config $config = null)
diff --git a/src/WmiScripting/Support/functions.php b/src/WmiScripting/Support/functions.php
index ce44b01..c2c06af 100644
--- a/src/WmiScripting/Support/functions.php
+++ b/src/WmiScripting/Support/functions.php
@@ -2,6 +2,7 @@
 
 namespace PhpWinTools\WmiScripting\Support {
 
+    use Closure;
     use ReflectionClass;
     use PhpWinTools\WmiScripting\Configuration\Config;
     use PhpWinTools\WmiScripting\Configuration\Resolver;
@@ -11,13 +12,63 @@
     use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
 
     /**
-     * @param Config|null $config
+     * Checks for the existence of a property.
      *
-     * @return Config
+     * @param $class
+     * @param $property_name
+     *
+     * @throws InvalidArgumentException
+     *
+     * @return bool
      */
-    function core(Config $config = null)
+    function class_has_property($class, $property_name): bool
     {
-        return $config ?? Config::instance();
+        $class = is_object($class) ? get_class($class) : $class;
+
+        try {
+            return (new ReflectionClass($class))->hasProperty($property_name);
+        } catch (\Exception $exception) {
+            throw new InvalidArgumentException("Unable to reflect on {$class}.", null, $exception);
+        }
+    }
+
+    /**
+     * Expects either a class name or instance and will return if the given trait exists on the class.
+     *
+     * @param $class
+     * @param $trait
+     *
+     * @return bool
+     */
+    function class_has_trait($class, $trait): bool
+    {
+        return array_key_exists($trait, class_traits(is_object($class) ? get_class($class) : $class));
+    }
+
+    /**
+     * Returns all traits of the given class or instance. It also checks the traits for any used traits.
+     *
+     * @param $class
+     *
+     * @return array
+     */
+    function class_traits($class)
+    {
+        $class = is_object($class) ? get_class($class) : $class;
+
+        $traits = class_uses($class);
+
+        while ($class = get_parent_class($class)) {
+            $traits += class_uses($class);
+        }
+
+        $trait_traits = [];
+
+        foreach ($traits as $trait) {
+            $trait_traits += trait_traits($trait);
+        }
+
+        return array_merge($trait_traits, $traits);
     }
 
     /**
@@ -49,35 +100,13 @@ function connection($connection = null, $default = null, Config $config = null)
     }
 
     /**
-     * @param string|null $class
-     * @param mixed       $parameters
-     *
-     * @return Resolver|mixed|null
-     */
-    function resolve(string $class = null, ...$parameters)
-    {
-        return core()($class, $parameters);
-    }
-
-    /**
-     * Checks for the existence of a property.
-     *
-     * @param $class
-     * @param $property_name
-     *
-     * @throws InvalidArgumentException
+     * @param Config|null $config
      *
-     * @return bool
+     * @return Config
      */
-    function class_has_property($class, $property_name): bool
+    function core(Config $config = null)
     {
-        $class = is_object($class) ? get_class($class) : $class;
-
-        try {
-            return (new ReflectionClass($class))->hasProperty($property_name);
-        } catch (\Exception $exception) {
-            throw new InvalidConnectionException("Unable to reflect on {$class}.", null, $exception);
-        }
+        return $config ?? Config::instance();
     }
 
     /**
@@ -98,44 +127,69 @@ function get_ancestor_property($class, $property_name)
     }
 
     /**
-     * Expects either a class name or instance and will return if the given trait exists on the class.
+     * Simple null check and replace only when $value is not null. Created to make string concatenations
+     * a little cleaner to avoid having to have an extra variable.
      *
-     * @param $class
-     * @param $trait
+     * @param mixed      $value
+     * @param mixed      $then
+     * @param mixed|null $else
      *
-     * @return bool
+     * @return mixed
      */
-    function class_has_trait($class, $trait): bool
+    function if_not_null($value, $then, $else = null)
     {
-        return array_key_exists($trait, class_traits(is_object($class) ? get_class($class) : $class));
+        return !is_null($value) ? $then : $else;
     }
 
     /**
-     * Returns all traits of the given class or instance. It also checks the traits for any used traits.
+     * @param string       $class
+     * @param mixed|object $instance
      *
-     * @param $class
-     *
-     * @return array
+     * @return bool
      */
-    function class_traits($class)
+    function is(string $class, $instance): bool
     {
-        $class = is_object($class) ? get_class($class) : $class;
-
-        $traits = class_uses($class);
+        return class_exists($class) && $instance instanceof $class;
+    }
 
-        while ($class = get_parent_class($class)) {
-            $traits += class_uses($class);
-        }
+    function is_closure($instance): bool
+    {
+        return is(Closure::class, $instance);
+    }
 
-        $trait_traits = [];
+    /**
+     * @param string       $class
+     * @param mixed|object $instance
+     *
+     * @return bool
+     */
+    function is_not(string $class, $instance): bool
+    {
+        return is($class, $instance) === false;
+    }
 
-        foreach ($traits as $trait) {
-            $trait_traits += trait_traits($trait);
-        }
+    /**
+     * @param $instance
+     *
+     * @return bool
+     */
+    function is_not_closure($instance): bool
+    {
+        return is_closure($instance) === false;
+    }
 
-        return array_merge($trait_traits, $traits);
+    /**
+     * @param string|null $class
+     * @param mixed       $parameters
+     *
+     * @return Resolver|mixed|null
+     */
+    function resolve(string $class = null, ...$parameters)
+    {
+        return core()($class, $parameters);
     }
 
+
     /**
      * Returns all of the traits that a trait uses including it's ancestors.
      *
diff --git a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
index 3b095de..160a619 100644
--- a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
@@ -38,8 +38,6 @@ public function it_can_count_events()
         $this->event->fire(new Event(new Context()));
         $this->event->fire(new Event(new Context()));
 
-//        dd(Config::instance());
-
         $this->assertEquals(3, $this->history->count());
         $this->assertEquals(3, $this->history->eventCount());
         $this->assertEquals(3, $this->history->eventCount(Event::class));
diff --git a/tests/WmiScripting/Support/Event/EventProviderTest.php b/tests/WmiScripting/Support/Event/EventProviderTest.php
index 43b3781..6c1af6c 100644
--- a/tests/WmiScripting/Support/Event/EventProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventProviderTest.php
@@ -20,19 +20,11 @@ class EventProviderTest extends TestCase
 
     protected function setUp()
     {
-        parent::setUp();
-
-        $this->event = new EventProvider();
+        $config = Config::newInstance();
+        $this->event = $config->eventProvider();
         $this->history = $this->event->history();
     }
 
-    protected function tearDown()
-    {
-        parent::tearDown();
-
-        Config::newInstance();
-    }
-
     /** @test */
     public function it_can_instantiate()
     {

From 5f864656ab829439af202dd7cc6c4e149eeaae94 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Sun, 22 Sep 2019 14:17:11 -0400
Subject: [PATCH 25/33] WIP

---
 src/WmiScripting/Configuration/Config.php     | 45 ++++++++++++-------
 .../Support/Events/EventProvider.php          |  4 +-
 src/WmiScripting/config/providers.php         | 17 +++++--
 3 files changed, 44 insertions(+), 22 deletions(-)

diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index 917b5b2..a5ec074 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -352,7 +352,7 @@ public function bind(string $abstract, $concrete)
      * Resolves the given concrete with either the given constructor arguments. If the first argument is a Closure
      * it will be used to resolve the given concrete.
      *
-     * @param          $class
+     * @param       $class
      * @param mixed ...$constructor
      *
      * @return mixed
@@ -371,14 +371,22 @@ public function make(string $class, ...$constructor)
     }
 
     /**
-     * @param string|null $alias
+     * @param string|null $abstract
      * @param array       $default
      *
      * @return mixed
      */
-    public function providers(string $alias = null, $default = [])
+    public function concreteProviders(string $abstract = null, $default = [])
     {
-        return $this->get('providers' . if_not_null($alias, ".{$alias}"), $default);
+        if (is_null($abstract)) {
+            return $this->get('providers.concrete', $default);
+        }
+
+        if (class_exists($abstract) && array_key_exists($abstract, $this->get('providers.aliases'))) {
+            return $abstract;
+        }
+
+        return $this->get("providers.concrete.{$abstract}", $default);
     }
 
     /**
@@ -399,15 +407,15 @@ public function getBoundProvider($alias, $default = null)
     }
 
     /**
-     * @param string              $alias
+     * @param string              $abstract
      * @param array|mixed|Closure ...$constructor
      *
      * @return mixed
      */
-    public function makeProvider(string $alias, ...$constructor)
+    public function makeProvider(string $abstract, ...$constructor)
     {
-        if (($provider = $this->providers($alias, false)) === false) {
-            throw new InvalidArgumentException("{$alias} is not a valid provider.");
+        if (($provider = $this->concreteProviders($abstract, false)) === false) {
+            throw new InvalidArgumentException("{$abstract} is not a valid provider.");
         }
 
         return $this->make($provider, ...$constructor);
@@ -555,16 +563,11 @@ public function registerProvider($alias, $instance = null)
      */
     protected function isProvider(string $abstract): bool
     {
-        return array_key_exists($abstract, $this->providers())
-            || $this->providers($abstract, false)
+        return array_key_exists($abstract, $this->concreteProviders())
+            || $this->concreteProviders($abstract, false)
             || $this->getProviderAlias($abstract) !== false;
     }
 
-    protected function getProviderAlias($abstract)
-    {
-        return class_exists($abstract) ? array_search($abstract, $this->providers(), true) : false;
-    }
-
     /**
      * @param string $abstract
      *
@@ -575,6 +578,16 @@ protected function isProviderClass(string $abstract): bool
         return class_exists($abstract) && $this->isProvider($abstract);
     }
 
+    /**
+     * @param string $abstract
+     *
+     * @return bool|false|int|string
+     */
+    protected function getProviderAlias(string $abstract)
+    {
+        return class_exists($abstract) ? array_search($abstract, $this->concreteProviders(), true) : false;
+    }
+
     protected function boot(array $config)
     {
         if (static::$test_mode) {
@@ -594,7 +607,7 @@ protected function registerProviders()
     {
         array_map(function ($alias) {
             $this->registerProvider($alias);
-        }, array_keys($this->providers()));
+        }, array_keys($this->concreteProviders()));
 
         return $this;
     }
diff --git a/src/WmiScripting/Support/Events/EventProvider.php b/src/WmiScripting/Support/Events/EventProvider.php
index 8b744ae..979bd37 100644
--- a/src/WmiScripting/Support/Events/EventProvider.php
+++ b/src/WmiScripting/Support/Events/EventProvider.php
@@ -3,6 +3,7 @@
 namespace PhpWinTools\WmiScripting\Support\Events;
 
 use PhpWinTools\WmiScripting\Configuration\Config;
+use function PhpWinTools\WmiScripting\Support\resolve;
 
 class EventProvider
 {
@@ -22,8 +23,7 @@ public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
 
-//        $this->history = $this->config->eventHistoryProvider();
-        $this->history = \PhpWinTools\WmiScripting\Support\resolve(EventHistoryProvider::class);
+        $this->history = resolve(EventHistoryProvider::class);
 
         static::$instance = $this->config->registerProvider(EventProvider::class, $this);
     }
diff --git a/src/WmiScripting/config/providers.php b/src/WmiScripting/config/providers.php
index bf18e4a..ed38121 100644
--- a/src/WmiScripting/config/providers.php
+++ b/src/WmiScripting/config/providers.php
@@ -6,8 +6,17 @@
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
 
 return [
-    'bus'           => CommandBus::class,
-    'cache'         => CacheProvider::class,
-    'event'         => EventProvider::class,
-    'event_history' => EventHistoryProvider::class,
+    'concrete' => [
+        'bus'           => CommandBus::class,
+        'cache'         => CacheProvider::class,
+        'event'         => EventProvider::class,
+        'event_history' => EventHistoryProvider::class,
+    ],
+
+    'aliases' => [
+        CommandBus::class           => 'bus',
+        CacheProvider::class        => 'cache',
+        EventProvider::class        => 'event',
+        EventHistoryProvider::class => 'event_history',
+    ],
 ];

From aadeb1fe0c03430111e801a0aa0d48b699416c29 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Sun, 22 Sep 2019 17:17:07 -0400
Subject: [PATCH 26/33] Testing CacheProvider

---
 .../Support/Bus/Events/CommandBusEvent.php    |   5 +-
 .../Bus/Events/CommandHandlerEvent.php        |   2 +-
 .../Support/Cache/ArrayDriver.php             |  22 +--
 .../Support/Cache/CacheDriver.php             |  67 ++++++++
 .../Support/Cache/CacheProvider.php           | 145 ++++++++++++++++--
 .../Support/Cache/Events/CacheCleared.php     |   7 +
 .../Support/Cache/Events/CacheEvent.php       |  17 ++
 .../Support/Cache/Events/CacheHit.php         |   7 +
 .../Support/Cache/Events/CacheKeyExpired.php  |   7 +
 .../Cache/Events/CacheKeyForgotten.php        |   7 +
 .../Support/Cache/Events/CacheKeyStored.php   |   7 +
 .../Support/Cache/Events/CacheMissed.php      |   7 +
 src/WmiScripting/Support/Events/Event.php     |  22 ++-
 .../Events/{Context.php => Payload.php}       |   2 +-
 src/WmiScripting/Support/functions.php        |  12 ++
 .../Support/Cache/ArrayCacheDriverTest.php    |  62 ++++++++
 .../Event/EventHistoryProviderTest.php        |  30 ++--
 .../Support/Event/EventProviderTest.php       |   8 +-
 18 files changed, 372 insertions(+), 64 deletions(-)
 create mode 100644 src/WmiScripting/Support/Cache/Events/CacheCleared.php
 create mode 100644 src/WmiScripting/Support/Cache/Events/CacheEvent.php
 create mode 100644 src/WmiScripting/Support/Cache/Events/CacheHit.php
 create mode 100644 src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
 create mode 100644 src/WmiScripting/Support/Cache/Events/CacheKeyForgotten.php
 create mode 100644 src/WmiScripting/Support/Cache/Events/CacheKeyStored.php
 create mode 100644 src/WmiScripting/Support/Cache/Events/CacheMissed.php
 rename src/WmiScripting/Support/Events/{Context.php => Payload.php} (97%)
 create mode 100644 tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php

diff --git a/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
index abfd2f8..a153e27 100644
--- a/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
+++ b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
@@ -4,7 +4,7 @@
 
 use PhpWinTools\WmiScripting\Support\Events\Event;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
-use PhpWinTools\WmiScripting\Support\Events\Context;
+use PhpWinTools\WmiScripting\Support\Events\Payload;
 
 class CommandBusEvent extends Event
 {
@@ -17,7 +17,6 @@ public function __construct(CommandBus $bus, $command = null)
         $this->bus = $bus;
         $this->command = $command;
 
-        parent::__construct((new Context())
-                ->add('bus', $this->bus)->add('subject', $this->command));
+        parent::__construct((new Payload())->add('bus', $this->bus)->add('subject', $this->command));
     }
 }
diff --git a/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php b/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php
index a993e3c..160ef30 100644
--- a/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php
+++ b/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php
@@ -12,6 +12,6 @@ public function __construct(CommandBus $bus, Command $command, CommandHandler $c
     {
         parent::__construct($bus, $command);
 
-        $this->context->add('handler', $commandHandler);
+        $this->payload->add('handler', $commandHandler);
     }
 }
diff --git a/src/WmiScripting/Support/Cache/ArrayDriver.php b/src/WmiScripting/Support/Cache/ArrayDriver.php
index 5e93688..3fe3c9a 100644
--- a/src/WmiScripting/Support/Cache/ArrayDriver.php
+++ b/src/WmiScripting/Support/Cache/ArrayDriver.php
@@ -4,7 +4,6 @@
 
 use Illuminate\Support\Arr;
 use Psr\SimpleCache\CacheInterface;
-use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
 
 class ArrayDriver extends CacheDriver implements CacheInterface
 {
@@ -51,7 +50,7 @@ public function getMultiple($keys, $default = null)
     public function setMultiple($values, $ttl = null)
     {
         foreach ($values as $key => $value) {
-            $this->set($key, $value);
+            $this->set($key, $value, $ttl);
         }
 
         return true;
@@ -74,23 +73,4 @@ public function has($key)
     {
         return Arr::has($this->store, $this->validateKey($key));
     }
-
-    public function exists($key)
-    {
-        return $this->has($key);
-    }
-
-    public function doesNotExist($key)
-    {
-        return $this->exists($key) === false;
-    }
-
-    protected function validateKey($key)
-    {
-        if (is_string($key)) {
-            return $key;
-        }
-
-        throw new CacheInvalidArgumentException("{$key} is not a valid key");
-    }
 }
diff --git a/src/WmiScripting/Support/Cache/CacheDriver.php b/src/WmiScripting/Support/Cache/CacheDriver.php
index c32f28a..6928555 100644
--- a/src/WmiScripting/Support/Cache/CacheDriver.php
+++ b/src/WmiScripting/Support/Cache/CacheDriver.php
@@ -4,6 +4,7 @@
 
 use Psr\SimpleCache\CacheInterface;
 use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
 
 abstract class CacheDriver implements CacheInterface
 {
@@ -15,4 +16,70 @@ public function __construct(Config $config = null)
     {
         $this->config = $config ?? Config::instance();
     }
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     *
+     * @throws CacheInvalidArgumentException
+     */
+    abstract public function has($key);
+
+
+    public function expired($key, $ttl = null): bool
+    {
+        return false;
+    }
+
+    public function valid($key, $ttl = null)
+    {
+        return $this->expired($key, $ttl) === false;
+    }
+
+    public function exists($key)
+    {
+        return $this->has($key);
+    }
+
+    public function doesNotExist($key)
+    {
+        return $this->exists($key) === false;
+    }
+
+    protected function validateKey($key)
+    {
+        if (is_string($key)) {
+            return $key;
+        }
+
+        throw new CacheInvalidArgumentException("{$key} is not a valid key");
+    }
+
+    public function canGet($key)
+    {
+        try {
+            return $this->has($key) || $this->valid($key);
+        } catch (CacheInvalidArgumentException $exception) {
+            return false;
+        }
+    }
+
+    public function canSet($key, $value)
+    {
+        try {
+            return $this->validateKey($key) === $key;
+        } catch (CacheInvalidArgumentException $exception) {
+            return false;
+        }
+    }
+
+    public function canDelete($key)
+    {
+        try {
+            return $this->has($key);
+        } catch (CacheInvalidArgumentException $exception) {
+            return false;
+        }
+    }
 }
diff --git a/src/WmiScripting/Support/Cache/CacheProvider.php b/src/WmiScripting/Support/Cache/CacheProvider.php
index 9f218e7..72c345f 100644
--- a/src/WmiScripting/Support/Cache/CacheProvider.php
+++ b/src/WmiScripting/Support/Cache/CacheProvider.php
@@ -2,60 +2,181 @@
 
 namespace PhpWinTools\WmiScripting\Support\Cache;
 
+use Closure;
+use DateInterval;
 use Psr\SimpleCache\CacheInterface;
+use Psr\SimpleCache\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use function PhpWinTools\WmiScripting\Support\reduce_value;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheHit;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheMissed;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheCleared;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyStored;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyExpired;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyForgotten;
+use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
 
 class CacheProvider implements CacheInterface
 {
+    /** @var Config */
     protected $config;
 
     /** @var CacheDriver */
-    protected $cacheDriver;
+    protected $driver;
 
-    public function __construct(Config $config = null, CacheDriver $cacheDriver = null)
+    /** @var EventProvider */
+    protected $events;
+
+    public function __construct(Config $config = null, CacheDriver $driver = null, EventProvider $events = null)
     {
         $this->config = $config ?? Config::instance();
-
-        $this->cacheDriver = $cacheDriver ?? $this->config->getCacheDriver();
+        $this->driver = $driver ?? $this->config->getCacheDriver();
+        $this->events = $events ?? $this->config->eventProvider();
     }
 
+    /**
+     * @param string|array       $key
+     * @param Closure|null|mixed $default
+     *
+     * @throws InvalidArgumentException|CacheInvalidArgumentException
+     *
+     * @return iterable|mixed|null
+     */
     public function get($key, $default = null)
     {
-        return $this->cacheDriver->get($key, $default);
+        if (is_array($key)) {
+            return $this->getMultiple($key);
+        }
+
+        $value = $this->driver()->get($key, null);
+
+        if ($this->driver()->expired($key)) {
+            $this->events(new CacheKeyExpired($this->driver(), $key, $value));
+            $this->delete($key);
+            $value = null;
+        }
+
+        if (is_null($value)) {
+            $this->events(new CacheMissed($this->driver(), $key));
+            $value = reduce_value($value);
+        } else {
+            $this->events(new CacheHit($this->driver(), $key, $value));
+        }
+
+        return $value;
     }
 
+    /**
+     * @param string|array          $key
+     * @param mixed                 $value
+     * @param null|int|DateInterval $ttl
+     *
+     * @throws InvalidArgumentException|CacheInvalidArgumentException
+     *
+     * @return bool
+     */
     public function set($key, $value, $ttl = null)
     {
-        return $this->cacheDriver->set($key, $value, $ttl);
+        if (is_array($key)) {
+            return $this->setMultiple($key, $ttl);
+        }
+
+        $result = $this->driver()->set($key, $value, $ttl);
+
+        if ($result) {
+            $this->events(new CacheKeyStored($this->driver(), $key, $value, $ttl));
+        }
+
+        return $result;
     }
 
     public function delete($key)
     {
-        return $this->cacheDriver->delete($key);
+        if (is_array($key)) {
+            return $this->deleteMultiple($key);
+        }
+
+        $result = $this->driver()->delete($key);
+
+        if ($result) {
+            $this->events(new CacheKeyForgotten($this->driver(), $key));
+        }
+
+        return $result;
     }
 
     public function clear()
     {
-        return $this->cacheDriver->clear();
+        $result = $this->driver->clear();
+
+        if ($result) {
+            $this->events(new CacheCleared($this->driver));
+        }
+
+        return $result;
     }
 
     public function getMultiple($keys, $default = null)
     {
-        return $this->cacheDriver->getMultiple($keys, $default);
+        $this->multiple('get', $keys);
+
+        return $this->driver()->getMultiple($keys, $default);
     }
 
     public function setMultiple($values, $ttl = null)
     {
-        return $this->cacheDriver->setMultiple($values, $ttl);
+        $this->multiple('set', $values, $ttl);
+
+        return $this->driver()->setMultiple($values, $ttl);
     }
 
     public function deleteMultiple($keys)
     {
-        return $this->cacheDriver->deleteMultiple($keys);
+        $this->multiple('delete', $keys);
+
+        return $this->driver()->deleteMultiple($keys);
     }
 
     public function has($key)
     {
-        return $this->cacheDriver->has($key);
+        return $this->driver()->has($key);
+    }
+
+    public function driver()
+    {
+        return $this->driver;
+    }
+
+    public function setEventProvider(EventProvider $event)
+    {
+        $this->events = $event;
+
+        return $this;
+    }
+
+    protected function multiple(string $method, $keys, $ttl = null)
+    {
+        foreach ($keys as $key => $value) {
+            if ($method === 'get' && $this->driver()->canGet($key)) {
+                $this->events(new CacheHit($this->driver, $key, $this->driver()->get($key)));
+            }
+
+            if ($method === 'set' && $this->driver()->canSet($key, $value)) {
+                $this->events(new CacheKeyStored($this->driver, $key, $value, $ttl));
+            }
+
+            if ($method === 'delete' && $this->driver()->canDelete($key)) {
+                $this->events(new CacheKeyForgotten($this->driver, $key));
+            }
+        }
+    }
+
+    protected function events(Event $event, string $key = null)
+    {
+        $this->events->fire($event);
+
+        return $this;
     }
 }
diff --git a/src/WmiScripting/Support/Cache/Events/CacheCleared.php b/src/WmiScripting/Support/Cache/Events/CacheCleared.php
new file mode 100644
index 0000000..23ca6dc
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheCleared.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheCleared extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheEvent.php b/src/WmiScripting/Support/Cache/Events/CacheEvent.php
new file mode 100644
index 0000000..917e425
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheEvent.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\Payload;
+use PhpWinTools\WmiScripting\Support\Cache\CacheDriver;
+
+class CacheEvent extends Event
+{
+    public function __construct(CacheDriver $driver, $key = null, $value = null, $ttl = null)
+    {
+        parent::__construct(
+            (new Payload())->add('driver', $driver)->add('key', $key)->add('value', $value)->add('ttl', $ttl)
+        );
+    }
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheHit.php b/src/WmiScripting/Support/Cache/Events/CacheHit.php
new file mode 100644
index 0000000..743d485
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheHit.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheHit extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php b/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
new file mode 100644
index 0000000..dc549bd
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheKeyExpired extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyForgotten.php b/src/WmiScripting/Support/Cache/Events/CacheKeyForgotten.php
new file mode 100644
index 0000000..1c7b5c3
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyForgotten.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheKeyForgotten extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyStored.php b/src/WmiScripting/Support/Cache/Events/CacheKeyStored.php
new file mode 100644
index 0000000..a4f2a34
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyStored.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheKeyStored extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheMissed.php b/src/WmiScripting/Support/Cache/Events/CacheMissed.php
new file mode 100644
index 0000000..5261afb
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheMissed.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheMissed extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Events/Event.php b/src/WmiScripting/Support/Events/Event.php
index 06467a2..d3c8f22 100644
--- a/src/WmiScripting/Support/Events/Event.php
+++ b/src/WmiScripting/Support/Events/Event.php
@@ -4,20 +4,28 @@
 
 class Event
 {
-    protected $context;
+    protected $payload;
 
-    public function __construct(Context $context)
+    protected $called_class;
+
+    public function __construct(Payload $payload)
+    {
+        $this->payload = $payload;
+        $this->called_class = get_called_class();
+    }
+
+    public static function new(Payload $payload)
     {
-        $this->context = $context;
+        return new static($payload);
     }
 
-    public static function new(Context $context)
+    public function payload(): Payload
     {
-        return new static($context);
+        return $this->payload;
     }
 
-    public function context(): Context
+    public function actual(): string
     {
-        return $this->context;
+        return $this->called_class;
     }
 }
diff --git a/src/WmiScripting/Support/Events/Context.php b/src/WmiScripting/Support/Events/Payload.php
similarity index 97%
rename from src/WmiScripting/Support/Events/Context.php
rename to src/WmiScripting/Support/Events/Payload.php
index f4184c6..d40c6b2 100644
--- a/src/WmiScripting/Support/Events/Context.php
+++ b/src/WmiScripting/Support/Events/Payload.php
@@ -2,7 +2,7 @@
 
 namespace PhpWinTools\WmiScripting\Support\Events;
 
-class Context
+class Payload
 {
     protected $context = [];
 
diff --git a/src/WmiScripting/Support/functions.php b/src/WmiScripting/Support/functions.php
index c2c06af..0eb54c4 100644
--- a/src/WmiScripting/Support/functions.php
+++ b/src/WmiScripting/Support/functions.php
@@ -178,6 +178,18 @@ function is_not_closure($instance): bool
         return is_closure($instance) === false;
     }
 
+    /**
+     * Reduce value if given a Closure otherwise return value as is.
+     *
+     * @param $value
+     *
+     * @return mixed
+     */
+    function reduce_value($value)
+    {
+        return is_closure($value) ? $value() : $value;
+    }
+
     /**
      * @param string|null $class
      * @param mixed       $parameters
diff --git a/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
new file mode 100644
index 0000000..0185e62
--- /dev/null
+++ b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Cache;
+
+use Tests\TestCase;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
+use PhpWinTools\WmiScripting\Support\Cache\CacheProvider;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheHit;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyStored;
+use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
+
+class ArrayCacheDriverTest extends TestCase
+{
+    /** @var CacheProvider */
+    protected $cache;
+
+    /** @var Config */
+    protected $config;
+
+    /** @var EventHistoryProvider  */
+    protected $eventHistory;
+
+    protected $cached_values = [
+        'key1' => 'value1',
+        'key2' => 'value2',
+        'key3' => 'value3',
+        'key4' => 'value4',
+    ];
+
+    protected $time_to_live_seconds = 60;
+
+    protected function setUp()
+    {
+        $this->config = Config::newInstance()->trackEvents();
+        $this->eventHistory = $this->config->eventHistoryProvider();
+        $this->cache = new CacheProvider($this->config, new ArrayDriver($this->config));
+        $this->cache->set($this->cached_values, $this->time_to_live_seconds);
+    }
+
+    /** @test */
+    public function it_uses_the_array_driver_when_specified()
+    {
+        $this->assertInstanceOf(ArrayDriver::class, $this->cache->driver());
+    }
+
+    /** @test */
+    public function it_fires_an_event_when_a_value_is_set()
+    {
+        $this->assertCount(4, $this->eventHistory->get(CacheKeyStored::class));
+        $this->cache->set('test', $value = 'value');
+        $this->assertCount(5, $this->eventHistory->get(CacheKeyStored::class));
+        $this->assertSame($value, $this->cache->get('test'));
+    }
+
+    /** @test */
+    public function it_can_return_a_cached_value_and_fire_event()
+    {
+        $this->assertSame($this->cached_values['key3'], $this->cache->get('key3'));
+        $this->assertTrue($this->eventHistory->hasFired(CacheHit::class));
+    }
+}
diff --git a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
index 160a619..4dd37f9 100644
--- a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
@@ -4,7 +4,7 @@
 
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Events\Event;
-use PhpWinTools\WmiScripting\Support\Events\Context;
+use PhpWinTools\WmiScripting\Support\Events\Payload;
 use PhpWinTools\WmiScripting\Support\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusEvent;
 
@@ -13,7 +13,7 @@ class EventHistoryProviderTest extends EventProviderTest
     /** @test */
     public function it_does_not_track_events_by_default()
     {
-        $this->event->fire(new Event(new Context()));
+        $this->event->fire(new Event(new Payload()));
 
         $this->assertEmpty($this->history->all());
     }
@@ -21,8 +21,8 @@ public function it_does_not_track_events_by_default()
     /** @test */
     public function it_can_track_events()
     {
-        $notFired = new class(new Context()) extends Event {};
-        $this->event->trackEvents()->fire(new Event(new Context()));
+        $notFired = new class(new Payload()) extends Event {};
+        $this->event->trackEvents()->fire(new Event(new Payload()));
 
         $this->assertTrue($this->history->hasFired(Event::class));
         $this->assertTrue($this->history->hasNotFired(get_class($notFired)));
@@ -32,11 +32,11 @@ public function it_can_track_events()
     public function it_can_count_events()
     {
         $this->event->trackEvents();
-        $child = new class(new Context()) extends Event {};
+        $child = new class(new Payload()) extends Event {};
 
         $this->event->fire($child);
-        $this->event->fire(new Event(new Context()));
-        $this->event->fire(new Event(new Context()));
+        $this->event->fire(new Event(new Payload()));
+        $this->event->fire(new Event(new Payload()));
 
         $this->assertEquals(3, $this->history->count());
         $this->assertEquals(3, $this->history->eventCount());
@@ -49,10 +49,10 @@ public function it_can_count_events_through_ancestry()
     {
         $this->event->trackEvents();
 
-        $child = new class(new Context()) extends Event {};
+        $child = new class(new Payload()) extends Event {};
 
         $this->event->fire($child);
-        $this->event->fire(Event::new(new Context()));
+        $this->event->fire(Event::new(new Payload()));
 
         $this->assertEquals(1, $this->history->eventCount(get_class($child)));
         $this->assertEquals(2, $this->history->eventCount(Event::class));
@@ -66,7 +66,7 @@ public function it_can_count_events_through_ancestry_without_over_counting()
         $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
         $notFired = new class($bus = Config::instance()->commandBus(), 'nothing') extends CommandBusEvent {};
 
-        $this->event->fire(Event::new(new Context()));
+        $this->event->fire(Event::new(new Payload()));
         $this->event->fire($child);
         $this->event->fire(new CommandBusEvent($bus, 'new test'));
 
@@ -83,8 +83,8 @@ public function it_returns_events_in_the_order_that_they_were_fired()
 
         $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
 
-        $this->event->fire($first = Event::new(new Context()));
-        $this->event->fire($second = Event::new(new Context()));
+        $this->event->fire($first = Event::new(new Payload()));
+        $this->event->fire($second = Event::new(new Payload()));
         $this->event->fire($child);
         $this->event->fire($forth = new CommandBusEvent($bus, 'new test'));
 
@@ -102,8 +102,8 @@ public function it_can_return_all_fired_events_by_name()
 
         $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
 
-        $this->event->fire($first = Event::new(new Context()));
-        $this->event->fire($second = Event::new(new Context()));
+        $this->event->fire($first = Event::new(new Payload()));
+        $this->event->fire($second = Event::new(new Payload()));
         $this->event->fire($child);
 
         $this->assertCount(1, $firedEvents = $this->history->get(get_class($child)));
@@ -134,7 +134,7 @@ public function it_can_return_an_array_of_fired_events_by_the_listener_name_trig
 
         $this->event->trackEvents()
             ->subscribe(Event::class, $listener)
-            ->fire($event = Event::new(new Context()));
+            ->fire($event = Event::new(new Payload()));
 
         $this->assertSame($event, $this->history->getFromListener(get_class($listener))[0]->event());
     }
diff --git a/tests/WmiScripting/Support/Event/EventProviderTest.php b/tests/WmiScripting/Support/Event/EventProviderTest.php
index 6c1af6c..0416959 100644
--- a/tests/WmiScripting/Support/Event/EventProviderTest.php
+++ b/tests/WmiScripting/Support/Event/EventProviderTest.php
@@ -5,7 +5,7 @@
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Events\Event;
-use PhpWinTools\WmiScripting\Support\Events\Context;
+use PhpWinTools\WmiScripting\Support\Events\Payload;
 use PhpWinTools\WmiScripting\Support\Events\Listener;
 use PhpWinTools\WmiScripting\Support\Events\EventProvider;
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
@@ -49,11 +49,11 @@ public function it_runs_listeners_when_an_applicable_event_is_fired()
             public $order = [];
         };
 
-        $child = new class(new Context()) extends Event {};
+        $child = new class(new Payload()) extends Event {};
 
         $this->event->subscribe(get_class($child), $this->makeListener('listener', $subject));
 
-        $this->event->fire(Event::new(new Context()));
+        $this->event->fire(Event::new(new Payload()));
         $this->assertEmpty($subject->order);
 
         $this->event->fire($child);
@@ -64,7 +64,7 @@ public function it_runs_listeners_when_an_applicable_event_is_fired()
     public function it_runs_ancestor_listeners_when_a_child_event_is_fired()
     {
         $subject = new class { public $order = []; };
-        $descendant = new class(new Context()) extends Event {};
+        $descendant = new class(new Payload()) extends Event {};
         $listener = $this->makeListener('listener_fired', $subject);
 
         $this->event->trackEvents()

From dadabd6a1d93578864856b2d75b9d0df28c3234e Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Mon, 23 Sep 2019 13:27:10 -0400
Subject: [PATCH 27/33] Finish up cache implementation

---
 composer.json                                 |   3 +-
 .../CacheInvalidArgumentException.php         |   1 -
 .../Support/Cache/ArrayDriver.php             |  25 ++-
 .../Support/Cache/CacheDriver.php             | 176 +++++++++++++++---
 .../Support/Cache/CacheProvider.php           | 165 ++++++++++++----
 .../Support/Cache/Events/CacheEvent.php       |   7 +-
 .../Support/Cache/Events/CacheKeyExpired.php  |   2 +-
 src/WmiScripting/config/bootstrap.php         |   1 +
 .../Support/Cache/ArrayCacheDriverTest.php    |  88 ++++++++-
 9 files changed, 401 insertions(+), 67 deletions(-)

diff --git a/composer.json b/composer.json
index 70c5d1f..b7dc4ef 100644
--- a/composer.json
+++ b/composer.json
@@ -30,7 +30,8 @@
         "illuminate/contracts": "^5.8",
         "symfony/process": "^4.2",
         "tightenco/collect": "^5.8",
-        "psr/simple-cache": "^1.0"
+        "psr/simple-cache": "^1.0",
+        "nesbot/carbon": "^1.26.3"
     },
     "require-dev": {
         "fzaninotto/faker": "^1.4",
diff --git a/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php b/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php
index 224e21a..b327ca2 100644
--- a/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php
+++ b/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php
@@ -6,5 +6,4 @@
 
 class CacheInvalidArgumentException extends InvalidArgumentException implements BaseException
 {
-
 }
diff --git a/src/WmiScripting/Support/Cache/ArrayDriver.php b/src/WmiScripting/Support/Cache/ArrayDriver.php
index 3fe3c9a..50d1059 100644
--- a/src/WmiScripting/Support/Cache/ArrayDriver.php
+++ b/src/WmiScripting/Support/Cache/ArrayDriver.php
@@ -2,21 +2,30 @@
 
 namespace PhpWinTools\WmiScripting\Support\Cache;
 
+use Carbon\Carbon;
 use Illuminate\Support\Arr;
 use Psr\SimpleCache\CacheInterface;
 
 class ArrayDriver extends CacheDriver implements CacheInterface
 {
+    /** @var array */
     protected $store = [];
 
     public function get($key, $default = null)
     {
-        return Arr::get($this->store, $this->validateKey($key), $default);
+        return Arr::get($this->store, "{$this->validateKey($key)}.value", $default);
     }
 
     public function set($key, $value, $ttl = null)
     {
-        return is_array(Arr::set($this->store, $this->validateKey($key), $value));
+        return is_array(Arr::set(
+            $this->store,
+            $this->validateKey($key),
+            [
+                'value' => $value,
+                'expires' => is_null($ttl) ? null : Carbon::now()->addSeconds($ttl)
+            ]
+        ));
     }
 
     public function delete($key)
@@ -69,6 +78,18 @@ public function deleteMultiple($keys)
         return true;
     }
 
+    public function expired($key): bool
+    {
+        $expires = Arr::get($this->store, "{$key}.expires");
+
+        return is_null($expires) ? false : Carbon::now()->greaterThan($expires);
+    }
+
+    public function empty(): bool
+    {
+        return empty($this->store);
+    }
+
     public function has($key)
     {
         return Arr::has($this->store, $this->validateKey($key));
diff --git a/src/WmiScripting/Support/Cache/CacheDriver.php b/src/WmiScripting/Support/Cache/CacheDriver.php
index 6928555..8a940a8 100644
--- a/src/WmiScripting/Support/Cache/CacheDriver.php
+++ b/src/WmiScripting/Support/Cache/CacheDriver.php
@@ -2,79 +2,171 @@
 
 namespace PhpWinTools\WmiScripting\Support\Cache;
 
+use Closure;
 use Psr\SimpleCache\CacheInterface;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
 
 abstract class CacheDriver implements CacheInterface
 {
+    /** @var Config */
     protected $config;
 
+    /** @var mixed */
     protected $store;
 
-    public function __construct(Config $config = null)
+    /** @var string|null */
+    protected $name = null;
+
+    public function __construct(Config $config = null, string $name = null)
     {
         $this->config = $config ?? Config::instance();
+        $this->name = $name;
     }
 
+    /**
+     * @param string             $key
+     * @param null|Closure|mixed $default
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return mixed
+     */
+    abstract public function get($key, $default = null);
+
+    /**
+     * @param iterable           $keys
+     * @param null|Closure|mixed $default
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return iterable
+     */
+    abstract public function getMultiple($keys, $default = null);
+
+    /**
+     * @param string         $key
+     * @param mixed          $value
+     * @param null|int|mixed $ttl
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return bool
+     */
+    abstract public function set($key, $value, $ttl = null);
+
+    /**
+     * @param iterable       $values
+     * @param null|int|mixed $ttl
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return bool
+     */
+    abstract  public function setMultiple($values, $ttl = null);
+
     /**
      * @param string $key
      *
+     * @throws CacheInvalidArgumentException
+     *
      * @return bool
+     */
+    abstract public function delete($key);
+
+    /**
+     * @param iterable $keys
      *
      * @throws CacheInvalidArgumentException
+     *
+     * @return bool
      */
-    abstract public function has($key);
+    abstract public function deleteMultiple($keys);
 
 
-    public function expired($key, $ttl = null): bool
-    {
-        return false;
-    }
+    /**
+     * @param string $key
+     *
+     * @return bool
+     *
+     * @throws CacheInvalidArgumentException
+     */
+    abstract public function has($key);
 
-    public function valid($key, $ttl = null)
-    {
-        return $this->expired($key, $ttl) === false;
-    }
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    abstract public function expired($key): bool;
 
-    public function exists($key)
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function notExpired($key)
     {
-        return $this->has($key);
+        return $this->expired($key) === false;
     }
 
-    public function doesNotExist($key)
+    /**
+     * @return bool
+     */
+    abstract public function empty(): bool;
+
+    /**
+     * @return bool
+     */
+    public function notEmpty(): bool
     {
-        return $this->exists($key) === false;
+        return $this->empty() === false;
     }
 
-    protected function validateKey($key)
+    /**
+     * @return string|null
+     */
+    public function name()
     {
-        if (is_string($key)) {
-            return $key;
-        }
-
-        throw new CacheInvalidArgumentException("{$key} is not a valid key");
+        return $this->name;
     }
 
-    public function canGet($key)
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function canGet($key): bool
     {
         try {
-            return $this->has($key) || $this->valid($key);
+            return $this->has($key) && $this->notExpired($key);
         } catch (CacheInvalidArgumentException $exception) {
             return false;
         }
     }
 
-    public function canSet($key, $value)
+    /**
+     * @param string         $key
+     * @param mixed          $value
+     * @param null|int|mixed $ttl
+     *
+     * @return bool
+     */
+    public function canSet($key, $value, $ttl = null): bool
     {
         try {
-            return $this->validateKey($key) === $key;
+            return $this->validateKey($key) === $key && $this->isValidValue($value) && $this->isValidTtl($ttl);
         } catch (CacheInvalidArgumentException $exception) {
             return false;
         }
     }
 
-    public function canDelete($key)
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function canDelete($key): bool
     {
         try {
             return $this->has($key);
@@ -82,4 +174,38 @@ public function canDelete($key)
             return false;
         }
     }
+
+    /**
+     * @param null|int|mixed $ttl
+     *
+     * @return bool
+     */
+    protected function isValidTtl($ttl): bool
+    {
+        return is_null($ttl) || is_int($ttl);
+    }
+
+    /**
+     * @param mixed $value
+     *
+     * @return bool
+     */
+    protected function isValidValue($value): bool
+    {
+        return $value === $value;
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return string
+     */
+    protected function validateKey($key)
+    {
+        if (is_string($key)) {
+            return $key;
+        }
+
+        throw new CacheInvalidArgumentException("Not a valid key.");
+    }
 }
diff --git a/src/WmiScripting/Support/Cache/CacheProvider.php b/src/WmiScripting/Support/Cache/CacheProvider.php
index 72c345f..96578fa 100644
--- a/src/WmiScripting/Support/Cache/CacheProvider.php
+++ b/src/WmiScripting/Support/Cache/CacheProvider.php
@@ -29,6 +29,9 @@ class CacheProvider implements CacheInterface
     /** @var EventProvider */
     protected $events;
 
+    /** @var array|Event[] */
+    protected $delayedEvents = [];
+
     public function __construct(Config $config = null, CacheDriver $driver = null, EventProvider $events = null)
     {
         $this->config = $config ?? Config::instance();
@@ -40,58 +43,68 @@ public function __construct(Config $config = null, CacheDriver $driver = null, E
      * @param string|array       $key
      * @param Closure|null|mixed $default
      *
-     * @throws InvalidArgumentException|CacheInvalidArgumentException
+     * @throws CacheInvalidArgumentException|InvalidArgumentException
      *
      * @return iterable|mixed|null
      */
     public function get($key, $default = null)
     {
         if (is_array($key)) {
-            return $this->getMultiple($key);
+            return $this->getMultiple($key, $default);
         }
 
         $value = $this->driver()->get($key, null);
 
         if ($this->driver()->expired($key)) {
-            $this->events(new CacheKeyExpired($this->driver(), $key, $value));
+            $this->fire(new CacheKeyExpired($this->driver(), $key, $value));
             $this->delete($key);
             $value = null;
         }
 
         if (is_null($value)) {
-            $this->events(new CacheMissed($this->driver(), $key));
-            $value = reduce_value($value);
+            $value = reduce_value($default);
+            $this->fire(new CacheMissed($this->driver(), $key));
         } else {
-            $this->events(new CacheHit($this->driver(), $key, $value));
+            $this->fire(new CacheHit($this->driver(), $key, $value));
         }
 
         return $value;
     }
 
     /**
+     * Accepts either a string or a key-value pair array. If the given an array than the second value is used as the
+     * ttl, unless the ttl is explicitly set.
+     *
      * @param string|array          $key
      * @param mixed                 $value
      * @param null|int|DateInterval $ttl
      *
-     * @throws InvalidArgumentException|CacheInvalidArgumentException
+     * @throws CacheInvalidArgumentException|InvalidArgumentException
      *
      * @return bool
      */
-    public function set($key, $value, $ttl = null)
+    public function set($key, $value = null, $ttl = null)
     {
         if (is_array($key)) {
-            return $this->setMultiple($key, $ttl);
+            return $this->setMultiple($key, $ttl ?? $value);
         }
 
         $result = $this->driver()->set($key, $value, $ttl);
 
         if ($result) {
-            $this->events(new CacheKeyStored($this->driver(), $key, $value, $ttl));
+            $this->fire(new CacheKeyStored($this->driver(), $key, $value, $ttl));
         }
 
         return $result;
     }
 
+    /**
+     * @param string|array $key
+     *
+     * @throws CacheInvalidArgumentException|InvalidArgumentException
+     *
+     * @return bool
+     */
     public function delete($key)
     {
         if (is_array($key)) {
@@ -101,82 +114,166 @@ public function delete($key)
         $result = $this->driver()->delete($key);
 
         if ($result) {
-            $this->events(new CacheKeyForgotten($this->driver(), $key));
+            $this->fire(new CacheKeyForgotten($this->driver(), $key));
         }
 
         return $result;
     }
 
+    /**
+     * @return bool
+     */
     public function clear()
     {
         $result = $this->driver->clear();
 
         if ($result) {
-            $this->events(new CacheCleared($this->driver));
+            $this->fire(new CacheCleared($this->driver));
         }
 
         return $result;
     }
 
+    /**
+     * @param iterable           $keys
+     * @param Closure|mixed|null $default
+     *
+     * @return iterable|mixed
+     */
     public function getMultiple($keys, $default = null)
     {
-        $this->multiple('get', $keys);
-
-        return $this->driver()->getMultiple($keys, $default);
+        return $this->multiple('get', $keys) ? $this->driver()->getMultiple($keys, $default) : reduce_value($default);
     }
 
+    /**
+     * @param iterable $values
+     * @param null     $ttl
+     *
+     * @return bool
+     */
     public function setMultiple($values, $ttl = null)
     {
-        $this->multiple('set', $values, $ttl);
-
-        return $this->driver()->setMultiple($values, $ttl);
+        return $this->multiple('set', $values, $ttl) ? $this->driver()->setMultiple($values, $ttl) : false;
     }
 
+    /**
+     * @param iterable $keys
+     *
+     * @return bool
+     */
     public function deleteMultiple($keys)
     {
-        $this->multiple('delete', $keys);
-
-        return $this->driver()->deleteMultiple($keys);
+        return $this->multiple('delete', $keys) ? $this->driver()->deleteMultiple($keys) : false;
     }
 
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
     public function has($key)
     {
         return $this->driver()->has($key);
     }
 
-    public function driver()
+    /**
+     * @return bool
+     */
+    public function empty(): bool
     {
-        return $this->driver;
+        return $this->driver()->empty();
     }
 
-    public function setEventProvider(EventProvider $event)
+    /**
+     * @return bool
+     */
+    public function notEmpty(): bool
     {
-        $this->events = $event;
+        return $this->driver()->notEmpty();
+    }
 
-        return $this;
+    /**
+     * @return CacheDriver
+     */
+    public function driver()
+    {
+        return $this->driver;
     }
 
-    protected function multiple(string $method, $keys, $ttl = null)
+    /**
+     * @param string         $method
+     * @param iterable       $keys
+     * @param null|int|mixed $ttl
+     *
+     * @return bool
+     */
+    protected function multiple(string $method, $keys, $ttl = null): bool
     {
         foreach ($keys as $key => $value) {
-            if ($method === 'get' && $this->driver()->canGet($key)) {
-                $this->events(new CacheHit($this->driver, $key, $this->driver()->get($key)));
+            $result = false;
+
+            if ($method === 'get' && $result = $this->driver()->canGet($value)) {
+                $this->delayedFire(new CacheHit($this->driver, $key, $this->driver()->get($value)));
             }
 
-            if ($method === 'set' && $this->driver()->canSet($key, $value)) {
-                $this->events(new CacheKeyStored($this->driver, $key, $value, $ttl));
+            if ($method === 'set' && $result = $this->driver()->canSet($key, $value)) {
+                $this->delayedFire(new CacheKeyStored($this->driver, $key, $value, $ttl));
             }
 
-            if ($method === 'delete' && $this->driver()->canDelete($key)) {
-                $this->events(new CacheKeyForgotten($this->driver, $key));
+            if ($method === 'delete' && $result = $this->driver()->canDelete($value)) {
+                $this->delayedFire(new CacheKeyForgotten($this->driver, $value));
+            }
+
+            if ($result === false) {
+                $this->flushDelayedEvents(false);
+                return false;
             }
         }
+
+        $this->flushDelayedEvents();
+
+        return true;
     }
 
-    protected function events(Event $event, string $key = null)
+    /**
+     * @param Event $event
+     *
+     * @return self
+     */
+    protected function fire(Event $event): self
     {
         $this->events->fire($event);
 
         return $this;
     }
+
+    /**
+     * @param bool $fire
+     *
+     * @return self
+     */
+    protected function flushDelayedEvents(bool $fire = true): self
+    {
+        if ($fire) {
+            array_map(function (Event $event) {
+                $this->fire($event);
+            }, $this->delayedEvents);
+        }
+
+        $this->delayedEvents = [];
+
+        return $this;
+    }
+
+    /**
+     * @param Event $event
+     *
+     * @return $this
+     */
+    protected function delayedFire(Event $event): self
+    {
+        $this->delayedEvents[] = $event;
+
+        return $this;
+    }
 }
diff --git a/src/WmiScripting/Support/Cache/Events/CacheEvent.php b/src/WmiScripting/Support/Cache/Events/CacheEvent.php
index 917e425..ab084d6 100644
--- a/src/WmiScripting/Support/Cache/Events/CacheEvent.php
+++ b/src/WmiScripting/Support/Cache/Events/CacheEvent.php
@@ -11,7 +11,12 @@ class CacheEvent extends Event
     public function __construct(CacheDriver $driver, $key = null, $value = null, $ttl = null)
     {
         parent::__construct(
-            (new Payload())->add('driver', $driver)->add('key', $key)->add('value', $value)->add('ttl', $ttl)
+            (new Payload())
+                ->add('driver', $driver)
+                ->add('name', $driver->name())
+                ->add('key', $key)
+                ->add('value', $value)
+                ->add('ttl', $ttl)
         );
     }
 }
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php b/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
index dc549bd..5be3295 100644
--- a/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
@@ -2,6 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Cache\Events;
 
-class CacheKeyExpired extends CacheEvent
+class CacheKeyExpired extends CacheKeyForgotten
 {
 }
diff --git a/src/WmiScripting/config/bootstrap.php b/src/WmiScripting/config/bootstrap.php
index bf33e9f..f4b3149 100644
--- a/src/WmiScripting/config/bootstrap.php
+++ b/src/WmiScripting/config/bootstrap.php
@@ -8,6 +8,7 @@
 return [
     'cache' => [
         'driver' => ArrayDriver::class,
+        'ttl' => 60 * 5,
     ],
 
     'com' => [
diff --git a/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
index 0185e62..c986cb8 100644
--- a/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
+++ b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
@@ -7,8 +7,12 @@
 use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
 use PhpWinTools\WmiScripting\Support\Cache\CacheProvider;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheHit;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheCleared;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyStored;
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyExpired;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyForgotten;
+use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
 
 class ArrayCacheDriverTest extends TestCase
 {
@@ -45,18 +49,98 @@ public function it_uses_the_array_driver_when_specified()
     }
 
     /** @test */
-    public function it_fires_an_event_when_a_value_is_set()
+    public function it_fires_an_event_when_a_value_is_set_and_returns_true_when_successful()
     {
         $this->assertCount(4, $this->eventHistory->get(CacheKeyStored::class));
-        $this->cache->set('test', $value = 'value');
+        $this->assertTrue($this->cache->set('test', $value = 'value'));
         $this->assertCount(5, $this->eventHistory->get(CacheKeyStored::class));
+        $this->assertTrue($this->cache->has('test'));
         $this->assertSame($value, $this->cache->get('test'));
     }
 
+    /** @test */
+    public function it_throws_an_exception_when_setting_an_invalid_key()
+    {
+        $this->expectException(CacheInvalidArgumentException::class);
+
+        $this->cache->set(new \stdClass(), 'test');
+    }
+
+    /** @test */
+    public function it_returns_false_when_unable_to_store_multiple_items()
+    {
+        $this->assertFalse($this->cache->setMultiple([1 => 'value']));
+    }
+
+    /** @test */
+    public function it_throws_an_exception_when_trying_to_get_an_invalid_key()
+    {
+        $this->expectException(CacheInvalidArgumentException::class);
+
+        $this->cache->get(new \stdClass());
+    }
+
+    /** @test */
+    public function it_returns_default_value_when_trying_to_get_multiple_keys_and_one_is_invalid_and_doesnt_fire_event()
+    {
+        $this->assertFalse($this->cache->get(['key2', new \stdClass()], false));
+        $this->assertTrue($this->eventHistory->hasNotFired(CacheHit::class));
+    }
+
     /** @test */
     public function it_can_return_a_cached_value_and_fire_event()
     {
         $this->assertSame($this->cached_values['key3'], $this->cache->get('key3'));
         $this->assertTrue($this->eventHistory->hasFired(CacheHit::class));
     }
+
+    /** @test */
+    public function it_can_delete_an_item_and_fire_an_event()
+    {
+        $this->assertSame($this->cached_values['key2'], $this->cache->get('key2'));
+        $this->assertTrue($this->cache->delete('key2'));
+        $this->assertFalse($this->cache->get('key2', false));
+        $this->assertTrue($this->eventHistory->hasFired(CacheKeyForgotten::class));
+    }
+
+    /** @test */
+    public function it_can_delete_multiple_items_fire_events()
+    {
+        $this->assertTrue($this->cache->notEmpty());
+        $this->assertTrue($this->cache->delete(['key1', 'key2', 'key3', 'key4']));
+        $this->assertCount(4, $this->eventHistory->get(CacheKeyForgotten::class));
+        $this->assertTrue($this->cache->empty());
+    }
+
+    /** @test */
+    public function it_returns_false_when_trying_delete_an_item_that_doesnt_exist_or_is_invalid()
+    {
+        $this->assertFalse($this->cache->delete('not a key'));
+        $this->assertFalse($this->cache->driver()->deleteMultiple(['not a key']));
+        $this->assertFalse($this->cache->delete([new \stdClass()]));
+    }
+
+    /** @test */
+    public function it_can_get_multiple_values_and_fire_events()
+    {
+        $this->assertSame($this->cached_values, $this->cache->get(['key1', 'key2', 'key3', 'key4']));
+        $this->assertCount(4, $this->eventHistory->get(CacheHit::class));
+    }
+
+    /** @test */
+    public function it_can_clear_its_cache_and_fire_an_event()
+    {
+        $this->assertTrue($this->cache->notEmpty());
+        $this->assertTrue($this->cache->clear());
+        $this->assertTrue($this->cache->empty());
+        $this->assertTrue($this->eventHistory->hasFired(CacheCleared::class));
+    }
+
+    /** @test */
+    public function it_removes_an_expired_key_and_fires_an_event()
+    {
+        $this->assertTrue($this->cache->set('test', 'value', -30));
+        $this->assertFalse($this->cache->get('test', false));
+        $this->assertTrue($this->eventHistory->hasFired(CacheKeyExpired::class));
+    }
 }

From b6b0075544f3c63aec5f6e8bebe9b0e707ca671a Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Mon, 23 Sep 2019 15:14:59 -0400
Subject: [PATCH 28/33] Clean up getMultiple

---
 .../Support/Cache/CacheDriver.php             | 69 ++++++++++-------
 .../Support/Cache/CacheProvider.php           | 75 ++++++++++++++-----
 ...heKeyForgotten.php => CacheKeyDeleted.php} |  2 +-
 .../Support/Cache/Events/CacheKeyExpired.php  |  2 +-
 .../Support/Cache/ArrayCacheDriverTest.php    | 27 +++++--
 5 files changed, 123 insertions(+), 52 deletions(-)
 rename src/WmiScripting/Support/Cache/Events/{CacheKeyForgotten.php => CacheKeyDeleted.php} (61%)

diff --git a/src/WmiScripting/Support/Cache/CacheDriver.php b/src/WmiScripting/Support/Cache/CacheDriver.php
index 8a940a8..642588d 100644
--- a/src/WmiScripting/Support/Cache/CacheDriver.php
+++ b/src/WmiScripting/Support/Cache/CacheDriver.php
@@ -15,13 +15,13 @@ abstract class CacheDriver implements CacheInterface
     /** @var mixed */
     protected $store;
 
-    /** @var string|null */
+    /** @var string */
     protected $name = null;
 
     public function __construct(Config $config = null, string $name = null)
     {
         $this->config = $config ?? Config::instance();
-        $this->name = $name;
+        $this->name = $name ?? get_called_class();
     }
 
     /**
@@ -124,9 +124,9 @@ public function notEmpty(): bool
     }
 
     /**
-     * @return string|null
+     * @return string
      */
-    public function name()
+    public function name(): string
     {
         return $this->name;
     }
@@ -138,11 +138,7 @@ public function name()
      */
     public function canGet($key): bool
     {
-        try {
-            return $this->has($key) && $this->notExpired($key);
-        } catch (CacheInvalidArgumentException $exception) {
-            return false;
-        }
+        return $this->isValidKey($key) && $this->has($key) && $this->notExpired($key);
     }
 
     /**
@@ -154,11 +150,7 @@ public function canGet($key): bool
      */
     public function canSet($key, $value, $ttl = null): bool
     {
-        try {
-            return $this->validateKey($key) === $key && $this->isValidValue($value) && $this->isValidTtl($ttl);
-        } catch (CacheInvalidArgumentException $exception) {
-            return false;
-        }
+        return $this->isValidKey($key) && $this->isValidValue($value) && $this->isValidTtl($ttl);
     }
 
     /**
@@ -168,21 +160,17 @@ public function canSet($key, $value, $ttl = null): bool
      */
     public function canDelete($key): bool
     {
-        try {
-            return $this->has($key);
-        } catch (CacheInvalidArgumentException $exception) {
-            return false;
-        }
+        return $this->isValidKey($key) && $this->has($key);
     }
 
     /**
-     * @param null|int|mixed $ttl
+     * @param string $key
      *
      * @return bool
      */
-    protected function isValidTtl($ttl): bool
+    protected function isValidKey($key): bool
     {
-        return is_null($ttl) || is_int($ttl);
+        return is_string($key);
     }
 
     /**
@@ -196,16 +184,47 @@ protected function isValidValue($value): bool
     }
 
     /**
-     * @param string $key
+     * @param null|int|mixed $ttl
+     *
+     * @return bool
+     */
+    protected function isValidTtl($ttl): bool
+    {
+        return is_null($ttl) || is_int($ttl);
+    }
+
+    /**
+     * @param mixed $key
+     *
+     * @throws CacheInvalidArgumentException
      *
      * @return string
      */
     protected function validateKey($key)
     {
-        if (is_string($key)) {
+        if ($this->isValidKey($key)) {
             return $key;
         }
 
-        throw new CacheInvalidArgumentException("Not a valid key.");
+        throw new CacheInvalidArgumentException("'{$this->coerceToString($key)}' is not a valid key.");
+    }
+
+
+    /**
+     * @param mixed $value
+     *
+     * @return string
+     */
+    protected function coerceToString($value): string
+    {
+        if (is_object($value)) {
+            $value = get_class($value);
+        }
+
+        if (is_array($value)) {
+            $value = 'array';
+        }
+
+        return (string) $value;
     }
 }
diff --git a/src/WmiScripting/Support/Cache/CacheProvider.php b/src/WmiScripting/Support/Cache/CacheProvider.php
index 96578fa..3792073 100644
--- a/src/WmiScripting/Support/Cache/CacheProvider.php
+++ b/src/WmiScripting/Support/Cache/CacheProvider.php
@@ -3,7 +3,6 @@
 namespace PhpWinTools\WmiScripting\Support\Cache;
 
 use Closure;
-use DateInterval;
 use Psr\SimpleCache\CacheInterface;
 use Psr\SimpleCache\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Configuration\Config;
@@ -14,8 +13,8 @@
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheMissed;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheCleared;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyStored;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyDeleted;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyExpired;
-use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyForgotten;
 use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
 
 class CacheProvider implements CacheInterface
@@ -56,8 +55,7 @@ public function get($key, $default = null)
         $value = $this->driver()->get($key, null);
 
         if ($this->driver()->expired($key)) {
-            $this->fire(new CacheKeyExpired($this->driver(), $key, $value));
-            $this->delete($key);
+            $this->removeExpired($key, $value);
             $value = null;
         }
 
@@ -72,12 +70,12 @@ public function get($key, $default = null)
     }
 
     /**
-     * Accepts either a string or a key-value pair array. If the given an array than the second value is used as the
-     * ttl, unless the ttl is explicitly set.
+     * Accepts either a string or a key-value pair array. If given an array then the second value is used as the
+     * ttl, unless $ttl is explicitly set.
      *
-     * @param string|array          $key
-     * @param mixed                 $value
-     * @param null|int|DateInterval $ttl
+     * @param string|array $key
+     * @param mixed        $value
+     * @param null|int     $ttl
      *
      * @throws CacheInvalidArgumentException|InvalidArgumentException
      *
@@ -114,7 +112,7 @@ public function delete($key)
         $result = $this->driver()->delete($key);
 
         if ($result) {
-            $this->fire(new CacheKeyForgotten($this->driver(), $key));
+            $this->fire(new CacheKeyDeleted($this->driver(), $key));
         }
 
         return $result;
@@ -135,14 +133,38 @@ public function clear()
     }
 
     /**
-     * @param iterable           $keys
-     * @param Closure|mixed|null $default
+     * @param iterable $keys
+     * @param null     $default
+     *
+     * @throws InvalidArgumentException|CacheInvalidArgumentException
      *
-     * @return iterable|mixed
+     * @return iterable
      */
     public function getMultiple($keys, $default = null)
     {
-        return $this->multiple('get', $keys) ? $this->driver()->getMultiple($keys, $default) : reduce_value($default);
+        $hit = [];
+
+        foreach ($keys as $key) {
+            if ($this->driver()->expired($key)) {
+                $this->removeExpired($key);
+            }
+
+            if ($this->driver()->canGet($key)) {
+                $hit[$key] = $key;
+            }
+        }
+
+        $results = $this->driver()->getMultiple($keys, $default);
+
+        foreach ($results as $key => $value) {
+            if (array_key_exists($key, $hit)) {
+                $this->fire(new CacheHit($this->driver(), $key, $value));
+            } else {
+                $this->fire(new CacheMissed($this->driver(), $key));
+            }
+        }
+
+        return $results;
     }
 
     /**
@@ -212,16 +234,12 @@ protected function multiple(string $method, $keys, $ttl = null): bool
         foreach ($keys as $key => $value) {
             $result = false;
 
-            if ($method === 'get' && $result = $this->driver()->canGet($value)) {
-                $this->delayedFire(new CacheHit($this->driver, $key, $this->driver()->get($value)));
-            }
-
             if ($method === 'set' && $result = $this->driver()->canSet($key, $value)) {
                 $this->delayedFire(new CacheKeyStored($this->driver, $key, $value, $ttl));
             }
 
             if ($method === 'delete' && $result = $this->driver()->canDelete($value)) {
-                $this->delayedFire(new CacheKeyForgotten($this->driver, $value));
+                $this->delayedFire(new CacheKeyDeleted($this->driver, $value));
             }
 
             if ($result === false) {
@@ -235,6 +253,25 @@ protected function multiple(string $method, $keys, $ttl = null): bool
         return true;
     }
 
+    /**
+     * @param      $key
+     * @param null $value
+     *
+     * @throws CacheInvalidArgumentException|InvalidArgumentException
+     *
+     * @return self
+     */
+    protected function removeExpired($key, $value = null): self
+    {
+        if ($this->driver()->expired($key)) {
+            $this->fire(new CacheKeyExpired($this->driver(), $key, $value));
+            $this->delete($key);
+        }
+
+
+        return $this;
+    }
+
     /**
      * @param Event $event
      *
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyForgotten.php b/src/WmiScripting/Support/Cache/Events/CacheKeyDeleted.php
similarity index 61%
rename from src/WmiScripting/Support/Cache/Events/CacheKeyForgotten.php
rename to src/WmiScripting/Support/Cache/Events/CacheKeyDeleted.php
index 1c7b5c3..c65bd66 100644
--- a/src/WmiScripting/Support/Cache/Events/CacheKeyForgotten.php
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyDeleted.php
@@ -2,6 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Cache\Events;
 
-class CacheKeyForgotten extends CacheEvent
+class CacheKeyDeleted extends CacheEvent
 {
 }
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php b/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
index 5be3295..20e45fa 100644
--- a/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
@@ -2,6 +2,6 @@
 
 namespace PhpWinTools\WmiScripting\Support\Cache\Events;
 
-class CacheKeyExpired extends CacheKeyForgotten
+class CacheKeyExpired extends CacheKeyDeleted
 {
 }
diff --git a/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
index c986cb8..46204de 100644
--- a/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
+++ b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
@@ -2,6 +2,7 @@
 
 namespace Tests\WmiScripting\Support\Cache;
 
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheMissed;
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
@@ -10,8 +11,8 @@
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheCleared;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyStored;
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyDeleted;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyExpired;
-use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyForgotten;
 use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
 
 class ArrayCacheDriverTest extends TestCase
@@ -81,10 +82,16 @@ public function it_throws_an_exception_when_trying_to_get_an_invalid_key()
     }
 
     /** @test */
-    public function it_returns_default_value_when_trying_to_get_multiple_keys_and_one_is_invalid_and_doesnt_fire_event()
+    public function it_sets_default_value_to_keys_that_do_not_exist_when_getting_multiple_and_fire_correct_events()
     {
-        $this->assertFalse($this->cache->get(['key2', new \stdClass()], false));
-        $this->assertTrue($this->eventHistory->hasNotFired(CacheHit::class));
+        $results = $this->cache->get(['key2', 'not a key'], false);
+
+        $this->assertSame($this->cached_values['key2'], $results['key2']);
+        $this->assertFalse($results['not a key']);
+        $this->assertCount(1, $hit = $this->eventHistory->get(CacheHit::class));
+        $this->assertCount(1, $miss = $this->eventHistory->get(CacheMissed::class));
+        $this->assertSame($results['key2'], $hit[0]->event()->payload()->get('value'));
+        $this->assertSame('not a key', $miss[0]->event()->payload()->get('key'));
     }
 
     /** @test */
@@ -100,7 +107,7 @@ public function it_can_delete_an_item_and_fire_an_event()
         $this->assertSame($this->cached_values['key2'], $this->cache->get('key2'));
         $this->assertTrue($this->cache->delete('key2'));
         $this->assertFalse($this->cache->get('key2', false));
-        $this->assertTrue($this->eventHistory->hasFired(CacheKeyForgotten::class));
+        $this->assertTrue($this->eventHistory->hasFired(CacheKeyDeleted::class));
     }
 
     /** @test */
@@ -108,7 +115,7 @@ public function it_can_delete_multiple_items_fire_events()
     {
         $this->assertTrue($this->cache->notEmpty());
         $this->assertTrue($this->cache->delete(['key1', 'key2', 'key3', 'key4']));
-        $this->assertCount(4, $this->eventHistory->get(CacheKeyForgotten::class));
+        $this->assertCount(4, $this->eventHistory->get(CacheKeyDeleted::class));
         $this->assertTrue($this->cache->empty());
     }
 
@@ -136,6 +143,14 @@ public function it_can_clear_its_cache_and_fire_an_event()
         $this->assertTrue($this->eventHistory->hasFired(CacheCleared::class));
     }
 
+    /** @test */
+    public function it_removes_expired_values_when_getting_multiple()
+    {
+        $this->cache->set('test', 'value', -30);
+        $this->assertFalse($this->cache->get(['test'], false)['test']);
+        $this->assertTrue($this->eventHistory->hasFired(CacheKeyExpired::class));
+    }
+
     /** @test */
     public function it_removes_an_expired_key_and_fires_an_event()
     {

From 5e0c2525c566dcf2ff9dcfa1351cd357dd303ae2 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Mon, 23 Sep 2019 15:19:11 -0400
Subject: [PATCH 29/33] Style

---
 tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
index 46204de..58189da 100644
--- a/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
+++ b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
@@ -2,12 +2,12 @@
 
 namespace Tests\WmiScripting\Support\Cache;
 
-use PhpWinTools\WmiScripting\Support\Cache\Events\CacheMissed;
 use Tests\TestCase;
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
 use PhpWinTools\WmiScripting\Support\Cache\CacheProvider;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheHit;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheMissed;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheCleared;
 use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyStored;
 use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;

From 6eacec8984f10b8fde66a6218b09ce60e7ae9664 Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 24 Sep 2019 09:30:17 -0400
Subject: [PATCH 30/33] Refactoring

---
 src/WmiScripting/Configuration/Config.php     |  2 +-
 .../Connections/ComConnection.php             | 10 ++++++-
 src/WmiScripting/Connections/Connection.php   |  2 +-
 .../Connections/ConnectionManager.php         |  9 ++++---
 src/WmiScripting/Query/Builder.php            | 27 ++++++++++---------
 src/WmiScripting/config/bootstrap.php         |  5 ++++
 src/WmiScripting/config/providers.php         |  3 +++
 tests/WmiScripting/BuilderTest.php            | 14 ++++++++++
 8 files changed, 53 insertions(+), 19 deletions(-)
 create mode 100644 tests/WmiScripting/BuilderTest.php

diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index a5ec074..622a82d 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -177,7 +177,7 @@ public function resolve()
      *
      * @return array
      */
-    public function resolveStack()
+    public function getResolveStack()
     {
         return $this->resolve_stack;
     }
diff --git a/src/WmiScripting/Connections/ComConnection.php b/src/WmiScripting/Connections/ComConnection.php
index c5c6400..10d664c 100644
--- a/src/WmiScripting/Connections/ComConnection.php
+++ b/src/WmiScripting/Connections/ComConnection.php
@@ -5,7 +5,7 @@
 use function PhpWinTools\WmiScripting\Support\resolve;
 use PhpWinTools\WmiScripting\Support\ApiObjects\Contracts\Services;
 
-class ComConnection
+class ComConnection implements Connection
 {
     const DEFAULT_SERVER = '.';
 
@@ -101,6 +101,14 @@ public function connect(): Services
         return $this->services;
     }
 
+    public function execQuery($query, $model, $relationships)
+    {
+        return $this->connect()
+            ->resolvePropertySets($relationships)
+            ->execQuery($query)
+            ->instantiateModels($model);
+    }
+
     /**
      * @return float
      */
diff --git a/src/WmiScripting/Connections/Connection.php b/src/WmiScripting/Connections/Connection.php
index 586d8d2..4edf989 100644
--- a/src/WmiScripting/Connections/Connection.php
+++ b/src/WmiScripting/Connections/Connection.php
@@ -4,5 +4,5 @@
 
 interface Connection
 {
-    //public static function instantiate(... $constructor_vars): self;
+    public function execQuery($query, $model, $relationships);
 }
diff --git a/src/WmiScripting/Connections/ConnectionManager.php b/src/WmiScripting/Connections/ConnectionManager.php
index 8cae972..9c54290 100644
--- a/src/WmiScripting/Connections/ConnectionManager.php
+++ b/src/WmiScripting/Connections/ConnectionManager.php
@@ -2,12 +2,15 @@
 
 namespace PhpWinTools\WmiScripting\Connections;
 
+use PhpWinTools\WmiScripting\Configuration\Config;
+
 class ConnectionManager
 {
-    protected $connection;
+    /** @var Config */
+    protected $config;
 
-    public function __construct(Connection $connection)
+    public function __construct(Config $config = null)
     {
-        $this->connection = $connection;
+        $this->config = $config ?? Config::instance();
     }
 }
diff --git a/src/WmiScripting/Query/Builder.php b/src/WmiScripting/Query/Builder.php
index fab8d87..084238c 100644
--- a/src/WmiScripting/Query/Builder.php
+++ b/src/WmiScripting/Query/Builder.php
@@ -2,6 +2,7 @@
 
 namespace PhpWinTools\WmiScripting\Query;
 
+use PhpWinTools\WmiScripting\Connections\Connection;
 use PhpWinTools\WmiScripting\Models\Win32Model;
 use PhpWinTools\WmiScripting\Connections\ComConnection;
 use PhpWinTools\WmiScripting\Collections\ModelCollection;
@@ -11,19 +12,22 @@ class Builder
 {
     protected $model;
 
-    protected $from;
-
     protected $services;
 
+    protected $connection;
+
+    protected $from;
+
     protected $selects = ['*'];
 
     protected $wheres = [];
 
     protected $relationships = [];
 
-    public function __construct(Win32Model $model, ComConnection $connection)
+    public function __construct(Win32Model $model, Connection $connection)
     {
         $this->model = $model;
+        $this->connection = $connection;
         $this->from = $model->getWmiClassNameAttribute();
         $this->services = $connection->connect();
     }
@@ -57,17 +61,14 @@ public function getObjectSet(): ObjectSet
         return $this->execQuery($this->queryString());
     }
 
-    /**
-     * @param string $query
-     *
-     * @return ObjectSet
-     */
-    public function execQuery($query): ObjectSet
+    public function execQuery($query)
     {
-        return $this->services
-            ->resolvePropertySets($this->relationships)
-            ->execQuery($query)
-            ->instantiateModels($this->model);
+        return $this->connection->execQuery($query, $this->model, $this->relationships);
+
+//        return $this->services
+//            ->resolvePropertySets($this->relationships)
+//            ->execQuery($query)
+//            ->instantiateModels($this->model);
     }
 
     /**
diff --git a/src/WmiScripting/config/bootstrap.php b/src/WmiScripting/config/bootstrap.php
index f4b3149..b1293d8 100644
--- a/src/WmiScripting/config/bootstrap.php
+++ b/src/WmiScripting/config/bootstrap.php
@@ -3,6 +3,7 @@
 use PhpWinTools\Support\COM\ComWrapper;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
+use PhpWinTools\WmiScripting\Connections\ComConnection;
 use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
 
 return [
@@ -19,6 +20,10 @@
         VariantWrapper::class       => VariantWrapper::class,
     ],
 
+    'connection' => [
+        'default_driver' => ComConnection::class,
+    ],
+
     'event' => [
         'track'  => false,
     ],
diff --git a/src/WmiScripting/config/providers.php b/src/WmiScripting/config/providers.php
index ed38121..84f97a5 100644
--- a/src/WmiScripting/config/providers.php
+++ b/src/WmiScripting/config/providers.php
@@ -1,5 +1,6 @@
 <?php
 
+use PhpWinTools\WmiScripting\Connections\ConnectionManager;
 use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
 use PhpWinTools\WmiScripting\Support\Cache\CacheProvider;
 use PhpWinTools\WmiScripting\Support\Events\EventProvider;
@@ -9,6 +10,7 @@
     'concrete' => [
         'bus'           => CommandBus::class,
         'cache'         => CacheProvider::class,
+        'connection'    => ConnectionManager::class,
         'event'         => EventProvider::class,
         'event_history' => EventHistoryProvider::class,
     ],
@@ -16,6 +18,7 @@
     'aliases' => [
         CommandBus::class           => 'bus',
         CacheProvider::class        => 'cache',
+        ConnectionManager::class    => 'connection',
         EventProvider::class        => 'event',
         EventHistoryProvider::class => 'event_history',
     ],
diff --git a/tests/WmiScripting/BuilderTest.php b/tests/WmiScripting/BuilderTest.php
new file mode 100644
index 0000000..740736f
--- /dev/null
+++ b/tests/WmiScripting/BuilderTest.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Tests\WmiScripting;
+
+use Tests\TestCase;
+
+class BuilderTest extends TestCase
+{
+    /** @test */
+    public function it_does_stuff()
+    {
+        $this->assertTrue(true);
+    }
+}

From ba8318a160afed234e625e89055ab61bd3fdea6d Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 24 Sep 2019 09:59:18 -0400
Subject: [PATCH 31/33] Beging removal of objectset direct dependency

---
 src/WmiScripting/Connections/Response.php           | 10 ++++++++++
 src/WmiScripting/Models/Win32Model.php              |  9 ++++++---
 src/WmiScripting/Query/Builder.php                  |  5 +++++
 src/WmiScripting/Support/ApiObjects/SWbemObject.php | 12 +++++++++++-
 4 files changed, 32 insertions(+), 4 deletions(-)
 create mode 100644 src/WmiScripting/Connections/Response.php

diff --git a/src/WmiScripting/Connections/Response.php b/src/WmiScripting/Connections/Response.php
new file mode 100644
index 0000000..3b33de0
--- /dev/null
+++ b/src/WmiScripting/Connections/Response.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Connections;
+
+use PhpWinTools\WmiScripting\Containers\Collection;
+
+class Response extends Collection
+{
+
+}
diff --git a/src/WmiScripting/Models/Win32Model.php b/src/WmiScripting/Models/Win32Model.php
index 52df451..e19ae6f 100644
--- a/src/WmiScripting/Models/Win32Model.php
+++ b/src/WmiScripting/Models/Win32Model.php
@@ -33,9 +33,13 @@ class Win32Model implements Arrayable, Jsonable, HasAttributes, HidesAttributes,
     /** @var string */
     protected $uuid;
 
-    /** @var ObjectPath|null */
+    /** @var ObjectPath|array|null */
     protected $objectPath;
 
+    protected $qualifiers = [];
+
+    protected $derivations = [];
+
     /** @var Builder|null */
     protected $queryBuilder;
 
@@ -58,10 +62,9 @@ class Win32Model implements Arrayable, Jsonable, HasAttributes, HidesAttributes,
     /* TODO: Remove ObjectPath dependency. Should be passed into attributes */
     public function __construct(array $attributes = [], ObjectPath $objectPath = null)
     {
+        $this->objectPath = $objectPath;
         $this->mergeHiddenAttributes($this->hidden_attributes, $this->merge_parent_hidden_attributes);
         $this->fill($attributes);
-
-        $this->objectPath = $objectPath;
     }
 
     /**
diff --git a/src/WmiScripting/Query/Builder.php b/src/WmiScripting/Query/Builder.php
index 084238c..d80a053 100644
--- a/src/WmiScripting/Query/Builder.php
+++ b/src/WmiScripting/Query/Builder.php
@@ -61,6 +61,11 @@ public function getObjectSet(): ObjectSet
         return $this->execQuery($this->queryString());
     }
 
+    /**
+     * @param $query
+     *
+     * @return mixed|ObjectSet
+     */
     public function execQuery($query)
     {
         return $this->connection->execQuery($query, $this->model, $this->relationships);
diff --git a/src/WmiScripting/Support/ApiObjects/SWbemObject.php b/src/WmiScripting/Support/ApiObjects/SWbemObject.php
index 0252c3a..3201a95 100644
--- a/src/WmiScripting/Support/ApiObjects/SWbemObject.php
+++ b/src/WmiScripting/Support/ApiObjects/SWbemObject.php
@@ -22,6 +22,8 @@ class SWbemObject extends AbstractWbemObject implements ObjectItem
 
     protected $qualifierSet;
 
+    protected $mergedProperties = [];
+
     protected $path;
 
     /** @var VariantWrapper|ObjectVariant */
@@ -37,6 +39,13 @@ public function __construct(VariantWrapper $variant, array $resolve_property_set
         $this->qualifierSet = resolve()->qualifierSet($this->object->Qualifiers_);
         $this->path = resolve()->objectPath($this->object->Path_);
 
+        $this->mergedProperties = array_merge(
+            $this->propertySet->toArray(),
+            ['qualifiers' => $this->qualifierSet->toArray()['qualifiers']],
+            ['objectPath' => $this->path->toArray()],
+            ['derivations' => $this->derivations]
+        );
+
         $this->instantiateWin32Model();
     }
 
@@ -56,7 +65,8 @@ public function instantiateWin32Model(Win32Model $model = null): Win32Model
             return $this->win32Model;
         }
 
-        return $this->win32Model = new $model($this->propertySet->toArray(), $this->path);
+        return $this->win32Model = new $model($this->mergedProperties);
+//        return $this->win32Model = new $model($this->propertySet->toArray(), $this->path);
     }
 
     protected function buildDerivations()

From 453ead524d4adf1e6265bab52c661ad1f94785fa Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 24 Sep 2019 11:01:31 -0400
Subject: [PATCH 32/33] Refactor connection

---
 src/WmiScripting/Models/Win32Model.php            | 15 ++++++---------
 .../Support/ApiObjects/SWbemObject.php            |  2 +-
 .../Support/ApiObjects/SWbemObjectSet.php         |  2 +-
 3 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/src/WmiScripting/Models/Win32Model.php b/src/WmiScripting/Models/Win32Model.php
index e19ae6f..11749d0 100644
--- a/src/WmiScripting/Models/Win32Model.php
+++ b/src/WmiScripting/Models/Win32Model.php
@@ -33,8 +33,8 @@ class Win32Model implements Arrayable, Jsonable, HasAttributes, HidesAttributes,
     /** @var string */
     protected $uuid;
 
-    /** @var ObjectPath|array|null */
-    protected $objectPath;
+    /** @var array|null */
+    protected $path = [];
 
     protected $qualifiers = [];
 
@@ -59,23 +59,20 @@ class Win32Model implements Arrayable, Jsonable, HasAttributes, HidesAttributes,
 
     protected $wmi_class_name;
 
-    /* TODO: Remove ObjectPath dependency. Should be passed into attributes */
-    public function __construct(array $attributes = [], ObjectPath $objectPath = null)
+    public function __construct(array $attributes = [])
     {
-        $this->objectPath = $objectPath;
         $this->mergeHiddenAttributes($this->hidden_attributes, $this->merge_parent_hidden_attributes);
         $this->fill($attributes);
     }
 
     /**
-     * @param array           $attributes
-     * @param ObjectPath|null $objectPath
+     * @param array $attributes
      *
      * @return Win32Model
      */
-    public static function newInstance(array $attributes = [], ObjectPath $objectPath = null)
+    public static function newInstance(array $attributes = [])
     {
-        return new static($attributes, $objectPath);
+        return new static($attributes);
     }
 
     /**
diff --git a/src/WmiScripting/Support/ApiObjects/SWbemObject.php b/src/WmiScripting/Support/ApiObjects/SWbemObject.php
index 3201a95..e999b01 100644
--- a/src/WmiScripting/Support/ApiObjects/SWbemObject.php
+++ b/src/WmiScripting/Support/ApiObjects/SWbemObject.php
@@ -42,7 +42,7 @@ public function __construct(VariantWrapper $variant, array $resolve_property_set
         $this->mergedProperties = array_merge(
             $this->propertySet->toArray(),
             ['qualifiers' => $this->qualifierSet->toArray()['qualifiers']],
-            ['objectPath' => $this->path->toArray()],
+            ['path' => $this->path->toArray()],
             ['derivations' => $this->derivations]
         );
 
diff --git a/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php b/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
index 021cf40..2526bc6 100644
--- a/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
+++ b/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
@@ -6,6 +6,7 @@
 use ArrayAccess;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
+use PhpWinTools\WmiScripting\Connections\Response;
 use PhpWinTools\WmiScripting\Models\Win32Model;
 use function PhpWinTools\WmiScripting\Support\resolve;
 use PhpWinTools\WmiScripting\Collections\ModelCollection;
@@ -61,7 +62,6 @@ public function instantiateModels(Win32Model $model): ObjectSet
     {
         $this->set = $this->set->map(function (ObjectItem $item) use ($model) {
             $item->instantiateWin32Model($model);
-
             return $item;
         });
 

From 6c2ab9d1196c688cc511b69a61b5724c9b1ec33b Mon Sep 17 00:00:00 2001
From: Joe Springe <518362+jspringe@users.noreply.github.com>
Date: Tue, 24 Sep 2019 16:29:09 -0400
Subject: [PATCH 33/33] Working on dot notation

---
 .../Concerns/HasArrayableAttributes.php       | 40 +++++++++++++++++--
 .../Concerns/HasHiddenAttributes.php          |  2 +
 .../Connections/ComConnection.php             |  4 +-
 src/WmiScripting/Connections/Connection.php   |  2 +-
 src/WmiScripting/Models/Win32Model.php        | 35 ++++++++--------
 src/WmiScripting/Query/Builder.php            | 35 +++++-----------
 .../ApiObjects/Contracts/ObjectItem.php       |  2 +
 .../ApiObjects/Contracts/ObjectSet.php        |  2 +
 .../Support/ApiObjects/SWbemObject.php        |  8 +++-
 .../Support/ApiObjects/SWbemObjectSet.php     |  7 ++++
 .../Integration/ScriptingTest.php             |  2 +
 11 files changed, 86 insertions(+), 53 deletions(-)

diff --git a/src/WmiScripting/Concerns/HasArrayableAttributes.php b/src/WmiScripting/Concerns/HasArrayableAttributes.php
index 6810410..a73af56 100644
--- a/src/WmiScripting/Concerns/HasArrayableAttributes.php
+++ b/src/WmiScripting/Concerns/HasArrayableAttributes.php
@@ -3,6 +3,7 @@
 namespace PhpWinTools\WmiScripting\Concerns;
 
 use Exception;
+use Illuminate\Support\Arr;
 use PhpWinTools\Support\StringModule;
 use PhpWinTools\WmiScripting\Contracts\Arrayable;
 use PhpWinTools\WmiScripting\Collections\ArrayCollection;
@@ -18,6 +19,11 @@ trait HasArrayableAttributes
 
     public function getAttribute($attribute, $default = null)
     {
+//        dump($attribute);
+//        if (strpos($attribute, '.') !== false) {
+//            dump(explode('.', $attribute));
+//        }
+
         if ($this->hasAttributeMethod($attribute)) {
             return $this->getAttributeMethodValue('get' . StringModule::studly($attribute) . 'Attribute', $attribute);
         }
@@ -187,7 +193,7 @@ protected function hasToArrayMethod($value)
         return false;
     }
 
-    protected function transformToArray($value)
+    protected function transformToArray($value, $key = null)
     {
         if (is_object($value) && ($value instanceof Arrayable || $value instanceof IlluminateArrayable)) {
             return $value->toArray();
@@ -202,24 +208,50 @@ protected function transformToArray($value)
         }
 
         if (is_array($value)) {
-            return $this->iterateAttributes($value);
+            $value = $this->reduceValueArray($value);
+        }
+
+        if (is_array($value)) {
+            return $this->iterateAttributes($value, $key);
         }
 
         return $value;
     }
 
-    protected function iterateAttributes(array $attributes)
+    protected function iterateAttributes(array $attributes, string $current_key = null)
     {
         $results = [];
 
         foreach ($attributes as $key => $value) {
+            $dot_key = is_null($current_key) ? $key : "{$current_key}.{$key}";
+
             if (class_has_trait(get_called_class(), HasHiddenAttributes::class) && $this->isHidden($key)) {
                 continue;
             }
 
-            $results[$this->replaceAttributeName($key)] = $this->transformToArray($this->getAttribute($key, $value));
+            $value = $this->getAttribute($dot_key, $value);
+
+            $results[$this->replaceAttributeName($key)] = $this->transformToArray($value, $dot_key);
         }
 
         return $results;
     }
+
+    /**
+     * @param $value
+     *
+     * @return mixed
+     */
+    protected function reduceValueArray($value)
+    {
+        if (!is_array($value)) {
+            return $value;
+        }
+
+        if (array_key_exists('value', $value)) {
+            $value = $value['value'];
+        }
+
+        return $value;
+    }
 }
diff --git a/src/WmiScripting/Concerns/HasHiddenAttributes.php b/src/WmiScripting/Concerns/HasHiddenAttributes.php
index 0a59541..644c2d7 100644
--- a/src/WmiScripting/Concerns/HasHiddenAttributes.php
+++ b/src/WmiScripting/Concerns/HasHiddenAttributes.php
@@ -15,9 +15,11 @@ trait HasHiddenAttributes
         'attribute_name_replacements',
         'unmapped_attributes',
 
+        'hidden_booted',
         'hidden_attributes',
         'merge_parent_hidden_attributes',
 
+        'casts_booted',
         'attribute_casting',
         'merge_parent_casting',
     ];
diff --git a/src/WmiScripting/Connections/ComConnection.php b/src/WmiScripting/Connections/ComConnection.php
index 10d664c..a6f4687 100644
--- a/src/WmiScripting/Connections/ComConnection.php
+++ b/src/WmiScripting/Connections/ComConnection.php
@@ -101,12 +101,12 @@ public function connect(): Services
         return $this->services;
     }
 
-    public function execQuery($query, $model, $relationships)
+    public function query($query, $model, $relationships)
     {
         return $this->connect()
             ->resolvePropertySets($relationships)
             ->execQuery($query)
-            ->instantiateModels($model);
+            ->get();
     }
 
     /**
diff --git a/src/WmiScripting/Connections/Connection.php b/src/WmiScripting/Connections/Connection.php
index 4edf989..201ac0c 100644
--- a/src/WmiScripting/Connections/Connection.php
+++ b/src/WmiScripting/Connections/Connection.php
@@ -4,5 +4,5 @@
 
 interface Connection
 {
-    public function execQuery($query, $model, $relationships);
+    public function query($query, $model, $relationships);
 }
diff --git a/src/WmiScripting/Models/Win32Model.php b/src/WmiScripting/Models/Win32Model.php
index 11749d0..19da511 100644
--- a/src/WmiScripting/Models/Win32Model.php
+++ b/src/WmiScripting/Models/Win32Model.php
@@ -19,7 +19,6 @@
 use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Exceptions\WmiClassNotFoundException;
 use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
-use PhpWinTools\WmiScripting\Support\ApiObjects\Contracts\ObjectPath;
 
 /**
  * @link https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-provider
@@ -34,7 +33,7 @@ class Win32Model implements Arrayable, Jsonable, HasAttributes, HidesAttributes,
     protected $uuid;
 
     /** @var array|null */
-    protected $path = [];
+    protected $objectPath = [];
 
     protected $qualifiers = [];
 
@@ -66,13 +65,20 @@ public function __construct(array $attributes = [])
     }
 
     /**
-     * @param array $attributes
+     * @param array       $attributes
+     * @param string|null $connection
      *
      * @return Win32Model
      */
-    public static function newInstance(array $attributes = [])
+    public static function newInstance(array $attributes = [], string $connection = null)
     {
-        return new static($attributes);
+        $instance = new static($attributes);
+
+        if ($connection) {
+            $instance->setConnection($connection);
+        }
+
+        return $instance;
     }
 
     /**
@@ -115,6 +121,11 @@ public function getConnection($connection = null)
         return connection($connection, $this->connection);
     }
 
+    public function setConnection(string $connection)
+    {
+        $this->connection = $connection;
+    }
+
     /**
      * @return string
      */
@@ -217,20 +228,6 @@ protected function fill(array $attributes)
         }
     }
 
-    /**
-     * @param $value
-     *
-     * @return mixed
-     */
-    protected function reduceValueArray($value)
-    {
-        if (is_array($value) && array_key_exists('value', $value)) {
-            $value = $value['value'];
-        }
-
-        return $value;
-    }
-
     /**
      * @param Mappings|string $mapping_string_class
      * @param mixed           $constant
diff --git a/src/WmiScripting/Query/Builder.php b/src/WmiScripting/Query/Builder.php
index d80a053..fd9971f 100644
--- a/src/WmiScripting/Query/Builder.php
+++ b/src/WmiScripting/Query/Builder.php
@@ -2,9 +2,8 @@
 
 namespace PhpWinTools\WmiScripting\Query;
 
-use PhpWinTools\WmiScripting\Connections\Connection;
 use PhpWinTools\WmiScripting\Models\Win32Model;
-use PhpWinTools\WmiScripting\Connections\ComConnection;
+use PhpWinTools\WmiScripting\Connections\Connection;
 use PhpWinTools\WmiScripting\Collections\ModelCollection;
 use PhpWinTools\WmiScripting\Support\ApiObjects\Contracts\ObjectSet;
 
@@ -34,31 +33,22 @@ public function __construct(Win32Model $model, Connection $connection)
 
     public function all()
     {
-        return $this->execQuery("select * from {$this->from}")->getSet();
+        return $this->hydrate($this->query("select * from {$this->from}"));
     }
 
-    /**
-     * @return ModelCollection|Win32Model[]
-     */
-    public function get()
+    public function hydrate(array $items = [])
     {
-        return $this->getModelCollection();
+        return new ModelCollection(array_map(function ($item) {
+            return $this->model::newInstance($item);
+        }, $items));
     }
 
     /**
      * @return ModelCollection|Win32Model[]
      */
-    public function getModelCollection()
-    {
-        return $this->getObjectSet()->getSet();
-    }
-
-    /**
-     * @return ObjectSet
-     */
-    public function getObjectSet(): ObjectSet
+    public function get()
     {
-        return $this->execQuery($this->queryString());
+        return $this->hydrate($this->query($this->queryString()));
     }
 
     /**
@@ -66,14 +56,9 @@ public function getObjectSet(): ObjectSet
      *
      * @return mixed|ObjectSet
      */
-    public function execQuery($query)
+    public function query($query)
     {
-        return $this->connection->execQuery($query, $this->model, $this->relationships);
-
-//        return $this->services
-//            ->resolvePropertySets($this->relationships)
-//            ->execQuery($query)
-//            ->instantiateModels($this->model);
+        return $this->connection->query($query, $this->model, $this->relationships);
     }
 
     /**
diff --git a/src/WmiScripting/Support/ApiObjects/Contracts/ObjectItem.php b/src/WmiScripting/Support/ApiObjects/Contracts/ObjectItem.php
index 5870fdd..44bc20c 100644
--- a/src/WmiScripting/Support/ApiObjects/Contracts/ObjectItem.php
+++ b/src/WmiScripting/Support/ApiObjects/Contracts/ObjectItem.php
@@ -11,5 +11,7 @@ interface ObjectItem extends WbemObject
      */
     public function getModel(): Win32Model;
 
+    public function get();
+
     public function instantiateWin32Model(Win32Model $model = null): Win32Model;
 }
diff --git a/src/WmiScripting/Support/ApiObjects/Contracts/ObjectSet.php b/src/WmiScripting/Support/ApiObjects/Contracts/ObjectSet.php
index c98ed74..5b44bf8 100644
--- a/src/WmiScripting/Support/ApiObjects/Contracts/ObjectSet.php
+++ b/src/WmiScripting/Support/ApiObjects/Contracts/ObjectSet.php
@@ -10,6 +10,8 @@ interface ObjectSet extends WbemObject, ArrayAccess
 {
     public function count();
 
+    public function get();
+
     /**
      * @return Win32Model[]|ModelCollection
      */
diff --git a/src/WmiScripting/Support/ApiObjects/SWbemObject.php b/src/WmiScripting/Support/ApiObjects/SWbemObject.php
index e999b01..fc34776 100644
--- a/src/WmiScripting/Support/ApiObjects/SWbemObject.php
+++ b/src/WmiScripting/Support/ApiObjects/SWbemObject.php
@@ -42,7 +42,7 @@ public function __construct(VariantWrapper $variant, array $resolve_property_set
         $this->mergedProperties = array_merge(
             $this->propertySet->toArray(),
             ['qualifiers' => $this->qualifierSet->toArray()['qualifiers']],
-            ['path' => $this->path->toArray()],
+            ['objectPath' => $this->path->toArray()],
             ['derivations' => $this->derivations]
         );
 
@@ -57,6 +57,11 @@ public function getModel(): Win32Model
         return $this->getAttribute('win32Model');
     }
 
+    public function get()
+    {
+        return $this->mergedProperties;
+    }
+
     public function instantiateWin32Model(Win32Model $model = null): Win32Model
     {
         $model = $model ?? Classes::getClass($this->path->getAttribute('class'));
@@ -66,7 +71,6 @@ public function instantiateWin32Model(Win32Model $model = null): Win32Model
         }
 
         return $this->win32Model = new $model($this->mergedProperties);
-//        return $this->win32Model = new $model($this->propertySet->toArray(), $this->path);
     }
 
     protected function buildDerivations()
diff --git a/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php b/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
index 2526bc6..88a7071 100644
--- a/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
+++ b/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
@@ -50,6 +50,13 @@ public function count(): int
         return $this->count;
     }
 
+    public function get()
+    {
+        return $this->set->map(function (ObjectItem $item) {
+            return $item->get();
+        })->toArray();
+    }
+
     /**
      * @return Win32Model[]|ModelCollection
      */
diff --git a/tests/WmiScripting/Integration/ScriptingTest.php b/tests/WmiScripting/Integration/ScriptingTest.php
index 2a86d4c..3c91e21 100644
--- a/tests/WmiScripting/Integration/ScriptingTest.php
+++ b/tests/WmiScripting/Integration/ScriptingTest.php
@@ -30,5 +30,7 @@ public function it_can_actually_return_a_logical_disk()
         $this->assertInstanceOf(LogicalDisk::class, $disks->first(function (LogicalDisk $disk) {
             return $disk->getAttribute('deviceID') === 'C:';
         }));
+
+//        dd($disks->first()->toArray());
     }
 }
