Curso de PHP

Atributos y métodos estáticos en PHP

Por Víctor Peña · Publicado el

Atributos y métodos estáticos en PHP

Hola, ¿cómo están? Continuando con el curso de PHP, hoy veremos los atributos y métodos estáticos: los que pertenecen a la clase en lugar de a cada objeto.

¡Empecemos!

Qué significa estático

Hasta ahora, cada objeto tenía sus propios valores. Si creabas dos mascotas, cada una llevaba su nombre.

Un miembro estático es distinto: pertenece a la clase, no a los objetos. Existe uno solo, compartido por todos, y se puede usar sin haber creado ningún objeto.

Propiedades estáticas

Se declaran con static y se acceden con :::

<?php
class Contador
{
    public static int $total = 0;

    public function __construct()
    {
        self::$total++;
    }
}

new Contador();
new Contador();
new Contador();

echo Contador::$total;   // 3

Fíjate en dos cosas:

  • Dentro de la clase se usa self::$total, no $this->total. $this se refiere al objeto; self a la clase.
  • Desde fuera se usa Contador::$total, sin necesidad de ningún objeto.

Y fíjate en el efecto: los tres objetos comparten el mismo contador. No hay tres copias, hay una.

Métodos estáticos

<?php
class Matematicas
{
    public static function sumar(float ...$numeros): float
    {
        return array_sum($numeros);
    }

    public static function promedio(float ...$numeros): float
    {
        return self::sumar(...$numeros) / count($numeros);
    }
}

echo Matematicas::sumar(10, 20, 30);      // 60
echo Matematicas::promedio(10, 20, 30);   // 20

No hace falta new. Se llama directamente sobre la clase.

Aquí aparecen los argumentos variádicos: float ...$numeros acepta cuantos argumentos le pases y los recibe como array. Y al llamar, ...$numeros los vuelve a desempaquetar.

$this no existe en un método estático

Es la limitación clave, y tiene toda la lógica: si el método pertenece a la clase y no a un objeto, no hay ningún objeto al que referirse.

<?php
class Ejemplo
{
    private string $nombre = "Víctor";

    public static function saludar(): string
    {
        return $this->nombre;   // Error fatal: $this no existe aquí
    }
}

Un método estático solo puede usar propiedades estáticas.

self, static y parent

Tres formas de referirse a clases dentro de una clase:

<?php
class Padre
{
    public static function crear(): static
    {
        return new static();   // la clase real que llamó
    }

    public static function crearFijo(): self
    {
        return new self();     // siempre Padre
    }
}

class Hijo extends Padre {}

var_dump(Hijo::crear());       // object(Hijo)
var_dump(Hijo::crearFijo());   // object(Padre)
  • self se resuelve en la clase donde está escrito.
  • static se resuelve en la clase que realmente se invocó. Se llama late static binding.
  • parent apunta a la clase padre.

En general quieres static, porque permite que las clases hijas funcionen correctamente. self solo cuando deliberadamente quieras fijar la clase.

El patrón más útil: constructores con nombre

Este es probablemente el mejor uso cotidiano de los métodos estáticos:

<?php
declare(strict_types=1);

class Dinero
{
    private function __construct(
        private readonly int $centavos,
        private readonly string $moneda,
    ) {}

    public static function desdeBolivianos(float $monto): static
    {
        return new static((int) round($monto * 100), 'BOB');
    }

    public static function desdeCentavos(int $centavos): static
    {
        return new static($centavos, 'BOB');
    }

    public static function cero(): static
    {
        return new static(0, 'BOB');
    }

    public function formatear(): string
    {
        return number_format($this->centavos / 100, 2) . ' ' . $this->moneda;
    }
}

echo Dinero::desdeBolivianos(150.75)->formatear();   // 150.75 BOB
echo Dinero::cero()->formatear();                    // 0.00 BOB

