📜 ⬆️ ⬇️

Another example of using closures in PHP

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

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

In the project, autocommit set to true by default. It can not be turned off for the whole project. Accordingly, the first thought was to disable autocommit before executing the SQL query, and after all actions (plus commit or rollback at the end), turn on autocommit again.

But this approach immediately proved to be untenable, since it is usually necessary to perform several methods in succession in which requests are made and, if an exception occurs in one of the methods, rollback be done. If you commit to each method, then the changes will be committed before all requests are executed.
')
Another option is to disable and enable autocommit after each associated group of methods has been completed. 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 there are two problems:
  1. I don’t really want to write this in every method.
  2. What if in some of the methods ( save() or append_log() ) there will also be executed several consecutive queries that need to be merged into a transaction? Then you have to determine whether or not autocommit is autocommit and commit depending on this, because if you commit , the parental changes will also be saved.


It is necessary to make the code for checking and fixing changes performed 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 the transaction() method our code inside an anonymous function. If autocommit enabled, the transaction disables it, then performs an anonymous function. Makes commit or rollback depending on the result, and then re-enables autocommit . If autocommit already turned off, then an anonymous function is simply executed - autocommit is taken care of somewhere else.

Usage example:

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


PS: $this in closures can be used since PHP version 5.4

Source: https://habr.com/ru/post/185320/


All Articles