<?php
/**
* La cadena vacía en PHP: usos correctos, antipatrones y alternativas
*
* PHP trata "" como un string válido en casi todos los contextos, pero
* eso no significa que sea el valor correcto en todos ellos. Este archivo
* recorre los siete usos más comunes algunos legítimos, otros peligrosos
* y muestra la alternativa correcta en cada caso.
*/
declare(strict_types=1);
// ============================================================
// 1. VALOR POR DEFECTO EN PROPIEDADES Y PARÁMETROS
// ============================================================
// CORRECTO: "" como default cuando el campo es siempre un string
// y "vacío" tiene sentido de negocio (p.ej. un nickname opcional).
class UserConDefault
{
public string $nickname = '';
public function hasNickname(): bool
{
return $this->nickname !== '';
}
}
// MEJOR cuando necesitas distinguir "nunca asignado" de "vacío deliberado".
// Si el usuario no rellenó el campo, $nickname es null.
// Si lo dejó en blanco a propósito, $nickname es ''.
class UserConNullable
{
public ?string $nickname = null;
public function hasNickname(): bool
{
return $this->nickname !== null && $this->nickname !== '';
}
}
// Mismo criterio en parámetros de función:
function saludar(string $nombre = ''): string
{
return $nombre !== '' ? "Hola, $nombre." : 'Hola, desconocido.';
}
function saludarNullable(?string $nombre = null): string
{
if ($nombre === null) {
return 'Nombre no proporcionado.';
}
return $nombre !== '' ? "Hola, $nombre." : 'El nombre está vacío.';
}
echo saludar(); // Hola, desconocido.
echo saludar('Ana'); // Hola, Ana.
echo saludarNullable(); // Nombre no proporcionado.
echo saludarNullable(''); // El nombre está vacío.
// ============================================================
// 2. CENTINELA DE ERROR: "" PARA INDICAR FALLO ANTIPATRÓN
// ============================================================
// ANTIPATRÓN: la función devuelve "" cuando falla y el token real
// cuando tiene éxito. Pero un token puede ser legítimamente vacío,
// o el desarrollador puede olvidar comprobar el resultado.
function extraerToken_mal(string $header): string
{
if (!str_contains($header, 'Bearer ')) {
return ''; // ¿fallo o token vacío válido?
}
return substr($header, 7);
}
$token = extraerToken_mal('Basic abc123');
if ($token === '') {
// Ambigüedad: ¿la cabecera no tenía Bearer, o el token era ''?
}
// CORRECTO: lanzar excepción cuando la entrada es inválida,
// o devolver null para indicar "ausencia de resultado".
function extraerToken(string $header): ?string
{
if (!str_contains($header, 'Bearer ')) {
return null; // explícito: no hay token
}
$token = substr($header, 7);
return $token !== '' ? $token : null;
}
$token = extraerToken('Authorization: Bearer eyJ0...');
if ($token === null) {
// Aquí sí sabemos con certeza que no había token.
}
// ============================================================
// 3. CENTINELA DE ÉXITO INVERTIDO: "" = OK ANTIPATRÓN
// ============================================================
// ANTIPATRÓN: la función devuelve "" si todo está bien
// y el mensaje de error si algo falla. Semántica invertida.
function validarEmail_mal(string $email): string
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return 'Email no válido.';
}
return ''; // silencio = éxito, confuso para el lector
}
$error = validarEmail_mal($input ?? '');
if ($error !== '') {
echo $error;
}
// CORRECTO: separar el resultado booleano del mensaje de error.
// Opción A bool + parámetro por referencia para el mensaje:
function validarEmail(string $email, string &$error = ''): bool
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = 'Email no válido.';
return false;
}
return true;
}
if (!validarEmail($input ?? '', $error)) {
echo $error;
}
// Opción B lanzar excepción (preferible en flujos críticos):
function validarEmailOException(string $email): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Email no válido: $email");
}
}
try {
validarEmailOException('no-es-un-email');
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
}
// ============================================================
// 4. CAST IMPLÍCITO CON '' . $valor FRÁGIL
// ============================================================
// ANTIPATRÓN: usar "" como operador de conversión de tipo.
// Con objetos sin __toString() lanza un TypeError en PHP 8+.
class SinToString {}
$obj = new SinToString();
// $texto = '' . $obj; // TypeError: Object of class SinToString could not be converted to string
// Con valores primitivos funciona, pero es opaco:
$numero = 42;
$texto = '' . $numero; // '42' funciona pero no es obvio
// CORRECTO: cast explícito siempre.
$texto = (string) $numero; // '42'
$texto = (string) null; // ''
$texto = (string) false; // ''
$texto = (string) true; // '1'
// Con objetos: garantizar que implementan Stringable o usar un método dedicado.
class Producto implements Stringable
{
public function __construct(private string $nombre) {}
public function __toString(): string
{
return $this->nombre;
}
}
$p = new Producto('Teclado');
echo (string) $p; // 'Teclado'
// ============================================================
// 5. ACUMULADOR EN LOOP CON .= INEFICIENTE
// ============================================================
$items = ['manzana', 'pera', 'naranja', 'kiwi'];
// ANTIPATRÓN: concatenar en cada iteración.
// PHP crea una nueva cadena en memoria en cada vuelta.
$resultado = '';
foreach ($items as $item) {
$resultado .= strtoupper($item) . ', ';
}
echo rtrim($resultado, ', '); // hay que limpiar la coma final a mano
// CORRECTO: acumular en array y unir al final.
// Una sola operación de memoria, separador explícito, sin rtrim.
$partes = [];
foreach ($items as $item) {
$partes[] = strtoupper($item);
}
echo implode(', ', $partes); // MANZANA, PERA, NARANJA, KIWI
// BONUS: para salidas HTML complejas con lógica condicional dentro del loop,
// ob_start() evita la concatenación sin perder claridad:
ob_start();
foreach ($items as $i => $item) {
if ($i > 0) {
echo ', ';
}
echo '' . htmlspecialchars(strtoupper($item)) . '';
}
$html = ob_get_clean();
echo $html;
// ============================================================
// 6. COMPARACIONES PELIGROSAS CON == TRAMPA CLÁSICA
// ============================================================
// PHP tiene comparación débil (==) y estricta (===).
// Con "" los resultados de == sorprenden:
var_dump('' == false); // true "" es falsy
var_dump('' == null); // true "" es falsy
var_dump('' == 0); // false en PHP 8 (era true en PHP 7!)
var_dump('' == '0'); // false
var_dump('' == []); // false
// Tabla de valores que == trata como iguales a "":
// "" == false ? true
// "" == null ? true
// "" == 0 ? FALSE en PHP 8 (cambio importante respecto a PHP 7)
// REGLA: usar siempre === cuando se compara con strings.
var_dump('' === false); // false correcto
var_dump('' === null); // false correcto
var_dump('' === 0); // false correcto
var_dump('' === ''); // true el único caso esperado
// Ejemplo real donde == falla silenciosamente:
function buscarUsuario_mal(string $id): ?string
{
$usuarios = ['admin' => 'Ana', '' => 'Fantasma'];
return $usuarios[$id] ?? null;
}
$id_recibido = null; // viene del request sin validar
// Con ==, null == '' es true, por lo que podrías servir el usuario 'Fantasma'.
// Con ===, null !== '', y el acceso se deniega correctamente.
if ($id_recibido === '') {
echo 'ID vacío';
}
// ============================================================
// 7. FUNCIONES QUE NO ACEPTAN CADENA VACÍA
// ============================================================
// ord() necesita exactamente un carácter.
// Desde PHP 8.5 emite E_WARNING con "".
// En versiones anteriores devolvía 0 sin aviso.
// ANTIPATRÓN:
$codigo = ord(''); // Warning en PHP 8.5+, resultado 0 (falso negativo)
// CORRECTO: validar antes de llamar.
function codigoAscii(string $char): int
{
if (strlen($char) !== 1) {
throw new LengthException(
'Se esperaba exactamente un carácter, recibido: ' . strlen($char)
);
}
return ord($char);
}
echo codigoAscii('A'); // 65
// codigoAscii(''); // LengthException
// Otras funciones que asumen string no vacío:
// - mb_substr_count('', 'a') ? 0 (no falla, pero el resultado puede confundir)
// - preg_match('/^.+$/', '') ? 0 (no coincide, resultado correcto)
// - base64_decode('') ? '' (silencioso, pero ¿esperabas eso?)
// - json_decode('') ? null + JSON_ERROR_SYNTAX (falla silenciosamente sin verificar json_last_error())
// PATRÓN DEFENSIVO para cualquier función que asuma string no vacío:
function procesarCadena(string $input): string
{
if ($input === '') {
throw new InvalidArgumentException('La cadena no puede estar vacía.');
}
// ... lógica real ...
return strtoupper($input);
}
La cadena vacía en PHP: usos correctos, antipatrones y alternativas
La cadena vacía ("") en PHP tiene más usos de los que parece, y no todos son correctos. Este tutorial recorre siete situaciones reales valor por defecto, centinela de error, cast implícito, acumulador en loop, comparaciones con ==, funciones que no la aceptan y muestra para cada una el antipatrón habitual y la alternativa limpia con código listo para copiar.
Descargar adjuntos
COMPARTE ESTE TUTORIAL
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP