Эмуляция именованных параметров функции в PHP, хорошая или плохая идея?

Именованные параметры функции можно эмулировать в PHP, если я пишу такие функции

function pythonic(array $kwargs)
{
    extract($kwargs);
    // .. rest of the function body
}

// if params are optional or default values are required
function pythonic(array $kwargs = array('name'=>'Jon skeet'))
{
    extract($kwargs);
    // .. rest of the function body
}

Помимо потери интеллекта в IDE, каковы другие возможные недостатки этого подхода?

Редактировать:

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


person Imran    schedule 25.03.2009    source источник


Ответы (5)


Я бы предложил использовать ассоциативный массив для передачи именованных параметров, но оставить их в массиве, не извлекая.

function myFunc(array $args) {
    echo "Hi, " . $args['name'];
    // etc
}

На это есть несколько причин. Глядя на эту функцию, вы совершенно ясно видите, что я имею в виду один из аргументов, переданных в функцию. Если вы извлечете их и не заметите extract(), вы (или следующий парень) будете чесать затылок, задаваясь вопросом, откуда взялась эта переменная "$name". Даже если вы действительно знаете, что извлекаете аргументы в локальные переменные, в определенной степени это все равно игра в угадайку.

Во-вторых, это гарантирует, что другой код не перезапишет аргументы. Возможно, вы написали свою функцию, ожидая наличия только аргументов с именами $foo и $bar, поэтому в другом коде вы определяете, например, $baz = 8;. Позже вы, возможно, захотите расширить свою функцию, чтобы принять новый параметр с именем «баз», но забыть изменить другие ваши переменные, поэтому независимо от того, что передается в аргументах, $baz всегда будет установлено на 8.

Использование массива также имеет некоторые преимущества (они в равной степени применимы к методам извлечения или оставления в массиве): вы можете установить переменную в начале каждой функции с именем $defaults:

function myFunc (array $args) {
    $default = array(
        "name" => "John Doe",
        "age" => "30"
    );
    // overwrite all the defaults with the arguments
    $args = array_merge($defaults, $args);
    // you *could* extract($args) here if you want

    echo "Name: " . $args['name'] . ", Age: " . $args['age'];
}

myFunc(array("age" => 25)); // "Name: John Doe, Age: 25"

Вы даже можете удалить все элементы из $args, которые не имеют соответствующего значения $default. Таким образом, вы точно знаете, какие переменные у вас есть.

person nickf    schedule 25.03.2009
comment
Хороший способ справиться с этим. Я сожалею, что не прочитал ваш ответ, прежде чем написать свой. - person Rolf; 04.11.2011
comment
Очень полезно, спасибо. Это можно комбинировать с обязательными параметрами, такими как function myFunc($foo,$bar,$args=array()). Я нашел полезным установить $args на пустое array() по умолчанию. Таким образом, можно вызывать myFunc(a, b), myFunc(a, b, array("name"=>"Mary")) и т. д. Почти так же удобно, как в Python :-) - person Laryx Decidua; 25.04.2019

Вот еще один способ сделать это.

/**
 * Constructor.
 * 
 * @named string 'algorithm'
 * @named string 'mode'
 * @named string 'key'
 */
public function __construct(array $parameter = array())
{
    $algorithm = 'tripledes';
    $mode = 'ecb';
    $key = null;
    extract($parameter, EXTR_IF_EXISTS);
    //...
}

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

(Кстати, создание значений по умолчанию из предоставленного вами примера не очень хорошо, потому что, если массив параметров предоставляется без индекса «имя», ваше значение по умолчанию будет потеряно.)

person Mario    schedule 25.03.2009
comment
Будет ли это работать для функций, которые не являются методами класса? (по умолчанию: я также получил это после публикации вопроса) - person Imran; 25.03.2009
comment
Конечно, это всего лишь фрагмент моего класса. Это работает для чего угодно. - person Mario; 25.03.2009
comment
Не могли бы вы объяснить, как заставить intellisense работать с тегом @named? Я использую PHPStorm, у которого отличный интеллект, но я не могу заставить его распознавать тег @named. Я не думаю, что это настоящий тег комментария PHP, не так ли? Его нет в списке тегов PHPDoc. - person Rich; 11.03.2013
comment
Серьезно, какую IDE вы используете, чтобы IntelliSense понял это? - person Denis Pshenov; 13.08.2015

По моему опыту, этот подход действительно полезен только в том случае, если верно одно из двух.

  1. По каким-то смягчающим причинам ваша подпись аргумента велика. Я предпочитаю максимум 6 — не по какой-то конкретной причине, хотя просто кажется правильным — но я свободно признаю, что это число произвольное.
  2. Все или многие из ваших аргументов являются необязательными, и иногда вам нужно только установить значение для 5-го или чего-то подобного. Стыдно писать someFunc( null, null, null, null, 1 );

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

При этом часто обе эти проблемы также могут быть решены с помощью рефакторинга.

person Peter Bailey    schedule 25.03.2009

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

function someFunc($requiredArg, $arg1 = "default11", $arg2 = "default2") {

Чтобы имитировать такое поведение при передаче всего массива, вам потребуется написать больше кода, а «подпись функции» будет менее «ясной и очевидной».

function someFunc($requiredArg, $optionalArgs) {
    // see other answers for good ways to simulate "named parameters" here

Мне интересно, будет ли хорошей идеей для PHP решить эту проблему в будущем выпуске, возможно, будет что-то вроде синтаксиса Pascal или VB, доступного для аргументов функции.

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

person Rolf    schedule 04.11.2011

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

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

И да и нет. Написанный вами код может (зависит от того, всегда ли вы инициализируете свои переменные после этого вызова) перезаписать ваши переменные. Пример:

function pythonic(array $kwargs = array('name'=>'Jon skeet'))
{
    $is_admin = check_if_is_admin();  // initialize some variable...

    extract($kwargs);

    // Q: what is the value of $is_admin now? 
    // A: Depends on how this function was called... 
    // hint: pythonic([ 'is_admin' => true ])
}

Что делает этот код «своего рода безопасным», так это то, что именно ВЫ вызываете его, поэтому пользователь не может указывать произвольные параметры (если вы, конечно, не перенаправляете туда POST-переменные;).

Как правило, вам следует избегать такой магии. Строка с extract() может иметь непреднамеренные побочные эффекты, поэтому ее не следует использовать. На самом деле, я не могу думать о законном использовании функции extract() в каком-либо приложении (я не думаю, что когда-либо использовал ее сам).

person johndodo    schedule 06.10.2014