Skip to content

9. Programación Orientada a Objetos

Programación Orientada a Objetos (POO) en PHP

Section titled “Programación Orientada a Objetos (POO) en PHP”

La Programación Orientada a Objetos (POO) es un paradigma de programación que utiliza “objetos” para modelar datos y comportamientos. PHP incorpora un modelo de objetos completo desde la versión 5, con características como clases, abstracción, encapsulamiento, herencia, polimorfismo y otras funcionalidades avanzadas.

Las clases son plantillas o “planos” que definen las características y comportamientos de un tipo de objeto. Los objetos son instancias de estas clases.

<?php
class Persona {
// Propiedades (atributos)
public $nombre;
public $edad;
public $email;
// Método constructor
public function __construct($nombre, $edad, $email) {
$this->nombre = $nombre;
$this->edad = $edad;
$this->email = $email;
}
// Métodos
public function saludar() {
return "Hola, mi nombre es {$this->nombre} y tengo {$this->edad} años.";
}
public function enviarEmail($mensaje) {
return "Enviando mensaje a {$this->email}: $mensaje";
}
}
?>
<?php
// Crear un objeto de la clase Persona
$persona1 = new Persona("María López", 28, "maria@ejemplo.com");
// Acceder a propiedades
echo $persona1->nombre; // Muestra: María López
echo $persona1->edad; // Muestra: 28
// Llamar a métodos
echo $persona1->saludar(); // Muestra: Hola, mi nombre es María López y tengo 28 años.
echo $persona1->enviarEmail("Hola, ¿cómo estás?"); // Muestra: Enviando mensaje a maria@ejemplo.com: Hola, ¿cómo estás?
// Crear otro objeto de la misma clase
$persona2 = new Persona("Carlos Rodríguez", 35, "carlos@ejemplo.com");
echo $persona2->saludar(); // Muestra: Hola, mi nombre es Carlos Rodríguez y tengo 35 años.
?>

El constructor (__construct()) es un método especial que se ejecuta automáticamente cuando se crea un objeto. El destructor (__destruct()) se ejecuta cuando el objeto se destruye o al final del script.

<?php
class Archivo {
private $manejador;
private $nombre;
public function __construct($nombre) {
$this->nombre = $nombre;
$this->manejador = fopen($nombre, 'w');
echo "Archivo {$nombre} abierto.<br>";
}
public function escribir($contenido) {
fwrite($this->manejador, $contenido);
}
public function __destruct() {
fclose($this->manejador);
echo "Archivo {$this->nombre} cerrado.<br>";
}
}
// Uso
$archivo = new Archivo('ejemplo.txt');
$archivo->escribir('Hola Mundo');
// Al finalizar el script, se llama automáticamente al destructor
?>

La palabra clave $this hace referencia al objeto actual y se utiliza para acceder a las propiedades y métodos del objeto desde dentro de la clase.

<?php
class Contador {
private $valor = 0;
public function incrementar() {
$this->valor++;
return $this; // Devuelve el objeto actual para encadenamiento de métodos
}
public function decrementar() {
$this->valor--;
return $this; // Devuelve el objeto actual para encadenamiento de métodos
}
public function obtenerValor() {
return $this->valor;
}
}
$contador = new Contador();
// Encadenamiento de métodos (method chaining)
echo $contador->incrementar()->incrementar()->decrementar()->obtenerValor(); // Muestra: 1
?>

Los métodos estáticos pertenecen a la clase, no a una instancia específica, y pueden ser llamados sin crear un objeto.

<?php
class Matematicas {
// Método estático
public static function sumar($a, $b) {
return $a + $b;
}
public static function restar($a, $b) {
return $a - $b;
}
}
// Llamar a métodos estáticos sin crear un objeto
echo Matematicas::sumar(5, 3); // Muestra: 8
echo Matematicas::restar(10, 4); // Muestra: 6
?>

Las constantes de clase mantienen valores que no cambian durante la ejecución del script.

<?php
class Config {
// Definir constantes de clase
const VERSION = '1.0';
const DB_HOST = 'localhost';
const DB_NAME = 'mi_base_datos';
public static function getVersion() {
// Acceder a una constante dentro de la clase
return self::VERSION;
}
}
// Acceder a constantes de clase sin crear un objeto
echo Config::VERSION; // Muestra: 1.0
echo Config::DB_HOST; // Muestra: localhost
echo Config::getVersion(); // Muestra: 1.0
?>

Las clases en PHP contienen propiedades (variables) y métodos (funciones). Estos elementos definen el estado y comportamiento de los objetos.

Las propiedades son variables que pertenecen a una clase. Pueden tener diferentes niveles de visibilidad (public, protected, private) y pueden ser inicializadas con valores por defecto.

<?php
class Producto {
// Propiedades con diferentes niveles de visibilidad
public $nombre; // Accesible desde cualquier lugar
protected $precio; // Accesible desde la clase y sus subclases
private $codigo; // Accesible solo desde esta clase
// Propiedades con valores por defecto
public $disponible = true;
public $stock = 0;
// Propiedades tipadas (PHP 7.4+)
public string $categoria = 'General';
public float $impuesto = 0.21;
// Propiedades estáticas (pertenecen a la clase, no a instancias)
public static $contador = 0;
public function __construct($nombre, $precio, $codigo) {
$this->nombre = $nombre;
$this->precio = $precio;
$this->codigo = $codigo;
// Incrementar el contador estático
self::$contador++;
}
// Getter para acceder a una propiedad privada
public function getCodigo() {
return $this->codigo;
}
// Setter para modificar una propiedad protegida
public function setPrecio($precio) {
if ($precio > 0) {
$this->precio = $precio;
return true;
}
return false;
}
// Método para acceder a una propiedad protegida
public function getPrecio() {
return $this->precio;
}
// Método estático para obtener el contador
public static function getContador() {
return self::$contador;
}
}
// Uso
$producto1 = new Producto('Laptop', 1200, 'LP001');
$producto2 = new Producto('Teléfono', 800, 'TF002');
echo $producto1->nombre; // Acceso directo a propiedad pública
// echo $producto1->precio; // Error: No se puede acceder a una propiedad protegida
// echo $producto1->codigo; // Error: No se puede acceder a una propiedad privada
echo $producto1->getPrecio(); // Acceso a través de método getter
echo $producto1->getCodigo(); // Acceso a través de método getter
$producto1->setPrecio(1300); // Modificación a través de método setter
// Acceso a propiedad estática
echo Producto::$contador; // Muestra: 2
echo Producto::getContador(); // Muestra: 2
?>

Desde PHP 7.4, se pueden declarar propiedades con tipos específicos:

<?php
class Usuario {
public int $id;
public string $nombre;
public ?string $email; // El signo ? permite que sea null
public array $roles = [];
public bool $activo = true;
public float $saldo = 0.0;
public function __construct(int $id, string $nombre) {
$this->id = $id;
$this->nombre = $nombre;
}
}
$usuario = new Usuario(1, "Ana");
$usuario->email = "ana@ejemplo.com";
$usuario->roles = ["editor", "admin"];
// $usuario->id = "uno"; // Error: Debe ser un entero
// $usuario->nombre = 123; // Error: Debe ser una cadena
?>

Los métodos son funciones que pertenecen a una clase y definen su comportamiento.

<?php
class Calculadora {
// Método simple
public function sumar($a, $b) {
return $a + $b;
}
// Método con parámetros por defecto
public function multiplicar($a, $b = 2) {
return $a * $b;
}
// Método con parámetros tipados y valor de retorno tipado (PHP 7+)
public function dividir(float $a, float $b): float {
if ($b == 0) {
throw new Exception("No se puede dividir por cero");
}
return $a / $b;
}
// Método privado (solo accesible dentro de la clase)
private function validar($numero) {
return is_numeric($numero);
}
// Método que usa un método privado
public function calcular($a, $b, $operacion) {
if (!$this->validar($a) || !$this->validar($b)) {
return "Error: Los valores deben ser números";
}
switch ($operacion) {
case 'suma':
return $this->sumar($a, $b);
case 'multiplicacion':
return $this->multiplicar($a, $b);
case 'division':
try {
return $this->dividir($a, $b);
} catch (Exception $e) {
return "Error: " . $e->getMessage();
}
default:
return "Operación no válida";
}
}
// Método estático
public static function potencia($base, $exponente) {
return pow($base, $exponente);
}
}
// Uso
$calc = new Calculadora();
echo $calc->sumar(5, 3); // Muestra: 8
echo $calc->multiplicar(4); // Muestra: 8 (usa el valor por defecto b=2)
echo $calc->dividir(10, 2); // Muestra: 5
// echo $calc->validar(5); // Error: Método privado no accesible
echo $calc->calcular(6, 3, 'suma'); // Muestra: 9
echo $calc->calcular(6, 0, 'division'); // Muestra: Error: No se puede dividir por cero
// Llamada a método estático
echo Calculadora::potencia(2, 3); // Muestra: 8
?>

PHP proporciona métodos mágicos que se ejecutan automáticamente en respuesta a ciertos eventos:

<?php
class Articulo {
private $datos = [];
// Constructor
public function __construct($titulo, $contenido) {
$this->datos['titulo'] = $titulo;
$this->datos['contenido'] = $contenido;
$this->datos['fecha'] = date('Y-m-d');
}
// Se llama al intentar acceder a propiedades no accesibles o inexistentes
public function __get($nombre) {
if (array_key_exists($nombre, $this->datos)) {
return $this->datos[$nombre];
}
return null;
}
// Se llama al intentar asignar un valor a propiedades no accesibles o inexistentes
public function __set($nombre, $valor) {
$this->datos[$nombre] = $valor;
}
// Se llama al usar isset() o empty() en propiedades no accesibles o inexistentes
public function __isset($nombre) {
return isset($this->datos[$nombre]);
}
// Se llama al usar unset() en propiedades no accesibles o inexistentes
public function __unset($nombre) {
unset($this->datos[$nombre]);
}
// Se llama cuando se intenta convertir el objeto a string
public function __toString() {
return "Artículo: {$this->datos['titulo']} ({$this->datos['fecha']})";
}
// Se llama cuando se intenta llamar al objeto como una función
public function __invoke($param) {
return "Invocando artículo con parámetro: $param";
}
// Se llama al serializar el objeto
public function __sleep() {
// Devuelve un array con los nombres de las propiedades a serializar
return ['datos'];
}
// Se llama al deserializar el objeto
public function __wakeup() {
// Código de inicialización después de deserializar
if (!isset($this->datos['fecha'])) {
$this->datos['fecha'] = date('Y-m-d');
}
}
// Se llama al clonar el objeto
public function __clone() {
$this->datos['titulo'] = "Copia de " . $this->datos['titulo'];
$this->datos['fecha'] = date('Y-m-d');
}
}
// Uso
$articulo = new Articulo("Introducción a PHP", "PHP es un lenguaje de programación...");
// __get y __set
echo $articulo->titulo; // Accede mediante __get
$articulo->autor = "María"; // Asigna mediante __set
echo $articulo->autor; // Accede mediante __get
// __isset y __unset
var_dump(isset($articulo->titulo)); // true
unset($articulo->autor);
var_dump(isset($articulo->autor)); // false
// __toString
echo $articulo; // Muestra: Artículo: Introducción a PHP (2023-08-06)
// __invoke
echo $articulo("parámetro"); // Muestra: Invocando artículo con parámetro: parámetro
// __clone
$copia = clone $articulo;
echo $copia->titulo; // Muestra: Copia de Introducción a PHP
?>

Estos tres conceptos son pilares fundamentales de la programación orientada a objetos y PHP los implementa de manera completa.

El encapsulamiento es el principio de ocultar los detalles internos de una clase y exponer solo lo necesario. Se implementa mediante los modificadores de acceso: public, protected y private.

<?php
class CuentaBancaria {
// Propiedades encapsuladas (privadas)
private $numeroCuenta;
private $saldo;
private $propietario;
private $tipo;
// Constructor
public function __construct($propietario, $tipo = 'Ahorro') {
$this->propietario = $propietario;
$this->tipo = $tipo;
$this->saldo = 0;
$this->numeroCuenta = $this->generarNumeroCuenta();
}
// Método privado - solo accesible dentro de la clase
private function generarNumeroCuenta() {
// En una aplicación real, esto sería más complejo
return 'CTA-' . rand(10000, 99999);
}
// Métodos públicos para interactuar con las propiedades privadas
public function depositar($cantidad) {
if ($cantidad > 0) {
$this->saldo += $cantidad;
return true;
}
return false;
}
public function retirar($cantidad) {
if ($cantidad > 0 && $cantidad <= $this->saldo) {
$this->saldo -= $cantidad;
return true;
}
return false;
}
// Getters - permiten acceso de solo lectura a propiedades privadas
public function getNumeroCuenta() {
return $this->numeroCuenta;
}
public function getSaldo() {
return $this->saldo;
}
public function getPropietario() {
return $this->propietario;
}
public function getTipo() {
return $this->tipo;
}
// Setter - permite modificar una propiedad privada con validación
public function setPropietario($propietario) {
if (strlen($propietario) > 0) {
$this->propietario = $propietario;
return true;
}
return false;
}
}
// Uso
$cuenta = new CuentaBancaria("Ana Martínez");
// Interacción a través de métodos públicos
echo $cuenta->getNumeroCuenta(); // Acceso a propiedad privada mediante getter
echo $cuenta->getSaldo(); // Muestra: 0
$cuenta->depositar(1000); // Modifica el saldo mediante método público
echo $cuenta->getSaldo(); // Muestra: 1000
$cuenta->retirar(300); // Modifica el saldo mediante método público
echo $cuenta->getSaldo(); // Muestra: 700
// No se puede acceder directamente a las propiedades privadas
// echo $cuenta->saldo; // Error: Cannot access private property
// $cuenta->numeroCuenta = "ABC"; // Error: Cannot access private property
// Modificación controlada mediante setter
$cuenta->setPropietario("Ana López Martínez");
echo $cuenta->getPropietario(); // Muestra: Ana López Martínez
?>

La herencia permite que una clase (subclase o clase hija) herede propiedades y métodos de otra clase (superclase o clase padre). PHP solo admite herencia simple (una clase solo puede heredar de una clase padre).

<?php
// Clase base o padre
class Vehiculo {
protected $marca;
protected $modelo;
protected $color;
protected $anio;
public function __construct($marca, $modelo, $color, $anio) {
$this->marca = $marca;
$this->modelo = $modelo;
$this->color = $color;
$this->anio = $anio;
}
public function getInfo() {
return "Vehículo: {$this->marca} {$this->modelo} ({$this->anio}) - Color: {$this->color}";
}
public function arrancar() {
return "El vehículo ha arrancado";
}
public function detener() {
return "El vehículo se ha detenido";
}
}
// Clase derivada o hija
class Automovil extends Vehiculo {
private $puertas;
private $transmision;
public function __construct($marca, $modelo, $color, $anio, $puertas, $transmision) {
// Llamar al constructor de la clase padre
parent::__construct($marca, $modelo, $color, $anio);
// Inicializar propiedades específicas de esta clase
$this->puertas = $puertas;
$this->transmision = $transmision;
}
// Sobrescribir un método de la clase padre
public function getInfo() {
// Llamar al método de la clase padre
$infoBase = parent::getInfo();
// Añadir información específica
return $infoBase . ", Puertas: {$this->puertas}, Transmisión: {$this->transmision}";
}
// Método específico de esta clase
public function abrirMaletero() {
return "Maletero abierto";
}
}
// Otra clase derivada
class Motocicleta extends Vehiculo {
private $cilindrada;
public function __construct($marca, $modelo, $color, $anio, $cilindrada) {
parent::__construct($marca, $modelo, $color, $anio);
$this->cilindrada = $cilindrada;
}
public function getInfo() {
return parent::getInfo() . ", Cilindrada: {$this->cilindrada}cc";
}
public function hacerCaballito() {
return "La moto está haciendo un caballito";
}
}
// Uso
$auto = new Automovil("Toyota", "Corolla", "Rojo", 2022, 4, "Automática");
echo $auto->getInfo(); // Muestra: Vehículo: Toyota Corolla (2022) - Color: Rojo, Puertas: 4, Transmisión: Automática
echo $auto->arrancar(); // Método heredado: El vehículo ha arrancado
echo $auto->abrirMaletero(); // Método propio: Maletero abierto
$moto = new Motocicleta("Honda", "CBR", "Negro", 2021, 600);
echo $moto->getInfo(); // Muestra: Vehículo: Honda CBR (2021) - Color: Negro, Cilindrada: 600cc
echo $moto->hacerCaballito(); // Método propio: La moto está haciendo un caballito
?>

El polimorfismo permite que objetos de diferentes clases respondan de manera diferente al mismo método. En PHP, se implementa principalmente a través de la herencia y las interfaces.

<?php
// Clase abstracta - No se puede instanciar directamente
abstract class Forma {
protected $color;
public function __construct($color) {
$this->color = $color;
}
// Método abstracto - debe ser implementado por las clases hijas
abstract public function calcularArea();
// Método concreto - común para todas las formas
public function getColor() {
return $this->color;
}
}
// Implementaciones concretas
class Circulo extends Forma {
private $radio;
public function __construct($color, $radio) {
parent::__construct($color);
$this->radio = $radio;
}
public function calcularArea() {
return pi() * pow($this->radio, 2);
}
public function getRadio() {
return $this->radio;
}
}
class Rectangulo extends Forma {
private $ancho;
private $alto;
public function __construct($color, $ancho, $alto) {
parent::__construct($color);
$this->ancho = $ancho;
$this->alto = $alto;
}
public function calcularArea() {
return $this->ancho * $this->alto;
}
}
class Triangulo extends Forma {
private $base;
private $altura;
public function __construct($color, $base, $altura) {
parent::__construct($color);
$this->base = $base;
$this->altura = $altura;
}
public function calcularArea() {
return ($this->base * $this->altura) / 2;
}
}
// Función que trabaja con cualquier objeto que sea una Forma
function imprimirAreaForma(Forma $forma) {
echo "Esta forma de color {$forma->getColor()} tiene un área de: {$forma->calcularArea()} unidades cuadradas.<br>";
}
// Uso - Polimorfismo en acción
$circulo = new Circulo("Rojo", 5);
$rectangulo = new Rectangulo("Azul", 4, 6);
$triangulo = new Triangulo("Verde", 3, 8);
// La misma función trabaja con diferentes tipos de formas
imprimirAreaForma($circulo); // Muestra: Esta forma de color Rojo tiene un área de: 78.539816339745 unidades cuadradas.
imprimirAreaForma($rectangulo); // Muestra: Esta forma de color Azul tiene un área de: 24 unidades cuadradas.
imprimirAreaForma($triangulo); // Muestra: Esta forma de color Verde tiene un área de: 12 unidades cuadradas.
// Array de diferentes formas
$formas = [$circulo, $rectangulo, $triangulo];
// Iterar y calcular el área de cada forma
foreach ($formas as $forma) {
echo "El área es: " . $forma->calcularArea() . "<br>";
}
?>

Las clases abstractas y las interfaces son herramientas fundamentales para implementar polimorfismo en PHP.

<?php
// Clase abstracta
abstract class Animal {
protected $nombre;
public function __construct($nombre) {
$this->nombre = $nombre;
}
// Método concreto
public function getNombre() {
return $this->nombre;
}
// Métodos abstractos - deben ser implementados por las clases hijas
abstract public function hacerSonido();
abstract public function moverse();
}
class Perro extends Animal {
private $raza;
public function __construct($nombre, $raza) {
parent::__construct($nombre);
$this->raza = $raza;
}
public function hacerSonido() {
return "¡Guau guau!";
}
public function moverse() {
return "Corriendo en cuatro patas";
}
public function getRaza() {
return $this->raza;
}
}
class Pajaro extends Animal {
private $puedeVolar;
public function __construct($nombre, $puedeVolar = true) {
parent::__construct($nombre);
$this->puedeVolar = $puedeVolar;
}
public function hacerSonido() {
return "¡Pío pío!";
}
public function moverse() {
if ($this->puedeVolar) {
return "Volando por el aire";
}
return "Saltando en el suelo";
}
}
// Uso
$perro = new Perro("Rex", "Pastor Alemán");
echo $perro->getNombre() . " dice: " . $perro->hacerSonido() . "<br>";
echo $perro->getNombre() . " se mueve: " . $perro->moverse() . "<br>";
$pajaro = new Pajaro("Piolin");
echo $pajaro->getNombre() . " dice: " . $pajaro->hacerSonido() . "<br>";
echo $pajaro->getNombre() . " se mueve: " . $pajaro->moverse() . "<br>";
// No se puede instanciar una clase abstracta
// $animal = new Animal("Generico"); // Error
?>

PHP proporciona mecanismos para cargar clases automáticamente y organizar el código en espacios de nombres, lo que facilita la creación de aplicaciones grandes y bien estructuradas.

El autoload permite cargar clases automáticamente cuando se necesitan, sin tener que incluir manualmente cada archivo de clase con require o include.

<?php
// Función de autoload personalizada
spl_autoload_register(function ($nombreClase) {
// Convertir el nombre de la clase a una ruta de archivo
$archivo = __DIR__ . '/clases/' . $nombreClase . '.php';
// Verificar si el archivo existe
if (file_exists($archivo)) {
require_once $archivo;
return true;
}
return false;
});
// Ahora podemos usar clases sin incluir manualmente sus archivos
$producto = new Producto('Laptop', 1200);
$usuario = new Usuario('Juan', 'juan@ejemplo.com');
?>

En proyectos modernos de PHP, se suele utilizar Composer para gestionar dependencias y autoload:

mi_proyecto/
├── composer.json
├── vendor/ (generado por Composer)
├── src/
│ ├── MiNamespace/
│ │ ├── Modelo/
│ │ │ ├── Usuario.php
│ │ │ └── Producto.php
│ │ └── Servicio/
│ │ └── Autenticacion.php
│ └── Utilidades/
│ └── Validador.php
└── public/
└── index.php

Los namespaces (espacios de nombres) permiten organizar el código y evitar conflictos de nombres entre clases, funciones y constantes.

Producto.php
<?php
namespace TiendaInventario;
class Producto {
private $nombre;
private $precio;
public function __construct($nombre, $precio) {
$this->nombre = $nombre;
$this->precio = $precio;
}
public function getPrecio() {
return $this->precio;
}
}
// Archivo: Cliente.php
namespace TiendaUsuarios;
class Cliente {
private $nombre;
private $email;
public function __construct($nombre, $email) {
$this->nombre = $nombre;
$this->email = $email;
}
}
// Archivo: index.php
// Incluir los archivos (en un proyecto real usaríamos autoload)
require_once 'Producto.php';
require_once 'Cliente.php';
// Usar clases con namespace completo
$producto = new TiendaInventarioProducto('Laptop', 1200);
// O con la declaración use
use TiendaUsuariosCliente;
$cliente = new Cliente('Juan', 'juan@ejemplo.com');
// Usar alias para evitar conflictos
use TiendaInventarioProducto as ProductoInventario;
use OtroNamespaceProducto as OtroProducto;
$prod1 = new ProductoInventario('Teclado', 50);
$prod2 = new OtroProducto();
?>

La Programación Orientada a Objetos en PHP proporciona herramientas poderosas para crear código modular, reutilizable y mantenible. Aquí hay algunas mejores prácticas a seguir:

  1. Encapsulamiento

    • Usa modificadores de acceso (private, protected, public) adecuadamente
    • Implementa getters y setters para controlar el acceso a propiedades
    • Valida los datos antes de asignarlos a propiedades
  2. Herencia

    • Usa herencia solo cuando exista una relación “es un” entre clases
    • Evita jerarquías de herencia profundas (más de 2-3 niveles)
    • Prefiere la composición sobre la herencia cuando sea posible
  3. Interfaces y Clases Abstractas

    • Usa interfaces para definir contratos que varias clases deben implementar
    • Usa clases abstractas para compartir código entre clases relacionadas
    • Diseña interfaces cohesivas y con un propósito único (principio de segregación de interfaces)
  4. Organización del Código

    • Usa namespaces para organizar las clases lógicamente
    • Sigue estándares como PSR-4 para la estructura de directorios y autoload
    • Una clase por archivo, con el nombre del archivo igual al nombre de la clase
  5. Principios SOLID

    • Single Responsibility: Una clase debe tener una sola responsabilidad
    • Open/Closed: Las clases deben estar abiertas para extensión pero cerradas para modificación
    • Liskov Substitution: Los objetos de una subclase deben poder sustituir a los de la superclase sin afectar la funcionalidad
    • Interface Segregation: Muchas interfaces específicas son mejores que una interfaz general
    • Dependency Inversion: Depender de abstracciones, no de implementaciones concretas
🐝