POO en PHP

La Programación Orientada a Objetos organiza el código en clases que agrupan datos y comportamiento. Es el paradigma central de PHP moderno y de frameworks como Laravel.

¿Qué es una Clase?

Una clase es un molde o plantilla que define cómo son los objetos de ese tipo. Un objeto es una instancia concreta de esa clase. Pensalo como: la clase es el plano de una casa, el objeto es la casa construida.

// Definir la clase
class Persona {
    // Propiedades — los datos del objeto
    public string $nombre;
    public int    $edad;

    // Método — el comportamiento del objeto
    public function saludar(): string {
        return "Hola, soy {$this->nombre} y tengo {$this->edad} años.";
    }
}

// Crear un objeto (instanciar la clase)
$ana = new Persona();
$ana->nombre = "Ana";
$ana->edad   = 28;

echo $ana->saludar(); // Hola, soy Ana y tengo 28 años.
$this

Dentro de un método, $this hace referencia al objeto actual. Es la forma en que el objeto habla de sí mismo: $this->nombre es el nombre de este objeto.

Probá el Ejemplo 1: Clase básica

Constructor — __construct()

El constructor es un método especial que se ejecuta automáticamente cuando creás un objeto con new. Sirve para inicializar las propiedades desde el principio.

class Producto {
    public string $nombre;
    public float  $precio;
    public int    $stock;

    public function __construct(string $nombre, float $precio, int $stock = 0) {
        $this->nombre = $nombre;
        $this->precio = $precio;
        $this->stock  = $stock;
    }

    public function info(): string {
        return "{$this->nombre} — \${$this->precio} ({$this->stock} en stock)";
    }

    public function disponible(): bool {
        return $this->stock > 0;
    }
}

// Ahora se crean con los datos desde el principio
$laptop = new Producto("Laptop", 1200.00, 5);
$mouse  = new Producto("Mouse",  25.50,   0);

echo $laptop->info();                                          // Laptop — $1200 (5 en stock)
echo $mouse->disponible() ? "Disponible" : "Sin stock";       // Sin stock
Promoted Properties (PHP 8+)

PHP 8 permite declarar y asignar propiedades directamente en el constructor: public function __construct(public string $nombre, public float $precio) — hace exactamente lo mismo pero en menos líneas.

Visibilidad: public, protected, private

La visibilidad controla quién puede acceder a las propiedades y métodos de un objeto. Es fundamental para encapsulamiento: ocultar los detalles internos y exponer solo lo necesario.

VisibilidadAccesible desdeUso típico
public Desde cualquier lugar Métodos que forman la interfaz pública
protected La clase y sus hijas Propiedades que heredan las subclases
private Solo la propia clase Datos internos que nadie más debe tocar
class CuentaBancaria {
    private float $saldo;      // solo esta clase puede modificarlo
    public string $titular;    // accesible desde afuera

    public function __construct(string $titular, float $saldoInicial = 0) {
        $this->titular = $titular;
        $this->saldo   = $saldoInicial;
    }

    public function depositar(float $monto): void {
        if ($monto > 0) $this->saldo += $monto;
    }

    public function extraer(float $monto): bool {
        if ($monto > $this->saldo) return false;
        $this->saldo -= $monto;
        return true;
    }

    public function getSaldo(): float {
        return $this->saldo;   // exponemos el saldo de forma controlada
    }
}

$cuenta = new CuentaBancaria("Ana García", 1000);
$cuenta->depositar(500);
$cuenta->extraer(200);
echo $cuenta->getSaldo();    // 1300

// $cuenta->saldo = 999999;  // ERROR — saldo es private
Probá el Ejemplo 2: Visibilidad y encapsulamiento

Herencia — extends

La herencia permite que una clase hija reutilice y extienda el código de una clase padre. La hija hereda todas las propiedades y métodos públicos y protegidos del padre, y puede agregar los suyos o sobreescribir los existentes.

// Clase padre (base)
class Animal {
    public string $nombre;

    public function __construct(string $nombre) {
        $this->nombre = $nombre;
    }

    public function describir(): string {
        return "Soy {$this->nombre}";
    }

    public function sonido(): string {
        return "...";   // las subclases lo sobreescriben
    }
}

// Clase hija — hereda de Animal
class Perro extends Animal {
    public string $raza;

    public function __construct(string $nombre, string $raza) {
        parent::__construct($nombre);  // llama al constructor del padre
        $this->raza = $raza;
    }

    // Sobreescribe el método del padre
    public function sonido(): string {
        return "¡Guau!";
    }

    // Método propio de Perro
    public function describir(): string {
        return parent::describir() . ", soy un {$this->raza}";
    }
}

class Gato extends Animal {
    public function sonido(): string { return "¡Miau!"; }
}

$perro = new Perro("Rex", "Labrador");
$gato  = new Gato("Mimi");

