PHP · POO · Arquitectura

POO en PHP: clases, interfaces y composición paso a paso

La programación orientada a objetos en PHP permite organizar aplicaciones alrededor de responsabilidades, objetos y contratos, en lugar de acumular funciones y variables sin una estructura clara. Aprender POO no significa crear una clase para todo: significa comprender cuándo una clase ayuda, cómo encapsular estado, cómo separar responsabilidades y cómo hacer colaborar diferentes componentes sin generar dependencias innecesarias.

Clases · Objetos Interfaces · Composición Dependencias · Arquitectura
Respuesta rápida

¿Qué es la programación orientada a objetos en PHP?

La POO en PHP es una forma de organizar programas mediante clases y objetos que agrupan información y comportamiento relacionado.

Una clase define una estructura y un objeto representa una instancia concreta de esa estructura.

Pero la POO profesional también incluye conceptos como encapsulación, interfaces, abstracción, composición, dependencias y separación de responsabilidades.

Su objetivo no es escribir más código, sino conseguir que un sistema sea más fácil de entender, modificar, probar y mantener cuando crece.

Publicidad
Conceptos principales

La POO en PHP se entiende mejor como un conjunto de decisiones de diseño

01

Clases

Definen estructura y comportamiento.

02

Objetos

Representan instancias concretas de una clase.

03

Encapsulación

Controla qué detalles se exponen.

04

Interfaces

Definen contratos entre componentes.

05

Composición

Construye objetos mediante colaboración.

06

Dependencias

Conecta servicios de forma explícita.

Publicidad
Antes de empezar

Aprende funciones antes de intentar resolver todo con clases

Si todavía estás aprendiendo variables, condiciones, arrays y funciones, no necesitas convertir inmediatamente cada ejercicio en orientación a objetos.

Las funciones ya enseñan una idea fundamental: separar una tarea en una responsabilidad concreta.

Primero comprende entradas y salidas

Una función recibe datos, realiza una operación y puede devolver un resultado.

Esa misma idea aparecerá después dentro de métodos y objetos.

¿Todavía estás empezando?

Domina primero los fundamentos de PHP

Variables, funciones, arrays, formularios y errores.

Aprender PHP desde cero →
Clases

Una clase define qué datos y operaciones pertenecen a un concepto

Imagina una aplicación que administra productos.

Un producto puede tener nombre, precio y determinadas operaciones.

Producto.php PHP
<?php

class Producto
{
    public string $nombre;
    public float $precio;
}

La clase describe qué propiedades puede tener un producto.

Pero todavía no tenemos un producto concreto

Para eso necesitamos crear un objeto.

Publicidad
Objetos

Un objeto es una instancia concreta de una clase

Objeto PHP
<?php

$producto = new Producto();

$producto->nombre = 'Teclado';
$producto->precio = 19990;

Ahora $producto representa un objeto concreto.

Podrías crear otro objeto de la misma clase con valores diferentes.

Otra instancia PHP
<?php

$otroProducto = new Producto();

$otroProducto->nombre = 'Monitor';
$otroProducto->precio = 129990;

Ambos objetos utilizan la misma definición, pero representan productos distintos.

Constructor

El constructor permite crear objetos en un estado válido desde el comienzo

En el ejemplo anterior, podríamos crear un producto vacío y olvidar asignarle un precio.

Podemos exigir determinados datos durante la construcción.

Constructor PHP
<?php

class Producto
{
    public function __construct(
        public string $nombre,
        public float $precio
    ) {}
}

$producto = new Producto(
    'Teclado',
    19990
);

Ahora nombre y precio son obligatorios

El objeto no puede construirse correctamente sin proporcionar esos valores.

Encapsulación

public, protected y private controlan qué partes del objeto pueden utilizarse desde fuera

Hacer todas las propiedades públicas permite modificarlas desde cualquier lugar.

Eso puede convertirse en un problema cuando necesitas proteger determinadas reglas.

public

Puede utilizarse desde fuera de la clase.

private

Solo puede accederse directamente desde la propia clase.

protected

También puede estar disponible dentro de clases derivadas.

Encapsulación PHP
<?php

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

    public function obtenerNombre(): string
    {
        return $this->nombre;
    }

    public function obtenerPrecio(): float
    {
        return $this->precio;
    }
}

Ahora el estado interno queda controlado por la clase.

Comportamiento

Un objeto debería proteger sus propias reglas cuando sea razonable

Supongamos que el precio nunca debería ser negativo.

Regla de dominio PHP
<?php

class Producto
{
    private float $precio;

    public function __construct(
        private string $nombre,
        float $precio
    ) {
        $this->cambiarPrecio($precio);
    }

