Annulla / ripeti l'implementazione per le modifiche ai file in PHP

0

Stavo pensando di archiviare tutte le operazioni sui file all'interno di un array, insieme alle operazioni inverse utilizzate per i annullamenti.

Esempio:

[

  [
     'op' => 'move',
     'parameters' => [$path_from, $path_to],
     'undo' => [
        'op' => 'move',
        'parameters' => [$path_to, $path_from],
     ]
  ],



  [
     'op' => 'create_folder',
     'parameters' => [$path],
     'undo' => [
        'op' => 'remove',
        'parameters' => [$path],
     ]
  ],    

  .........


]

Questo è un elenco di operazioni sui file che sono state fatte dall'utente. Il valore "op" corrisponde a una funzione e i "parametri" sono gli argomenti passati alla funzione.

Quando l'utente richiede di annullare tutto fino alla prima operazione, vengono eseguiti gli "annullamenti".

Funzionerà? Ci sono opzioni migliori? Ci sono alcune classi di supporto PHP che posso usare per scrivere meno codice?

    
posta kitty 29.01.2016 - 07:39
fonte

2 risposte

3

Questo funzionerà principalmente. Nella programmazione orientata agli oggetti, questa idea è conosciuta come Modello di comando . Definiamo un oggetto che sa come eseguire alcune operazioni in un unico passaggio. Estendiamo anche quell'oggetto con una procedura per annullare quel passo. Questo ci consente di implementare funzionalità di annullamento / ripristino come comunemente utilizzato nei processori di testo.

La difficoltà è che alcune operazioni possono essere difficili da annullare, o che l'operazione di annullamento funziona tranne alcuni aspetti minori. Il libro Design Patterns paragona questo problema a isteresi : un'azione e l'azione inversa divergono.

es. un'operazione come move è abbastanza facile da annullare, come hai notato. Tuttavia, l'operazione di annullamento potrebbe non ripristinare correttamente i timestamp, i controlli di accesso o altri metadati del file. In molti casi ciò sarebbe del tutto irrilevante, in altri casi d'uso questo potrebbe essere un rompicapo. È quindi utile pensare ai requisiti esatti. Per esempio. quando il sistema di controllo del codice sorgente Git tiene traccia delle versioni dei file, registra se un file è impostato come eseguibile, ma scarta principalmente i bit di autorizzazione utente / gruppo / tutti i file.

Altre operazioni sono distruttive e non possono essere annullate se non salvando l'intero stato precedente. Per esempio. un comando "in un file foo.txt, sostituisci ogni occorrenza di Fred con George" è facile da implementare, ma l'operazione inversa non è "sostituisci ogni occorrenza di George con Fred". Invece, dobbiamo tenere traccia di ogni posizione di sostituzione (probabilmente difficile da implementare, a meno che non si utilizzi uno strumento come diff ), o di dover eseguire il backup del file per offrire l'annullamento (potrebbe utilizzare molta memoria). Ad esempio, dovresti salvare l'intero file per fornire il contrario di un'operazione remove .

Per questi motivi, è utile limitare il buffer di annullamento (ad esempio a 50 voci) e non implementare l'annullamento in assenza di semantica sensibile. In questi casi in cui l'utente si aspetterebbe di annullare, ma non ce n'è, sarebbe importante avvisare l'utente di questo problema.

    
risposta data 29.01.2016 - 08:22
fonte
2

In breve, sì, funzionerà. La risposta più lunga inizia dicendo dipende .

Il modello di comando

Quello che hai descritto è lo schema di comando; questo è un argomento abbastanza ampio ma riassume come segue: link

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

In termini semplici, ogni azione eseguita dall'utente viene archiviata insieme alle istruzioni su come annullare quell'azione. Proprio come hai descritto nella tua domanda.

Oggetto orientato

Ai fini dell'efficienza, suggerirei un approccio orientato agli oggetti piuttosto che un semplice array di dati. L'array di dati diventerà rapidamente ingestibile all'aumentare del numero di operazioni supportate; un approccio orientato agli oggetti fornirà una base di codice molto più adatta. Il motivo principale è che hai un chiaro singolo punto di responsabilità quando definisci come un'operazione deve essere done e annullata .

Tutte le operazioni sono definite come classi che implementano un'interfaccia comune; per esempio:

interface Operation
{
    public function do();
    public function undo();
}

Ciascuno degli oggetti "operazione" contiene tutte le proprietà e i metodi richiesti per preparare ed eseguire l'operazione. Si dispone quindi di un oggetto "unità di lavoro" che tiene traccia delle operazioni eseguite dall'utente. Si tratta semplicemente di memorizzare gli oggetti operativi in un contenitore.

Puoi quindi annullare le azioni istruendo la 'unità di lavoro' per annullare l'ultima operazione. La classe 'unità di lavoro' potrebbe essere simile a:

class UnitOfWork
{
    private $history;
    private $pos;

    public function __construct()
    {
        $this->history = [];
        $this->pos = false;
    }

    public function do(Operation $op)
    {
        // Try to perform the operation, handle errors as you go
        try {
            $op->do();
        } catch(Exception $e) {
            throw new RuntimeException('Unknown error when executing the operation; error: '.$e->getMessage());
        }

        // If successful, discard any previously undone actions and add the new action to the array
        if ($this->pos === false) {
            $this->history = [$op];
            $this->pos = 0;
        } else {
            $this->history = array_slice($this->history, 0, ($this->pos+1));
            $this->history[] = $op;
            $this->pos++;
        }
    }

    public function undo()
    {
        // Try to undo the last action, handle errors as you go
        if ($this->pos != false) {
            try {
                $this->history[$this->pos]->undo();
            } catch(Exception $e) {
                throw new RuntimeException('Unknown error when undoing the operation; error: '.$e->getMessage());
            }
        } else {
            throw new UnderflowException('There are no operations to undo');
        }

        // if successful, move back one place in the history
        if ($this->pos <= 0) {
            $this->pos = false;
        } else {
            $this->pos--;
        }
    }

    public function redo()
    {
        // Try to redo the next action, handle errors as you go
        if (count($this->history) > ($this->pos+1) || count($this->history) > 0 && ($this->pos===false)) {
            try {
                $this->history[$this->pos+1]->do();
            } catch(Exception $e) {
                throw new RuntimeException('Unknown error when redoing the operation; error: '.$e->getMessage());
            }
        } else {
            throw new UnderflowException('There are no operations to redo');
        }

        // if successful, move forward one place in the history
        if ($this->pos === false) {
            $this->pos = 0;
        } else {
            $this->pos++;
        }
    }
}

Se desideri un esempio funzionante del modello di comando , puoi trovarne uno qui: link

Le complicazioni

Alcune operazioni non hanno un'inversione chiara. Questo di solito è dovuto al fatto che le informazioni richieste per annullare l'azione originale vengono distrutte durante l'esecuzione dell'atto. L'unico modo per aggirare questo è quello di registrare in modo esplicito le informazioni che dovranno successivamente annullare l'azione.

@amon ha già fornito una descrizione di questo nella sua risposta, quindi leggi la sua risposta per maggiori dettagli sulle potenziali complicazioni.

    
risposta data 29.01.2016 - 09:30
fonte

Leggi altre domande sui tag