Fíjate en que el constructor es privado: la única forma de crear un objeto es a través de los métodos estáticos, cuyos nombres dicen exactamente qué esperan recibir. Dinero::desdeBolivianos(150.75) es infinitamente más claro que new Dinero(15075, 'BOB').

Y de paso, esto resuelve el problema del dinero con decimales que mencionamos en la lección de operadores aritméticos: internamente se guarda en centavos como entero.

Constantes de clase

Las constantes que vimos en la lección de constantes también se acceden con :::

<?php
class Pedido
{
    public const ESTADO_PENDIENTE = 'pendiente';
    public const ESTADO_PAGADO = 'pagado';

    public static function estadosValidos(): array
    {
        return [self::ESTADO_PENDIENTE, self::ESTADO_PAGADO];
    }
}

echo Pedido::ESTADO_PAGADO;
print_r(Pedido::estadosValidos());

Cuándo usar estáticos

Buenos usos:

  • Funciones utilitarias sin estado: formatear, validar, convertir.
  • Constructores con nombre, como el ejemplo de Dinero.
  • Contadores o registros compartidos por todas las instancias.
  • Constantes de clase.

Malos usos:

  • Guardar el estado de la aplicación en propiedades estáticas.
  • Como sustituto de pasar dependencias.

Por qué conviene no abusar

Aquí hay algo importante que casi ningún tutorial menciona.

Una llamada estática ata tu código a una clase concreta:

<?php
class ServicioDePedidos
{
    public function confirmar(int $id): void
    {
        Mailer::enviar("Pedido $id confirmado");   // atado
    }
}

Compáralo con lo que vimos en interfaces: si en lugar de la llamada estática recibes un Mailer por constructor, puedes sustituirlo en pruebas o cambiar de proveedor sin tocar esta clase. Con la llamada estática, no puedes.

Además, el estado estático se comparte durante toda la ejecución. Si una parte del programa lo modifica, las demás lo ven. Es una variable global con otro nombre, y trae los mismos problemas: comportamiento impredecible y errores difíciles de reproducir.

La regla práctica: usa estáticos cuando el método no dependa de ningún estado. Si necesita datos o colaboradores, mejor un objeto normal.

Un ejemplo práctico

<?php
declare(strict_types=1);

class Validador
{
    public static function email(string $valor): bool
    {
        return filter_var($valor, FILTER_VALIDATE_EMAIL) !== false;
    }

    public static function telefonoBoliviano(string $valor): bool
    {
        return preg_match('/^[67]\d{7}$/', $valor) === 1;
    }

    public static function noVacio(?string $valor): bool
    {
        return $valor !== null && trim($valor) !== '';
    }
}

$datos = [
    'correo' => 'cliente@empresa.com',
    'telefono' => '76980507',
    'nombre' => '',
];

var_dump(Validador::email($datos['correo']));          // true
var_dump(Validador::telefonoBoliviano($datos['telefono'])); // true
var_dump(Validador::noVacio($datos['nombre']));        // false

Este es el caso ideal para estáticos: funciones puras que reciben un valor, devuelven un resultado y no guardan nada.

Errores comunes

  • Usar $this dentro de un método estático.
  • Usar -> en lugar de :: para acceder a un miembro estático.
  • Olvidar self:: al usar una propiedad estática dentro de la clase.
  • Guardar estado de la aplicación en propiedades estáticas.
  • Usar self donde tocaba static, rompiendo el comportamiento en las clases hijas.

Para cerrar

Los miembros estáticos pertenecen a la clase y existen sin necesidad de crear objetos. Son perfectos para utilidades sin estado y para constructores con nombre; peligrosos cuando guardan información que cambia.

La pregunta antes de declarar algo estático: ¿esto depende de algún dato del objeto? Si la respuesta es sí, no debería serlo.

En la siguiente lección veremos los arrays en profundidad.

Saludos y éxitos.

Norvic Software

Desarrollamos el software que tu empresa necesita

Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp