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.
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.
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
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.
| Visibilidad | Accesible desde | Uso 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::__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 GestorDePedidosen vez declass 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