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
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
Another option is to disable and enable
But here two problems arise:
We need to make sure that the code for checking and fixing changes is executed around the method without our participation.
We send
Usage example:
PS:
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
autocommit
set 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 commit
or rollback
at the end), turn autocommit
it 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 commit
in each method, then the changes will be committed before all requests are completed. Another option is to disable and enable
autocommit
after 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:
- I don’t really want to write this in each method
- What if one of the methods (
save()
orappend_log()
) also executes several consecutive requests that need to be combined into a transaction? Then you have to determine whether they were disabled or notautocommit
, and performcommit
depending on this, because if you docommit
, 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 autocommit
enabled, transaction
it turns it off, then performs an anonymous function. Depending on the result, it does commit
or rollback
, and then turns it on again autocommit
. If it is autocommit
already 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:
$this
in closures can be used starting with PHP version 5.4