Added EventContextData containers.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core;
|
||||
use LotGD\Core\Events\EventContext;
|
||||
|
||||
interface EventHandler
|
||||
{
|
||||
|
||||
+13
-2
@@ -5,11 +5,13 @@ namespace LotGD\Core;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use LotGD\Core\Events\EventContext;
|
||||
use LotGD\Core\Models\EventSubscription;
|
||||
use LotGD\Core\EventHandler;
|
||||
use LotGD\Core\Exceptions\ClassNotFoundException;
|
||||
use LotGD\Core\Exceptions\SubscriptionNotFoundException;
|
||||
use LotGD\Core\Exceptions\WrongTypeException;
|
||||
use LotGD\Core\Events\EventContextDataContainer;
|
||||
|
||||
/**
|
||||
* Manages a simple publish/subscribe system based on regular expressions
|
||||
@@ -33,8 +35,9 @@ class EventManager
|
||||
* are run.
|
||||
*
|
||||
* @param string $event The name of the event to publish.
|
||||
* @param EventContextDataContainer $contextData The Data context
|
||||
*/
|
||||
public function publish(string $event, array &$context)
|
||||
public function publish(string $event, EventContextDataContainer $contextData): EventContextDataContainer
|
||||
{
|
||||
// For right now, implement the naive approach of iterating every entry
|
||||
// in the subscription database, checking the regular expression. We
|
||||
@@ -49,9 +52,17 @@ class EventManager
|
||||
if (preg_match($s->getPattern(), $event)) {
|
||||
$class = $s->getClass();
|
||||
$this->g->getLogger()->addDebug(" Handling with {$class}.");
|
||||
$c = $class::handleEvent($this->g, $event, $context);
|
||||
|
||||
$eventContext = new EventContext($event, $s->getPattern(), $contextData);
|
||||
|
||||
$returnedEventContext = $class::handleEvent($this->g, $eventContext);
|
||||
if ($returnedEventContext->hasDataChanged($contextData)) {
|
||||
$contextData = $returnedEventContext->getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $contextData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core;
|
||||
namespace LotGD\Core\Events;
|
||||
use LotGD\Core\Exceptions\ArgumentException;
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ class EventContext
|
||||
return $this->matchingPattern;
|
||||
}
|
||||
|
||||
public function hasDataType($type): bool
|
||||
{
|
||||
return $this->data instanceof $type ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the immutable data container.
|
||||
* @return EventContextDataContainer
|
||||
@@ -87,4 +92,14 @@ class EventContext
|
||||
{
|
||||
$this->data = $this->data->setFields($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given original data is the same as currently held within this context.
|
||||
* @param EventContextDataContainer $originalData
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDataChanged(EventContextDataContainer $originalData): bool
|
||||
{
|
||||
return $this->data === $originalData ? false : true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Events;
|
||||
|
||||
|
||||
/**
|
||||
* Data container for default, unspecific data without any sanitizing on the given fields.
|
||||
* @package LotGD\Core\Events
|
||||
*/
|
||||
class EventContextData extends EventContextDataContainer
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core;
|
||||
|
||||
namespace LotGD\Core\Events;
|
||||
|
||||
use LotGD\Core\Exceptions\ArgumentException;
|
||||
|
||||
class EventContextDataContainer
|
||||
abstract class EventContextDataContainer
|
||||
{
|
||||
private $data;
|
||||
|
||||
@@ -39,7 +38,7 @@ class EventContextDataContainer
|
||||
*/
|
||||
public function has(string $field): bool
|
||||
{
|
||||
return isset($this->data[$field]);
|
||||
return array_key_exists($field, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Events;
|
||||
|
||||
|
||||
use Doctrine\Common\Util\Debug;
|
||||
use Doctrine\DBAL\Schema\View;
|
||||
use LotGD\Core\Exceptions\ArgumentException;
|
||||
use LotGD\Core\Models\Character;
|
||||
use LotGD\Core\Models\Scene;
|
||||
use LotGD\Core\Models\Viewpoint;
|
||||
|
||||
|
||||
class NavigateToScene extends EventContextDataContainer
|
||||
{
|
||||
protected function __construct(array $data)
|
||||
{
|
||||
$mustHaveForm = ["referrer", "viewpoint", "scene", "parameters", "redirect"];
|
||||
$doesHaveForm = array_keys($data);
|
||||
sort($mustHaveForm); sort($doesHaveForm);
|
||||
|
||||
if ($doesHaveForm !== $mustHaveForm) {
|
||||
throw new ArgumentException("A new NavigateToScene event must have referrer, viewpoint, scene, parameters and redirect.");
|
||||
}
|
||||
|
||||
if ($data["referrer"] instanceof Scene === false and $data["referrer"] !== null) {
|
||||
throw new ArgumentException(sprintf(
|
||||
"data[referrer] must be an instance of %s, %s given.",
|
||||
Scene::class,
|
||||
get_class($data["referrer"])
|
||||
));
|
||||
}
|
||||
|
||||
if ($data["scene"] instanceof Scene === false) {
|
||||
throw new ArgumentException(sprintf(
|
||||
"data[scene] must be an instance of %s, %s given.",
|
||||
Scene::class,
|
||||
get_class($data["scene"])
|
||||
));
|
||||
}
|
||||
|
||||
if ($data["viewpoint"] instanceof Viewpoint === false) {
|
||||
throw new ArgumentException(sprintf(
|
||||
"data[viewpoint] must be an instance of %s, %s given.",
|
||||
Viewpoint::class,
|
||||
get_class($data["viewpoint"])
|
||||
));
|
||||
}
|
||||
|
||||
parent::__construct($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Events;
|
||||
|
||||
|
||||
use LotGD\Core\Exceptions\ArgumentException;
|
||||
use LotGD\Core\Models\Character;
|
||||
use LotGD\Core\Models\Scene;
|
||||
|
||||
class NewViewpoint extends EventContextDataContainer
|
||||
{
|
||||
protected function __construct(array $data)
|
||||
{
|
||||
if (array_keys($data) !== ["character", "scene"]) {
|
||||
throw new ArgumentException("A NewViewpoint event must have only character and scene.");
|
||||
}
|
||||
|
||||
if (!$data["character"] instanceof Character) {
|
||||
throw new ArgumentException(sprintf(
|
||||
"NewViewpoint data[character] must be an instance of %s, %s given.",
|
||||
Character::class,
|
||||
get_class($data)
|
||||
));
|
||||
}
|
||||
|
||||
if ($data["scene"] !== null and !$data["scene"] instanceof Scene) {
|
||||
throw new ArgumentException(sprintf(
|
||||
"NewViewpoint data[scene] must be an instance of %s or null, %s given.",
|
||||
Scene::class,
|
||||
get_class($data)
|
||||
));
|
||||
}
|
||||
|
||||
parent::__construct($data);
|
||||
}
|
||||
}
|
||||
+13
-9
@@ -5,6 +5,8 @@ namespace LotGD\Core;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use LotGD\Core\Events\NavigateToScene;
|
||||
use LotGD\Core\Events\NewViewpoint;
|
||||
use Monolog\Logger;
|
||||
|
||||
use LotGD\Core\Models\{
|
||||
@@ -192,13 +194,14 @@ class Game
|
||||
if ($v === null) {
|
||||
// No viewpoint set up for this user. Run the hook to find the default
|
||||
// scene.
|
||||
$context = [
|
||||
$contextData = NewViewpoint::create([
|
||||
'character' => $this->getCharacter(),
|
||||
'scene' => null
|
||||
];
|
||||
$this->getEventManager()->publish('h/lotgd/core/default-scene', $context);
|
||||
]);
|
||||
|
||||
$s = $context['scene'];
|
||||
$contextData = $this->getEventManager()->publish('h/lotgd/core/default-scene', $contextData);
|
||||
|
||||
$s = $contextData->get("scene");
|
||||
if ($s === null) {
|
||||
throw new InvalidConfigurationException("No subscriber to h/lotgd/core/default-scene returned a scene.");
|
||||
}
|
||||
@@ -292,17 +295,18 @@ class Game
|
||||
// Let and installed listeners (ie modules) make modifications to the
|
||||
// new viewpoint, including the ability to redirect the user to
|
||||
// a different scene, by setting $context['redirect'] to a new scene.
|
||||
$context = [
|
||||
$contextData = NavigateToScene::create([
|
||||
'referrer' => $referrer,
|
||||
'viewpoint' => $viewpoint,
|
||||
'scene' => $scene,
|
||||
'parameters' => $parameters,
|
||||
'redirect' => null
|
||||
];
|
||||
$hook = 'h/lotgd/core/navigate-to/' . $scene->getTemplate();
|
||||
$this->getEventManager()->publish($hook, $context);
|
||||
]);
|
||||
|
||||
$scene = $context['redirect'];
|
||||
$hook = 'h/lotgd/core/navigate-to/' . $scene->getTemplate();
|
||||
$contextData = $this->getEventManager()->publish($hook, $contextData);
|
||||
|
||||
$scene = $contextData->get('redirect');
|
||||
if ($scene !== null) {
|
||||
$id = $scene->getId();
|
||||
$this->getLogger()->debug("Redirecting to sceneId={$id}");
|
||||
|
||||
@@ -9,18 +9,6 @@ use LotGD\Core\Models\Module as ModuleModel;
|
||||
*/
|
||||
interface Module extends EventHandler
|
||||
{
|
||||
/**
|
||||
* Called when an event is published that is handled by this class.
|
||||
*
|
||||
* @param Game $g The game.
|
||||
* @param string $event Name of this event.
|
||||
* @param array $context Arbitrary dictionary representing context around this event.
|
||||
* @return array|null Return an array if you want to make changes to the $context before
|
||||
* the next handler is called. Otherwise, return null. Any changes made will be propogated
|
||||
* to the event publisher as well.
|
||||
*/
|
||||
public static function handleEvent(Game $g, string $event, array &$context);
|
||||
|
||||
/**
|
||||
* Lifecycle method called when this module is initially installed. Use
|
||||
* this method to perform one-time setup operations like adding tables
|
||||
|
||||
@@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Tests;
|
||||
|
||||
use LotGD\Core\Events\EventContext;
|
||||
use LotGD\Core\Events\EventContextData;
|
||||
use LotGD\Core\Game;
|
||||
use LotGD\Core\EventManager;
|
||||
use LotGD\Core\EventHandler;
|
||||
@@ -20,7 +22,9 @@ class EventManagerTestInvalidSubscriber
|
||||
|
||||
class EventManagerTestSubscriber implements EventHandler
|
||||
{
|
||||
public static function handleEvent(Game $g, string $event, array &$context) {}
|
||||
public static function handleEvent(Game $g, EventContext $context): EventContext {
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
|
||||
class EventManagerTest extends CoreModelTestCase
|
||||
@@ -129,9 +133,11 @@ class EventManagerTest extends CoreModelTestCase
|
||||
$em = new EventManager($this->g);
|
||||
|
||||
$event = 'test.foo.something_here';
|
||||
$context = array('foo' => 'bar');
|
||||
$contextData = EventContextData::create(["foo" => "bar"]);
|
||||
|
||||
$em->publish($event, $context);
|
||||
$this->assertEquals($context['foo'], 'baz');
|
||||
// The event is expected to change foo from bar to baz.
|
||||
$contextDataModified = $em->publish($event, $contextData);
|
||||
$this->assertNotSame($contextData, $contextDataModified);
|
||||
$this->assertEquals("baz", $contextDataModified->get("foo"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
namespace LotGD\Core\Tests\FakeModule;
|
||||
|
||||
use LotGD\Core\Game;
|
||||
use LotGD\Core\Events\EventContext;
|
||||
use LotGD\Core\Module as ModuleInterface;
|
||||
use LotGD\Core\Models\Module as ModuleModel;
|
||||
|
||||
class Module implements ModuleInterface {
|
||||
public static function handleEvent(Game $g, string $event, array &$context) {
|
||||
$context['foo'] = 'baz';
|
||||
public static function handleEvent(Game $g, EventContext $context): EventContext
|
||||
{
|
||||
$context->setDataField("foo", "baz");
|
||||
return $context;
|
||||
}
|
||||
public static function onRegister(Game $g, ModuleModel $module) {}
|
||||
|
||||
+23
-26
@@ -8,26 +8,16 @@ use Doctrine\ORM\EntityManager;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\NullHandler;
|
||||
|
||||
use LotGD\Core\Action;
|
||||
use LotGD\Core\ActionGroup;
|
||||
use LotGD\Core\Bootstrap;
|
||||
use LotGD\Core\Configuration;
|
||||
use LotGD\Core\ComposerManager;
|
||||
use LotGD\Core\DiceBag;
|
||||
use LotGD\Core\EventHandler;
|
||||
use LotGD\Core\EventManager;
|
||||
use LotGD\Core\Game;
|
||||
use LotGD\Core\TimeKeeper;
|
||||
use LotGD\Core\ModuleManager;
|
||||
use LotGD\Core\Models\Character;
|
||||
use LotGD\Core\Models\Viewpoint;
|
||||
use LotGD\Core\Models\Scene;
|
||||
use LotGD\Core\Exceptions\ {
|
||||
ActionNotFoundException,
|
||||
CharacterNotFoundException,
|
||||
InvalidConfigurationException
|
||||
use LotGD\Core\{
|
||||
Action, ActionGroup, Bootstrap, Configuration, ComposerManager, DiceBag, EventHandler, EventManager, Events\NewViewpoint, Game, TimeKeeper, ModuleManager
|
||||
};
|
||||
use LotGD\Core\Tests\CoreModelTestCase;
|
||||
use LotGD\Core\Models\{
|
||||
Character, Viewpoint, Scene
|
||||
};
|
||||
use LotGD\Core\Exceptions\ {
|
||||
ActionNotFoundException, CharacterNotFoundException, InvalidConfigurationException
|
||||
};
|
||||
use LotGD\Core\Events\EventContext;
|
||||
|
||||
class DefaultSceneProvider implements EventHandler
|
||||
{
|
||||
@@ -35,17 +25,22 @@ class DefaultSceneProvider implements EventHandler
|
||||
public static $attachments = ['actions'];
|
||||
public static $data = ['data'];
|
||||
|
||||
public static function handleEvent(Game $g, string $event, array &$context)
|
||||
public static function handleEvent(Game $g, EventContext $context): EventContext
|
||||
{
|
||||
switch ($event) {
|
||||
switch ($context->getEvent()) {
|
||||
case 'h/lotgd/core/default-scene':
|
||||
if (!isset($context['character'])) {
|
||||
throw new \Exception("Key 'character' was expected on event h/lotgd/core/default-scene.");
|
||||
if (!$context->hasDataType(NewViewpoint::class)) {
|
||||
throw new \Exception(sprintf(
|
||||
"Context was expected to be %s, %s instead.",
|
||||
NewViewpoint::class,
|
||||
get_class($context->getData())
|
||||
));
|
||||
}
|
||||
$context['scene'] = $g->getEntityManager()->getRepository(Scene::class)->find(1);
|
||||
|
||||
$context->setDataField("scene", $g->getEntityManager()->getRepository(Scene::class)->find(1));
|
||||
break;
|
||||
case 'h/lotgd/core/navigate-to/lotgd/tests/village':
|
||||
$v = $context['viewpoint'];
|
||||
$v = $context->getDataField('viewpoint');
|
||||
|
||||
self::$actionGroups = [new ActionGroup('default', 'Title', 0)];
|
||||
self::$actionGroups[0]->setActions([
|
||||
@@ -58,6 +53,8 @@ class DefaultSceneProvider implements EventHandler
|
||||
$v->setData(self::$data);
|
||||
break;
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +121,7 @@ class GameTest extends CoreModelTestCase
|
||||
$c = $this->getEntityManager()->getRepository(Character::class)->find(1);
|
||||
$this->g->setCharacter($c);
|
||||
|
||||
// There shouldnt be any listeners to provide a default scene.
|
||||
// There should'nt be any listeners to provide a default scene.
|
||||
$this->expectException(InvalidConfigurationException::class);
|
||||
$this->g->getViewpoint();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user