    public function cambiarPrecio(
        float $precio
    ): void {

        if ($precio < 0) {
            throw new InvalidArgumentException(
                'El precio no puede ser negativo.'
            );
        }

        $this->precio = $precio;
    }

    public function obtenerPrecio(): float
    {
        return $this->precio;
    }
}

La clase ahora protege una condición

No dependemos de recordar esa validación cada vez que modificamos un precio.

Encapsular no significa crear getters y setters para absolutamente todo.

El objetivo es proteger invariantes y comportamiento, no añadir métodos de manera mecánica.

Publicidad
$this

$this representa el objeto actual dentro de sus métodos

Cuando escribimos:

$this PHP
$this->precio

estamos accediendo a la propiedad precio del objeto que actualmente ejecuta el método.

Si existen veinte objetos Producto, cada uno mantiene su propio estado.

Interfaces

Una interfaz define qué debe poder hacer un componente sin decidir cómo lo hará

Imagina una aplicación que necesita enviar notificaciones.

Puede enviarlas por email, SMS o cualquier otro canal.

Interface PHP
<?php

interface Notificador
{
    public function enviar(
        string $destinatario,
        string $mensaje
    ): void;
}

La interfaz define un contrato.

Cualquier clase que implemente Notificador deberá proporcionar ese comportamiento.

Implementación

Varias clases pueden cumplir el mismo contrato de maneras diferentes

Email PHP
<?php

final class NotificadorEmail
    implements Notificador
{
    public function enviar(
        string $destinatario,
        string $mensaje
    ): void {

        // Enviar correo.
    }
}
SMS PHP
<?php

final class NotificadorSms
    implements Notificador
{
    public function enviar(
        string $destinatario,
        string $mensaje
    ): void {

        // Enviar SMS.
    }
}

El resto de la aplicación puede depender del contrato Notificador en lugar de una implementación concreta.

Inyección de dependencias

Un objeto puede recibir sus colaboradores en lugar de crearlos internamente

Supongamos que un servicio registra usuarios y después envía una notificación.

Servicio PHP
<?php

final class RegistroUsuario
{
    public function __construct(
        private Notificador $notificador
    ) {}

    public function registrar(
        string $email
    ): void {

        // Crear usuario.

        $this->notificador->enviar(
            $email,
            'Tu cuenta fue creada.'
        );
    }
}

RegistroUsuario no crea el notificador

Lo recibe desde el exterior.

Eso permite utilizar distintas implementaciones sin modificar la lógica de registro.

Composición PHP
<?php

$notificador = new NotificadorEmail();

$registro = new RegistroUsuario(
    $notificador
);
La inyección de dependencias no necesita un framework.

En su forma más simple, consiste simplemente en proporcionar a un objeto los colaboradores que necesita.

Publicidad
Composición

Composición significa construir comportamiento combinando objetos

En el ejemplo anterior, RegistroUsuario utiliza un Notificador.

No necesita heredar de Notificador.

Simplemente colabora con otro objeto.

“Tiene un” frente a “es un”

Un servicio de registro tiene un notificador.

No tiene sentido decir que un registro es un notificador.

Esta distinción ayuda mucho a decidir entre composición y herencia.

Herencia

Herencia puede ser útil, pero no debería ser la primera respuesta para reutilizar código

Herencia PHP
<?php

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

class Administrador extends Usuario
{
    public function obtenerNombre(): string
    {
        return $this->nombre;
    }
}

Administrador hereda comportamiento y estado de Usuario.

El problema aparece cuando la jerarquía crece demasiado

Si comienzas a crear muchas capas de herencia, las clases pueden quedar fuertemente acopladas.

Cambiar una clase base puede producir efectos inesperados en múltiples clases hijas.

No utilices herencia solamente para evitar repetir tres líneas de código.

Primero pregunta si realmente existe una relación conceptual de tipo “es un”.

Abstracción

Una clase abstracta puede compartir estructura y exigir comportamiento a sus clases hijas

Abstract PHP
<?php

abstract class MetodoPago
{
    abstract public function pagar(
        float $monto
    ): bool;

    public function montoValido(
        float $monto
    ): bool {
        return $monto > 0;
    }
}

Una clase abstracta puede proporcionar implementación compartida y también declarar métodos abstractos.

¿Interface o abstract?

Una interfaz suele expresar principalmente un contrato.

Una clase abstracta puede además compartir estado y comportamiento.

No son sustitutos perfectos: resuelven problemas diferentes.

final

final puede expresar que una clase o método no está diseñado para extenderse

final PHP
<?php

final class CalculadoraImpuestos
{
    public function calcular(
        float $neto
    ): float {
        return $neto * 0.19;
    }
}

Esto comunica una decisión de diseño: la clase no está pensada como punto de extensión mediante herencia.

Publicidad
static

Los miembros static pertenecen a la clase, no a una instancia concreta

Static PHP
<?php

final class Calculadora
{
    public static function sumar(
        float $a,
        float $b
    ): float {
        return $a + $b;
    }
}

$total = Calculadora::sumar(
    10,
    20
);

No necesitamos crear un objeto Calculadora para ejecutar ese método.

Pero no conviertas todo en static

El uso excesivo puede dificultar sustituir dependencias, mantener estado y probar componentes de forma aislada.

Traits

Los traits permiten reutilizar implementación entre clases sin crear una jerarquía de herencia

Trait PHP
<?php

trait RegistraActividad
{
    public function registrar(
        string $mensaje
    ): void {
        error_log($mensaje);
    }
}

class PedidoService
{
    use RegistraActividad;
}

Un trait puede resultar útil para compartir determinado comportamiento.

Pero también puede ocultar dependencias si se utiliza indiscriminadamente.

Reutilización de código no debería ser el único criterio de diseño.

También importa que las dependencias y responsabilidades resulten claras.

Diseño

Una clase enorme que hace todo sigue siendo una mala organización

Introducir POO no soluciona automáticamente los problemas de arquitectura.

Puedes tener una clase de cinco mil líneas que:

01

Consulta la base de datos.

02

Envía emails.

03

Genera HTML.

04

Procesa pagos.

05

Valida formularios.

06

Gestiona archivos.

Técnicamente sería POO, pero no necesariamente buen diseño.

Divide por responsabilidades

Cada componente debería tener una razón clara para cambiar.

Publicidad
Ejemplo real

Separar acceso a datos y lógica con PDO

Podemos aprovechar lo aprendido en nuestra guía de PHP con MySQL para crear una estructura un poco más limpia.

Repository para usuarios

UsuarioRepository.php PHP
<?php

final class UsuarioRepository
{
    public function __construct(
        private PDO $pdo
    ) {}

    public function buscarPorEmail(
        string $email
    ): ?array {

        $sql = "
            SELECT
                id,
                nombre,
                email,
                activo
            FROM usuarios
            WHERE email = :email
            LIMIT 1
        ";

        $stmt = $this->pdo->prepare($sql);

        $stmt->execute([
            'email' => $email
        ]);

        $usuario = $stmt->fetch();

        return $usuario ?: null;
    }
}

Servicio de usuarios

UsuarioService.php PHP
<?php

final class UsuarioService
{
    public function __construct(
        private UsuarioRepository $usuarios
    ) {}

    public function existeEmail(
        string $email
    ): bool {

        return $this->usuarios
            ->buscarPorEmail($email) !== null;
    }
}

El servicio ya no necesita conocer detalles de SQL.

Puede concentrarse en reglas relacionadas con usuarios.

Persistencia

POO cobra mucho más sentido cuando comienzas a construir sistemas reales

Combínala con PDO, SQL y separación de responsabilidades.

PHP con MySQL →
Contratos

Una interfaz puede separar aún más el servicio de la implementación de datos

Contrato PHP
<?php

interface Usuarios
{
    public function buscarPorEmail(
        string $email
    ): ?array;
}

Después, una implementación puede utilizar PDO.

Implementación PHP
<?php

final class UsuariosPdo
    implements Usuarios
{
    public function __construct(
        private PDO $pdo
    ) {}

    public function buscarPorEmail(
        string $email
    ): ?array {

        // Consulta PDO.

        return null;
    }
}

Ahora el servicio puede depender de Usuarios y no necesariamente de PDO.

Testing

Las dependencias explícitas facilitan probar componentes de forma aislada

Si una clase crea dentro de sí misma una conexión, un cliente HTTP, un sistema de email y otras dependencias, probarla se vuelve más difícil.

Cuando recibe sus colaboradores desde fuera, puedes sustituirlos durante una prueba.

Puedes crear una implementación de prueba

Fake PHP
<?php

final class UsuariosMemoria
    implements Usuarios
{
    public function buscarPorEmail(
        string $email
    ): ?array {

        if ($email === 'test@ciborg.cl') {
            return [
                'id' => 1,
                'email' => $email
            ];
        }

        return null;
    }
}

El servicio puede probarse sin consultar una base de datos real.

Publicidad
POO + WordPress

La POO también puede ayudarte a estructurar plugins WordPress grandes

Un plugin de unas pocas líneas puede funcionar perfectamente con funciones.

Pero cuando incorpora administración, WooCommerce, endpoints, base de datos, cron, emails y servicios externos, separar componentes comienza a ser útil.

Una clase para administración

Puede registrar menús, formularios y configuración.

Otra para APIs

Puede gestionar endpoints e integraciones.

Otra para datos

Puede concentrar operaciones específicas de persistencia.

El objetivo no es convertir cada hook en una clase, sino evitar un plugin imposible de mantener.

WordPress

Aplica PHP moderno dentro del ecosistema WordPress

Hooks, plugins, seguridad, REST API y arquitectura.

PHP para WordPress →
POO + APIs

Las interfaces son especialmente útiles alrededor de servicios externos

Una aplicación puede depender de un proveedor de pagos, correo, almacenamiento o inteligencia artificial.

Si todo el sistema depende directamente de una implementación, cambiar de proveedor puede ser costoso.

Define el comportamiento que necesitas

Por ejemplo:

Contrato PHP
<?php

interface ProcesadorPago
{
    public function cobrar(
        int $monto,
        string $referencia
    ): ResultadoPago;
}

Luego puedes adaptar diferentes proveedores a ese contrato.

Explorar APIs e integraciones →
Errores comunes

POO mal aplicada puede hacer un proyecto más complejo, no menos

Error Crear una clase para todo
Simplifica
Error Crear getters y setters automáticamente
Diseña
Error Usar herencia solo para reutilizar código
Compón
Error Crear clases gigantes
Separa
Error Ocultar todas las dependencias
Hazlas explícitas
Error Aplicar patrones sin problema real
Justifica
Error Confundir más abstracción con mejor código
Claridad
Ruta de aprendizaje

En qué orden aprender POO en PHP

1

Funciones

Parámetros, retorno y responsabilidades.

2

Clases y objetos

Estructura e instancias.

3

Constructores

Inicializa objetos correctamente.

4

Visibilidad

public, protected y private.

5

Encapsulación

Protege estado y reglas.

6

Interfaces

Define contratos.

7

Composición

Conecta objetos pequeños.

8

Herencia

Úsala cuando la relación lo justifique.

9

Dependencias

Haz explícitos los colaboradores.

10

Proyecto real

Aplica POO con base de datos o APIs.

Publicidad
Conclusión

Aprender POO en PHP significa aprender a diseñar responsabilidades, no solamente aprender la palabra class

Empieza por clases, objetos y constructores.

Aprende visibilidad para controlar el estado interno.

Utiliza encapsulación para proteger reglas importantes.

Aprende interfaces para expresar contratos claros.

Prefiere composición cuando varios objetos simplemente necesitan colaborar.

Utiliza herencia solamente cuando exista una relación conceptual adecuada.

Haz explícitas las dependencias para facilitar mantenimiento y testing.

Y evita añadir abstracción solamente porque “parece más profesional”: el objetivo de la POO sigue siendo producir software comprensible y mantenible.

Cluster PHP

Continúa profundizando en PHP

Publicidad
Preguntas frecuentes

Preguntas sobre POO en PHP

POO significa programación orientada a objetos. En PHP permite organizar aplicaciones mediante clases y objetos que agrupan estado y comportamiento, además de utilizar conceptos como encapsulación, interfaces, composición y herencia.

Una clase define una estructura y comportamiento. Un objeto es una instancia concreta creada a partir de esa clase y mantiene su propio estado.

Encapsulación consiste en controlar el acceso al estado interno de un objeto y exponer únicamente las operaciones necesarias. PHP dispone de visibilidad public, protected y private para ayudar a expresarlo.

Una interfaz define un contrato que las clases implementadoras deben cumplir. Permite que distintos componentes ofrezcan el mismo comportamiento mediante implementaciones diferentes.

Depende del problema. La composición suele ser adecuada cuando un objeto necesita colaborar con otros componentes. La herencia tiene más sentido cuando existe una relación conceptual clara de tipo “es un” y la jerarquía resulta estable.

Es una técnica donde un objeto recibe desde el exterior los servicios o componentes que necesita, en lugar de crearlos directamente dentro de sí mismo. Esto puede reducir acoplamiento y facilitar testing.

No para cada personalización. Un plugin pequeño puede utilizar funciones. Sin embargo, POO puede ayudar a organizar plugins mayores que incluyen administración, APIs, WooCommerce, datos e integraciones.

Conviene aprenderla después de comprender variables, arrays, condiciones, funciones, parámetros, valores de retorno y tipos. Después puedes aplicar POO dentro de pequeños proyectos reales.

Publicidad
De sintaxis a arquitectura

La POO empieza a tener sentido cuando tus programas dejan de ser ejercicios aislados

Aprende clases, encapsulación, interfaces y composición. Después aplícalas junto con PDO, APIs o WordPress dentro de proyectos que necesiten una estructura mantenible.

Continuar con PHP y MySQL →
Carrito de compra
Scroll al inicio