Por qué funciones
En la lección 1 el sistema calcula el precio con IVA con una multiplicación suelta. Funciona mientras haya una sola pantalla. Cuando el sistema crece, esa cuenta aparece en la lista de precios, en el ticket, en el reporte de ventas y en el presupuesto que se le manda a un cliente mayorista: cuatro copias de la misma fórmula. El día que el IVA cambia —o que aparece un producto con IVA reducido— hay que acordarse de las cuatro, y siempre queda una sin tocar.
Una función resuelve eso: la regla se escribe una vez, con un nombre, y se llama desde todos lados. Si cambia, se cambia en un solo lugar.
| Sin función | Con función |
|---|---|
$precio * 1.21 repetido en cada archivo |
precioConIva($precio) |
| El 21 % es un número que aparece de la nada | El nombre dice qué se está calculando |
| Cambiar la alícuota es buscar y reemplazar en todo el proyecto | Cambiar la alícuota es cambiar una línea |
| Cada copia se puede haber escrito distinto | Todas dan el mismo resultado, siempre |
Hay una segunda ventaja, menos visible: una función se puede probar sola. Si mañana alguien duda de que el total esté bien, alcanza con llamar a la función con dos o tres precios conocidos y mirar qué devuelve, sin abrir el navegador ni la base de datos.
Definir y llamar
Una función se declara con la palabra function, un nombre, la lista de parámetros entre paréntesis y el cuerpo entre llaves:
<?php
const IVA = 0.21;
function precioConIva(float $precio): float
{
return $precio * (1 + IVA);
}
echo precioConIva(1850.50); // 2239.105
El nombre sigue la misma convención que las variables —camelCase, en minúscula inicial— y conviene que empiece con un verbo: calcularTotal, normalizarNombre, hayStock. La llave de apertura va en la línea siguiente, como pide PSR-12, y eso no es un capricho: cuando todas las funciones del proyecto se ven igual, se leen más rápido.
Para usarla se la llama por su nombre con los argumentos entre paréntesis. Esa llamada se puede hacer desde cualquier parte del archivo, incluso antes de donde está escrita la función, porque PHP primero lee todas las declaraciones y después ejecuta:
echo precioConIva(1200); // 1452
function precioConIva(float $precio): float
{
return $precio * 1.21;
}
Los nombres de las funciones no distinguen mayúsculas: precioConIva() y precioconiva() son la misma función. Aun así se escriben siempre igual, porque quien lee no lo sabe. Y no se puede declarar dos veces la misma función: si se incluye dos veces el archivo que la define, el error es fatal. Para eso existe function_exists() y, más adelante, require_once.
Parámetros
Los parámetros son los datos que la función necesita para trabajar. Se declaran con su tipo y su nombre, y son variables locales: existen solo mientras la función se está ejecutando.
Valores por defecto
Un parámetro puede traer su valor puesto. Así la función sirve para el caso general y también para el particular:
function precioConIva(float $precio, float $tasa = 0.21): float
{
return $precio * (1 + $tasa);
}
echo precioConIva(1000); // 1210
echo precioConIva(1000, 0.105); // 1105: IVA reducido
Los parámetros con valor por defecto van siempre al final de la lista. Un parámetro obligatorio después de uno opcional no tiene sentido, y PHP lo rechaza.
Argumentos con nombre
Un argumento se puede pasar indicando el nombre del parámetro, sin depender del orden. Sirve cuando la función tiene varios y solo se quiere cambiar uno:
echo precioConIva(tasa: 0.105, precio: 1000); // 1105
Es cómodo y, sobre todo, se lee: quien mira la llamada sabe qué es cada número sin abrir la función.
Cantidad variable de argumentos
Para una función que recibe una lista —los precios de un ticket, por ejemplo— se usa el operador ...:
function total(float ...$precios): float
{
return array_sum($precios);
}
echo total(1850.50, 1200, 990.75); // 4041.25
Dentro de la función, $precios es un array común. También funciona al revés: si ya se tiene un array, se lo puede desparramar con total(...$carrito).
Devolver valores
return hace dos cosas: devuelve un valor y termina la función en ese mismo instante. Todo lo que esté después no se ejecuta. Eso permite escribir las validaciones primero y el trabajo después:
function precioConIva(mixed $precio): ?float
{
if (!is_numeric($precio)) {
return null; // no es un precio: se termina acá
}
$valor = (float) $precio;
if ($valor < 0) {
return null;
}
return round($valor * 1.21, 2);
}
Ese estilo —comprobar lo que puede fallar y salir— se llama cláusula de guarda y deja el camino normal sin anidar. Es más fácil de leer que un if dentro de otro if, y es el que se usa en todo el sistema.
Qué devuelve una función que no devuelve nada
Si la función no tiene return, devuelve null. Es un detalle que causa errores silenciosos: se llama a la función esperando un resultado, el resultado es null y la cuenta sigue con cero. Para que eso no pase, se declara el tipo de retorno:
function mostrarNombre(string $nombre): void
{
echo htmlspecialchars($nombre, ENT_QUOTES, 'UTF-8');
}
void significa «no devuelve nada» y PHP avisa si se intenta usar el resultado. Si se olvidó el return en una función que declara : float, el error aparece en la definición y no tres pantallas después.
Devolver varias cosas
Una función devuelve un valor, pero ese valor puede ser un array. Es lo que se hace para armar el resumen de una venta:
function resumenVenta(array $carrito): array
{
$total = array_sum($carrito);
return [
'cantidad' => count($carrito),
'total' => round($total, 2),
'conIva' => round($total * 1.21, 2),
];
}
$resumen = resumenVenta([1850.50, 1200]);
echo $resumen['conIva']; // 3691.11
Devolver un array con claves es más claro que devolver una lista donde el segundo elemento «es» el total: quien recibe el resultado no tiene que adivinar el orden.
Ámbito y paso por valor
Las variables de una función son suyas. No ve las de afuera, y las de afuera no ven las de adentro:
$iva = 0.21; // variable global
function calcular(float $precio): float
{
return $precio * (1 + $iva); // Warning: $iva no existe acá
}
La solución no es global: es pasar lo que hace falta como parámetro. Una función que recibe todo lo que necesita se entiende, se prueba y se reutiliza sin sorpresas.
Los arrays se pasan por valor
PHP copia el argumento al entrar a la función —en realidad lo copia recién cuando se modifica, pero el efecto es el mismo—. Si la función cambia el array, el original queda intacto:
function aplicarDescuento(array $precios): array
{
foreach ($precios as $i => $precio) {
$precios[$i] = $precio * 0.9;
}
return $precios; // hay que devolverlo
}
$catalogo = [1850, 1200];
$rebajado = aplicarDescuento($catalogo);
echo $catalogo[0]; // 1850: el original no cambió
echo $rebajado[0]; // 1665
Esto es una ventaja: quien llama sabe que su array no va a cambiar por debajo. Pero obliga a devolver el resultado, que es justo lo que hace la función de arriba.
Existe la forma de modificar el original con un & delante del parámetro —function aplicarDescuento(array &$precios)—. Se usa en contadas ocasiones, como sort(), y conviene evitarla: una función que cambia sus argumentos sin devolver nada es difícil de seguir cuando el proyecto crece. Los objetos son la excepción, y eso se ve en la lección 5.
Declarar los tipos
En la lección 1 se vio declare(strict_types=1). Con eso activo, PHP deja de convertir por su cuenta y las firmas se vuelven un contrato:
<?php
declare(strict_types=1);
function precioConIva(float $precio, float $tasa = 0.21): float
{
return round($precio * (1 + $tasa), 2);
}
Además de los tipos simples, las firmas aceptan combinaciones:
| Se escribe | Significa |
|---|---|
int|float $precio | Entero o decimal. Útil para precios escritos sin centavos. |
?string $nombre | Texto o null. Es lo mismo que string|null. |
array $carrito | Un array, de cualquier forma. |
mixed $dato | Cualquier cosa. Se usa cuando de verdad puede venir cualquier cosa, y obliga a comprobar antes de operar. |
: void | No devuelve nada. |
Declarar los tipos no hace al programa más lento y no es burocracia: mueve los errores al lugar donde se producen. Sin tipos, un null que se cuela como precio explota en el total del día; con tipos, explota en la función que lo recibió, con el nombre del parámetro en el mensaje.
Funciones anónimas
Una función no tiene por qué tener nombre. Sirve para pasarla como argumento a otra función, que es exactamente lo que piden array_map, array_filter y usort:
$precios = [1850.50, 1200, 990.75];
$conDescuento = array_map(function (float $precio): float {
return round($precio * 0.9, 2);
}, $precios);
print_r($conDescuento); // [1665.45, 1080, 891.68]
La forma corta es la función flecha, que además toma sola las variables de afuera:
$descuento = 0.10;
$conDescuento = array_map(
fn (float $precio): float => round($precio * (1 - $descuento), 2),
$precios
);
En la versión con function, para usar una variable de afuera hay que declararla con use ($descuento). La flecha lo hace sola. Las dos devuelven el resultado de la expresión, sin return.
Arrays
El array es la estructura central de PHP y la que mejor representa el catálogo: una lista de productos, donde cada producto es a su vez un array con sus datos.
$catalogo = [
[
'codigo' => 1042,
'nombre' => 'Yerba mate 1 kg',
'precio' => 1850.50,
'stock' => 24,
],
[
'codigo' => 1043,
'nombre' => 'Azúcar 1 kg',
'precio' => 1200,
'stock' => 0,
],
[
'codigo' => 1044,
'nombre' => 'Fideos 500 g',
'precio' => 990.75,
'stock' => 7,
],
];
Leer, agregar y quitar
echo $catalogo[0]['nombre']; // Yerba mate 1 kg
echo count($catalogo); // 3
$catalogo[] = [ // agrega al final, con la clave siguiente
'codigo' => 1045,
'nombre' => 'Arroz 1 kg',
'precio' => 1350,
'stock' => 12,
];
$catalogo[1]['stock'] = 6; // modifica un dato de un producto
unset($catalogo[2]); // quita el tercero, pero deja el hueco
Después de un unset las claves quedan salteadas: 0, 1, 3. Para que el array vuelva a estar numerado de corrido se usa array_values($catalogo). Es un detalle que importa cuando después se accede por posición.
Preguntar si una clave existe
isset() devuelve falso también cuando la clave existe pero vale null. Si lo que se quiere saber es si la clave está, sin importar el valor, se usa array_key_exists():
$producto = ['nombre' => 'Azúcar', 'precio' => null];
var_dump(isset($producto['precio'])); // false: el valor es null
var_dump(array_key_exists('precio', $producto)); // true: la clave está
En el sistema la diferencia es real: un producto con precio pendiente tiene la clave y vale null. Con isset parecería que el dato no existe; con array_key_exists se sabe que el producto está incompleto.
Recorrer el catálogo
Un foreach dentro de otro recorre la estructura completa y arma la tabla de precios:
foreach ($catalogo as $producto) {
echo $producto['nombre'] . ': $' . number_format($producto['precio'], 2, ',', '.') . "\n";
}
Cuando además hace falta la posición —para numerar las filas—, se recorre con clave: foreach ($catalogo as $i => $producto).
Las funciones de array que se usan siempre
PHP trae más de ochenta funciones para arrays. Estas son las que aparecen todos los días en un sistema como el de la tienda:
| Función | Qué hace | Ejemplo con el catálogo |
|---|---|---|
count() | Cuenta elementos. | Cuántos productos hay. |
array_sum() | Suma los valores. | Total de un carrito de precios. |
array_map() | Aplica una función a cada elemento. | Precio con IVA de cada producto. |
array_filter() | Deja solo los que cumplen una condición. | Los que tienen stock. |
array_reduce() | Recorre y acumula un solo valor. | Total de la venta. |
in_array() | Pregunta si un valor está. | ¿La categoría está habilitada? |
array_search() | Devuelve la clave de un valor. | ¿En qué posición está el código 1044? |
array_keys() | Devuelve todas las claves. | La lista de códigos. |
array_column() | Saca una columna de un array de arrays. | Todos los nombres del catálogo. |
array_merge() | Une dos arrays. | Agregar los productos nuevos. |
array_slice() | Corta un fragmento. | La primera página del listado. |
array_unique() | Quita repetidos. | Las categorías que hay. |
Filtrar y quedarse con los que tienen stock
$disponibles = array_filter($catalogo, fn (array $p): bool => $p['stock'] > 0);
echo count($disponibles); // 2
array_filter conserva las claves originales: si los productos 0 y 2 pasaron el filtro, el resultado tiene las claves 0 y 2. Para que quede una lista numerada de nuevo hay que envolverlo: array_values(array_filter(...)). Es el detalle que más veces se olvida, y se nota cuando después se recorre con for.
Transformar y acumular
$conIva = array_map(
fn (array $p): array => [...$p, 'precio' => round($p['precio'] * 1.21, 2)],
$catalogo
);
$total = array_reduce(
$catalogo,
fn (float $acumulado, array $p): float => $acumulado + $p['precio'],
0.0
);
echo round($total, 2); // 4041.25
El ... delante de $p en el primer ejemplo es el spread de arrays: copia el array y le cambia una clave, sin escribir las demás a mano. Es la forma corta de «este producto, con el precio modificado».
Buscar: cuidado con la comparación floja
$roles = ['vendedor', 'administrador'];
var_dump(in_array('vendedor', $roles)); // true
var_dump(in_array('cliente', $roles, true)); // false
$posicion = array_search(1044, array_column($catalogo, 'codigo'));
if ($posicion === false) {
echo 'No está';
}
A in_array se le pasa true como tercer argumento para que compare con ===. Sin eso la comparación es floja, y en un control de permisos eso significa dar acceso a quien no corresponde. Y array_search devuelve false cuando no encuentra, pero también puede devolver 0 cuando lo encuentra en la primera posición: la comprobación correcta es === false, nunca == false ni !$posicion.
Ordenar
Ordenar parece simple hasta que se mezclan listas y diccionarios. La regla es una sola: ordenar cambia el array original y devuelve un booleano. No se escribe $ordenado = sort($precios): eso deja $ordenado en true y los precios dados vuelta.
| Función | Ordena por | Qué pasa con las claves |
|---|---|---|
sort() / rsort() | Valor, de menor a mayor o al revés. | Se pierden: renumera 0, 1, 2… |
asort() / arsort() | Valor. | Se conservan. |
ksort() / krsort() | Clave. | Se conservan. |
usort() | Lo que diga la función que se le pasa. | Se pierden: renumera. |
uasort() | Lo que diga la función. | Se conservan. |
Para un catálogo —un array de arrays— sort() no sirve: compararía arrays entre sí. Se usa usort() con una función que diga cuál va primero, y para eso está el operador <=>:
usort($catalogo, fn (array $a, array $b): int => $a['precio'] <=> $b['precio']);
// Del más caro al más barato
usort($catalogo, fn (array $a, array $b): int => $b['precio'] <=> $a['precio']);
El operador devuelve -1, 0 o 1, que es exactamente lo que usort espera: negativo si el primero va antes, cero si dan igual, positivo si va después. Para ordenar por un texto se usa strcmp() en lugar de <=>, porque comparar textos con el operador depende de la configuración regional.
Ordenar por dos criterios
usort($catalogo, function (array $a, array $b): int {
return [$a['stock'] > 0, $a['precio']] <=> [$b['stock'] > 0, $b['precio']];
});
Comparar arrays con <=> los compara elemento por elemento, en orden. Así quedan primero los que tienen stock y, dentro de cada grupo, del más barato al más caro. Es un truco corto que evita una cadena de if.
Strings
Los nombres de los productos llegan del proveedor como vienen: con mayúsculas sueltas, espacios de más, acentos y abreviaturas distintas. Trabajar con texto es la mitad del trabajo real de un sistema de gestión.
| Función | Qué hace | Ejemplo |
|---|---|---|
strlen() | Largo en bytes. | strlen('yerba') → 5 |
mb_strlen() | Largo en caracteres. | mb_strlen('azúcar') → 6 |
mb_strtoupper() | Pasa a mayúsculas respetando acentos. | 'AZÚCAR' |
trim() | Quita espacios de los extremos. | ' yerba ' → 'yerba' |
substr() | Corta un fragmento. | substr('1042', 0, 2) → '10' |
str_replace() | Reemplaza. | Cambiar «kg» por «kilogramo». |
explode() | Parte un texto en un array. | Una línea CSV en campos. |
implode() | Une un array en un texto. | Lista separada por comas. |
str_contains() | Pregunta si contiene un texto. | ¿El nombre dice «yerba»? |
str_pad() | Rellena hasta un largo. | Códigos de 6 dígitos. |
ucfirst() | Pone en mayúscula la primera letra. | 'yerba' → 'Yerba' |
number_format() | Formatea un número. | 1.850,50 |
El acento cuenta como dos bytes
Esta es la trampa clásica del español en PHP. Las funciones strlen, substr y strtoupper trabajan con bytes, y en UTF-8 una letra acentuada ocupa dos:
echo strlen('azúcar'); // 7, no 6
echo mb_strlen('azúcar'); // 6
echo strtoupper('azúcar'); // AZúCAR: rompe el acento
echo mb_strtoupper('azúcar'); // AZÚCAR
La misma diferencia vale para cortar texto: substr('azúcar', 0, 3) puede dejar un carácter partido al medio. Cuando el texto puede tener acentos —siempre, en un sistema en español—, se usan las funciones mb_*. Para eso tiene que estar cargada la extensión mbstring, que viene con PHP en las instalaciones habituales y se comprueba con php -m | grep mbstring.
Partir y unir
$linea = '1042;Yerba mate 1 kg;1850.50;24';
$campos = explode(';', $linea);
print_r($campos); // ['1042', 'Yerba mate 1 kg', '1850.50', '24']
echo implode(' | ', $campos); // 1042 | Yerba mate 1 kg | 1850.50 | 24
explode y implode son inversas, y son la base de la importación del catálogo que se ve en la lección 4. Ojo con el orden de los argumentos de implode: primero el separador, después el array.
Buscar y reemplazar con criterio
$nombre = 'Yerba mate 1 kg';
var_dump(str_contains($nombre, 'mate')); // true
var_dump(str_starts_with($nombre, 'Yerba')); // true
var_dump(str_ends_with($nombre, 'kg')); // true
$corto = str_replace(' 1 kg', '', $nombre); // 'Yerba mate'
str_contains, str_starts_with y str_ends_with responden verdadero o falso, que es lo que se quiere en una condición. La alternativa clásica es strpos(), que devuelve la posición o false: si se la usa, la comparación correcta es strpos(...) !== false, porque la posición 0 es un resultado válido y se evalúa como falso.
Un caso real: normalizar para buscar
El buscador del sistema tiene que encontrar «yerba» cuando alguien escribe « YERBA », «Yérba» o «Yerba Mate». Eso se resuelve con una función que deja el texto en una forma comparable, y con la decisión de comparar siempre en esa forma:
function normalizar(string $texto): string
{
$texto = trim($texto);
$texto = mb_strtolower($texto, 'UTF-8');
$texto = strtr($texto, [
'á' => 'a', 'é' => 'e', 'í' => 'i', 'ó' => 'o', 'ú' => 'u',
'ü' => 'u', 'ñ' => 'n',
]);
return preg_replace('/\s+/', ' ', $texto);
}
Con esa función, el filtro del catálogo queda en una línea:
$busqueda = normalizar($_GET['q'] ?? '');
$encontrados = array_filter(
$catalogo,
fn (array $p): bool => str_contains(normalizar($p['nombre']), $busqueda)
);
Normalizar sirve para comparar, no para mostrar: lo que se imprime en pantalla es el nombre original, escapado con htmlspecialchars. Si se guardara el nombre normalizado en la base, se perderían las mayúsculas y los acentos que el producto tiene que mostrar.
Errores frecuentes
| Lo que pasa | La causa | Cómo se arregla |
|---|---|---|
| Call to undefined function precio_con_iva() | El nombre está mal escrito o el archivo que la define no se incluyó. | Revisar el nombre y agregar el require_once. PHP no avisa de una función que no existe hasta que la llama. |
| Too few arguments to function | Falta un parámetro obligatorio. | Pasar todos los argumentos, o darle un valor por defecto al que es opcional. |
La función devuelve null y la cuenta da 0 |
Falta el return. |
Declarar el tipo de retorno (: float): así el error aparece en la función. |
| El array cambia afuera de la función | Se pasó por referencia con & sin querer, o se modificó una propiedad de un objeto. |
Quitar el &. Para los arrays, devolver el resultado. |
| Undefined array key | Se lee una clave que no está: $p['precio'] en un producto incompleto. |
$p['precio'] ?? null, y comprobar antes de operar. |
Después de filtrar, el for saltea elementos |
array_filter conserva las claves originales. |
array_values(array_filter(...)). |
| Se pierden los nombres al ordenar | Se usó sort() en un array asociativo. |
asort() conserva las claves; usort() para arrays de arrays. |
in_array encuentra cosas que no debería |
Comparación floja: "1" coincide con 1. |
in_array($valor, $lista, true). |
| Array to string conversion al imprimir | Se hizo echo de un array. |
implode() o print_r(), según qué se quiera ver. |
| El largo de un nombre con acento no coincide | strlen cuenta bytes. |
mb_strlen y el resto de las mb_*. |
Un hábito que ahorra la mitad de estos errores: escribir la función pensando en el caso borde antes que en el caso normal. Qué pasa si el array viene vacío, si el texto es null, si el precio es cero. Casi todos los errores de esta tabla son un caso borde que nadie probó.
Ejercicios
Nueve ejercicios sobre el catálogo de la tienda, resolubles con lo visto en esta página. Los datos de partida son estos:
$catalogo = [
['codigo' => 1042, 'nombre' => 'Yerba mate 1 kg', 'precio' => 1850.50, 'stock' => 24],
['codigo' => 1043, 'nombre' => 'Azúcar 1 kg', 'precio' => 1200, 'stock' => 0],
['codigo' => 1044, 'nombre' => 'Fideos 500 g', 'precio' => 990.75, 'stock' => 7],
['codigo' => 1045, 'nombre' => 'Arroz 1 kg', 'precio' => 1350, 'stock' => 3],
];
1. La función del IVA
Escribir precioConIva() que reciba el precio y una tasa opcional, devuelva el precio final redondeado a dos decimales y devuelva null si el precio no es numérico o es negativo.
2. Clasificar el stock
Escribir estadoStock() que devuelva 'sin stock', 'últimas unidades' o 'disponible', con un umbral de 5 unidades, usando cláusulas de guarda.
3. Paso por valor
Escribir una función que aplique un 10 % de descuento a un array de precios. Llamarla y comprobar que el array original no cambió. Después escribir la versión que sí lo modifica y explicar cuál conviene.
4. Descuento con función flecha
Usando array_map y una función flecha, obtener la lista de precios con un descuento del 15 %.
5. Filtrar disponible y barato
Obtener los productos con stock mayor que cero y precio menor a 1500, ordenados de menor a mayor precio.
6. Ordenar por nombre
Ordenar el catálogo alfabéticamente por nombre y mostrar solo los nombres, uno por línea.
7. Buscar sin acentos ni mayúsculas
Escribir normalizar() y usarla para encontrar los productos que contengan «azucar» escritos de cualquier forma.
8. Importar una línea
Convertir la línea '1046;Aceite 900 ml;2450.00;8' en un array asociativo con las claves codigo, nombre, precio y stock, con los tipos correctos.
9. Buscar los errores
El siguiente código intenta mostrar el total del stock valorizado y tiene cinco errores. Encontrarlos y corregirlos:
<?php
$catalogo = [
['nombre' => 'Yerba', 'precio' => 1850.50, 'stock' => 24],
['nombre' => 'Azúcar', 'precio' => 1200, 'stock' => 0],
];
$ordenado = sort($catalogo)
foreach ($ordenado as $p) {
$total = $total + $p['precio'] * $p['stock'];
}
echo "Total: " . $total;
echo "Productos: " . $catalogo;
echo "Primero: " . $catalogo[0][nombre];
Respuestas sugeridas
1.
function precioConIva(mixed $precio, float $tasa = 0.21): ?float
{
if (!is_numeric($precio)) {
return null;
}
$valor = (float) $precio;
if ($valor < 0) {
return null;
}
return round($valor * (1 + $tasa), 2);
}
echo precioConIva(1850.50); // 2239.11
echo precioConIva(1000, 0.105); // 1105
var_dump(precioConIva('mucho')); // NULL
El tipo de retorno ?float obliga a quien llama a contemplar el null. Si se declarara : float, la función no podría devolver null y habría que elegir otra estrategia, como lanzar una excepción, que se ve en la lección 4.
2.
function estadoStock(int $stock): string
{
if ($stock <= 0) {
return 'sin stock';
}
if ($stock < 5) {
return 'últimas unidades';
}
return 'disponible';
}
Las guardas van de menor a mayor: primero se descarta el caso imposible, después el urgente, y lo que queda es el normal. Sin guardas habría que anidar un if dentro de otro.
3.
function conDescuento(array $precios): array
{
return array_map(fn (float $p): float => round($p * 0.9, 2), $precios);
}
function aplicarDescuento(array &$precios): void
{
foreach ($precios as $i => $precio) {
$precios[$i] = round($precio * 0.9, 2);
}
}
$precios = [1850.50, 1200];
$rebajados = conDescuento($precios);
echo $precios[0]; // 1850.5: no cambió
echo $rebajados[0]; // 1665.45
Conviene la primera: devuelve un array nuevo y deja claro en la firma que el original no se toca. La segunda modifica el argumento y no devuelve nada, así que quien lee la llamada no sabe que sus datos cambiaron.
4.
$descuento = 0.15;
$rebajados = array_map(
fn (array $p): array => [...$p, 'precio' => round($p['precio'] * (1 - $descuento), 2)],
$catalogo
);
La función flecha toma $descuento del ámbito de afuera sin necesidad de use. El ... copia todos los datos del producto y solo reemplaza el precio.
5.
$seleccion = array_values(array_filter(
$catalogo,
fn (array $p): bool => $p['stock'] > 0 && $p['precio'] < 1500
));
usort($seleccion, fn (array $a, array $b): int => $a['precio'] <=> $b['precio']);
foreach ($seleccion as $p) {
echo $p['nombre'] . "\n"; // Fideos 500 g, Arroz 1 kg
}
array_values es necesario porque array_filter deja las claves 2 y 3, y después se recorre con foreach sin problema, pero cualquier acceso por posición daría error.
6.
usort($catalogo, fn (array $a, array $b): int => strcmp($a['nombre'], $b['nombre']));
foreach ($catalogo as $p) {
echo $p['nombre'] . "\n";
}
El orden queda Arroz, Azúcar, Fideos, Yerba. Con strcmp el orden no depende de la configuración regional del servidor, que es lo que pasaría usando < o <=> entre textos.
7.
function normalizar(string $texto): string
{
$texto = trim($texto);
$texto = mb_strtolower($texto, 'UTF-8');
return strtr($texto, [
'á' => 'a', 'é' => 'e', 'í' => 'i', 'ó' => 'o', 'ú' => 'u',
'ü' => 'u', 'ñ' => 'n',
]);
}
$busqueda = normalizar('AZUCAR');
$encontrados = array_filter(
$catalogo,
fn (array $p): bool => str_contains(normalizar($p['nombre']), $busqueda)
);
Así «AZUCAR», «azúcar» y « Azúcar » encuentran el mismo producto. La clave está en normalizar los dos lados de la comparación: normalizar solo lo que escribe el usuario no alcanza.
8.
$linea = '1046;Aceite 900 ml;2450.00;8';
[$codigo, $nombre, $precio, $stock] = explode(';', $linea);
$producto = [
'codigo' => (int) $codigo,
'nombre' => trim($nombre),
'precio' => (float) $precio,
'stock' => (int) $stock,
];
explode devuelve todo como texto, porque el texto no sabe de tipos. La conversión es parte de la importación: si se deja el precio como '2450.00', la primera multiplicación lo va a convertir sola, pero cualquier comparación con === va a fallar.
9. Los errores son: falta el punto y coma después de sort($catalogo); sort() ordena en el lugar y devuelve true, así que $ordenado es un booleano y el foreach sobre un booleano no recorre nada; $total no está inicializado, así que PHP avisa y arranca en null; se hace echo de un array, que produce «Array to string conversion»; y $catalogo[0][nombre] va sin comillas, que es un error fatal por constante no definida. La versión corregida:
<?php
declare(strict_types=1);
$catalogo = [
['nombre' => 'Yerba', 'precio' => 1850.50, 'stock' => 24],
['nombre' => 'Azúcar', 'precio' => 1200, 'stock' => 0],
];
$total = 0.0;
foreach ($catalogo as $p) {
$total += $p['precio'] * $p['stock'];
}
echo 'Total: ' . number_format($total, 2, ',', '.');
echo 'Productos: ' . count($catalogo);
echo 'Primero: ' . htmlspecialchars($catalogo[0]['nombre'], ENT_QUOTES, 'UTF-8');
Para leer más
- Funciones, con las reglas de parámetros y valores por defecto.
- Arrays: el índice completo, ordenado por tema.
- Funciones de strings y multibyte: las dos tablas que se consultan todo el tiempo.
Resumen
| Tema | Lo que hay que retener |
|---|---|
| Funciones | Una regla se escribe una vez. Nombre con verbo, parámetros tipados, tipo de retorno declarado. |
| Parámetros | Valores por defecto al final, argumentos con nombre y ...$lista para cantidad variable. |
| Retorno | return corta la función. Sin return devuelve null. Las guardas evitan anidar. |
| Ámbito | Los arrays se pasan por valor: si la función los modifica, hay que devolverlos. Evitar &. |
| Arrays | array_filter conserva claves; sort pierde claves; in_array y array_search van con true y === false. |
| Strings | Con acentos, siempre mb_*. explode e implode para importar y exportar. |
| Normalizar | Se normaliza para comparar y buscar; se muestra el original, escapado. |
En la próxima lección el sistema deja de mostrar datos escritos a mano: aparece el primer formulario, el login del vendedor y la sesión que recuerda quién está trabajando.
Licencia. El material de esta página es elaboración propia de Prof Mauro Bobyk —a partir de las fuentes que detalla el pie del sitio— y se publica bajo una licencia Creative Commons BY-NC-SA 4.0.