Hoy traigo un artículo algo diferente. No tanto por la dificultad del problema, sino porque es uno de esos casos que aparecen trabajando con código real y que pueden tenerte unas cuantas horas dando vueltas hasta encontrar una solución razonable.
El escenario es sencillo: tenemos un formulario y necesitamos enviar a PHP tanto el ID de una categoría como su nombre para poder trabajar posteriormente con esos datos, por ejemplo, realizando una inserción o actualización en la base de datos.
El problema aparece cuando las opciones del <select> se generan dinámicamente desde una consulta a la base de datos.
Tenemos algo parecido a esto:
<select name="tipoP">
<?php
$query = "SELECT idTipo, tipoProducto FROM tipoproducto";
$data = $producto->prepare($query);
$data->execute();
while ($row = $data->fetch(PDO::FETCH_ASSOC)) {
echo '<option value="' . htmlspecialchars($row['idTipo']) . '">'
. htmlspecialchars($row['tipoProducto'])
. '</option>';
}
?>
</select>
En este caso el usuario verá el nombre de la categoría, pero el formulario enviará únicamente su ID.
Y aquí aparece la pregunta:
¿Y si necesito enviar también el nombre de la categoría?
Podemos hacerlo sin necesidad de construir una cadena y posteriormente intentar averiguar dónde termina el nombre y dónde comienza el ID.
Una solución más limpia: atributos data-*
HTML permite añadir información adicional a cualquier elemento mediante los atributos personalizados data-*.
Podemos aprovecharlo para guardar el ID y el nombre de la categoría dentro de cada opción:
<select name="tipoP" id="tipoP">
<?php
$query = "SELECT idTipo, tipoProducto FROM tipoproducto";
$data = $producto->prepare($query);
$data->execute();
while ($row = $data->fetch(PDO::FETCH_ASSOC)) {
$idTipo = (int) $row['idTipo'];
$tipoProducto = htmlspecialchars($row['tipoProducto'], ENT_QUOTES, 'UTF-8');
echo '<option
value="' . $idTipo . '"
data-categoria="' . $tipoProducto . '"
>'
. $tipoProducto .
'</option>';
}
?>
</select>
Ahora tenemos algo mucho más claro.
El usuario seguirá viendo únicamente el nombre de la categoría, mientras que internamente cada opción contiene también su ID:
<option value="23" data-categoria="Ruedas Chinas">
Ruedas Chinas
</option>
El value contiene el ID y el atributo data-categoria contiene el nombre.
¿Y cómo recuperamos ambos valores?
Aquí podemos utilizar JavaScript para obtener los dos valores de la opción seleccionada.
const select = document.getElementById('tipoP');
select.addEventListener('change', function () {
const opcion = this.options[this.selectedIndex];
const idTipo = opcion.value;
const tipoCategoria = opcion.dataset.categoria;
console.log(idTipo);
console.log(tipoCategoria);
});
Si el usuario selecciona:
Ruedas Chinas
obtendremos:
idTipo: 23 tipoCategoria: Ruedas Chinas
Ya tenemos ambos valores separados y sin necesidad de hacer ningún preg_replace() para intentar reconstruir posteriormente la información.
¿Y si necesito enviar ambos valores mediante el formulario?
En ese caso podemos utilizar un campo oculto para almacenar el nombre de la categoría seleccionada.
<select name="tipoP" id="tipoP">
<option value="23" data-categoria="Ruedas Chinas">
Ruedas Chinas
</option>
<option value="24" data-categoria="Neumáticos">
Neumáticos
</option>
</select>
<input type="hidden" name="tipoCategoria" id="tipoCategoria">
Y actualizamos el campo oculto cuando cambie la selección:
const select = document.getElementById('tipoP');
const categoria = document.getElementById('tipoCategoria');
select.addEventListener('change', function () {
const opcion = this.options[this.selectedIndex];
categoria.value = opcion.dataset.categoria;
});
De esta forma, al enviar el formulario, PHP recibirá ambos valores:
$idTipo = $_POST['tipoP']; $tipoCategoria = $_POST['tipoCategoria'];
Y podremos trabajar con ellos independientemente.
¿Qué hacía el código original?
En el código original intentaba resolver el problema introduciendo ambos datos dentro del mismo value.
Por ejemplo:
Ruedas Chinas23
Después, una vez recibido el valor mediante $_POST, había que separar el texto y el número utilizando expresiones regulares:
$split_str = $_POST['tipoP'];
$idTipo = intval(
preg_replace('/[^0-9]+/', '', $split_str)
);
$tipoCategoria = strval(
preg_replace('/[^a-z A-Z áéíóúÁÉÍÓÚñÑ.]+/iu', ' ', $split_str)
);
La idea funciona en determinados escenarios, pero tiene un problema evidente: estamos intentando deducir la estructura de los datos a partir de una cadena.
¿Qué ocurre si una categoría contiene números?
Ruedas 4x4 23
¿El 4 forma parte del nombre o es el ID?
Ahí empezamos a tener un problema que realmente no necesitábamos tener.
Separar los datos desde el principio
Esta es la idea importante que me llevo de este caso.
Si tenemos dos datos diferentes, lo más limpio es mantenerlos como dos datos diferentes.
No tiene demasiado sentido convertir esto:
ID + nombre
en:
"Ruedas Chinas23"
para después tener que volver a separarlo.
Es mucho más sencillo mantener una estructura explícita:
ID: 23 Categoría: Ruedas Chinas
Y precisamente para eso tenemos herramientas como los atributos data-* de HTML.
Un detalle importante: el ID es el dato realmente importante
Hay además una consideración que conviene tener en cuenta cuando trabajamos con bases de datos.
Normalmente no necesitamos enviar el nombre de la categoría al servidor para identificarla. El dato realmente importante es su ID.
Por ejemplo:
<option value="23">Ruedas Chinas</option>
El usuario ve:
Ruedas Chinas
pero PHP recibe:
23
Con ese ID, el servidor puede consultar la categoría correspondiente en la base de datos.
Esto es especialmente importante si el nombre de la categoría cambia. El ID seguirá identificando el mismo registro.
No confíes en los datos enviados por el navegador
Aunque el formulario muestre un nombre determinado, cualquier dato enviado por el cliente puede ser manipulado. Si el ID tiene importancia para una operación de base de datos, conviene validarlo y comprobarlo en el servidor antes de utilizarlo.
Conclusión
El problema que planteaba este artículo era bastante concreto: necesitaba trabajar con el ID y el nombre de una categoría que se generaba dinámicamente dentro de un <select>.
La primera solución consistía en concatenar ambos valores y separarlos posteriormente mediante expresiones regulares.
Hoy lo plantearía de una forma diferente: mantener cada dato separado desde el principio.
Para un formulario sencillo, probablemente ni siquiera necesitaría enviar el nombre. Utilizaría el ID como value y dejaría que PHP obtuviera el resto de información desde la base de datos.
Porque al final, cuando el código empieza a crecer, una buena solución no es únicamente conseguir que funcione.
También consiste en evitar problemas que realmente no necesitábamos crear.
¡A picar con cabeza!
Visitas: 554
Automation & Data Specialist | Web Development | Data Processing & Integration
