Las propiedades y métodos estáticos en PHP pertenecen a la clase, no a una instancia concreta. Se accede a ellos con :: y permiten compartir estado entre objetos o crear métodos utilitarios sin necesidad de instanciar. La diferencia entre self:: y static:: es crucial cuando hay herencia.
Propiedades y métodos estáticos básicos
<?php
class Contador
{
private static int $total = 0;
public function __construct()
{
self::$total++;
}
public static function getTotal(): int
{
return self::$total;
}
public static function reiniciar(): void
{
self::$total = 0;
}
}
new Contador();
new Contador();
new Contador();
echo Contador::getTotal(); // 3
Contador::reiniciar();
echo Contador::getTotal(); // 0
?>
self:: vs static:: (Late Static Binding)
self:: siempre hace referencia a la clase donde se declaró el método. static:: se resuelve en tiempo de ejecución según la clase que invocó el método, lo que resulta esencial para patrones con herencia:
<?php
class ModeloBase
{
protected static string $tabla = 'base';
public static function getTabla(): string
{
return self::$tabla; // siempre devuelve 'base'
}
public static function getTablaDinamica(): string
{
return static::$tabla; // devuelve la tabla de la clase llamante
}
}
class Usuario extends ModeloBase
{
protected static string $tabla = 'usuarios';
}
class Producto extends ModeloBase
{
protected static string $tabla = 'productos';
}
echo ModeloBase::getTabla(); // base
echo Usuario::getTabla(); // base ? self:: no funciona aquí
echo Usuario::getTablaDinamica(); // usuarios ? static:: sí funciona
echo Producto::getTablaDinamica(); // productos
?>
Patrón Singleton
El Singleton garantiza que solo exista una instancia de una clase. Se implementa con una propiedad estática privada y un método de fábrica estático:
<?php
class ConexionBD
{
private static ?self $instancia = null;
private function __construct(
private readonly string $dsn,
) {}
public static function getInstance(): static
{
if (static::$instancia === null) {
static::$instancia = new static('mysql:host=localhost;dbname=app');
}
return static::$instancia;
}
// Evitar clonado y deserialización
private function __clone() {}
public function __wakeup(): never
{
throw new RuntimeException('No se puede deserializar el Singleton');
}
public function getDsn(): string
{
return $this->dsn;
}
}
$a = ConexionBD::getInstance();
$b = ConexionBD::getInstance();
var_dump($a === $b); // true misma instancia
?>
Fábricas estáticas (named constructors)
<?php
class Color
{
private function __construct(
public readonly int $r,
public readonly int $g,
public readonly int $b,
) {}
public static function desdeHex(string $hex): static
{
$hex = ltrim($hex, '#');
return new static(
hexdec(substr($hex, 0, 2)),
hexdec(substr($hex, 2, 2)),
hexdec(substr($hex, 4, 2)),
);
}
public static function desdeRGB(int $r, int $g, int $b): static
{
return new static($r, $g, $b);
}
public function toHex(): string
{
return sprintf('#%02x%02x%02x', $this->r, $this->g, $this->b);
}
}
$c1 = Color::desdeHex('#1a2b3c');
$c2 = Color::desdeRGB(255, 128, 0);
echo $c1->toHex(); // #1a2b3c
echo $c2->toHex(); // #ff8000
?>
Cuándo evitar static
El estado estático es global y persiste durante toda la petición, lo que dificulta el testing. Antes de usar static, valora si una instancia inyectada por dependencias no es mejor opción:
<?php
// PROBLEMÁTICO en tests: estado compartido entre casos de prueba
class Cache
{
private static array $datos = [];
public static function get(string $k): mixed { return self::$datos[$k] ?? null; }
public static function set(string $k, mixed $v): void { self::$datos[$k] = $v; }
}
// MEJOR: instancia inyectable, fácil de mockear
class CacheServicio
{
private array $datos = [];
public function get(string $k): mixed { return $this->datos[$k] ?? null; }
public function set(string $k, mixed $v): void { $this->datos[$k] = $v; }
}
?>
La documentación oficial sobre propiedades y métodos estáticos y la de Late Static Binding explican el comportamiento de static:: en contextos de herencia compleja.
