Использование аргументов по умолчанию в функции

178

Меня смущают значения по умолчанию для функций PHP. Скажем, у меня есть такая функция:

function foo($blah, $x = "some value", $y = "some other value") {
    // code here!
}

Что, если я хочу использовать аргумент по умолчанию для $ x и установить другой аргумент для $ y?

Я экспериментировал с разными способами, и я просто запутался. Например, я попробовал эти два:

foo("blah", null, "test");
foo("blah", "", "test");

Но оба из них не приводят к правильному аргументу по умолчанию для $ x. Я также попытался установить его по имени переменной.

foo("blah", $x, $y = "test");   

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

renosis
источник
68
Вы, вероятно, ничего не упускаете, разработчики PHP, вероятно, пропустили это.
Матти Вирккунен
Это интересный вопрос. У меня никогда не было необходимости делать это, как вы заявили, поскольку я использую аргументы по умолчанию для расширения функций, унаследованных другими программистами, когда я не уверен, что зависит от оригинала. Мне интересно узнать ответ.
Кай Цин
3
Один из лучших вопросов, которые я когда-либо видел! Вы пытались ничего не передавать? foo("blah", , "test");?
Призрак Мадары
2
Правило состоит в том, что аргументы по умолчанию могут следовать только за аргументами. То есть у вас может быть foo ("бла") и x, y по умолчанию или foo ("бла", "xyz") и y по умолчанию.
Mouse Food
1
Ожидаемое поведение. См. Раздел «Значения аргументов по умолчанию» в: php.net/manual/en/functions.arguments.php - нулевое значение и пустая строка принимаются в качестве фактических аргументов функции
jmdavalos

Ответы:

164

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

function foo($blah, $x = null, $y = null) {
    if (null === $x) {
        $x = "some value";
    }

    if (null === $y) {
        $y = "some other value";
    }

    code here!

}

Таким образом, вы можете сделать вызов как foo('blah', null, 'non-default y value');и заставить его работать, как вы хотите, где второй параметр$x прежнему получает значение по умолчанию.

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

Как указано в других ответах,

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

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

