PHP项目备忘录与撤销

wen PHP项目 1

本文目录导读:

PHP项目备忘录与撤销

  1. 备忘录模式实现
  2. 实际应用示例 - 文本编辑器
  3. 数据库操作撤销
  4. 完整的UndoManager类
  5. 实际应用示例
  6. 最佳实践建议

我来为您介绍如何在PHP项目中实现备忘录(Memento)模式和撤销功能。

备忘录模式实现

基础备忘录类

<?php
/**
 * 备忘录类 - 存储状态快照
 */
class Memento
{
    private $state;
    private $timestamp;
    public function __construct($state)
    {
        $this->state = $state;
        $this->timestamp = time();
    }
    public function getState()
    {
        return $this->state;
    }
    public function getTimestamp()
    {
        return $this->timestamp;
    }
    public function getName()
    {
        return date('Y-m-d H:i:s', $this->timestamp);
    }
}
/**
 * 发起人 - 创建和恢复备忘录
 */
class Originator
{
    private $state;
    public function setState($state)
    {
        echo "设置状态为: " . $state . "\n";
        $this->state = $state;
    }
    public function getState()
    {
        return $this->state;
    }
    // 保存状态到备忘录
    public function saveStateToMemento()
    {
        echo "保存状态: " . $this->state . "\n";
        return new Memento($this->state);
    }
    // 从备忘录恢复状态
    public function getStateFromMemento(Memento $memento)
    {
        $this->state = $memento->getState();
        echo "恢复到状态: " . $this->state . "\n";
    }
}
/**
 * 管理者 - 管理备忘录历史记录
 */
class Caretaker
{
    private $mementoList = [];
    private $currentIndex = -1;
    private $maxHistory = 50; // 最大历史记录数
    // 添加备忘录
    public function add(Memento $memento)
    {
        // 如果在撤销后添加新操作,删除当前索引后的所有记录
        if ($this->currentIndex < count($this->mementoList) - 1) {
            array_splice($this->mementoList, $this->currentIndex + 1);
        }
        $this->mementoList[] = $memento;
        // 限制历史记录数量
        if (count($this->mementoList) > $this->maxHistory) {
            array_shift($this->mementoList);
        }
        $this->currentIndex = count($this->mementoList) - 1;
    }
    // 获取上一个状态(撤销)
    public function undo()
    {
        if ($this->currentIndex > 0) {
            $this->currentIndex--;
            return $this->mementoList[$this->currentIndex];
        }
        return null;
    }
    // 获取下一个状态(重做)
    public function redo()
    {
        if ($this->currentIndex < count($this->mementoList) - 1) {
            $this->currentIndex++;
            return $this->mementoList[$this->currentIndex];
        }
        return null;
    }
    // 获取当前状态
    public function getCurrent()
    {
        if ($this->currentIndex >= 0 && $this->currentIndex < count($this->mementoList)) {
            return $this->mementoList[$this->currentIndex];
        }
        return null;
    }
    // 获取历史记录数量
    public function getHistoryCount()
    {
        return count($this->mementoList);
    }
    // 获取当前索引
    public function getCurrentIndex()
    {
        return $this->currentIndex;
    }
    // 是否可以撤销
    public function canUndo()
    {
        return $this->currentIndex > 0;
    }
    // 是否可以重做
    public function canRedo()
    {
        return $this->currentIndex < count($this->mementoList) - 1;
    }
    // 清空历史
    public function clear()
    {
        $this->mementoList = [];
        $this->currentIndex = -1;
    }
}

实际应用示例 - 文本编辑器

<?php
/**
 * 文本编辑器 - 使用备忘录模式实现撤销/重做
 */
