Менеджер контекста в PHP
В эти дни я трачу гораздо больше времени на написание и анализ кода на Python, чем на PHP. Это было освежающее изменение темпа, и интересно изучать различные паттерны, представленные на разных языках программирования.
Если вы не нашли время осмотреться и посмотреть, что делают другие языки или фреймворки, я настоятельно рекомендую это сделать.
Одним из лучших шаблонов в Python является Context Manager. В Python определенные объекты и функции могут быть заключены в блок with, используемый для предоставления определенного контекста к коду, выполняющемуся внутри него. Например, открытие файлов становится невероятно простым:
with open('output.txt', 'w') as f: f.write('Hello world!')
Прелесть этого шаблона заключается в автоматической очистке ресурсов. Вам не нужно управлять указателями файлов вручную. Вам не нужно инициализировать и отключать сеанс базы данных. Менеджер контекста обрабатывает вещи для вас автоматически.
Менеджер контекста базы данных может управлять созданием и закрытием соединения с самой базой данных (или с используемым пулом соединений). Или его можно использовать для переноса транзакции и автоматической фиксации результатов в конце блока. В любом случае, вам не нужно управлять контекстом самостоятельно.
PHP не имеет такого шаблона изначально. Я думаю, что добавление конструкции with было бы огромной прибавкой для языка, но мои навыки кодирования на более низком уровне немного устарели, чтобы написать быстрое подтверждение концепции. Вместо этого я могу использовать генераторы и циклы для имитации аналогичных функций с существующей системой.
Приведенный выше пример Python использовал функцию open() для демонстрации управления контекстом применительно к файлам. Мы можем сделать нечто подобное в PHP с помощью следующей вспомогательной функции:
function open($file, $mode = 'r') { $f = fopen($file, $mode); yield $f; fclose($f); }
Поскольку эта функция является генератором, мы можем использовать ее изначально внутри цикла foreach.
foreach(open('output.txt', 'w') as $file) { fwrite($file, 'Hello, world!'); }
Поскольку генератор возвращает только один yield , мы получаем только один элемент для итерации в нашем цикле foreach. Цикл также автоматически вернется к генератору после итерации, что дает нам возможность очистить наш контекст и область видимости.
Это немного более многословно, чем в Python, и нам нужно создавать свои собственные менеджеры контекста, но это гораздо более чистый способ убедиться, что мы убираем за собой.
Делая шаг вперед, предположим, что нам нужно работать с соединением PDO. Мы хотим подключиться к базе данных, выполнить некоторую работу в транзакции и автоматически зафиксировать результат. Это выглядело бы примерно так:
try { $dbh = new PDO($dsn, $user, $pass, $options); } catch (Exception $e) { die("Unable to connect: " . $e->getMessage()); } try { $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $dbh->beginTransaction(); $dbh->exec("insert into users (id, first, last) values (5, 'Eric', 'Mann')"); $dbh->exec('insert into authors (id) values (5)'); $dbh->commit(); } catch (Exception $e) { $dbh->rollBack(); echo "Failed: " . $e->getMessage(); }
Та же самая операция, использующая шаблон менеджера контекста, была бы намного проще:
function transaction() { $dbh = new PDO($dsn, $user, $pass, $options); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $dbh->beginTransaction(); yield $dbh; try { $dbh->commit(); } catch (Exception $e) { $dbh->rollBack(); } } // Теперь используем транзакцию foreach(transaction() as $dbh) { $dbh->exec("insert into users (id, first, last) values (5, 'Eric', 'Mann')"); $dbh->exec('insert into authors (id) values (5)'); }
Это сложнее, чем в первом примере. Суть в том, что код установки и разрыва соединения с базой данных и транзакции происходит вне вашей бизнес-логики. Здесь функция transaction() может находиться в отдельном файле или пространстве имен от остальной части вашего кода, что обеспечивает чистоту логики выполнения и избавляет от необходимости беспокоиться о настройке / завершении работы.
Файлы, базы данных, удаленные подключения к ресурсам, блокировки, потоки. Это все интенсивные операции, которые могут извлечь выгоду из использования этого шаблона – и которые используют этот шаблон в Python и других языках.
Это шаблон, который будет иметь значение в вашем коде? Это, конечно, отличается от того, как многие из нас пишут PHP сегодня. Как еще можно использовать эту модель? Было бы более разумно, если бы мы расширили язык с помощью нашей собственной реализации with?