📜 ⬆️ ⬇️

Defer: from Go to PHP

Go has a useful defer construct. It is usually used to release resources and works as follows: a function is passed as an argument to defer, which is placed in the list of functions. This list of functions is performed when leaving the ambient function.


Defer has several obvious and not so merits:



For example, such code:


class Utils { public function copyFile(string $sourceName, string $destName): void { $readHandle = fopen($sourceName, "r"); if ($readHandle === false) { throw new \Exception(); } $writeHandle = fopen($destName, "w"); if ($writeHandle === false) { fclose($readHandle); throw new \Exception(); } while (($buffer = fgets($readHandle)) !== false) { $wasFailure = fwrite($writeHandle, $buffer); if ($wasFailure) { fclose($readHandle); fclose($writeHandle); throw new \Exception(); } } if (!feof($readHandle)) { fclose($readHandle); fclose($writeHandle); throw new \Exception(); } fclose($readHandle); fclose($writeHandle); } } 

It could be turned into such:


 class Utils { public function copyFile(string $sourceName, string $destName): void { $readHandle = fopen($sourceName, "r"); if ($readHandle === false) { throw new \Exception(); } defer fclose($readHandle); $writeHandle = fopen($destName, "w"); if ($writeHandle === false) { throw new \Exception(); } defer fclose($writeHandle); while (($buffer = fgets($readHandle)) !== false) { $wasFailure = fwrite($writeHandle, $buffer); if ($wasFailure) { throw new \Exception(); } } if (!feof($readHandle)) { throw new \Exception(); } } } 

In the second case, working with closing files is much easier - each file needs to be closed only once. It reduces the likelihood that someone will forget to close the file, especially if they are not 2, but more.


But unfortunately, there is no defer in PHP. But you can write your own implementation. It looks like this:


 class DeferredContext { protected $deferredActions = []; public function defer(callable $deferAction) { $this->deferredActions[] = $deferAction; } public function executeDeferredActions() { $actionsCount = count($this->deferredActions); if ($actionsCount > 0) { for ($i = $actionsCount - 1; $i >= 0; $i--) { $action = $this->deferredActions[$i]; try { $action(); } catch (\Exception $e) { } unset($this->deferredActions[$i]); } } $this->deferredActions = []; } } trait DeferredTrait { private function deferred(callable $callback) { $context = new DeferredContext(); try { $callback($context); } finally { $context->executeDeferredActions(); } } } 

DeferredContext - a class in which handler functions accumulate. When exiting the function, you must call the executeDeferredActions () method, which will execute all the handlers. In order not to manually create DeferredContext, you can use the DeferredTrait treit, which encapsulates the logic of working with DeferredContext.


Using this approach, the code from the example above will look like this:


 class Utils { use DeferredTrait; public function copyFile(string $sourceName, string $destName): void { $this->deferred(function(DeferredContext $context) use ($destName, $sourceName) { $readHandle = fopen($sourceName, "r"); if ($readHandle === false) { throw new \Exception(); } $context->defer(function() use ($readHandle) { fclose($readHandle); }); $writeHandle = fopen($destName, "w"); if ($writeHandle === false) { throw new \Exception(); } $context->defer(function() use ($writeHandle) { fclose($writeHandle); }); while (($buffer = fgets($readHandle)) !== false) { $wasFailure = fwrite($writeHandle, $buffer); if ($wasFailure) { throw new \Exception(); } } if (!feof($readHandle)) { throw new \Exception(); } }); } } 

I hope that this idea will help you reduce the number of bugs in the code and create more reliable programs.


')

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


All Articles