Uso de argumentos predeterminados en una función

177

Estoy confundido acerca de los valores predeterminados para las funciones de PHP. Digamos que tengo una función como esta:

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

¿Qué sucede si quiero usar el argumento predeterminado para $ x y establecer un argumento diferente para $ y?

He estado experimentando con diferentes formas y me estoy confundiendo cada vez más. Por ejemplo, probé estos dos:

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

Pero ambos no dan como resultado un argumento predeterminado adecuado para $ x. También he intentado configurarlo por nombre de variable.

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

Esperaba que algo así funcionara. Pero no funciona como esperaba en absoluto. Parece que no importa lo que haga, tendré que terminar escribiendo los argumentos predeterminados de todos modos, cada vez que invoque la función. Y debo estar perdiendo algo obvio.

renosis
fuente
68
Probablemente no te pierdas nada, los desarrolladores de PHP probablemente se lo perdieron.
Matti Virkkunen
Esa es una pregunta interesante. En realidad, nunca he tenido la necesidad de hacer esto, como has dicho, ya que solo uso argumentos predeterminados para expandir las funciones heredadas por otros programadores cuando no estoy seguro de qué depende del original. Tengo curiosidad por una respuesta.
Kai Qing
3
¡Una de las mejores preguntas que he visto en mucho tiempo! ¿Has intentado pasar nada? foo("blah", , "test");?
El fantasma de Madara el
2
La regla es que los argumentos predeterminados solo pueden seguir argumentos. Es decir, podría tener foo ("bla") y x, y son los valores predeterminados, o foo ("Bla", "xyz") e y es los valores predeterminados.
Mouse Food
1
Comportamiento esperado. Consulte la sección "Valores de argumento predeterminados" en: php.net/manual/en/functions.arguments.php - el valor nulo y la cadena vacía se aceptan como argumentos reales de la función
jmdavalos

Respuestas:

164

Propondría cambiar la declaración de función de la siguiente manera para que pueda hacer lo que quiera:

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

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

    code here!

}

De esta manera, puede realizar una llamada como foo('blah', null, 'non-default y value');y hacer que funcione como desee, donde el segundo parámetro $xaún obtiene su valor predeterminado.

Con este método, pasar un valor nulo significa que desea el valor predeterminado para un parámetro cuando desea anular el valor predeterminado para un parámetro que viene después.

Como se indicó en otras respuestas,

Los parámetros predeterminados solo funcionan como los últimos argumentos de la función. Si desea declarar los valores predeterminados en la definición de la función, no hay forma de omitir un parámetro y anular uno que lo siga.

Si tengo un método que puede aceptar un número variable de parámetros y parámetros de diferentes tipos, a menudo declaro la función similar a la respuesta mostrada por Ryan P.

Aquí hay otro ejemplo (esto no responde a su pregunta, pero es de esperar 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
fuente
¿Por qué no $ x = isset ($ x)? $ x: 'valor predeterminado'; ?
zloctb
@zloctb este es un cheque nulo limpio y legible. Parece que es preferencia del desarrollador. ¿$ X = isset ($ x)? $ x: 'valor predeterminado'; o $ x = is_null ($ x)? 'valor predeterminado': $ x; funcionaría tan bien como esta solución. Es una opción de preferencia sobre legibilidad y mantenimiento.
DeveloperWeeks
9
Solo para aclarar para futuros lectores. La primera solución obviamente hace que sea imposible asignar un nullvalor real al parámetro, ya que cada vez asignaría el valor predeterminado. Incluso en el caso de que tuviera otro valor especial para cuando realmente desea un valor nulo (por ejemplo, cuando $ x == "use_null" hace $ x = nulo), entonces no podrá asignar un valor tan especial como el valor literal de parámetro (en este caso, la cadena "use_null"). Así que asegúrese de usar una "clave" única y nunca deseada para cuando quiera usar el valor predeterminado (y quiera nullser una opción válida)
DiegoDD
37

Los argumentos opcionales solo funcionan al final de una llamada de función. No hay forma de especificar un valor para $ y en su función sin especificar también $ x. Algunos idiomas admiten esto mediante parámetros con nombre (VB / C #, por ejemplo), pero no PHP.

Puede emular esto si usa una matriz asociativa para parámetros en lugar de argumentos, es decir

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

    ...
}

Luego llame a la función así:

foo(array('y' => 'my value'));
Ryan P
fuente
1
Su condición realmente debería ser isset($args['x']), ya que actualmente reemplazaría una cadena vacía, una matriz vacía o falsecon el valor predeterminado.
Tim Cooper
@TimCooper lol Fui a cambiar en función de su primer comentario de que debería ser '=== nulo', fui y cambié mi respuesta para usar isset en su lugar, luego volví a ver que usted también cambió su comentario. : D
Ryan P
Ah, mocoso! Eso no me gusta para nada ... Bueno, no puedes tenerlo todo. Supongo que tendré que pensar un poco más en el orden de mis argumentos predeterminados. ¡Gracias!
Renosis
Desafortunadamente, no podrá asignar nulo con esta respuesta. Esta es una mejor opción: $x = !array_key_exists('x',$args) ? 'default x value' : $args['x'];
Frank Forte
20

En realidad es posible:

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

Si quieres hacerlo es otra historia :)


