Safe Editor

Custom Workflow: Changing the EditorWorkflow

The Istarel Workshop Application Framework is built around the concept of workflows, and there are a number of typical workflows that are part of the framework. A very commonly used one is the EditorWorkflow, which consists of four states (ListState, EditState, SaveState, DeleteState) and transitions among those states.

The DeleteState, for example, can be reached from either the ListState or EditState.

In some case, it might not be desirable to delete an object from the list (the most common reason is to prevent accidents — it is very easy to inadvertently tap “delete” on a list).

What to do?

Subclass

The first approach is to subclass the workflow. As you know, subclassing does require some knowledge of the parent class, though in this case we simply override the method that initializes the transitions.

class MySafeEditorWorkflow extends EditorWorkflow
{
    protected function transitions(): array
    {
        return [
            [ObjectTransition::NAME,    ListState::ID,   EditState::ID],
            [NewObjectTransition::NAME, ListState::ID,   EditState::ID],
            [ReturnTransition::NAME,    EditState::ID,   ListState::ID],
            [SubmitTransition::NAME,    EditState::ID,   SaveState::ID],
            [ObjectTransition::NAME,    EditState::ID,   DeleteState::ID],
            [WorkflowTransition::NAME,  SaveState::ID,   ListState::ID],
            [WorkflowTransition::NAME,  DeleteState::ID, ListState::ID]
        ];
    }
}

class MyModule implements EditorModule
{
    ...

    public function workflow(): Workflow
    {
        return new MySafeEditorWorkflow($this, $this->data_source);
    }

    ...
}

All that we have done is remove an existing transition configuration in the array (namely, an ObjectTransition from the ListState to DeleteState). The advantage here is that once created, numerous modules can use this new (and simple) workflow.

Modify

Instead of creating an entirely new workflow, we can also modify the workflow. So, in our module that uses the EditorWorkflow, we remove the transition.

class MyModule implements EditorModule
{
    ...

    public function workflow(): Workflow
    {
        $this->workflow = new EditorWorkflow($this, $this->data_source);
        $this->workflow->removeTransition(ListState::ID, DeleteState::ID);
    }

    ...
}

The advantage here is that we are not creating a new workflow, but it does require us to understand how the workflow in question works. (Removing some transitions will end badly.)

Configure

The final approach involves using the existing tools a module has for dealing with its workflow. In particular, each module is a TransitionDelegate for the workflow, which means (among other things) it implements a method called permitTransition.

class MyModule implements EditorModule
{
    ...

    public function workflow(): Workflow
    {
        return new EditorWorkflow($this, $this->data_source);
    }

    ...

    public function permitTransition(Transition $transition): bool
    {
        if ($transition->origin() instanceof ListState && $transition->destination() instanceof DeleteState) return false;
        return true;
    }

}

The advantage of this approach is that we are not creating anything new, but instead using our power as a TransitionDelegate. Of course, if you are never permitting a particular transition, it may be more elegant to use one of the other approaches.