The talking bit

A blog about programming, mainly PHP, and maybe other things


Project maintained by franiglesias Hosted on GitHub Pages — Theme by mattgraham

Splat operator

por Fran Iglesias

Aunque está entre nosotros desde la versión 5.6 de PHP, splat operator es relativamente desconocido y su uso no deja de ser un poco confuso.

Splat operator se expresa con el símbolo ... y tiene dos utilidades principales:

  • Ayudarnos en la creación de funciones variádicas.
  • Argument unpacking o desempaquetado de argumentos.

Veamos esto más en detalle.

Funciones variádicas

La primera utilidad de splat operator tiene que ver con la creación de funciones variádicas.

Las funciones variádicas son funciones a las que podemos pasar un número variable de parámetros (variádicas, variable… lo pillas, ¿no?). No era un concepto nuevo de PHP en la versión 5.6, en la que se introdujo el operador, pero éste es ciertamente más cómodo.

La utilidad de las funciones variádicas es la de poder tener funciones o métodos que acepten una cantidad de argumentos que no podemos anticipar, con frecuencia del mismo tipo aunque no necesariamente.

Intentaré poner un par de ejemplos que se me han ocurrido, pero antes veamos qué aporta el operador al respecto de la forma tradicional de hacerlo.

Old school

Antes del splat, las funciones variádicas se hacían así, recurriendo a func_get_args():

function old_style_variadic_function ()
{
    $arguments = func_get_args();

    foreach ($arguments as $key => $argument) {
        printf('%s -> %s'.PHP_EOL, $key, $argument);
    }
}

old_style_variadic_function ('one', 'two', 'three');

Lo que da como resultado:

0 -> one
1 -> two
2 -> three

Alternativamente, puedes acceder a uno de los argumentos si conoces su posición:

function old_style_variadic_function ()
{
    printf('%s -> %s'.PHP_EOL, 1, func_get_arg(1));
}

old_style_variadic_function ('one', 'two', 'three');

Que devuelve:

1 -> two

Splat operator …

Usando el splat operator nos ahorramos un par de líneas, además de que la signatura de la función es más explícita, ya que nos indica que espera un número indefinido de argumentos:

function old_style_variadic_function (...$arguments)
{
    foreach ($arguments as $key => $argument) {
        printf('%s -> %s'.PHP_EOL, $key, $argument);
    }
}

old_style_variadic_function ('one', 'two', 'three');

El resultado es el mismo:

0 -> one
1 -> two
2 -> three

Pero es que además admite type hinting, por lo que si uno de los argumentos no es del tipo indicado la función fallará:

declare(strict_types=1);

function old_style_variadic_function (string ...$arguments)
{
    foreach ($arguments as $key => $argument) {
        printf('%s -> %s'.PHP_EOL, $key, $argument);
    }
}

old_style_variadic_function ('one', 3, 'three');

Que da como resultado un Fatal error: Uncaught TypeError. En el ejemplo he declarado tipado estricto para evitar que PHP convierta al vuelo los tipos escalares.

Ideas para usar funciones variádicas

Concatena un número indefinido de cadenas para componer textos largos de una sola tacada:

declare(strict_types=1);

function concat (string ...$arguments)
{
    return implode(PHP_EOL.PHP_EOL, $arguments);
}

print (concat(
    'Estimada amiga: ',
    'Le comunicamos que ha sido agraciada con el premio:',
    'Mejor programadora PHP del año 2018',
    'Reciba un cordial saludo.'
));

El resultado:

Estimada amiga

Le comunicamos que ha sido agraciada con el premio:

Mejor programadora PHP del año 2018

Reciba un cordial saludo.

Para pasar opciones a una función de modo que no importe ni el orden ni la cantidad de argumentos:

declare(strict_types=1);

function draw(string $shape, string ...$arguments)
{
    $result = sprintf('Draw a %s with ', $shape);

    foreach ($arguments as $key => $option) {
        switch ($option) {
            case 'border':
                $result .= sprintf(' border %s px thick', $arguments[$key+1]);
                break;
            case 'color':
                $result .= sprintf(' colored with %s', $arguments[$key+1]);
                break;
            default:
                break;
        }
    }
    $result .= PHP_EOL;
    return $result;
}

print(draw('Square', 'border', '3'));
// --> Draw a Square with  border 3 px thick

print(draw('Square', 'color', 'red'));
// --> Draw a Square with  colored with red

print(draw('Square', 'color', 'red', 'border', '1'));
// --> Draw a Square with  colored with red border 1 px thick

print(draw('Square', 'border', '2', 'color', 'green'));
// --> Draw a Square with  border 2 px thick colored with green

Si esto te suena a los argumentos posicionales de bash, estás en lo cierto.

Argument unpacking

El desempaquetado de argumentos actúa más o menos en el sentido inverso. Con el splat operator podemos hacer que un array se mapee contra los argumentos de una función. En este caso, usamos el operador para actuar sobre el array que pasamos como argumento.

declare(strict_types=1);

function pass_me_two_arguments(string $shape, string $color)
{
    return sprintf('Draw a %s with color %s.', $shape, $color). PHP_EOL;
}

print(pass_me_two_arguments('Square', 'blue'));
// Draw a Square with color blue.

print(pass_me_two_arguments(...['Triangle', 'yellow']));
// Draw a Triangle with color yellow.

Si el array tiene menos elementos que el número de argumentos se lanzará un error.

En cambio, si pasamos array con más argumentos de los necesarios, los que sobren serán ignorados. Sin embargo, me he encontrado algún ejemplo en el que también se lanza error, porque la función espera un número exacto de parámetros.

Referencias

September 6, 2018

Etiquetas: php   tips  

Temas

php good-practices blogtober19 testing refactoring tdd design-principles legacy misc design-patterns bdd tips python ddd tools soft-skills bbdd api sql ethics testing, swift javascript