Вот еще один пример (это не отвечает на ваш вопрос, но, надеюсь, информативно:

public function __construct($params = null)
{
    if ($params instanceof SOMETHING) {
        // single parameter, of object type SOMETHING
    } else if (is_string($params)) {
        // single argument given as string
    } else if (is_array($params)) {
        // params could be an array of properties like array('x' => 'x1', 'y' => 'y1')
    } else if (func_num_args() == 3) {
        $args = func_get_args();

        // 3 parameters passed
    } else if (func_num_args() == 5) {
        $args = func_get_args();
        // 5 parameters passed
    } else {
        throw new InvalidArgumentException("Could not figure out parameters!");
    }
}
drew010
источник
Почему не $ x = isset ($ x)? $ x: «значение по умолчанию»; ?
zloctb
@zloctb - это чистая и читаемая проверка нуля. Похоже, это предпочтение разработчика. Либо $ x = isset ($ x)? $ x: «значение по умолчанию»; или $ x = is_null ($ x)? 'значение по умолчанию': $ x; будет работать так же, как это решение. Это предпочтительный выбор для удобства чтения и обслуживания.
DeveloperWeeks
9
Просто чтобы уточнить для будущих читателей. Первое решение, очевидно, делает невозможным присвоение действительного nullзначения параметру, поскольку каждый раз он назначает значение по умолчанию. Даже в том случае, если у вас есть другое специальное значение для того, когда вы на самом деле хотите нулевое значение (например, когда $ x == "use_null" составляет $ x = null), вы не сможете назначить такое специальное значение в качестве литерального значения параметр (в данном случае строка «use_null»). Поэтому просто обязательно используйте уникальный, никогда не желаемый «ключ», когда вы хотите использовать значение по умолчанию (и вы хотите nullбыть допустимым параметром)
DiegoDD
37

Необязательные аргументы работают только в конце вызова функции. Невозможно указать значение $ y в вашей функции без указания $ x. Некоторые языки поддерживают это с помощью именованных параметров (например, VB / C #), но не PHP.

Вы можете эмулировать это, если вы используете ассоциативный массив для параметров вместо аргументов - т.е.

function foo(array $args = array()) {
    $x = !isset($args['x']) ? 'default x value' : $args['x'];
    $y = !isset($args['y']) ? 'default y value' : $args['y'];

    ...
}

Затем вызовите функцию так:

foo(array('y' => 'my value'));
Райан П
источник
1
Ваше условие действительно должно быть isset($args['x']), так как оно в настоящее время заменяет пустую строку, пустой массив или falseзначение по умолчанию.
Тим Купер
@TimCooper lol Я изменил на основании вашего первого комментария, что это должно быть '=== null', пошел и изменил мой ответ, чтобы использовать isset, а затем вернулся, чтобы увидеть, что вы тоже изменили свой комментарий. : D
Райан П
Ах, Драт! Мне это совсем не нравится ... О, хорошо, у тебя не может быть всего. Полагаю, мне придется немного больше подумать о порядке моих аргументов по умолчанию. Спасибо!
Реноз
К сожалению, вы не сможете присвоить нуль с этим ответом. Это лучший вариант: $x = !array_key_exists('x',$args) ? 'default x value' : $args['x'];
Фрэнк Форте
20

На самом деле это возможно:

foo( 'blah', (new ReflectionFunction('foo'))->getParameters()[1]->getDefaultValue(), 'test');

Хотели бы вы сделать это - другая история :)


ОБНОВИТЬ:

Причины, чтобы избежать этого решения:

  • это (возможно) безобразно
  • это имеет очевидные накладные расходы.
  • как другие ответы доказательство, есть альтернативы

Но это может быть полезно в ситуациях, когда:

  • Вы не хотите / не можете изменить исходную функцию.

  • Вы можете изменить функцию, но:

    • использование null(или эквивалент) не вариант (см. комментарий DiegoDD )
    • Вы не хотите идти с ассоциативным или с func_num_args()
    • ваша жизнь зависит от сохранения нескольких LOC

Что касается производительности, очень простой тест показывает, что использование Reflection API для получения параметров по умолчанию делает вызов функции в 25 раз медленнее , хотя это по-прежнему занимает менее одной микросекунды . Вы должны знать, если вы можете жить с этим.

Конечно, если вы хотите использовать его в цикле, вы должны получить значение по умолчанию заранее.

Дэвид
источник
1
В чем причина нежелания этого делать? Кажется, очень удобно.
Брайан
10
function image(array $img)
{
    $defaults = array(
        'src'    => 'cow.png',
        'alt'    => 'milk factory',
        'height' => 100,
        'width'  => 50
    );

    $img = array_merge($defaults, $img);
    /* ... */
}
zloctb
источник
2
Я что-то упускаю? Это, кажется, не отвечает на вопрос вообще, но у него есть 7 голосов. Может быть, добавление небольшого количества объяснения поможет?
Шон Боб
3
@SeantheBean: правда, объяснение было бы лучше, но его суть заключается в следующем: поскольку в PHP нет именованных аргументов, используйте ассоциативный массив в качестве единственного параметра.
MestreLion
1
Более полезно использовать пример, приведенный в вопросе, например$defaults = [ 'x' => 'some value', 'y' => 'some other value'];
Фрэнк Форте
4

Единственный способ, которым я знаю, - это пропустить параметр. Единственный способ пропустить параметр - это переставить список параметров так, чтобы тот, который вы хотите опустить, находится после параметров, которые вы ДОЛЖНЫ установить. Например:

function foo($blah, $y = "some other value", $x = "some value")

Тогда вы можете позвонить Foo, как:

foo("blah", "test");

Это приведет к:

$blah = "blah";
$y = "test";
$x = "some value";
Уэс Кроу
источник
3

Я недавно имел эту проблему и нашел этот вопрос и ответы. Хотя вышеуказанные вопросы работают, проблема в том, что они не показывают значения по умолчанию для IDE, которые его поддерживают (например, PHPStorm).

введите описание изображения здесь

если вы используете, nullвы не будете знать, какое значение будет, если вы оставите это поле пустым.

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

protected function baseItemQuery(BoolQuery $boolQuery, $limit=1000, $sort = [], $offset = 0, $remove_dead=true)
{
    if ($limit===null) $limit =1000;
    if ($sort===null) $sort = [];
    if ($offset===null) $offset = 0;
    ...

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

Yehosef
источник
Внутреннее использование ReflectionFunction на себя сохранит цену!
SubjectDelta
2

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

function foo($blah, $x = false, $y = false) {
  if (!$x) $x = "some value";
  if (!$y) $y = "some other value";

  // code
}
KBA
источник
6
Это бы сработало, но я нахожу использование nullв этом случае концептуально более аккуратным, чем false.
TRiG
Добавление к комментарию TriG falseявляется более рискованным выбором, чем nullphp - язык не строгий. !$xбудет верно для нескольких разных входов, которые могут удивить программиста: 0, ''. Принимая во внимание null, что, вы можете использовать !issetв качестве более строгого теста.
ToolmakerSteve
1
<?php
function info($name="George",$age=18) {
echo "$name is $age years old.<br>";
}
info();     // prints default values(number of values = 2)
info("Nick");   // changes first default argument from George to Nick
info("Mark",17);    // changes both default arguments' values

?>
Один
источник
1

Мои 2 цента с нулевым оператором объединения ?? (начиная с PHP 7)

function foo($blah, $x = null, $y = null) {
    $varX = $x ?? 'Default value X';
    $varY = $y ?? 'Default value Y';
    // ...
}

Вы можете проверить больше примеров на моем repl.it

SandroMarques
источник
0

Это тот случай, когда объект лучше - потому что вы можете настроить свой объект для удержания x и y, установить значения по умолчанию и т. Д.

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

Конечно, вы всегда можете сделать несколько трюков, чтобы установить ноль или что-то вроде этого по умолчанию

SergeS
источник
0

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

foo('blah', "", 'non-default y value', null);

Ниже функции:

function foo($blah, $x = null, $y = null, $z = null) {
    if (null === $x || "" === $x) {
        $x = "some value";
    }

    if (null === $y || "" === $y) {
        $y = "some other value";
    }

    if (null === $z || "" === $z) {
        $z = "some other value";
    }

    code here!

}

Неважно, если вы заполните nullили "", вы все равно получите тот же результат.

Мустафа Экичи
источник
0

Передайте массив функции вместо отдельных параметров и используйте оператор объединения нулей (PHP 7+).

Ниже я передаю массив с 2 элементами. Внутри функции я проверяю, установлено ли значение для item1, если не назначено хранилище по умолчанию.

$args = ['item2' => 'item2',
        'item3' => 'value3'];

    function function_name ($args) {
        isset($args['item1']) ? $args['item1'] : 'default value';
    }
Роджер
источник
Вы можете использовать $args['item1'] = $args['item1']??'default value'; в PHP 7+. если $ args ['item1'] равен нулю, то default valueстрока будет присваиваться $ args ['item1']
Sadee