class TextEditor
{
    private $content = '';
    private $caretaker;
    private $originator;
    public function __construct()
    {
        $this->caretaker = new Caretaker();
        $this->originator = new Originator();
    }
    // 写入文本
    public function write($text)
    {
        // 保存当前状态
        $this->saveState();
        $this->content .= $text;
        echo "写入: '" . $text . "'\n";
    }
    // 删除文本
    public function delete($length = null)
    {
        $this->saveState();
        if ($length === null) {
            $deleted = substr($this->content, -1);
            $this->content = substr($this->content, 0, -1);
            echo "删除: '" . $deleted . "'\n";
        } else {
            $deleted = substr($this->content, -$length);
            $this->content = substr($this->content, 0, -$length);
            echo "删除: '" . $deleted . "'\n";
        }
    }
    // 替换文本
    public function replace($search, $replace)
    {
        $this->saveState();
        $this->content = str_replace($search, $replace, $this->content);
        echo "替换: '" . $search . "' -> '" . $replace . "'\n";
    }
    // 清空文本
    public function clear()
    {
        $this->saveState();
        $this->content = '';
        echo "清空文本\n";
    }
    // 保存当前状态
    private function saveState()
    {
        $this->originator->setState($this->content);
        $this->caretaker->add($this->originator->saveStateToMemento());
    }
    // 撤销
    public function undo()
    {
        $memento = $this->caretaker->undo();
        if ($memento) {
            $this->content = $memento->getState();
            echo "撤销操作\n";
        } else {
            echo "无法撤销\n";
        }
    }
    // 重做
    public function redo()
    {
        $memento = $this->caretaker->redo();
        if ($memento) {
            $this->content = $memento->getState();
            echo "重做操作\n";
        } else {
            echo "无法重做\n";
        }
    }
    // 获取当前内容
    public function getContent()
    {
        return $this->content;
    }
    // 显示状态信息
    public function getStatus()
    {
        return [
            'content' => $this->content,
            'canUndo' => $this->caretaker->canUndo(),
            'canRedo' => $this->caretaker->canRedo(),
            'historyCount' => $this->caretaker->getHistoryCount()
        ];
    }
}
// 使用示例
$editor = new TextEditor();
echo "=== 文本编辑器演示 ===\n\n";
$editor->write("Hello");
echo "内容: '" . $editor->getContent() . "'\n\n";
$editor->write(" World");
echo "内容: '" . $editor->getContent() . "'\n\n";
$editor->write("!");
echo "内容: '" . $editor->getContent() . "'\n\n";
// 撤销两次
$editor->undo();
echo "内容: '" . $editor->getContent() . "'\n\n";
$editor->undo();
echo "内容: '" . $editor->getContent() . "'\n\n";
// 重做一次
$editor->redo();
echo "内容: '" . $editor->getContent() . "'\n\n";

数据库操作撤销

<?php
/**
 * 数据库操作撤销管理器
 */
