Недавно я создал приложение с Laravel 8.0, которое использует множество консольных команд по разным служебным причинам. Для большинства этих методов я хочу использовать dry-run тест, чтобы отклонять изменения по умолчанию.

Первый наиболее очевидный подход - использовать параметр, который вы можете проверить при каждом вызове функции. Это будет выглядеть так:

public function myMethod() {
    if ($this->dryrun) {
        return;
    }
 
    // Execute destructive code...
}

Это оказывает слишком большое давление на каждую функцию, чтобы знать, что такое даже dry-run, как с ним проверять, и это также напрямую связывает логику.

Подход, который я выбрал вместо этого, использует магию PHP __call(), чтобы помочь делать более точные определения; большая часть Laravel также использует это.

Для моих команд у меня есть BaseCommand.php файл, который будет запрашивать параметр dryrun, сохранять значение и предоставлять необходимые магические методы. Вот волшебный метод, который я написал для этого:

Подпись __call($method, $arguments) предоставляется PHP.

Как видите, мы используем регулярное выражение для проверки слов safely и test. Это дает нам гибкость для добавления префиксов к большему количеству типов вызовов в будущем, где мы могли бы писать: safelyExecuteMethod или testExecuteMethod, или потенциально queueExecuteMethod и т. Д.

Мы вызываем метод, соответствующий такому шаблону, а затем он продолжает его анализ. Он порождает две вещи:

  1. Обертка внутреннего вызова, например safelyCall, testCall или queueCall
  2. Имя метода в верблюжьем регистре, например myMethod, а не MyMethod

Мы проверяем, существуют ли safelyCall и myMethod в экземпляре, и если это так, мы вызываем его.

Откуда safelyCall? Это специальный метод, который мы пишем на основе префиксов, которые хотим поддерживать. Вот мой:

Это функция, которая будет вызываться при вводе префикса safely, и вы можете видеть, что она принимает $classMethod, который вы пытаетесь вызвать, и дополнительные аргументы.

Этот промежуточный слой важен. Вы можете увидеть, как мы проверяем dryrun здесь, а не в каждом отдельном методе, который может нуждаться в нем. Это неплохое промежуточное ПО на уровне класса.

Если сложить все вместе, то получится следующее:

Как вы могли заметить, нам не хватает testCall метода, описанного выше, но вы можете определить, хотите ли вы его добавить. Как уже упоминалось, существует регулярное выражение /(safely|test|whatever|you|want)(.*)/, которое позволит вам определять подписи для safelyCall, testCall, whateverCall, youCall и wantCall. Если метода не существует, он будет пропущен.

Как это использовать?

Допустим, мы хотим запустить несколько потенциально разрушительных SQL-запросов, чтобы очистить заброшенный контент. Обратите внимание, что следующий дизайн предназначен для демонстрационных целей.

У нас может быть функция, которая ищет контент, который мы хотим удалить, и другую, которая фактически удаляет его. Разделение методов поиск и уничтожение обеспечивает большую гибкость и позволяет такой структуре работать.

Вы можете видеть, что наш cleanContentTable является основным вызовом, который будет искать контент, находить список идентификаторов, а затем запрашивать их уничтожение.

На самом деле их уничтожает функция removeContentByIds, но вы заметите, что мы буквально вызываем что-то другое; мы фактически вызываем safelyRemoveContentByIds, которого технически не существует в классе.

Вот где происходит волшебство. Поскольку метода на самом деле не существует, он перенаправляется на __call(), предоставленный PHP. В нашем предыдущем коде мы написали некоторую логику для анализа имени метода и делегирования дальнейших обязанностей.

Добавляя к нашему деструктивному вызову префикс safely с самокомментариями, мы можем вызывать методы без написания излишне повторяющейся логики. С этого момента вы можете написать любой тип префикса, суффикса или чего угодно, чтобы разумно обернуть вызовы методов самодокументированным кодом.

Но ты должен помнить ...

Магия может быть очень опасной и часто неправильный выбор!

Некоторые люди используют магию, чтобы скрыть плохо написанный код. Это все равно, что попросить ребенка убраться в своей комнате, а затем обнаружить, что он просто запихнул все свои игрушки в шкаф; это не чистый ребенок. (Это то, чем я занимался в молодости.)

Если вы используете магию таким образом, вы злоупотребляете системой.

Я бы сказал, что использование магии в этом примере ценно, потому что оно предназначено для уменьшения повторяющегося кода на языке, который не поддерживает декораторы, но при этом расширяет ценность для разработчиков.

Есть несколько фреймворков, которые позволяют украшать функции комментариями, но лично я считаю, что это ужасная идея. Комментарии никогда не должны позволять выполнять код.