Added EventContextData containers.

This commit is contained in:
Vassyli
2017-04-07 10:47:43 +02:00
parent 214b1de95f
commit e6e9e6e102
12 changed files with 187 additions and 60 deletions
+1
View File
@@ -2,6 +2,7 @@
declare(strict_types=1);
namespace LotGD\Core;
use LotGD\Core\Events\EventContext;
interface EventHandler
{
+13 -2
View File
@@ -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;
}
}
+14
View File
@@ -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);
}
/**
+53
View File
@@ -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);
}
}
+37
View File
@@ -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
View File
@@ -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}");
-12
View File
@@ -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
+10 -4
View File
@@ -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"));
}
}
+4 -2
View File
@@ -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
View File
@@ -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();
}