ACTUALIZAR:

Las razones para evitar esta solución son:

  • es (posiblemente) feo
  • Tiene una sobrecarga obvia.
  • como lo prueba la otra respuesta, hay alternativas

Pero en realidad puede ser útil en situaciones donde:

  • no quieres / no puedes cambiar la función original.

  • podrías cambiar la función pero:

    • usar null(o equivalente) no es una opción (ver el comentario de DiegoDD )
    • no quieres ir con un asociativo o con func_num_args()
    • tu vida depende de salvar un par de LOC

Sobre el rendimiento, una prueba muy simple muestra que el uso de la API de Reflection para obtener los parámetros predeterminados hace que la llamada a la función sea 25 veces más lenta , mientras que todavía toma menos de un microsegundo . Debes saber si puedes vivir con eso.

Por supuesto, si quiere usarlo en un bucle, debe obtener el valor predeterminado de antemano.

David
fuente
1
¿Cuál es la razón para no querer hacer esto? 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
fuente
2
¿Me estoy perdiendo de algo? Esto no parece responder a la pregunta, pero tiene 7 votos a favor. ¿Quizás ayudaría agregar un poco de explicación?
Sean the Bean
3
@SeantheBean: cierto, una explicación sería mejor, pero su punto es: dado que PHP no tiene argumentos con nombre, use una matriz asociativa como su único parámetro.
MestreLion
1
Es más útil usar el ejemplo dado en la pregunta, por ejemplo$defaults = [ 'x' => 'some value', 'y' => 'some other value'];
Frank Forte el
4

La única manera que sé de hacerlo es omitiendo el parámetro. La única forma de omitir el parámetro es reorganizar la lista de parámetros para que el que desea omitir sea después de los parámetros que DEBE configurar. Por ejemplo:

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

Entonces puedes llamar a foo como:

foo("blah", "test");

Esto resultará en:

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

Recientemente tuve este problema y encontré esta pregunta y respuestas. Si bien las preguntas anteriores funcionan, el problema es que no muestran los valores predeterminados a los IDE que lo admiten (como PHPStorm).

ingrese la descripción de la imagen aquí

si lo usa null, no sabrá cuál sería el valor si lo deja en blanco.

La solución que prefiero es poner el valor predeterminado en la definición de la función también:

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

La única diferencia es que necesito asegurarme de que sean iguales, pero creo que es un pequeño precio a pagar por la claridad adicional.

Yehosef
fuente
¡El uso interno de ReflectionFunction en sí mismo ahorraría el precio!
SubjectDelta
2

No puede hacer esto directamente, pero un poco de violín de código hace posible emular.

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

  // code
}
kba
fuente
66
Eso funcionaría, pero creo que usar nullen este caso es conceptualmente más ordenado que false.
TRiG
Agregar al comentario de TriG, falsees una opción más arriesgada que null, porque php es un lenguaje no estricto. !$xserá cierto para varias entradas diferentes, que pueden sorprender al programador: 0, ''. Mientras que con null, puede utilizar !issetcomo una prueba más estricta.
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

?>
Odin
fuente
1

Mis 2 centavos con operador de fusión nulo ?? (desde PHP 7)

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

Puedes consultar más ejemplos en mi repl.it

SandroMarques
fuente
0

Este es el caso, cuando los objetos son mejores, porque puede configurar su objeto para mantener x e y, configurar valores predeterminados, etc.

El enfoque con la matriz está cerca para crear el objeto (de hecho, el objeto es un grupo de parámetros y funciones que funcionarán sobre el objeto, y la función que toma la matriz funcionará sobre algunos parámetros del grupo)

Es cierto que siempre puedes hacer algunos trucos para establecer nulo o algo así como predeterminado

SergeS
fuente
0

También puede verificar si tiene una cadena vacía como argumento para que pueda llamar como:

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

Debajo de la función:

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!

}

No importa si llena nullo "", todavía obtendrá el mismo resultado.

Mustafa Ekici
fuente
0

Pase una matriz a la función, en lugar de parámetros individuales y use un operador de fusión nulo (PHP 7+).

A continuación, paso una matriz con 2 elementos. Dentro de la función, estoy verificando si el valor para el elemento 1 está configurado, si no se asigna la bóveda predeterminada.

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

    function function_name ($args) {
        isset($args['item1']) ? $args['item1'] : 'default value';
    }
Roger
fuente
Puedes usar $args['item1'] = $args['item1']??'default value'; en PHP 7+. si $ args ['item1'] es nulo, la default valuecadena se asignará a $ args ['item1']
Sadee