Аргументы функции PHP - используйте массив или нет?

Мне нравится создавать мои функции PHP, используя пары ключ = > значение (массивы) в качестве аргументов вместо отдельных параметров.

Например, я предпочитаю:

function useless_func($params) {
    if (!isset($params['text'])) { $params['text'] = "default text"; }     
    if (!isset($params['text2'])) { $params['text2'] = "default text2"; }   
    if (!isset($params['text3'])) { $params['text3'] = "default text3"; }   
    echo $params['text'].$params['text2'].$params['text3'];
    return;
}

И мне не нравится:

function useless_func($text = "default text", $text2 = "default text2", $text3 = "default text3") {
        echo $text.$text2.$text3;
    return;
}

Я впервые видел, как это делалось так широко в кодовой базе Wordpress.

Причина, по которой я предпочитаю массивы:

  • Аргументы функций могут предоставляться в любом порядке
  • Легче читать код/​​более самостоятельно документировать (на мой взгляд)
  • Менее подвержен ошибкам, потому что при вызове функции я должен исследовать правильные ключи массива

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

Я ищу некоторые общие рекомендации и рекомендации экспертов, которые могли бы дать представление: какой лучший или более правильный способ сделать это?

Ответ 1

Хорошо, это полезно. Но для некоторых аргументов, которые всегда проходят, лучше использовать классический переход, как function some($a1, $a2). Я делаю это в своем коде:

function getSome(SomeClass $object, array $options = array())
{
    // $object is required to be an instance of SomeClass, and there no need to get element by key, then check if it an object and it an instance of SomeClass

    // Set defaults for all passed options
    $options = array_merge(array(
        'property1' => 'default1',
        'property2' => 'default2',
        ... => ...
    ), $options); 
}

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

Ответ 2

Не делай этого!

Передача всего массива - это плохая идея в большинстве случаев.

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

Похоже на противоположное впрыскивание в функции, в которой она нуждается.

Аргументы функций могут предоставляться в любом порядке

У меня нет такого предпочтения. Я не понимаю, что нужно.

Легче читать код/​​более самостоятельно документировать (на мой взгляд)

Большинство IDE представят вам разные аргументы, необходимые для функции. Если вы видите объявление функции как foo(Someclass $class, array $params, $id), то очень ясно, что нужно функции. Я не согласен с тем, что один параметр param легче читать или самостоятельно документировать.

Менее подвержен ошибкам, потому что при вызове функции я должен исследовать правильные ключи массива

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


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

Ответ 3

Я предполагаю, что вы спрашиваете, следует ли A Good Thing писать все функции, чтобы они принимали только один аргумент, а для этого аргумента был массив

Если вы единственный человек, который когда-либо будет работать над вашим кодом, тогда вы можете делать то, что вам нравится. Однако, передавая все значения аргументов через массив, кто-то еще должен будет усердно работать, чтобы понять, что делает функция и почему/как они могут ее использовать, особенно если они используют IDE с автозаполнением для имен функций и т.д. Они не называют его "сигнатурой функции".

Я бы рекомендовал, чтобы параметры массива были зарезервированы либо для элементов, где вы не знаете, сколько их будет (например, серии элементов данных), либо для групп связанных параметров/настроек (что может быть и тем, что происходит в примере Wordpress, который вы упоминаете?).

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

Ответ 4

Ваш коллега прав. Это не только код для одинаковой функциональности, тем сложнее читать и, вероятно, снижает производительность (так как вам нужно вызвать isset для каждого параметра, и вам нужно получить доступ к массиву для установки значений).

Ответ 5

Это граничит с Программирование Cargo Cult. Вы говорите, что это более читаемо и самодокументируемо. Я бы спросил, как? Чтобы узнать, как использовать вашу функцию/метод, я должен прочитать сам код. Я никак не могу понять, как использовать его из самой подписи. Если вы используете любую наполовину достойную IDE или редактор, который поддерживает подсказку подписи метода, это будет реальная PITA. Кроме того, вы не сможете использовать синтаксис подсказок типа PHP.

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

Ответ 6

Использование array_merge() работает нормально, но использование оператора + также может быть использовано; он работает по-другому, он только добавляет значения по умолчанию, если они еще не были предоставлены.

