Usando argumentos padrão em uma função

177

Estou confuso sobre valores padrão para funções PHP. Digamos que eu tenho uma função como esta:

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

E se eu quiser usar o argumento padrão para $ x e definir um argumento diferente para $ y?

Tenho experimentado maneiras diferentes e estou ficando cada vez mais confusa. Por exemplo, eu tentei estes dois:

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

Mas ambos não resultam em um argumento padrão adequado para $ x. Eu também tentei defini-lo pelo nome da variável.

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

Eu esperava que algo assim funcionasse. Mas não funciona como eu esperava. Parece que não importa o que eu faça, terei que digitar os argumentos padrão de qualquer maneira, toda vez que invocar a função. E devo estar perdendo algo óbvio.

renosis
fonte
68
Você provavelmente não está perdendo nada, os desenvolvedores do PHP provavelmente perderam isso.
Matti Virkkunen
Essa é uma pergunta interessante. Na verdade, nunca tive a necessidade de fazer isso, como você afirmou, pois só uso args padrão para expandir funções herdadas por outros programadores quando não tenho certeza do que depende do original. Estou curioso sobre uma resposta.
Kai Qing
3
Uma das melhores perguntas que já vi há algum tempo! Você já tentou passar nada? foo("blah", , "test");?
Ghost de Madara
2
A regra é que argumentos padrão só podem seguir argumentos. Ou seja, você pode ter foo ("blá") e x, y são o padrão, ou foo ("blá", "xyz") e y é o padrão.
Mouse Food
1
Comportamento esperado. Consulte a seção "Valores padrão dos argumentos" em: php.net/manual/en/functions.arguments.php - valor nulo e string vazia são aceitos como argumentos reais da função
jmdavalos

Respostas:

164

Eu proporia alterar a declaração da função da seguinte maneira para que você possa fazer o que deseja:

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

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

    code here!

}

Dessa forma, você pode fazer uma chamada como foo('blah', null, 'non-default y value');e fazê-la funcionar como quiser, onde o segundo parâmetro $xainda obtém seu valor padrão.

Com esse método, passar um valor nulo significa que você deseja o valor padrão para um parâmetro quando deseja substituir o valor padrão para um parâmetro que vem depois dele.

Como indicado em outras respostas,

parâmetros padrão funcionam apenas como os últimos argumentos para a função. Se você deseja declarar os valores padrão na definição da função, não há como omitir um parâmetro e substituir outro após ele.

Se eu tenho um método que pode aceitar números variáveis ​​de parâmetros e parâmetros de tipos variados, geralmente declaro a função semelhante à resposta mostrada por Ryan P.

Aqui está outro exemplo (isso não responde à sua pergunta, mas é esperançosamente informativo:

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
fonte
Por que não $ x = isset ($ x)? $ x: 'valor padrão'; ?
Zloctb 19/10/2015
@zloctb, esta é uma verificação nula limpa e legível. Parece que é a preferência do desenvolvedor. Ou $ x = isset ($ x)? $ x: 'valor padrão'; ou $ x = is_null ($ x)? 'valor padrão': $ x; funcionaria tão bem quanto esta solução. É uma escolha preferida sobre legibilidade e manutenção.
DeveloperWeeks
9
Apenas para esclarecer para futuros leitores. A primeira solução obviamente torna impossível atribuir um nullvalor real ao parâmetro, já que toda vez ele atribui o valor padrão. Mesmo que você tenha outro valor especial para quando você realmente deseja um nulo (por exemplo, quando $ x == "use_null" cria $ x = null), não seria possível atribuir um valor especial como o valor literal do parâmetro (neste caso, a cadeia "use_null"). Então, só não se esqueça de usar uma "chave" única, nunca desejou para quando você quiser usar o valor padrão (e você quer nullser uma opção válida)
DiegoDD
37

Argumentos opcionais funcionam apenas no final de uma chamada de função. Não há como especificar um valor para $ y em sua função sem especificar também $ x. Algumas linguagens suportam isso por meio de parâmetros nomeados (VB / C # por exemplo), mas não PHP.

Você pode emular isso se usar uma matriz associativa para parâmetros em vez de argumentos - ou seja,

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

    ...
}

Em seguida, chame a função da seguinte maneira:

foo(array('y' => 'my value'));
Ryan P
fonte
1
Sua condição deve ser realmente isset($args['x']), pois atualmente substituiria uma sequência vazia, uma matriz vazia ou falsepelo valor padrão.
Tim Cooper
@ TimCooper lol Fui mudar com base no seu primeiro comentário que deveria ser '=== null', fui e mudei minha resposta para usar o isset, em seguida, voltei para ver que você também mudou seu comentário. : D
Ryan P
Ah, droga! Não gosto nada disso ... Bem, você não pode ter tudo. Suponho que terei que pensar um pouco mais na ordem dos meus argumentos padrão. Obrigado!
Renosis 06/02/12
Infelizmente, você não poderá atribuir nulo a esta resposta. Esta é uma opção melhor: $x = !array_key_exists('x',$args) ? 'default x value' : $args['x'];
Frank Forte
20

É realmente possível:

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

Se você gostaria de fazer isso é outra história :)


ATUALIZAR:

Os motivos para evitar essa solução são:

  • é (sem dúvida) feio
  • tem uma sobrecarga óbvia.
  • como as outras respostas comprovam, existem alternativas

Mas na verdade pode ser útil em situações em que:

  • você não quer / não pode alterar a função original.

  • você pode alterar a função, mas:

    • usar null(ou equivalente) não é uma opção (consulte o comentário de DiegoDD )
    • você não quer ir com uma associativa ou com func_num_args()
    • sua vida depende de salvar alguns LOCs

Sobre o desempenho, um teste muito simples mostra que o uso da API do Reflection para obter os parâmetros padrão torna a chamada de função 25 vezes mais lenta , enquanto ainda leva menos de um microssegundo . Você deve saber se pode viver com isso.

Obviamente, se você quiser usá-lo em um loop, deverá obter o valor padrão antecipadamente.

David
fonte
1
Qual o motivo de não querer fazer isso? Parece bastante útil.
Bryan
10
function image(array $img)
{
    $defaults = array(
        'src'    => 'cow.png',
        'alt'    => 'milk factory',
        'height' => 100,
        'width'  => 50
    );

    $img = array_merge($defaults, $img);
    /* ... */
}
zloctb
fonte
2
Estou esquecendo de algo? Isso parece não responder à pergunta, mas tem 7 votos positivos. Talvez adicionar um pouco de explicação ajudaria?
Sean the Bean
3
@SeantheBean: true, uma explicação seria melhor, mas seu argumento é: como o PHP não possui argumentos nomeados, use uma matriz associativa como seu único parâmetro.
MestreLion 15/09/18
1
É mais útil usar o exemplo dado na questão, por exemplo$defaults = [ 'x' => 'some value', 'y' => 'some other value'];
Frank Forte
4

A única maneira que eu sei de fazê-lo é omitindo o parâmetro. A única maneira de omitir o parâmetro é reorganizar a lista de parâmetros para que o que você deseja omitir seja após os parâmetros que você PRECISA definir. Por exemplo:

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

Então você pode chamar foo como:

foo("blah", "test");

Isso resultará em:

$blah = "blah";
$y = "test";
$x = "some value";
Wes Crow
fonte
3

Recentemente, tive esse problema e encontrei esta pergunta e respostas. Enquanto as perguntas acima funcionam, o problema é que elas não mostram os valores padrão para os IDEs que os suportam (como PHPStorm).

insira a descrição da imagem aqui

se você usar null, não saberá qual seria o valor se deixá-lo em branco.

A solução que prefiro é colocar também o valor padrão na definição da função:

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;
    ...

A única diferença é que preciso garantir que eles sejam iguais - mas acho que é um preço pequeno a pagar pela clareza adicional.

Yehosef
fonte
O uso interno do ReflectionFunction por si só economizaria o preço!
precisa saber é o seguinte
2

Você não pode fazer isso diretamente, mas um pouco de manipulação de código torna possível emular.

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

  // code
}
kba
fonte
6
Isso funcionaria, mas acho que o uso nullnesse caso é conceitualmente mais organizado do que false.
TRiG 16/04/12
Acrescentando ao comentário do TriG, falseé uma opção mais arriscada do que null, porque php é uma linguagem não estrita. !$xserá verdadeiro para várias entradas diferentes, que podem surpreender o programador: 0, ''. Considerando que null, com , você pode usar !issetcomo um teste mais rigoroso.
Home
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

?>
Odin
fonte
1

Meus 2 centavos com operador coalescente nulo ?? (desde PHP 7)

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

Você pode conferir mais exemplos no meu repl.it

SandroMarques
fonte
0

Nesse caso, quando o objeto é melhor - porque você pode configurá-lo para conter xey, configurar padrões etc.

A abordagem com o array está próxima de criar o objeto (de fato, o objeto é um monte de parâmetros e funções que trabalharão sobre o objeto, e o array de tomada de função funcionará sobre alguns parâmetros do grupo)

Certamente, você sempre pode fazer alguns truques para definir nulo ou algo assim como padrão

SergeS
fonte
0

Você também pode verificar se possui uma sequência vazia como argumento para poder chamar como:

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

Abaixo da função:

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!

}

Não importa se você preencher nullou "", você ainda obterá o mesmo resultado.

Mustafa Ekici
fonte
0

Passe uma matriz para a função, em vez de parâmetros individuais e use o operador coalescente nulo (PHP 7+).

Abaixo, estou passando uma matriz com 2 itens. Dentro da função, estou verificando se o valor do item1 está definido, se não for atribuído o cofre padrão.

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

    function function_name ($args) {
        isset($args['item1']) ? $args['item1'] : 'default value';
    }
Roger
fonte
Você pode usar $args['item1'] = $args['item1']??'default value'; no PHP 7+. se $ args ['item1'] for nulo, a default valuestring será atribuída a $ args ['item1'] #
3026 Sadee