Tabla de contenido

En el artículo anterior vimos los conceptos básicos de la Programación Orientada a Objetos: qué son las clases, atributos y métodos. Ahora profundizamos en dos pilares fundamentales: el encapsulamiento y la herencia. Con estos dos conceptos ya puedes estructurar código PHP de forma profesional.

Repaso rápido: clases y objetos

Una clase es como un molde. Un objeto es la instancia concreta creada a partir de ese molde:

<?php
class Usuario {
  public string $nombre;
  public string $email;
  
  public function saludar(): string {
    return "Hola, soy " . $this->nombre;
  }
}

$usuario = new Usuario();
$usuario->nombre = "Abraham";
$usuario->email = "hola@8devmx.com";

echo $usuario->saludar(); // "Hola, soy Abraham"
?>

El constructor

El constructor es un método especial que se ejecuta automáticamente cuando creas un nuevo objeto. Te permite inicializar los atributos al momento de la creación:

<?php
class Usuario {
  public string $nombre;
  public string $email;
  public string $ciudad;
  
  public function __construct(string $nombre, string $email, string $ciudad = "Cancún") {
    $this->nombre = $nombre;
    $this->email  = $email;
    $this->ciudad = $ciudad;
  }
  
  public function presentarse(): string {
    return "Soy {$this->nombre}, vivo en {$this->ciudad}.";
  }
}

$usuario1 = new Usuario("Abraham", "hola@8devmx.com");
$usuario2 = new Usuario("Brenda", "brenda@ejemplo.com", "Mérida");

echo $usuario1->presentarse(); // "Soy Abraham, vivo en Cancún."
echo $usuario2->presentarse(); // "Soy Brenda, vivo en Mérida."
?>

El parámetro $ciudad = "Cancún" es opcional con valor por defecto.

Encapsulamiento: public, private y protected

El encapsulamiento controla quién puede acceder a los atributos y métodos de una clase. En PHP hay tres niveles de acceso:

  • public: accesible desde cualquier lugar
  • private: solo accesible desde dentro de la misma clase
  • protected: accesible desde la clase y sus clases hijas
<?php
class CuentaBancaria {
  public string $titular;
  private float $saldo;        // solo esta clase puede tocarlo
  protected string $moneda;   // esta clase y sus hijas
  
  public function __construct(string $titular, float $saldo, string $moneda = "MXN") {
    $this->titular = $titular;
    $this->saldo   = $saldo;
    $this->moneda  = $moneda;
  }
  
  public function getSaldo(): float {
    return $this->saldo;
  }
  
  public function depositar(float $monto): void {
    if ($monto > 0) {
      $this->saldo += $monto;
    }
  }
  
  public function retirar(float $monto): bool {
    if ($monto > 0 && $monto <= $this->saldo) {
      $this->saldo -= $monto;
      return true;
    }
    return false;
  }
}

$cuenta = new CuentaBancaria("Abraham", 5000.00);
echo $cuenta->getSaldo(); // 5000
$cuenta->depositar(1000);
echo $cuenta->getSaldo(); // 6000

// Esto daría ERROR (saldo es private):
// echo $cuenta->saldo;
?>

Getters y Setters

Cuando un atributo es private pero necesitas acceder o modificarlo desde fuera, usas getters (para leer) y setters (para escribir):

<?php
class Producto {
  private string $nombre;
  private float $precio;
  
  public function __construct(string $nombre, float $precio) {
    $this->nombre = $nombre;
    $this->setPrecio($precio); // usamos el setter para validar
  }
  
  // Getter
  public function getNombre(): string {
    return $this->nombre;
  }
  
  // Getter
  public function getPrecio(): float {
    return $this->precio;
  }
  
  // Setter con validación
  public function setPrecio(float $precio): void {
    if ($precio < 0) {
      throw new InvalidArgumentException("El precio no puede ser negativo");
    }
    $this->precio = $precio;
  }
  
  public function getPrecioFormateado(): string {
    return "$ " . number_format($this->precio, 2) . " {MXN}";
  }
}

$producto = new Producto("Laptop", 15999.99);
echo $producto->getNombre();           // Laptop
echo $producto->getPrecioFormateado(); // $ 15,999.99 {MXN}
$producto->setPrecio(14500.00);
echo $producto->getPrecio();           // 14500
?>

La ventaja del setter: puedes agregar validaciones y lógica sin que quien usa la clase tenga que preocuparse por ello.

Herencia con extends

La herencia permite que una clase “herede” atributos y métodos de otra clase. La clase padre se llama superclase o clase base, y la que hereda se llama subclase o clase hija.

<?php
// Clase padre (superclase)
class Animal {
  public string $nombre;
  public string $especie;
  
  public function __construct(string $nombre, string $especie) {
    $this->nombre  = $nombre;
    $this->especie = $especie;
  }
  
  public function describir(): string {
    return "{$this->nombre} es un {$this->especie}.";
  }
  
  public function hacerSonido(): string {
    return "...";
  }
}

// Clase hija — hereda todo de Animal
class Perro extends Animal {
  public string $raza;
  
  public function __construct(string $nombre, string $raza) {
    parent::__construct($nombre, "perro"); // llamamos al constructor del padre
    $this->raza = $raza;
  }
  
  public function hacerSonido(): string {
    return "¡Guau!";
  }
  
  public function traer(): string {
    return "{$this->nombre} va a buscar la pelota.";
  }
}

class Gato extends Animal {
  public function __construct(string $nombre) {
    parent::__construct($nombre, "gato");
  }
  
  public function hacerSonido(): string {
    return "¡Miau!";
  }
}

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

echo $perro->describir();   // "Rex es un perro." (heredado del padre)
echo $perro->hacerSonido(); // "¡Guau!" (sobreescrito)
echo $perro->traer();       // "Rex va a buscar la pelota." (propio)

echo $gato->describir();    // "Misu es un gato."
echo $gato->hacerSonido();  // "¡Miau!"
?>

La palabra clave parent

parent:: se usa para llamar a métodos de la clase padre desde la clase hija:

<?php
class Empleado extends Usuario {
  public string $puesto;
  public float $salario;
  
  public function __construct(
    string $nombre,
    string $email,
    string $puesto,
    float  $salario
  ) {
    parent::__construct($nombre, $email); // construye la parte de Usuario
    $this->puesto  = $puesto;
    $this->salario = $salario;
  }
  
  public function presentarse(): string {
    $intro = parent::presentarse(); // usa la presentación del padre
    return $intro . " Trabajo como {$this->puesto}.";
  }
}
?>

Sobrescribir métodos (Override)

Cuando una clase hija define un método con el mismo nombre que el padre, sobrescribe el comportamiento original:

<?php
class Notificacion {
  public function enviar(string $mensaje): void {
    echo "Enviando: $mensaje";
  }
}

class NotificacionEmail extends Notificacion {
  private string $destinatario;
  
  public function __construct(string $destinatario) {
    $this->destinatario = $destinatario;
  }
  
  public function enviar(string $mensaje): void {
    // sobreescribe el método del padre
    echo "Enviando email a {$this->destinatario}: $mensaje";
  }
}

class NotificacionSMS extends Notificacion {
  private string $telefono;
  
  public function __construct(string $telefono) {
    $this->telefono = $telefono;
  }
  
  public function enviar(string $mensaje): void {
    echo "Enviando SMS al {$this->telefono}: $mensaje";
  }
}

$notificaciones = [
  new NotificacionEmail("hola@8devmx.com"),
  new NotificacionSMS("+529981234567"),
];

foreach ($notificaciones as $notif) {
  $notif->enviar("¡Tu pedido está listo!");
}
?>

Aquí vemos algo importante: podemos tratar objetos de tipos diferentes de forma uniforme porque todos heredan de Notificacion. A esto se le llama polimorfismo.

El encapsulamiento y la herencia son los pilares que hacen que la POO valga la pena. Una vez que los dominas, tu código se vuelve mucho más organizado, reutilizable y fácil de mantener.