class DatabaseOperationManager
{
    private $history = [];
    private $currentIndex = -1;
    private $pdo;
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }
    // 执行数据库操作
    public function execute($sql, $params = [], $rollbackSql = null)
    {
        // 开始事务
        $this->pdo->beginTransaction();
        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            // 保存操作记录
            $operation = [
                'sql' => $sql,
                'params' => $params,
                'rollbackSql' => $rollbackSql,
                'timestamp' => time()
            ];
            // 如果在撤销后添加新操作,删除当前索引后的记录
            if ($this->currentIndex < count($this->history) - 1) {
                array_splice($this->history, $this->currentIndex + 1);
            }
            $this->history[] = $operation;
            $this->currentIndex = count($this->history) - 1;
            $this->pdo->commit();
            return true;
        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
    // 撤销操作
    public function undo()
    {
        if ($this->currentIndex < 0) {
            return false;
        }
        $operation = $this->history[$this->currentIndex];
        if ($operation['rollbackSql']) {
            $this->pdo->beginTransaction();
            try {
                $stmt = $this->pdo->prepare($operation['rollbackSql']);
                $stmt->execute();
                $this->pdo->commit();
                $this->currentIndex--;
                return true;
            } catch (Exception $e) {
                $this->pdo->rollBack();
                throw $e;
            }
        }
        return false;
    }
    // 重做操作
    public function redo()
    {
        $nextIndex = $this->currentIndex + 1;
        if ($nextIndex >= count($this->history)) {
            return false;
        }
        $operation = $this->history[$nextIndex];
        $this->pdo->beginTransaction();
        try {
            $stmt = $this->pdo->prepare($operation['sql']);
            $stmt->execute($operation['params']);
            $this->pdo->commit();
            $this->currentIndex = $nextIndex;
            return true;
        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
    // 获取操作历史
    public function getHistory()
    {
        $history = [];
        for ($i = 0; $i <= $this->currentIndex; $i++) {
            $op = $this->history[$i];
            $history[] = [
                'index' => $i,
                'time' => date('Y-m-d H:i:s', $op['timestamp']),
                'sql' => $op['sql']
            ];
        }
        return $history;
    }
}

完整的UndoManager类

<?php
/**
 * 通用撤销管理器
 */
class UndoManager
{
    private static $instance;
    private $stacks = [];
    private function __construct() {}
    public static function getInstance()
    {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    // 为特定操作创建堆栈
    public function createStack($name)
    {
        if (!isset($this->stacks[$name])) {
            $this->stacks[$name] = [
                'undo' => [],
                'redo' => [],
                'maxSize' => 50
            ];
        }
    }
    // 执行操作并保存撤销信息
    public function execute($stackName, $action, $undoData)
    {
        if (!isset($this->stacks[$stackName])) {
            $this->createStack($stackName);
        }
        $stack = &$this->stacks[$stackName];
        // 清空重做堆栈(新操作后不能重做)
        $stack['redo'] = [];
        // 添加到撤销堆栈
        $stack['undo'][] = [
            'action' => $action,
            'data' => $undoData,
            'timestamp' => time()
        ];
        // 限制堆栈大小
        if (count($stack['undo']) > $stack['maxSize']) {
            array_shift($stack['undo']);
        }
    }
    // 撤销
    public function undo($stackName)
    {
        if (!isset($this->stacks[$stackName]) || empty($this->stacks[$stackName]['undo'])) {
            return null;
        }
        $stack = &$this->stacks[$stackName];
        $operation = array_pop($stack['undo']);
        // 添加到重做堆栈
        $stack['redo'][] = $operation;
        return $operation;
    }
    // 重做
    public function redo($stackName)
    {
        if (!isset($this->stacks[$stackName]) || empty($this->stacks[$stackName]['redo'])) {
            return null;
        }
        $stack = &$this->stacks[$stackName];
        $operation = array_pop($stack['redo']);
        // 添加到撤销堆栈
        $stack['undo'][] = $operation;
        return $operation;
    }
    // 检查是否可以撤销
    public function canUndo($stackName)
    {
        return isset($this->stacks[$stackName]) && !empty($this->stacks[$stackName]['undo']);
    }
    // 检查是否可以重做
    public function canRedo($stackName)
    {
        return isset($this->stacks[$stackName]) && !empty($this->stacks[$stackName]['redo']);
    }
    // 清空堆栈
    public function clearStack($stackName)
    {
        if (isset($this->stacks[$stackName])) {
            $this->stacks[$stackName]['undo'] = [];
            $this->stacks[$stackName]['redo'] = [];
        }
    }
    // 获取堆栈状态
    public function getStackStatus($stackName)
    {
        if (!isset($this->stacks[$stackName])) {
            return null;
        }
        return [
            'undoCount' => count($this->stacks[$stackName]['undo']),
            'redoCount' => count($this->stacks[$stackName]['redo']),
            'canUndo' => $this->canUndo($stackName),
            'canRedo' => $this->canRedo($stackName)
        ];
    }
}

实际应用示例

<?php
// 示例1:文档编辑器
class Document
{
    private $content = '';
    private $undoManager;
    public function __construct()
    {
        $this->undoManager = UndoManager::getInstance();
        $this->undoManager->createStack('document');
    }
    public function insert($text)
    {
        $oldContent = $this->content;
        $this->content .= $text;
        $this->undoManager->execute('document', 'insert', [
            'oldContent' => $oldContent,
            'newContent' => $this->content
        ]);
        echo "插入: '$text'\n";
    }
    public function undo()
    {
        $operation = $this->undoManager->undo('document');
        if ($operation) {
            $data = $operation['data'];
            $this->content = $data['oldContent'];
            echo "撤销插入\n";
        }
    }
    public function redo()
    {
        $operation = $this->undoManager->redo('document');
        if ($operation) {
            $data = $operation['data'];
            $this->content = $data['newContent'];
            echo "重做插入\n";
        }
    }
    public function getContent()
    {
        return $this->content;
    }
}
// 使用示例
$doc = new Document();
$doc->insert("Hello");
$doc->insert(" World");
$doc->insert("!");
echo "内容: " . $doc->getContent() . "\n";
$doc->undo();
echo "撤销后: " . $doc->getContent() . "\n";
$doc->undo();
echo "再次撤销: " . $doc->getContent() . "\n";
$doc->redo();
echo "重做后: " . $doc->getContent() . "\n";

最佳实践建议

性能优化

<?php
/**
 * 带压缩的备忘录
 */
class CompressedMemento
{
    private $compressedState;
    private $timestamp;
    public function __construct($state)
    {
        // 压缩状态以节省内存
        $this->compressedState = gzcompress(serialize($state));
        $this->timestamp = time();
    }
    public function getState()
    {
        return unserialize(gzuncompress($this->compressedState));
    }
}
/**
 * 差异备忘录 - 只存储变化部分
 */
class DiffMemento
{
    private $diff;
    private $timestamp;
    public function __construct($oldState, $newState)
    {
        // 计算差异
        $this->diff = $this->computeDiff($oldState, $newState);
        $this->timestamp = time();
    }
    private function computeDiff($old, $new)
    {
        // 简单差异计算
        if (is_string($old) && is_string($new)) {
            $minLen = min(strlen($old), strlen($new));
            $diffStart = 0;
            for ($i = 0; $i < $minLen; $i++) {
                if ($old[$i] !== $new[$i]) {
                    $diffStart = $i;
                    break;
                }
            }
            return [
                'start' => $diffStart,
                'deleted' => substr($old, $diffStart),
                'inserted' => substr($new, $diffStart)
            ];
        }
        return ['old' => $old, 'new' => $new];
    }
}

清理策略

<?php
/**
 * 自动清理的备忘录管理器
 */
class AutoCleanCaretaker
{
    private $mementos = [];
    private $maxSize = 100;
    private $timeToLive = 3600; // 1小时
    public function add(Memento $memento)
    {
        $this->cleanup();
        $this->mementos[] = $memento;
        if (count($this->mementos) > $this->maxSize) {
            array_shift($this->mementos);
        }
    }
    private function cleanup()
    {
        $now = time();
        $this->mementos = array_filter($this->mementos, function($memento) use ($now) {
            return ($now - $memento->getTimestamp()) < $this->timeToLive;
        });
    }
}

这个实现提供了:

  1. 基础的备忘录模式 - 保存和恢复对象状态
  2. 完整的撤销/重做功能 - 支持多级撤销和重做
  3. 历史记录管理 - 限制历史记录数量,防止内存溢出
  4. 实际应用示例 - 文本编辑器、数据库操作等
  5. 性能优化 - 压缩、差异存储等
  6. 线程安全 - 使用单例模式

使用注意事项:

  • 合理设置历史记录上限
  • 考虑内存消耗,特别是大对象的序列化
  • 实现清理策略,定期清理过期记录
  • 注意对象克隆的深度问题

抱歉,评论功能暂时关闭!