Autor wpisu: sokzzuka, dodany: 12.07.2011 09:54, tagi: php
Ostatnio w pracy miałem za zadanie zrobić upload plików w module zarządzania plikami w firmowym CMS-ie. Jak pewnie się domyślacie, oprócz uploadu samego pliku, należy wpisać jakieś informacje o nim do bazy danych, cały proces jest generalnie dość oczywisty nawet dla początkującego. Jeżeli chcemy zachować spójność operacji – czyli mieć pewność, że nie wpiszemy niczego do bazy jeżeli plik nie został poprawnie wrzucony lub na odwrót, że w razie błędu połączenia z bazą danych plik zostanie usunięty z dysku możemy wybrać dwie drogi postępowania:
- instrukcje warunkowe (zwane popularnie „ifami” ;p )
- transakcje
Druga metoda zwykle sprowadza się do bloku „try-catch” oraz transakcji na bazie danych. Ja natomiast dzisiaj chciałbym zaprezentować podejście alternatywne oparte na wzorcu o wdzięcznie przetłumaczonej na polski nazwie „Polecenie” (Command).
Wzorzec „Command” polega na enkapsulacji całej logiki potrzebnej do wykonania jakiejś czynności. Jeżeli cała logika została zaenkapsulowana, to mamy też wszelkie informację by taką operację cofnąć. Poniżej przedstawię implementację.
Implementacja składa się z kilku podstawowych elementów:
Interfejs IExecutable wskazuje na zdolność klasy do bycia wykonanym:
interface IExecutable { /** * @return boolean */ public function execute(); }
Interfejs IUndoable wskazuje na zdolność klasy do cofnięcia operacji:
interface IUndoable { /** * @return void */ public function undo(); }
Całość zostaje złożona w abstrakcyjny typ ACommand:
abstract class ACommand implements IUndoable, IExecutable { }
Kolejno implementacja konkretnych operacji – przyjęcia pliku, oraz aktualizacji bazy danych. Upload:
class UploadHandler extends ACommand { private $_field; private $_destination; private $_targetPath; public function __construct($field, $destination) { $this->_field = $field; $this->_destination = $destination; } public function execute() { $filename = $_FILES[$this->_field]['tmp_name']; $targetPath = $this->_destination . $_FILES[$this->_field]['name']; $result = move_uploaded_file($filename, $targetPath); $this->_targetPath = $targetPath; return $result; } public function undo() { if (file_exists($this->_targetPath)) { unlink($this->_targetPath); } } }
Aktualizacja bazy:
class DbUpdater extends ACommand { private $_db; private $_table; private $_data; private $_where; private $_savePoint; public function __construct(Zend_Db_Adapter_Abstract $db, $table, array $data, $where) { $this->_db = $db; $this->_table = $table; $this->_data = $data; $this->_where = $where; } public function execute() { try { $query = $this->_db->select(); $query->from($this->_table); $query->where($this->_where); $this->_savePoint = $this->_db->fetchRow($query); $this->_db->update($this->_table, $this->_data, $this->_where); return true; } catch (Exception $e) { return false; } } public function undo() { if (!empty($this->_savePoint)) { $this->_db->update($this->_table, $this->_savePoint, $this->_where); } } }
By ułatwić korzystanie z undo, stworzyłem jeszcze jedną klasę, reprezentującą transakcje: