Ещё один пример использования замыканий в PHP

от автора

На Хабре уже было несколько статей с примерами использования замыканий в PHP. Некоторые из них были достаточно абстрактными, некоторые нет. Я приведу ещё один способ применения замыканий в реальных условиях.

При добавлении нового функционала в один проект на PHP без фреймворка, возникла необходимость использования транзакций (используется MySQL c InnoDB и PHP 5.4 с MYSQLi).

В проекте по умолчанию autocommit установлен в true. Выключить его для всего проекта нельзя. Соответственно первой мыслью было перед выполнением SQL-запроса отключать autocommit, а после всех действий (плюс commit или rollback в конце), включать autocommit снова.

Но такой подход сразу оказался несостоятельным, так как обычно необходимо выполнять последовательно несколько методов, в которых делаются запросы и, если в каком-то из методов возникает исключение, делать rollback. Если же делать commit в каждом методе, то изменения будут фиксироваться раньше, чем выполнятся все запросы.

Другой вариант — отключать и включать autocommit после выполнения каждой связанной группы методов. Условный код (действие происходит в классе):

public function save() { 	$result = $this->db->update(...); 	//ошибка может быть не только из-за неверного запроса, но и в процессе валидации и пр. 	if (!$result) throw new Exception('Error while saving'); }  public function append_log() { 	$result = $this->db->insert(...); 	if (!$result) throw new Exception('Error while append'); }  public function add() { 	$this->db->autocommit(false); 	try { 		$this->save(); 		$this->append_log();         $this->db->commit(); 	} catch (Exception $e) { 		$this->db->rollback(); 	}	 	$this->db->autocommit(true); } 

Но тут возникают две проблемы:

  1. Писать такое в каждом методе не очень хочется
  2. Что, если в каком-то из методов (save() или append_log()) будет также исполняться несколько последовательных запросов, которые надо объединить в транзакцию? Тогда придётся определять, отключали или нет autocommit, и выполнять commit в зависимости от этого, так как если выполнить commit, родительские изменения тоже будут сохранены.

Нужно сделать так, чтобы код проверки и фиксирования изменений выполнялся вокруг метода без нашего участия.

public function transaction(callable $block) { 	$exception = null; 	if ($need_to_off = $this->isAutocommitOn())              $this->mysqli->autocommit(false);  	try { 		$block(); 	} catch (Exception $e) { 		$exception = $e; 	}  	if ($need_to_off) 	{ 		if ($exception == null) { 			$this->db->mysqli->commit(); 		} else { 			$this->db->mysqli->rollback(); 		} 	} 	if ($exception) throw $exception;	 }  public function isAutocommitOn() { 	if ($result = $this->db->mysqli->query("SELECT @@autocommit")) { 		$row = $result->fetch_row(); 		$result->free(); 	} 	return isset($row[0]) && $row[0] == 1; } 

Мы посылаем методу transaction() наш код внутри анонимной функции. Если autocommit включен, transaction его отключает, затем выполняет анонимную функцию. В зависимости от результата делает commit или rollback, а затем заново включает autocommit. Если же autocommit уже выключен, то просто выполняется анонимная функция — об autocommit заботятся где-то в другом месте.

Пример использования:

public function save_all() { 	$this->transaction(function(){ 		$this->save(); 		$this->append_log(); 	}); } 

P.S.: $this в замыканиях можно использовать, начиная с PHP версии 5.4

ссылка на оригинал статьи http://habrahabr.ru/post/185320/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *