🧱 Programación Orientada a Objetos

Clases y objetos

Una clase es un molde para crear objetos. Un objeto es una instancia de una clase:

<?php
class Coche {
    // Propiedades (atributos)
    public string $marca;
    public string $modelo;
    public int $velocidad = 0;

    // Constructor (se ejecuta al crear el objeto)
    public function __construct(string $marca, string $modelo) {
        $this->marca = $marca;
        $this->modelo = $modelo;
    }

    // Métodos
    public function acelerar(int $km): void {
        $this->velocidad += $km;
    }

    public function frenar(int $km): void {
        $this->velocidad = max(0, $this->velocidad - $km);
    }

    public function estado(): string {
        return "{$this->marca} {$this->modelo} → {$this->velocidad} km/h";
    }
}

// Crear objetos
$coche1 = new Coche("Seat", "Ibiza");
$coche2 = new Coche("Toyota", "Corolla");

$coche1->acelerar(50);
$coche1->acelerar(30);
$coche1->frenar(20);
echo $coche1->estado(); // Seat Ibiza → 60 km/h
?>

Constructor simplificado (PHP 8+)

En PHP 8, los parámetros del constructor pueden declarar propiedades directamente:

<?php
class Producto {
    public function __construct(
        public string $nombre,
        public float $precio,
        public int $stock = 0
    ) {}

    public function aplicar_descuento(float $porcentaje): void {
        $this->precio *= (1 - $porcentaje / 100);
    }
}

$p = new Producto("Portátil", 999.99, 10);
echo $p->nombre;  // Portátil
$p->aplicar_descuento(10);
echo $p->precio;   // 899.991
?>

Visibilidad: public, protected, private

ModificadorDesde la claseDesde clases hijasDesde fuera
public
protected
private

Herencia

Una clase hija hereda métodos y propiedades de la clase padre con extends:

<?php
class Vehiculo {
    public function __construct(
        protected string $marca,
        protected string $modelo
    ) {}

    public function arrancar(): string {
        return "{$this->marca} {$this->modelo} arrancando...";
    }
}

class Moto extends Vehiculo {
    private bool $tieneSidecar = false;

    public function __construct(string $marca, string $modelo, bool $sidecar = false) {
        parent::__construct($marca, $modelo);
        $this->tieneSidecar = $sidecar;
    }

    public function hacer_caballito(): string {
        return "{$this->marca} haciendo caballito! 🏍️";
    }

    // Override del método padre
    public function arrancar(): string {
        return parent::arrancar() . " ¡BRUM BRUM!";
    }
}

$moto = new Moto("Honda", "CBR600");
echo $moto->arrancar(); // Honda CBR600 arrancando... ¡BRUM BRUM!
echo $moto->hacer_caballito();
?>

Clases abstractas

No se pueden instanciar directamente, solo sirven como base para otras clases. Definen métodos que las hijas deben implementar:

<?php
abstract class Animal {
    protected string $nombre;

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

    // Método abstracto: las hijas deben implementarlo
    abstract public function sonido(): string;

    // Método concreto: se hereda tal cual
    public function dormir(): string {
        return "{$this->nombre} está durmiendo... zzz";
    }
}

class Perro extends Animal {
    public function sonido(): string {
        return "¡Guau guau!";
    }
}

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

$animales = [new Perro("Rex"), new Gato("Garfield")];
foreach ($animales as $a) {
    echo $a->sonido() . "\n";
}
?>

Interfaces

Definen un contrato: qué métodos debe tener una clase, pero no cómo se implementan:

<?php
interface Imprimible {
    public function imprimir(): string;
}

interface Exportable {
    public function exportar(string $formato): string;
}

// Una clase puede implementar múltiples interfaces
class Factura implements Imprimible, Exportable {
    public function __construct(
        private int $numero,
        private float $total
    ) {}

    public function imprimir(): string {
        return "Factura #{$this->numero}: {$this->total}€";
    }

    public function exportar(string $formato): string {
        if ($formato === "json") {
            return json_encode(["num" => $this->numero, "total" => $this->total]);
        }
        return $this->imprimir();
    }
}

$f = new Factura(123, 450.50);
echo $f->imprimir();
$f instanceof Imprimible; // true
$f instanceof Exportable; // true
?>

Trait

Los traits permiten reutilizar métodos entre clases sin herencia (evitando la duplicación de código):

<?php
trait Logeable {
    public function log(string $mensaje): void {
        echo "[LOG: " . date("Y-m-d H:i:s") . "] $mensaje\n";
    }
}

trait Timestampable {
    public function getTimestamp(): string {
        return date("Y-m-d H:i:s");
    }
}

class Usuario {
    use Logeable, Timestampable;

    public function __construct(private string $nombre) {}

    public function guardar(): void {
        $this->log("Guardando usuario {$this->nombre}");
        // ... lógica de guardado ...
    }
}

$user = new Usuario("Ana");
$user->guardar();
echo $user->getTimestamp();
?>

Namespaces

Evitan colisiones de nombres organizando las clases en espacios:

<?php
// Archivo: src/Modelos/Usuario.php
namespace App\Modelos;

class Usuario {
    public function __construct(public string $nombre) {}
}

// Archivo: src/Controladores/UsuarioController.php
namespace App\Controladores;

use App\Modelos\Usuario as ModeloUsuario;

class UsuarioController {
    public function mostrar(): void {
        $user = new ModeloUsuario("Ana");
        echo $user->nombre;
    }
}

// Archivo: index.php (punto de entrada)
require_once "src/Modelos/Usuario.php";
require_once "src/Controladores/UsuarioController.php";

use App\Controladores\UsuarioController;

$controller = new UsuarioController();
$controller->mostrar();
?>

Autoload con Composer

En lugar de usar require manualmente, Composer carga las clases automáticamente:

{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

// composer dump-autoload

// En index.php, una sola línea carga todo:
require_once __DIR__ . "/vendor/autoload.php";

use App\Modelos\Usuario;
use App\Controladores\UsuarioController;

$user = new Usuario("Carlos");
?>

Métodos y propiedades estáticas

Pertenecen a la clase, no a los objetos. Se accede con :::

<?php
class Config {
    private static array $valores = [
        "db_host" => "localhost",
        "db_name" => "tienda"
    ];

    public static function get(string $clave, mixed $default = null): mixed {
        return self::$valores[$clave] ?? $default;
    }

    public static function set(string $clave, mixed $valor): void {
        self::$valores[$clave] = $valor;
    }
}

echo Config::get("db_host"); // localhost
Config::set("app_version", "1.0");
echo Config::get("app_version"); // 1.0
?>

Métodos mágicos

<?php
class Persona {
    private array $datos = [];

    // __get: se llama al leer propiedades inaccesibles
    public function __get(string $nombre): mixed {
        return $this->datos[$nombre] ?? null;
    }

    // __set: se llama al escribir propiedades inaccesibles
    public function __set(string $nombre, mixed $valor): void {
        $this->datos[$nombre] = $valor;
    }

    // __toString: al hacer echo del objeto
    public function __toString(): string {
        return json_encode($this->datos);
    }

    // __call: al llamar un método que no existe
    public function __call(string $nombre, array $args): mixed {
        return "Método '$nombre' no definido";
    }
}

$p = new Persona();
$p->nombre = "Ana";       // __set
echo $p->nombre . "\n";   // __get → Ana
echo $p;                   // __toString
echo $p->metodoInexistente(); // __call
?>

Comparativa: clase abstracta vs interfaz

CaracterísticaClase abstractaInterfaz
Puede tener propiedades
Puede tener métodos con código❌ (solo firma)
Se puede heredar de varias❌ (solo una)✅ (múltiples)
Constructor

Ejercicios

  1. Crea una clase CuentaBancaria con propiedades titular, saldo y métodos ingresar(), retirar().
  2. Crea una clase abstracta Figura con un método abstracto area(): float. Implementa Circulo y Rectangulo.
  3. Define la interfaz JSONSerializable con método toJSON(): string e impleméntala en Articulo.
  4. Usa un trait para compartir un método validarEmail() entre Usuario y Cliente.
  5. Organiza las clases anteriores en namespaces y escribe el autoload de Composer.

← Volver al curso