Another example of using closures in PHP

On Habré there were already several articles with examples of using closures in PHP. Some of them were quite abstract, some not. I will give another way to apply closures in real conditions.

When adding new functionality to one project in PHP without a framework, it became necessary to use transactions (using MySQL with InnoDB and PHP 5.4 with MYSQLi).

The default project is autocommitset to true. You cannot turn it off for the entire project. Accordingly, the first thought was to disable before executing the SQL query autocommit, and after all actions (plus commitor rollbackat the end), turn autocommitit on again.

But this approach immediately proved to be untenable, since it is usually necessary to execute several methods in succession in which requests are made and, if an exception occurs in one of the methods, do it rollback. If done commitin each method, then the changes will be committed before all requests are completed.

Another option is to disable and enable autocommitafter each related group of methods has been executed. Conditional code (the action takes place in the class):

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


But here two problems arise:
  1. I don’t really want to write this in each method
  2. What if one of the methods ( save()or append_log()) also executes several consecutive requests that need to be combined into a transaction? Then you have to determine whether they were disabled or not autocommit, and perform commitdepending on this, because if you do commit, the parent changes will also be saved.


We need to make sure that the code for checking and fixing changes is executed around the method without our participation.

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();
		}
        $this->mysqli->autocommit(true);
	}
	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;
}


We send transaction()our code to the method inside an anonymous function. If autocommitenabled, transactionit turns it off, then performs an anonymous function. Depending on the result, it does commitor rollback, and then turns it on again autocommit. If it is autocommitalready turned off, then an anonymous function is simply executed - autocommit is taken care of elsewhere.

Usage example:

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


PS: $thisin closures can be used starting with PHP version 5.4

Also popular now: