Home > Article > Backend Development > Complete implementation of redo and undo
System block diagram:
The command distribution controller mainly has four tasks:
1. System initialization, loading system configuration parameters and caching these data, these application-level configuration parameters You can use the serialization mechanism to cache data instead of reading the file every time to speed up access efficiency.
2. Based on the front-end request, collect parameters to generate a request (Request).
3. Map the request to the command module (Command) of specific business logic.
4. Perform the operation and return the result to the front-end view.
The business logic layer can obtain execution parameters based on the incoming context object. After execution, the execution result can be returned to the previous layer through the context object.
Implementation of command distribution controller:
class Controller{ private function __construct() {} static function run(){ $instance = new Controller(); $instance->init(); $instance->handleRequest(); } function init(){ $application = \base\ApplicationHelper::instance(); $application->system_init(); } function handleRequest(){ $request = new \controller\Request(); $cmd_r = new \command\CommandResolver(); $cmd = $cmd_r->get_command($request); $cmd->execute($request); } }By declaring the constructor as private, the controller is a singleton.
For an interpreted language like PHP, to implement the undeo/redo mechanism, some caching mechanism (session) must be used to save the history of command execution. The session module here is mainly responsible for maintaining a command history record, and its implementation is as follows:
namespace base; require_once('session_registry.php'); class SessionMementoTaker extends SessionRegistry{ const COMMAND_COUNT = 5; private $persent = 0; private $cmd_stack = array(); static public function instance(){ return parent::instance(); } public function push_command(Command $cmd){ $this->cmd_stack = self::instance()->get('cmd_stack'); if(!empty($this->cmd_stack)){ if(count($this->cmd_stack) >self::COMMAND_COUNT){ array_shift($this->cmd_stack); reset($this->cmd_stack); } } array_push($this->cmd_stack, $cmd); $this->persent = count($this->cmd_stack) + 1; self::instance()->set('cmd_stack', $this->cmd_stack); self::instance()->set('cmd_persent', $this->persent); } public function get_undo_command(){ $this->persent = self::instance()->get('cmd_persent'); $this->cmd_stack = self::instance()->get('cmd_stack'); if(!empty($this->cmd_stack) && $this->persent > 0){ $command = $this->cmd_stack[--$this->persent]; self::instance()->set('cmd_persent', $this->persent); return $command; } return null; } public function get_redo_command(){ $this->persent = self::instance()->get('cmd_persent'); $this->cmd_stack = self::instance()->get('cmd_stack'); if(!empty($this->cmd_stack) && $this->persent < count($this->cmd_stack)){ $command = $this->cmd_stack[$this->persent++]; self::instance()->set('cmd_persent', $this->persent); return $command; } return null; } }
Command base class implementation:
namespace woo\command; require_once('../memento/state.php'); require_once('../memento/memento.php'); abstract class Command { protected $state; final function __construct(){ $this->state = new \woo\memento\State(); } function execute(\woo\controller\Request $request) { $this->state->set('request', $request); $this->do_execute($request); } abstract function do_execute(\woo\controller\Request $request); function do_unexecute(\woo\controller\Request $request) {} public function get_state(){ return $this->state; } public function set_state(State $state){ $this->state = $state; } public function get_request(){ if(isset($this->state)){ return $this->state->get('request'); } return null; } public function set_request(\woo\controller\Request $request){ if(isset($this->state)){ return $this->state->set('request', $request); } } public function create_memento(){ \woo\base\SessionMementoTaker::push_command($this); $mem = new \woo\memento\Memento(); $mem->set_state($this->state); return $mem; } public function set_memento(Memento $mem){ $this->state = $mem->get_state(); } }
An object to save the command status:
class State{ private $values = array(); function __construct(){ } public function set($key, $value){ $this->values[$key] = $value; } public function get($key){ if(isset($this->values[$key])) { return $this->values[$key]; } return null; } }
namespace woo\command; require_once('request.php'); require_once('command.php'); require_once('../base/registry.php'); require_once('../file_manager.php'); require_once('../base/session_memento.php'); class CopyCommand extends Command { function do_execute(\controller\Request $request) { $src_path = $request->get_property('src'); $dst_path = $request->get_property('dst'); $this->state->set('src_path', $src_path); $this->state->set('dst_path', $dst_path); $this->create_memento(); $file_manager = \base\Registry::file_manager(); $ret = $file_manager->copy($src_path, $dst_path); $request->add_feedback($ret); //... } }The task of the command object is relatively simple: obtain parameters (verification parameters), save necessary status information, and transfer control to specific business logic objects. Add execution results and return. Different commands require different request parameters. Some commands do not require or support the undo operation at all, so the create_memento operation can be selectively performed.
The last step is to implement undo-redo. Here I regard undo/redo as a normal command request without the need for additional distribution processing in the controller.
Undo command:
namespace woo\command; require_once('request.php'); require_once('command.php'); require_once('../base/registry.php'); require_once('../base/session_memento.php'); class UndoCommand extends Command{ public function do_execute(\controller\Request $request){ $command = \base\SessionMementoTaker::get_undo_command(); if(isset($command)){ $old_req = $command->get_request(); $command->do_unexecute($old_req); $request->set_feedback($old_req->get_feedback()); } else{ $request->add_feedback('undo command not fount'); } return; } }
namespace woo\command; require_once('request.php'); require_once('command.php'); require_once('../base/registry.php'); class RedoCommand extends Command { public function do_execute(\woo\controller\Request $request){ $command = \woo\base\SessionMementoTaker::get_redo_command(); if(isset($command)){ $old_req = $command->get_request(); $command->do_execute($old_req); $request->set_feedback($old_req->get_feedback()); } else{ $request->add_feedback('undo command not fount'); } return; } }
The above introduces the complete implementation of redo and undo, including the relevant aspects. I hope it will be helpful to friends who are interested in PHP tutorials.