echo $perro->describir();  // Soy Rex, soy un Labrador
echo $perro->sonido();     // ¡Guau!
echo $gato->sonido();      // ¡Miau!
parent::

parent::__construct() llama al constructor de la clase padre. parent::metodo() llama a cualquier método del padre — útil cuando sobreescribís un método pero querés conservar parte del comportamiento original.

Propiedades y métodos estáticos

Las propiedades y métodos static pertenecen a la clase, no a los objetos. Se acceden con NombreClase::propiedad sin necesidad de instanciar.

class Contador {
    private static int $total = 0;

    public function __construct() {
        self::$total++;   // self:: para acceder a lo estático dentro de la clase
    }

    public static function getTotal(): int {
        return self::$total;
    }
}

$a = new Contador();
$b = new Contador();
$c = new Contador();

echo Contador::getTotal();  // 3 — se comparte entre todas las instancias
// Uso común: constantes y utilidades que no necesitan estado
class Matematica {
    public static function potencia(float $base, int $exp): float {
        return $base ** $exp;
    }

    public static function porcentaje(float $valor, float $pct): float {
        return $valor * $pct / 100;
    }
}

echo Matematica::potencia(2, 10);        // 1024
echo Matematica::porcentaje(1500, 15);   // 225

Interfaces y Clases Abstractas

Interfaces — un contrato

Una interfaz define qué métodos debe tener una clase, sin implementarlos. Garantiza que distintas clases compartan la misma forma.

interface Pagable {
    public function pagar(float $monto): bool;
    public function getSaldo(): float;
}

// Cualquier clase que implemente Pagable DEBE tener esos métodos
class TarjetaCredito implements Pagable {
    private float $limite;
    private float $usado = 0;

    public function __construct(float $limite) {
        $this->limite = $limite;
    }

    public function pagar(float $monto): bool {
        if ($this->usado + $monto > $this->limite) return false;
        $this->usado += $monto;
        return true;
    }

    public function getSaldo(): float {
        return $this->limite - $this->usado;
    }
}

Clases Abstractas — base parcial

abstract class Forma {
    abstract public function area(): float;     // las hijas deben implementarlo

    public function describir(): string {       // esto sí está implementado
        return "Esta forma tiene un área de " . $this->area();
    }
}

class Circulo extends Forma {
    public function __construct(private float $radio) {}

    public function area(): float {
        return M_PI * $this->radio ** 2;
    }
}

class Rectangulo extends Forma {
    public function __construct(private float $ancho, private float $alto) {}

    public function area(): float {
        return $this->ancho * $this->alto;
    }
}

$c = new Circulo(5);
$r = new Rectangulo(4, 6);

echo $c->describir();  // Esta forma tiene un área de 78.54...
echo $r->describir();  // Esta forma tiene un área de 24
Probá el Ejemplo 3: Herencia e interfaces

Ejemplo Completo — Sistema de carrito

Un mini-sistema de e-commerce que combina clases, herencia, encapsulamiento y métodos estáticos:

class Producto {
    public function __construct(
        public readonly string $nombre,
        public readonly float  $precio,
        private int            $stock
    ) {}

    public function disponible(): bool { return $this->stock > 0; }

    public function reservar(int $cantidad): bool {
        if ($cantidad > $this->stock) return false;
        $this->stock -= $cantidad;
        return true;
    }
}

class ItemCarrito {
    public function __construct(
        public readonly Producto $producto,
        public int               $cantidad
    ) {}

    public function subtotal(): float {
        return $this->producto->precio * $this->cantidad;
    }
}

class Carrito {
    private array $items = [];

    public function agregar(Producto $producto, int $cantidad = 1): bool {
        if (!$producto->reservar($cantidad)) return false;
        $this->items[] = new ItemCarrito($producto, $cantidad);
        return true;
    }

    public function total(): float {
        return array_sum(array_map(fn($i) => $i->subtotal(), $this->items));
    }

    public function resumen(): void {
        foreach ($this->items as $item) {
            echo "{$item->producto->nombre} x{$item->cantidad} = \${$item->subtotal()}\n";
        }
        echo "Total: \${$this->total()}\n";
    }
}
Probá el Ejemplo 4: Carrito de compras

Buenas Prácticas

  • Una clase, una responsabilidad — si una clase hace demasiadas cosas, dividila
  • Propiedades private por defecto — exponé solo lo necesario con getters/setters o métodos descriptivos
  • Constructores simples — el constructor inicializa, no ejecuta lógica compleja
  • Nombres descriptivos — class GestorDePedidos en vez de class Clase1
  • Preferí composición sobre herencia — si dos clases no tienen una relación "es un", usá composición
  • readonly para datos inmutables — si una propiedad no debe cambiar después de construida, declarala readonly