function useless_func(array $params = array())
{
    $params += array(
        'text' => 'default text',
        'text2' => 'default text2',
        'text3' => 'default text3',
    );
}

См. также: Функция Передача массива в определенный ключ

Несколько вещей, которые вы не можете использовать с массивами в качестве аргументов функции:

  • проверка типов (применима только к объектам и массивам, но может быть полезна и в некоторых случаях ожидается). Текстовые редакторы
  • smart (er) имеют функцию прокрутки кода, которая покажет аргументы, которые функция понимает; использование массивов отнимает эту функцию, хотя вы можете добавить возможные ключи в функцию docblock.
  • из-за # 2 это на самом деле становится более подверженным ошибкам, потому что вы можете ошибочно использовать ключ массива.

Ответ 7

Ваш сотрудник сумасшедший. Это вполне приемлемо для передачи в массив как аргумент функции. Он распространен во многих приложениях с открытым исходным кодом, включая Symfony и Doctrine. Я всегда следовал правилу 2 аргументов, если для функции требуется более двух аргументов, или вы думаете, что в будущем будет использовать более двух аргументов, используйте массив. IMO это обеспечивает максимальную гибкость и уменьшает любые дефекты вызывающего кода, которые могут возникнуть, если аргумент передан неправильно.

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

Вот фрагмент кода, отображающий требуемые необязательные параметры, чтобы дать вам представление:

// Class will tokenize a string based on params
public static function tokenize(array $params)
{
    // Validate required elements
    if (!array_key_exists('value', $params)) {
        throw new Exception(sprintf('Invalid $value: %s', serialize($params)));
    }        

    // Localize optional elements
    $value            = $params['value'];
    $separator        = (array_key_exists('separator', $params)) ? $params['separator'] : '-';
    $urlEncode        = (array_key_exists('urlEncode', $params)) ? $params['urlEncode'] : false;
    $allowedChars     = (array_key_exists('allowedChars', $params)) ? $params['allowedChars'] : array();
    $charsToRemove    = (array_key_exists('charsToRemove', $params)) ? $params['charsToRemove'] : array();

....

Ответ 8

@Mike, вы также можете "извлечь()" ваш аргумент $params в локальные переменные, например:

// Class will tokenize a string based on params
public static function tokenize(array $params)
{
    extract($params);
    // Validate required elements
    if (!isset($value)) {
        throw new Exception(sprintf('Invalid $value: %s', serialize($params)));
    }

    // Localize optional elements
    $value         = isset($value) ? $value : '';
    $separator     = isset($separator) ? $separator] : '-';
    $urlEncode     = isset($urlEncode) ? $urlEncode : false;
    $allowedChars  = isset($allowedChars) ? $allowedChars : array();
    $charsToRemove = isset($charsToRemove) ? $charsToRemove : array();

....

Такая же реализация, но короче.

Ответ 9

Я использовал массивы, чтобы во многих случаях подставлять длинный список параметров, и он работал хорошо. Я согласен с теми, кто в этом сообщении, которые упомянули о редакторах кода, которые не могут дать подсказки для аргументов. Проблема в том, что если у меня есть 10 аргументов, а первые 9 пустые/нулевые, это просто становится громоздким при вызове этой функции.

Мне также будет интересно узнать, как перепроектировать функцию, требующую большого количества аргументов. Например, когда у нас есть функция, которая строит SQL-инструкции на основе определенных аргументов:

function ($a1, $a2, ... $a10){

        if($a1 == "Y"){$clause_1 = " something = ".$a1." AND ";}
        ...
        if($a10 == "Y"){$clause_10 = " something_else = ".$a10." AND ";}

        $sql = "
        SELECT * FROM some_table 
        WHERE
        ".$clause_1." 
        ....
        ".$clause_10." 
        some_column = 'N'
        ";

        return $sql;
    }

Я хотел бы, чтобы PHP развлекал добавление встроенной вспомогательной функции, которая могла бы использоваться в вызываемой функции, которая помогла бы передать массив параметров, проведя необходимую проверку типов. PHP признал это в определенной степени, создав функцию func_get_args(), которая позволяет передавать аргументы в любом порядке. НО это будет проходить только COPY значений, поэтому, если вы хотите передать объекты функции, это будет проблемой. Если бы такая функция существовала, то редакторы кода могли бы ее поднять и предоставить подробную информацию о возможных аргументах.