Módulo 02: Sintaxis, variables y tipos
Objetivos del módulo
- Usar correctamente las etiquetas de PHP y saber cuándo omitir la de cierre
- Escribir instrucciones y comentarios (incluidos los bloques PHPDoc)
- Mostrar información con
echoyprint, y mezclar PHP con HTML - Crear variables siguiendo las reglas y convenciones de nombres
- Distinguir la asignación por valor de la asignación por referencia
- Definir constantes con
define()yconst, y usar las constantes mágicas - Conocer todos los tipos de datos de PHP y cómo inspeccionarlos
- Entender las conversiones de tipo automáticas y explícitas (y sus sorpresas)
- Activar
declare(strict_types=1)y usar declaraciones de tipo modernas - Diferenciar
isset(),empty()eis_null() - Comprender el ámbito de las variables
1. Las etiquetas de PHP
Un fichero .php puede contener HTML normal y bloques de código PHP. El intérprete solo ejecuta lo que está entre las etiquetas de PHP; todo lo demás lo envía tal cual a la salida.
<p>Esto es HTML y sale tal cual.</p>
<?php
echo '<p>Esto lo genera PHP.</p>';
?>
<p>Y esto vuelve a ser HTML.</p>
| Etiqueta | Significado | ¿Usarla? |
|---|---|---|
<?php ... ?> | Bloque de código PHP | ✅ Siempre |
<?= expresión ?> | Atajo de <?php echo expresión; ?> | ✅ En plantillas HTML |
<? ... ?> | Etiqueta corta (short open tag) | ❌ Depende de la configuración short_open_tag; no es portable |
La etiqueta de impresión <?=
Es la forma más limpia de insertar un valor en HTML:
<?php $usuario = 'Lucía'; ?>
<!-- Forma larga -->
<p>Bienvenida, <?php echo $usuario; ?></p>
<!-- Forma corta, equivalente -->
<p>Bienvenida, <?= $usuario ?></p>
💡 Consejo: Dentro de
<?= ?>no hace falta punto y coma: la etiqueta de cierre ya termina la instrucción.
Ficheros que solo contienen PHP: sin etiqueta de cierre
Cuando un fichero contiene únicamente código PHP (una clase, funciones, configuración…), la norma profesional es no poner la etiqueta de cierre ?>:
<?php
declare(strict_types=1);
function saludar(string $nombre): string
{
return "Hola, $nombre";
}
// Fin del fichero: SIN ?>
⚠️ Importante: ¿Por qué omitirla? Cualquier espacio o salto de línea que quede después de
?>se envía al navegador como salida. Si después intentas enviar una cabecera HTTP (conheader(), al iniciar una sesión o al poner una cookie), PHP dará el error “Cannot modify header information - headers already sent”. Sin?>el problema desaparece. Lo recomiendan los estándares PSR y lo hacen todos los frameworks.
2. Instrucciones y mayúsculas
El punto y coma
Cada instrucción termina en punto y coma ;. Olvidarlo es el error más común al empezar:
<?php
$precio = 10 // ← falta el ;
echo $precio;
PHP Parse error: syntax error, unexpected token "echo" in index.php on line 4
Fíjate en que PHP señala la línea 4, aunque el error está en la 3: el intérprete descubre el fallo al encontrar algo inesperado después. Cuando un error de sintaxis no tenga sentido en la línea indicada, mira la línea anterior.
¿Distingue mayúsculas y minúsculas?
| Elemento | ¿Distingue mayúsculas? | Ejemplo |
|---|---|---|
| Variables | ✅ Sí | $nombre y $Nombre son variables distintas |
| Constantes | ✅ Sí | IVA e iva son distintas |
| Palabras clave | ❌ No | echo, ECHO y Echo funcionan igual |
| Funciones | ❌ No | strlen() y STRLEN() llaman a la misma función |
| Clases | ❌ No | DateTime y datetime son la misma clase |
💡 Consejo: Aunque PHP te deje escribir
ECHOoStrLen(), escribe siempre en minúsculas las palabras clave y las funciones nativas, y respeta el nombre exacto de clases y funciones. Es lo que esperan los demás programadores (y las herramientas de análisis).
3. Comentarios
Los comentarios son texto que el intérprete ignora. Sirven para explicar el porqué del código.
<?php
// Comentario de una línea (el más habitual)
# También es un comentario de una línea (estilo shell, poco usado)
/*
* Comentario de varias líneas.
* Útil para explicaciones largas o para desactivar
* temporalmente un bloque de código.
*/
$total = 100; // También pueden ir al final de una línea
⚠️ Importante: Desde PHP 8,
#[inicia un atributo (como#[\Override]o#[\NoDiscard]), no un comentario. Nunca empieces un comentario con#[.
Comentarios de documentación: PHPDoc
Un bloque que empieza por /** (dos asteriscos) es un bloque PHPDoc. Lo leen los editores (Intelephense), los generadores de documentación y las herramientas de análisis estático como PHPStan:
<?php
declare(strict_types=1);
/**
* Calcula el precio final aplicando el IVA.
*
* @param float $base Precio sin impuestos.
* @param float $tipoIva Tipo de IVA en tanto por uno (0.21 = 21 %).
*
* @return float Precio con IVA, redondeado a 2 decimales.
*/
function precioConIva(float $base, float $tipoIva = 0.21): float
{
return round($base * (1 + $tipoIva), 2);
}
/** @var list<string> $ciudades Lista de ciudades sin claves */
$ciudades = ['Madrid', 'Sevilla', 'Bilbao'];
💡 Consejo: Con las declaraciones de tipo modernas, muchos
@paramy@returnsobran. Usa PHPDoc para lo que el tipo no dice: qué significa el valor, sus unidades, o tipos más precisos comolist<string>oarray<string, int>que PHP todavía no puede expresar por sí mismo.
4. Mostrar información: echo y print
echo
echo envía uno o varios valores a la salida. No es una función, sino una construcción del lenguaje, por eso no necesita paréntesis:
<?php
echo 'Hola';
echo 'Hola', ' ', 'mundo', PHP_EOL; // Varios argumentos separados por comas
echo 'Total: ' . 42 . PHP_EOL; // Concatenando con el operador .
print
print hace casi lo mismo, pero solo admite un argumento y devuelve siempre 1, lo que le permite usarse dentro de expresiones (algo que en la práctica casi nunca se hace):
<?php
print 'Hola mundo' . PHP_EOL;
$resultado = print ''; // $resultado vale 1
| Característica | echo | print |
|---|---|---|
| Número de argumentos | Uno o varios (separados por comas) | Solo uno |
| Valor de retorno | Ninguno | Siempre 1 |
| ¿Se puede usar en una expresión? | No | Sí |
| Velocidad | Ligeramente más rápido | Prácticamente igual |
| Uso habitual | ✅ El estándar | Poco frecuente |
💡 Consejo: Cuando uses
var_dump()oprint_r()en el navegador, envuélvelos en<pre>para que se respeten los saltos de línea:echo '<pre>'; var_dump($datos); echo '</pre>';. Con Xdebug en mododevelop,var_dump()ya sale formateado.
Saltos de línea: PHP_EOL frente a <br>
- En la terminal, un salto de línea es
"\n"(oPHP_EOL). - En el navegador, los saltos de línea del código se ignoran: necesitas
<br>o etiquetas de bloque como<p>.
Mezclar PHP y HTML
La gran fortaleza de PHP es poder “entrar y salir” del HTML. Patrón recomendado: primero la lógica (arriba, en PHP) y después la presentación (abajo, HTML con <?= ?>):
<?php
declare(strict_types=1);
// 1. Lógica: preparamos los datos
$tienda = 'Librería Cervantes';
$producto = 'El Quijote (edición ilustrada)';
$precio = 24.90;
$enOferta = true;
$precioFinal = $enOferta ? $precio * 0.8 : $precio;
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title><?= $tienda ?></title>
</head>
<body>
<!-- 2. Presentación: solo mostramos -->
<h1><?= $tienda ?></h1>
<article>
<h2><?= $producto ?></h2>
<p>Precio: <?= number_format($precioFinal, 2, ',', '.') ?> €</p>
<?php if ($enOferta): ?>
<p><strong>¡20 % de descuento!</strong></p>
<?php endif; ?>
</article>
</body>
</html>
⚠️ Importante: En este ejemplo los datos los escribimos nosotros. Cuando un dato venga del usuario (un formulario, la URL), habrá que escaparlo con
htmlspecialchars()antes de mostrarlo para evitar ataques XSS. Lo verás en los módulos 06, 08 y 16.
5. Variables
📘 Concepto: Una variable es una caja con nombre donde guardas un valor para usarlo después. En PHP, el nombre de toda variable empieza por el símbolo
$. No hace falta declararla antes: la variable nace la primera vez que le asignas un valor.
<?php
$nombre = 'Carlos'; // Texto (string)
$edad = 34; // Número entero (int)
$altura = 1.78; // Número decimal (float)
$esSocio = true; // Booleano (bool)
echo "$nombre tiene $edad años" . PHP_EOL; // Carlos tiene 34 años
$edad = 35; // Se puede cambiar el valor...
$edad = 'treinta y cinco'; // ...e incluso el tipo (tipado dinámico)
Reglas para los nombres
| Regla | Válido | No válido |
|---|---|---|
Empieza por $ | $total | total |
Después de $, una letra o _ | $_temporal, $precio | $1precio |
Luego letras, números o _ | $precio2, $precio_final | $precio-final, $precio final |
| Distingue mayúsculas | $total ≠ $Total | — |
$this está reservada | — | $this = 5; |
💡 Consejo: PHP admite tildes y eñes en los nombres (
$añofunciona), pero evítalas: pueden dar problemas con codificaciones y herramientas, y dificultan la colaboración internacional. Usa$anioo$ejercicio.
Convenciones de nombres
| Estilo | Ejemplo | Dónde se usa en PHP |
|---|---|---|
| camelCase | $precioFinal, calcularTotal() | Variables, funciones de usuario, métodos (lo usaremos en el curso) |
| PascalCase | CarritoCompra | Clases, interfaces, enums |
| snake_case | $precio_final, str_replace() | Funciones nativas de PHP, algunos proyectos (WordPress) |
| MAYÚSCULAS_CON_GUIONES | IVA_GENERAL, MAX_INTENTOS | Constantes |
💡 Consejo: Lo importante es ser coherente dentro del proyecto. Y elige nombres que expliquen el contenido:
$precioConIvaes infinitamente mejor que$p2o$x.
Asignación por valor
Por defecto, al asignar una variable a otra se copia el valor. Las dos variables son independientes:
<?php
$original = 10;
$copia = $original; // Se copia el valor 10
$copia = 99;
echo $original; // 10 ← no ha cambiado
echo $copia; // 99
Asignación por referencia
Con & haces que dos nombres apunten al mismo valor. Es como poner dos etiquetas en la misma caja:
<?php
$original = 10;
$alias = &$original; // $alias es otro nombre para la misma caja
$alias = 99;
echo $original; // 99 ← ¡ha cambiado!
echo $alias; // 99
unset($alias); // Quita la etiqueta $alias; la caja sigue existiendo
echo $original; // 99
💡 Consejo: Las referencias tienen usos legítimos (modificar un array dentro de un
foreach, parámetros que una función debe modificar), pero hacen el código más difícil de seguir. Úsalas solo cuando sea necesario. Los objetos, como verás en el módulo 11, ya se comportan de forma parecida sin necesidad de&.
6. Constantes
📘 Concepto: Una constante es un nombre asociado a un valor que no puede cambiar durante la ejecución. Úsalas para valores fijos: el tipo de IVA, el número máximo de intentos de login, la versión de tu aplicación. No llevan
$.
const y define()
<?php
declare(strict_types=1);
// Con const (en tiempo de compilación) - la forma recomendada
const IVA_GENERAL = 0.21;
const NOMBRE_APP = 'Mi Tienda';
const DIAS_SEMANA = ['lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo'];
// Con define() (en tiempo de ejecución)
define('MAX_INTENTOS', 3);
echo NOMBRE_APP . PHP_EOL; // Mi Tienda
echo 100 * (1 + IVA_GENERAL) . PHP_EOL; // 121
echo DIAS_SEMANA[0] . PHP_EOL; // lunes
// IVA_GENERAL = 0.10; // ❌ Error de sintaxis: no se puede reasignar
| Característica | const | define() |
|---|---|---|
| Momento | Al compilar | Al ejecutar |
¿Dentro de un if o función? | ❌ No (solo en el nivel superior o en clases) | ✅ Sí |
| ¿Nombre calculado? | ❌ No | ✅ Sí: define('CLAVE_' . $x, 1) |
| ¿En clases? | ✅ Sí (constantes de clase) | ❌ No |
| Respeta namespaces | ✅ Sí | Hay que indicar el nombre completo |
| Recomendación | ✅ La opción por defecto | Solo si necesitas definirla condicionalmente |
Constantes predefinidas útiles
| Constante | Valor de ejemplo | Significado |
|---|---|---|
PHP_VERSION | "8.5.3" | Versión de PHP |
PHP_OS_FAMILY | "Windows", "Linux", "Darwin" | Familia del sistema operativo |
PHP_EOL | "\n" o "\r\n" | Salto de línea del sistema |
PHP_INT_MAX | 9223372036854775807 | Mayor entero posible (64 bits) |
PHP_INT_MIN | -9223372036854775808 | Menor entero posible |
PHP_FLOAT_EPSILON | 2.220446049250313E-16 | Menor diferencia distinguible entre floats |
M_PI | 3.141592653589793 | El número π |
E_ALL | — | Nivel de error “todos los errores” |
Constantes mágicas
Las constantes mágicas cambian de valor según dónde se usen. Empiezan y terminan con dos guiones bajos:
| Constante | Valor |
|---|---|
__LINE__ | Número de línea actual |
__FILE__ | Ruta completa del fichero actual |
__DIR__ | Carpeta del fichero actual (equivale a dirname(__FILE__)) |
__FUNCTION__ | Nombre de la función actual |
__CLASS__ | Nombre de la clase actual |
__METHOD__ | Nombre del método actual (Clase::metodo) |
__NAMESPACE__ | Namespace actual |
__TRAIT__ | Nombre del trait actual |
__PROPERTY__ | Nombre de la propiedad, dentro de un property hook (PHP 8.4) |
<?php
echo 'Estás en la línea ' . __LINE__ . PHP_EOL; // Estás en la línea 3
echo 'Fichero: ' . __FILE__ . PHP_EOL; // Fichero: C:\curso-php\scripts\magicas.php
echo 'Carpeta: ' . __DIR__ . PHP_EOL; // Carpeta: C:\curso-php\scripts
function depurar(): void
{
echo 'Dentro de ' . __FUNCTION__ . PHP_EOL; // Dentro de depurar
}
depurar();
💡 Consejo:
__DIR__es imprescindible para incluir ficheros de forma fiable:require __DIR__ . '/config.php';funciona siempre, se ejecute el script desde donde se ejecute. Una ruta relativa comorequire 'config.php';depende de la carpeta de trabajo y puede fallar.
7. Tipos de datos
PHP tiene estos tipos:
| Categoría | Tipo | Ejemplo | Descripción |
|---|---|---|---|
| Escalares | bool | true, false | Verdadero o falso |
int | 42, -7, 0x1F | Número entero | |
float | 3.14, 1.5e3 | Número decimal (coma flotante) | |
string | 'Hola', "Adiós" | Cadena de texto | |
| Compuestos | array | [1, 2, 3], ['a' => 1] | Colección ordenada de pares clave-valor |
object | new DateTime() | Instancia de una clase | |
callable | 'strlen', fn($x) => $x * 2 | Algo que se puede llamar como función | |
iterable | Un array o un objeto recorrible | Cualquier cosa que admita foreach | |
| Especiales | null | null | Ausencia de valor |
resource | Un fichero abierto con fopen() | Referencia a un recurso externo (tipo heredado) | |
| Pseudotipo | mixed | Cualquiera | Acepta cualquier valor (solo en declaraciones de tipo) |
int: enteros
<?php
$decimal = 255;
$negativo = -42;
$hexadecimal = 0xFF; // 255 en base 16
$octal = 0o377; // 255 en base 8 (prefijo 0o desde PHP 8.1)
$binario = 0b11111111; // 255 en base 2
$legible = 1_000_000; // Separador de miles (PHP 7.4): vale 1000000
var_dump(PHP_INT_MAX); // int(9223372036854775807) en sistemas de 64 bits
var_dump(PHP_INT_MAX + 1); // float(9.2233720368547758E+18) ← ¡se convierte en float!
📘 Concepto: Si un entero supera
PHP_INT_MAX, PHP no da error: lo convierte silenciosamente enfloat(perdiendo precisión). Para números realmente grandes (criptografía, finanzas de altísima precisión) existen las extensiones BCMath y GMP.
float: números decimales
<?php
$precio = 19.99;
$cientifica = 1.5e3; // 1500.0
$pequeno = 2.5E-4; // 0.00025
var_dump(0.1 + 0.2); // float(0.30000000000000004)
var_dump(0.1 + 0.2 == 0.3); // bool(false) ← ¡sorpresa!
⚠️ Importante: Los
floatse guardan en binario y muchos decimales (como 0.1) no tienen representación exacta, igual que 1/3 no la tiene en decimal (0,3333…). Nunca compares floats con==. Compara con una tolerancia o redondea:
<?php
$a = 0.1 + 0.2;
$b = 0.3;
var_dump(abs($a - $b) < PHP_FLOAT_EPSILON); // bool(true)
var_dump(round($a, 2) === round($b, 2)); // bool(true)
💡 Consejo: Para dinero, la práctica profesional es trabajar en céntimos con enteros (
1999en lugar de19.99) o usar una librería de dinero/BCMath. Así evitas errores de redondeo acumulados.
null: ausencia de valor
null es el único valor del tipo null y significa “no hay valor”. Una variable vale null si se le asigna explícitamente o si se ha eliminado con unset():
<?php
$fechaBaja = null; // El cliente no se ha dado de baja
var_dump($fechaBaja); // NULL
8. Inspeccionar tipos
var_dump(): la navaja suiza
<?php
var_dump(42); // int(42)
var_dump(3.14); // float(3.14)
var_dump('Hola'); // string(4) "Hola"
var_dump(true); // bool(true)
var_dump(null); // NULL
📘 Concepto: En
string(4) "Hola", el número es la longitud en bytes, no en caracteres. Por esovar_dump('ñu')muestrastring(3): la ñ ocupa 2 bytes en UTF-8. Lo verás en el módulo de cadenas.
gettype() frente a get_debug_type()
| Valor | gettype() | get_debug_type() (PHP 8.0) |
|---|---|---|
42 | "integer" | "int" |
3.14 | "double" | "float" |
'hola' | "string" | "string" |
true | "boolean" | "bool" |
null | "NULL" | "null" |
[1, 2] | "array" | "array" |
new DateTime() | "object" | "DateTime" |
fn() => 1 | "object" | "Closure" |
💡 Consejo: Usa
get_debug_type(): devuelve los mismos nombres que usas en las declaraciones de tipo (int,float,bool) y, para objetos, el nombre de la clase.gettype()mantiene nombres históricos confusos como"double".
9. Conversiones de tipo
PHP convierte tipos de dos formas: explícitamente (tú lo pides) o automáticamente (type juggling, “malabarismo de tipos”).
Conversión explícita: casting
Se escribe el tipo entre paréntesis delante del valor:
<?php
var_dump((int) '42'); // int(42)
var_dump((int) '42.9'); // int(42) - trunca, no redondea
var_dump((int) 3.99); // int(3)
var_dump((int) -3.99); // int(-3)
var_dump((int) '12abc'); // int(12) - lee hasta donde puede
var_dump((int) 'abc'); // int(0)
var_dump((float) '3.14xyz'); // float(3.14)
var_dump((float) '1,5'); // float(1) - ¡la coma no es separador decimal!
var_dump((string) 42); // string(2) "42"
var_dump((string) true); // string(1) "1"
var_dump((string) false); // string(0) ""
var_dump((bool) 'false'); // bool(true) - ¡cualquier cadena no vacía salvo "0"!
var_dump((array) 'hola'); // array(1) { [0]=> string(4) "hola" }
⚠️ Importante: PHP 8.5 declara obsoletos los nombres alternativos de los casts:
(integer),(boolean),(double)y(binary). Usa siempre las formas canónicas(int),(bool),(float)y(string). Además, PHP 8.5 avisa cuando conviertes aintun float que no cabe en un entero (como1e20oNAN).
¿Qué es “verdadero” y qué es “falso”?
Cuando PHP necesita un booleano (en un if, por ejemplo), convierte el valor. Estos valores se consideran falsos (falsy); todo lo demás es verdadero:
| Valor | (bool) |
|---|---|
false | false |
0 y -0 | false |
0.0 | false |
'' (cadena vacía) | false |
'0' (la cadena con un cero) | false |
[] (array vacío) | false |
null | false |
'0.0', ' ', 'false' | ⚠️ true (¡son cadenas no vacías y distintas de '0'!) |
[0], [false] | ⚠️ true (el array no está vacío) |
Conversión automática: type juggling
En modo no estricto, PHP convierte los operandos según la operación:
<?php
var_dump('10' + 5); // int(15) - la cadena se convierte en número
var_dump('3.5' * 2); // float(7)
var_dump('5' . 5); // string(2) "55" - el . convierte a cadena
var_dump(true + true); // int(2)
var_dump(null + 5); // int(5)
var_dump(10 / 4); // float(2.5) - la división puede dar float
var_dump(10 / 2); // int(5)
var_dump('12abc' + 1); // int(13) + Warning: A non-numeric value encountered
var_dump('abc' + 1); // TypeError: Unsupported operand types: string + int
Tabla de sorpresas
| Expresión | Resultado | Por qué |
|---|---|---|
'10' + '5' | 15 | + siempre es suma numérica |
'10' . '5' | '105' | . siempre es concatenación |
0.1 + 0.2 == 0.3 | false | Imprecisión de los float |
(int) '1e3' | 1000 | '1e3' es notación científica |
(int) '0x1A' | 0 | Las cadenas hexadecimales no se interpretan |
(bool) '0' | false | La cadena '0' es el único texto no vacío falso |
(bool) '0.0' | true | En cambio, '0.0' es verdadero |
(float) '1,5' | 1.0 | PHP usa el punto como separador decimal |
'5' * '4' | 20 (int) | Las cadenas numéricas se convierten en número al operar |
'abc' == 0 | false en PHP 8 (true en PHP 7) | PHP 8 cambió la comparación cadena-número |
PHP_INT_MAX + 1 | float | Desbordamiento a float |
7 / 2 | 3.5 | La división no es entera; usa intdiv(7, 2) → 3 |
💡 Consejo: Las conversiones automáticas son la fuente de muchos errores difíciles de encontrar. La solución moderna es combinar declaraciones de tipo,
strict_typesy la comparación estricta===(módulo 03).
10. Tipado estricto y declaraciones de tipo
Declaraciones de tipo
Puedes indicar el tipo de los parámetros y del valor de retorno de una función. PHP lo comprobará en cada llamada:
<?php
function doble(int $numero): int
{
return $numero * 2;
}
echo doble(21); // 42
Modo coercitivo (por defecto) frente a modo estricto
Sin strict_types, PHP intenta convertir el argumento al tipo esperado:
<?php
// SIN declare(strict_types=1)
function doble(int $numero): int
{
return $numero * 2;
}
echo doble('5'); // 10 ← convierte '5' en 5 sin avisar
echo doble('5.0'); // 10
echo doble('cinco'); // TypeError: doble(): Argument #1 ($numero) must be of type int, string given
Con strict_types, el tipo debe coincidir exactamente:
<?php
declare(strict_types=1); // Debe ser la PRIMERA instrucción del fichero
function doble(int $numero): int
{
return $numero * 2;
}
echo doble(5); // 10
echo doble('5'); // TypeError: doble(): Argument #1 ($numero) must be of type int, string given
📘 Concepto:
declare(strict_types=1)afecta a las llamadas hechas desde ese fichero (y a losreturnde las funciones definidas en él). Por eso debe estar en todos los ficheros. Hay una única excepción a la rigidez: se permite pasar unintdonde se espera unfloat(5se convierte en5.0), porque no se pierde información.
💡 Consejo: En este curso todos los ejemplos usan
declare(strict_types=1);. Es la práctica habitual en proyectos modernos, Symfony y Laravel. Los errores de tipo aparecen al instante y en la línea exacta, en lugar de convertirse en datos corruptos que descubres semanas después.
Tipos disponibles en declaraciones
| Declaración | Significado | Desde |
|---|---|---|
int, float, string, bool | Escalares | PHP 7.0 |
array, callable, iterable, object | Compuestos | PHP 7.x |
NombreClase | Un objeto de esa clase (o hija) | PHP 5 |
?string | Nullable: string o null | PHP 7.1 |
void | La función no devuelve nada (solo retorno) | PHP 7.1 |
int\|float | Unión: uno u otro | PHP 8.0 |
mixed | Cualquier valor | PHP 8.0 |
static | La clase actual (solo retorno, en métodos) | PHP 8.0 |
never | La función nunca termina normalmente (solo retorno) | PHP 8.1 |
A&B | Intersección: cumple ambas interfaces | PHP 8.1 |
null, false, true | Como tipos por sí mismos | PHP 8.2 |
(A&B)\|null | Tipos DNF (combinación de unión e intersección) | PHP 8.2 |
Tipos unión
<?php
declare(strict_types=1);
function formatearPrecio(int|float $precio): string
{
return number_format($precio, 2, ',', '.') . ' €';
}
echo formatearPrecio(10) . PHP_EOL; // 10,00 €
echo formatearPrecio(1234.5) . PHP_EOL; // 1.234,50 €
Tipos nullable
?string es una forma abreviada de string|null. Úsalo cuando un valor puede faltar:
<?php
declare(strict_types=1);
function saludar(?string $nombre = null): string
{
// ?? devuelve 'invitado' si $nombre es null (módulo 03)
return 'Hola, ' . ($nombre ?? 'invitado');
}
echo saludar('Marta') . PHP_EOL; // Hola, Marta
echo saludar() . PHP_EOL; // Hola, invitado
void y never
<?php
declare(strict_types=1);
// void: hace algo, pero no devuelve ningún valor
function registrar(string $mensaje): void
{
echo '[' . date('H:i:s') . "] $mensaje" . PHP_EOL;
// return 5; ❌ Error: una función void no puede devolver un valor
}
// never: la función NUNCA vuelve (lanza una excepción o termina el script)
function abortar(string $motivo): never
{
throw new RuntimeException($motivo);
}
registrar('Inicio del proceso');
false y true como tipos
Muchas funciones nativas antiguas devuelven un valor o false si fallan (por ejemplo, strpos()). Con PHP 8 puedes expresarlo con precisión:
<?php
declare(strict_types=1);
/** Devuelve la posición del primer dígito o false si no hay ninguno. */
function posicionPrimerDigito(string $texto): int|false
{
for ($i = 0; $i < strlen($texto); $i++) {
if (ctype_digit($texto[$i])) {
return $i;
}
}
return false;
}
var_dump(posicionPrimerDigito('abc7d')); // int(3)
var_dump(posicionPrimerDigito('abcd')); // bool(false)
💡 Consejo:
falsecomo tipo se usa casi siempre en uniones (int|false) para reflejar el comportamiento de funciones heredadas. En tu código nuevo, para indicar “no hay resultado” suele ser más claro devolvernull(?int) o lanzar una excepción.
11. isset(), empty() e is_null()
Tres funciones que parecen similares pero responden preguntas distintas:
| Función | Pregunta que responde |
|---|---|
isset($v) | ¿Existe la variable y no es null? |
empty($v) | ¿No existe o su valor es “falso”? |
is_null($v) | ¿Su valor es null? (avisa si la variable no existe) |
Tabla comparativa
Valor de $v | isset($v) | empty($v) | is_null($v) | (bool) $v |
|---|---|---|---|---|
| No definida | false | true | true + Warning | false + Warning |
null | false | true | true | false |
0 | true | true | false | false |
'0' | true | true | false | false |
'' | true | true | false | false |
[] | true | true | false | false |
false | true | true | false | false |
'0.0' | true | false | false | true |
'hola' | true | false | false | true |
42 | true | false | false | true |
<?php
$usuario = ['nombre' => 'Pablo', 'telefono' => null, 'hijos' => 0];
var_dump(isset($usuario['nombre'])); // bool(true)
var_dump(isset($usuario['telefono'])); // bool(false) - existe la clave pero vale null
var_dump(isset($usuario['email'])); // bool(false) - no existe (sin aviso)
var_dump(empty($usuario['hijos'])); // bool(true) - ¡0 cuenta como vacío!
// Para saber si la clave existe aunque valga null:
var_dump(array_key_exists('telefono', $usuario)); // bool(true)
⚠️ Importante: Ten cuidado con
empty(): considera vacíos el0y la cadena'0'. Si un usuario escribe “0” en el campo “número de hijos” de un formulario,empty()dirá que no ha rellenado el campo. Para validar formularios, compara explícitamente:$valor === ''o$valor === null.
12. Variables de variables (y por qué evitarlas)
PHP permite usar el valor de una variable como nombre de otra, con $$:
<?php
$campo = 'ciudad';
$$campo = 'Valencia'; // Crea la variable $ciudad
echo $ciudad; // Valencia
echo ${$campo}; // Valencia (forma con llaves)
Parece ingenioso, pero es una mala práctica:
| Problema | Explicación |
|---|---|
| Ilegible | Nadie sabe qué variables existen leyendo el código |
| Sin ayuda del editor | Intelephense no puede autocompletar ni detectar errores |
| Inseguro | Si el nombre viene del usuario, puede sobrescribir variables importantes ($esAdmin) |
| Difícil de depurar | Las variables “aparecen” sin una asignación visible |
La alternativa correcta casi siempre es un array asociativo:
<?php
// ❌ Con variables de variables
foreach (['nombre', 'email'] as $campo) {
$$campo = $_POST[$campo] ?? '';
}
// ✅ Con un array
$datos = [];
foreach (['nombre', 'email'] as $campo) {
$datos[$campo] = $_POST[$campo] ?? '';
}
echo $datos['nombre'];
13. Ámbito de las variables
📘 Concepto: El ámbito (scope) de una variable es la zona del código donde existe y se puede usar. En PHP es sencillo: una variable creada fuera de las funciones vive en el ámbito global; una creada dentro de una función vive solo en esa función (ámbito local).
Las funciones no ven las variables de fuera
A diferencia de JavaScript, en PHP una función no tiene acceso automático a las variables globales:
<?php
declare(strict_types=1);
$iva = 0.21;
function precioFinal(float $base): float
{
return $base * (1 + $iva); // Warning: Undefined variable $iva
}
global y $GLOBALS (desaconsejados)
Existen formas de acceder a las variables globales, pero crean dependencias ocultas y hacen el código difícil de probar:
<?php
$contadorVisitas = 10;
function incrementar(): void
{
global $contadorVisitas; // Importa la variable global
$contadorVisitas++;
}
function mostrar(): void
{
echo $GLOBALS['contadorVisitas']; // Acceso a través del array $GLOBALS
}
incrementar();
mostrar(); // 11
⚠️ Importante: Evita
globaly$GLOBALSen tu código. Si una función necesita un dato, pásaselo como parámetro; si un valor no cambia nunca, usa una constante.
Variables static
Una variable static dentro de una función conserva su valor entre llamadas:
<?php
declare(strict_types=1);
function siguienteNumeroFactura(): int
{
static $numero = 0; // Solo se inicializa la primera vez
return ++$numero;
}
echo siguienteNumeroFactura() . PHP_EOL; // 1
echo siguienteNumeroFactura() . PHP_EOL; // 2
echo siguienteNumeroFactura() . PHP_EOL; // 3
📘 Concepto: Recuerda el modelo share nothing del módulo 00: una variable
staticsolo vive durante una petición. En la siguiente petición vuelve a empezar en 0. Para numerar facturas de verdad, el contador tendría que estar en una base de datos.
14. Ejercicios
Ejercicio 1: Ficha personal
Crea ficha.php con declare(strict_types=1); y variables para tu nombre, edad, altura en metros, ciudad y si tienes carné de conducir. Muestra en la terminal, usando echo y PHP_EOL, una ficha como esta:
Nombre: Laura Martín
Edad: 29 años
Altura: 1.68 m
Ciudad: Zaragoza
Carné de conducir: sí
Pista: para el carné, usa $tieneCarne ? 'sí' : 'no'.
Ejercicio 2: Constantes de una tienda
Define con const las constantes NOMBRE_TIENDA, IVA_GENERAL (0.21), IVA_REDUCIDO (0.10) y GASTOS_ENVIO (4.95). Calcula y muestra el total de un pedido de 3 libros de 18,50 € (IVA reducido) más gastos de envío. Muestra también la línea en la que se hace el cálculo con __LINE__.
Ejercicio 3: Detective de tipos
Para cada uno de estos valores, muestra en una tabla HTML su valor con var_export(), el resultado de get_debug_type(), de (bool) y de (int): '0', '0.0', '', '12 monos', 3.99, -0.5, null, [], [0], true. Antes de ejecutarlo, apunta tus predicciones y compáralas.
Ejercicio 4: Modo estricto en acción
Escribe una función calcularMedia(float $a, float $b, float $c): float. Llámala con (5, 7, 9), con (5.5, '7', 9) y con (5, 7, null). Ejecútala primero sin strict_types y luego con él. Anota qué llamadas funcionan en cada caso y explica por qué 5 (int) se acepta como float incluso en modo estricto.
Ejercicio 5: Referencias
Predice la salida de este código y después compruébala:
<?php
$a = 1;
$b = $a;
$c = &$a;
$b = 2;
$c = 3;
echo "$a $b $c" . PHP_EOL;
unset($c);
$c = 4;
echo "$a $b $c" . PHP_EOL;
Ejercicio 6: Validación con isset y empty
Simula los datos de un formulario con el array $form = ['nombre' => 'Iván', 'edad' => '0', 'email' => '', 'telefono' => null];. Para cada campo (nombre, edad, email, telefono, direccion), muestra si “existe” (isset), si “está vacío” (empty) y si la clave está presente (array_key_exists). Explica por qué edad sale como vacía y cómo lo validarías correctamente.
Resumen
| Concepto | Sintaxis / ejemplo |
|---|---|
| Etiquetas | <?php ... ?>, <?= $valor ?> |
| Fichero solo PHP | Sin ?> al final |
| Comentarios | // línea, /* bloque */, /** PHPDoc */ |
| Salida | echo 'a', 'b';, print 'a'; |
| Variable | $nombreVariable = valor; |
| Por referencia | $alias = &$original; |
| Constantes | const IVA = 0.21;, define('IVA', 0.21); |
| Constantes mágicas | __LINE__, __FILE__, __DIR__, __FUNCTION__ |
| Tipos escalares | bool, int, float, string |
| Tipos compuestos | array, object, callable, iterable |
| Especiales | null, mixed |
| Ver el tipo | get_debug_type($v) (mejor que gettype()) |
| Casting | (int), (float), (string), (bool), (array) |
| Modo estricto | declare(strict_types=1); (primera línea) |
| Tipos en funciones | function f(int $a): string |
| Unión / nullable | int\|float, ?string |
| Retornos especiales | void, never, int\|false, true |
| Comprobaciones | isset(), empty(), is_null(), array_key_exists() |
| Ámbito | Local en funciones; static conserva valor; evitar global |