forked from silverstripe/silverstripe-versioned-snapshots
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NEW: Event-driven snapshot actions #3
Closed
unclecheese
wants to merge
8
commits into
silverstripe-terraformers:feature/rework
from
unclecheese:feature/actions-as-events
Closed
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e307b95
First cut
efa6592
It's working. README updated
3e1cc8c
Debugging, refinements
914614b
Change ListenerContext to EventContext
5838c80
Remove custom event contexts. Use one class
8a10e51
Revisions per Mojmir
f338583
Use operation name when provided for graphql middleware
befa887
Remove gridfield reorder from core
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
en: | ||
SilverStripe\Snapshots\Handler\Form\FormSubmissionHandler: | ||
HANDLER_unpublish: 'Unpublish page' | ||
HANDLER_doSave: 'Save item' | ||
HANDLER_doDelete: 'Delete item' | ||
SilverStripe\Snapshots\Handler\Form\SaveHandler: | ||
HANDLER_save: 'Save page' | ||
SilverStripe\Snapshots\Handler\Form\PublishHandler: | ||
HANDLER_publish: 'Publish page' | ||
SilverStripe\Snapshots\Handler\CMSMain\ActionHandler: | ||
HANDLER_savetreenode: 'Reordered site tree' | ||
SilverStripe\Snapshots\Handler\GraphQL\MutationHandler: | ||
HANDLER_graphql_crud_create: 'GraphQL create' | ||
HANDLER_graphql_crud_update: 'GraphQL update' | ||
HANDLER_graphql_crud_delete: 'GraphQL delete' | ||
SilverStripe\Snapshots\Handler\GridField\URLActionHandler: | ||
HANDLER_deleterecord: 'Delete record' | ||
HANDLER_archiverecord: 'Archive record' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Snapshots\Dispatch; | ||
|
||
use SilverStripe\Core\Injector\Injectable; | ||
use SilverStripe\Snapshots\Handler\HandlerInterface; | ||
use InvalidArgumentException; | ||
use Exception; | ||
use SilverStripe\Snapshots\Listener\EventContext; | ||
|
||
class Dispatcher | ||
{ | ||
use Injectable; | ||
|
||
/** | ||
* @var EventHandlerLoader[] | ||
*/ | ||
private $loaders = []; | ||
|
||
/** | ||
* @var array HandlerInterface[] | ||
*/ | ||
private $handlers = []; | ||
|
||
/** | ||
* @var bool | ||
*/ | ||
private $initialised = false; | ||
|
||
/** | ||
* @param EventHandlerLoader[] $loaders | ||
* @return $this | ||
*/ | ||
public function setLoaders($loaders = []) | ||
{ | ||
foreach ($loaders as $loader) { | ||
if (!$loader instanceof EventHandlerLoader) { | ||
throw new InvalidArgumentException(sprintf( | ||
'%s not passed an instance of %s', | ||
__CLASS__, | ||
EventHandlerLoader::class | ||
)); | ||
} | ||
} | ||
$this->loaders = $loaders; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @param array $handlers | ||
* @throws Exception | ||
*/ | ||
public function setHandlers(array $handlers) | ||
{ | ||
foreach ($handlers as $spec) { | ||
if (!isset($spec['handler']) || !isset($spec['on'])) { | ||
throw new InvalidArgumentException('Event handlers must have a "on" and "handler" nodes'); | ||
} | ||
$on = is_array($spec['on']) ? $spec['on'] : [$spec['on']]; | ||
$handler = $spec['handler']; | ||
|
||
if (!$handler instanceof HandlerInterface) { | ||
throw new InvalidArgumentException(sprintf( | ||
'Handler for %s is not an instance of %s', | ||
implode(', ', $on), | ||
HandlerInterface::class | ||
)); | ||
} | ||
|
||
foreach ($on as $eventName => $shouldInclude) { | ||
if ($shouldInclude) { | ||
$this->addListener($eventName, $handler); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param string $event | ||
* @param HandlerInterface $handler | ||
* @return $this | ||
* @throws Exception | ||
*/ | ||
public function addListener(string $event, HandlerInterface $handler): self | ||
{ | ||
if (!isset($this->handlers[$event])) { | ||
$this->handlers[$event] = []; | ||
} | ||
|
||
foreach ($this->handlers[$event] as $existing) { | ||
if ($existing === $handler) { | ||
throw new Exception(sprintf( | ||
'Handler for %s has already been added', | ||
$event | ||
)); | ||
} | ||
} | ||
$this->handlers[$event][] = $handler; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @param string $event | ||
* @param HandlerInterface $handler | ||
* @return $this | ||
*/ | ||
public function removeListener(string $event, HandlerInterface $handler): self | ||
{ | ||
$handlers = $this->handlers[$event] ?? []; | ||
$this->handlers = array_filter($handlers, function ($existing) use ($handler) { | ||
return $existing !== $handler; | ||
}); | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @param string $event | ||
* @param string $className | ||
* @return $this | ||
*/ | ||
public function removeListenerByClassName(string $event, string $className): self | ||
{ | ||
$handlers = $this->handlers[$event] ?? []; | ||
$this->handlers = array_filter($handlers, function ($existing) use ($className) { | ||
return get_class($existing) !== $className; | ||
}); | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @param string $event | ||
* @param EventContext $context | ||
*/ | ||
public function trigger(string $event, EventContext $context): void | ||
{ | ||
// TODO: This could be moved to procedural code in something like _config.php, | ||
// or add a new class that bootstraps the dispatcher. | ||
$this->initialise(); | ||
|
||
$action = $context->getAction(); | ||
if ($action === null) { | ||
return; | ||
} | ||
|
||
// First fire listeners to <eventName.actionName>, then just fire generic <eventName> listeners | ||
$eventsToFire = [ $event . '.' . $action, $event]; | ||
foreach ($eventsToFire as $event) { | ||
$handlers = $this->handlers[$event] ?? []; | ||
/* @var HandlerInterface $handler */ | ||
foreach ($handlers as $handler) { | ||
$handler->fire($context); | ||
} | ||
} | ||
} | ||
|
||
private function initialise(): void | ||
{ | ||
if ($this->initialised) { | ||
return; | ||
} | ||
|
||
foreach ($this->loaders as $loader) { | ||
$loader->addToDispatcher($this); | ||
} | ||
$this->initialised = true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
|
||
namespace SilverStripe\Snapshots\Dispatch; | ||
|
||
interface EventHandlerLoader | ||
{ | ||
public function addToDispatcher(Dispatcher $dispatcher): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
|
||
|
||
namespace SilverStripe\Snapshots\Handler\CMSMain; | ||
|
||
use SilverStripe\CMS\Model\SiteTree; | ||
use SilverStripe\Control\HTTPResponse; | ||
use SilverStripe\ORM\DataObject; | ||
use SilverStripe\ORM\ValidationException; | ||
use SilverStripe\Snapshots\Handler\HandlerAbstract; | ||
use SilverStripe\Snapshots\Listener\EventContext; | ||
use SilverStripe\Snapshots\Snapshot; | ||
|
||
class Handler extends HandlerAbstract | ||
{ | ||
/** | ||
* @param EventContext $context | ||
* @return Snapshot|null | ||
* @throws ValidationException | ||
*/ | ||
protected function createSnapshot(EventContext $context): ?Snapshot | ||
{ | ||
$action = $context->getAction(); | ||
if ($action === null) { | ||
return null; | ||
} | ||
|
||
/* @var HTTPResponse $result */ | ||
$result = $context->get('result'); | ||
if (!$result instanceof HTTPResponse) { | ||
return null; | ||
} | ||
if ((int) $result->getStatusCode() !== 200) { | ||
return null; | ||
} | ||
|
||
$className = $context->get('treeClass'); | ||
$id = (int) $context->get('id'); | ||
|
||
if (!$id) { | ||
return null; | ||
} | ||
|
||
/** @var SiteTree $page */ | ||
$page = DataObject::get_by_id($className, $id); | ||
|
||
if ($page === null) { | ||
return null; | ||
} | ||
|
||
$message = $this->getMessage($action); | ||
|
||
return Snapshot::singleton()->createSnapshotFromAction($page, null, $message); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're going to do an early exit at the
trigger
level, why not leaveaction
as a required param forEventContext
? Then the handlers an guarantee they haveaction
on the context objects. We shouldn't be firing events in the first place if the action is indeterminate.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not all contexts get an action as an input param. Some of them (for example Graph QL) have to determine action from context data. This is the place where we currently attempt to determine action from data which may fail. This is a valid case and needs to be supported as we can't guarantee that we can determine action from data in all cases.
I think we can move this check to listeners.