Home  >  Article  >  Backend Development  >  Complete implementation of redo and undo

Complete implementation of redo and undo

WBOY
WBOYOriginal
2016-08-08 09:32:36960browse
Undo-redo requires memo mode and command mode for support. I have learned some basic knowledge of command mode and memento mode before. Here we need to combine the two modes to implement a module for undo-redo operations to consolidate the knowledge learned.

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;
	}
}

The implementation of SessionMementoTaker is based on a previously implemented session (URL registration mechanism). Restoring different object data based on the session ID stored in cookies can achieve the purpose of the same user requesting access to the same object data multiple times. SessionMementoTaker provides three additional interfaces. The push_command operation adds commands to the historical command list. The maximum length of the history command list is 5. If it exceeds 5, the first command will be removed. In addition, push_command is equivalent to adding a new command and moving the command pointer (persent) to the latest position. Discard the previous state. get_undo_command gets the last executed historical command and updates the pointer, the same goes for get_redo_command.
Historical command list: command1---command2---command3---* The asterisk indicates persistent, pointing to the latest command to be executed.
An undo operation: command1---command2-*--command3--- After the rollback, the persistent pointer moves backward.
An undo operation: command1--*command2----command3--- After the rollback, the persistent pointer moves backward.
A redo operation: command1---command2-*--command3--- After redo, the persistent pointer moves forward.
Push_command: command1---command2---command3---command4---* persist is updated to the front end
Here a single command object is regarded as an originator. Actively create a memento as needed to save its internal state at the moment, and put the command object into the historical command record list.

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();
	}
}

The command task saves the parameters of the requested command at the beginning of execution, and can also save other necessary parameters during the command execution. Since some commands do not support undo operations, there is an empty unexecute in the parent class implementation;

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;
	}
}

A command that supports undo-redo to copy files:

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;
	}
}

Redo command:

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 end.

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.

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn