Programación orientada a objetos en PHP: clases, objetos, herencia e interfaces


Hasta ahora trabajamos con PHP utilizando variables, funciones, formularios, PDO, sesiones, autenticación y seguridad. El siguiente paso es aprender programación orientada a objetos, una forma de organizar el código en clases y objetos.

Este enfoque es fundamental para trabajar en proyectos medianos o grandes y es la base de muchos frameworks modernos, incluido Laravel.

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

La programación orientada a objetos, también llamada POO u OOP, organiza el código alrededor de objetos que representan entidades del sistema.

Por ejemplo, en una aplicación podemos tener objetos como:

  • Usuario;
  • Producto;
  • Pedido;
  • Curso;
  • Factura.

Cada objeto puede tener datos y comportamientos propios.

¿Qué es una clase?

Una clase funciona como una plantilla para crear objetos.

<?php

class Usuario
{
}

?>

Crear un objeto

Para crear una instancia utilizamos new:

$usuario = new Usuario();

$usuario es ahora un objeto de la clase Usuario.

Propiedades

Las propiedades representan los datos del objeto.

class Usuario
{
    public string $nombre;
    public string $email;
}

Podemos asignar valores:

$usuario = new Usuario();

$usuario->nombre = 'Ana';
$usuario->email = 'ana@email.com';

Métodos

Los métodos representan comportamientos del objeto.

class Usuario
{
    public string $nombre;

    public function saludar(): string
    {
        return 'Hola, soy ' . $this->nombre;
    }
}

Para llamarlo:

$usuario = new Usuario();
$usuario->nombre = 'Ana';

echo $usuario->saludar();

¿Qué significa $this?

$this hace referencia al objeto actual.

Por ejemplo:

$this->nombre

significa “la propiedad nombre de este objeto”.

El constructor

El constructor se ejecuta automáticamente al crear un objeto.

class Usuario
{
    public string $nombre;
    public string $email;

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

Ahora podemos crear el objeto directamente con sus datos:

$usuario = new Usuario(
    'Ana',
    'ana@email.com'
);

Promoción de propiedades en el constructor

En versiones modernas de PHP podemos simplificar el código:

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

Visibilidad: public, protected y private

Las propiedades y métodos pueden tener distintos niveles de acceso.

public

Puede utilizarse desde cualquier parte:

public string $nombre;

private

Solo puede utilizarse dentro de la misma clase:

private string $password;

protected

Puede utilizarse dentro de la clase y sus clases hijas:

protected string $rol;

Encapsulamiento

El encapsulamiento consiste en proteger el estado interno de un objeto y controlar cómo se accede a él.

class Cuenta
{
    private float $saldo = 0;

    public function depositar(float $importe): void
    {
        if ($importe <= 0) {
            return;
        }

        $this->saldo += $importe;
    }

    public function obtenerSaldo(): float
    {
        return $this->saldo;
    }
}

El saldo no puede modificarse directamente desde afuera.

Getters y setters

Los getters permiten leer valores y los setters modificarlos de forma controlada.

class Producto
{
    private float $precio;

    public function setPrecio(float $precio): void
    {
        if ($precio < 0) {
            throw new InvalidArgumentException(
                'El precio no puede ser negativo'
            );
        }

        $this->precio = $precio;
    }

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

Herencia

Una clase puede heredar propiedades y métodos de otra utilizando extends.

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

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

class Administrador extends Usuario
{
    public function eliminarUsuario(): string
    {
        return 'Usuario eliminado';
    }
}

Sobrescribir métodos

Una clase hija puede redefinir un método de la clase padre.

class Usuario
{
    public function descripcion(): string
    {
        return 'Usuario del sistema';
    }
}

class Administrador extends Usuario
{
    public function descripcion(): string
    {
        return 'Administrador del sistema';
    }
}

parent::

Podemos llamar a un método de la clase padre utilizando parent::.

class Administrador extends Usuario
{
    public function descripcion(): string
    {
        return parent::descripcion()
            . ' con permisos administrativos';
    }
}

Clases abstractas

Una clase abstracta sirve como base para otras clases y no puede instanciarse directamente.

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

    abstract public function calcularSueldo(): float;
}

Una clase hija debe implementar el método abstracto:

class EmpleadoMensual extends Empleado
{
    public function __construct(
        string $nombre,
        private float $sueldo
    ) {
        parent::__construct($nombre);
    }

    public function calcularSueldo(): float
    {
        return $this->sueldo;
    }
}

Interfaces

Una interfaz define qué métodos debe implementar una clase.

interface Notificable
{
    public function enviar(string $mensaje): void;
}

Después podemos crear distintas implementaciones:

class NotificacionEmail implements Notificable
{
    public function enviar(string $mensaje): void
    {
        echo 'Enviando email: ' . $mensaje;
    }
}

Polimorfismo

Gracias a una interfaz podemos trabajar con diferentes implementaciones utilizando el mismo contrato.

function notificar(
    Notificable $servicio,
    string $mensaje
): void {
    $servicio->enviar($mensaje);
}

La función no necesita saber cómo se envía realmente la notificación.

Propiedades y métodos static

Un miembro static pertenece a la clase y no a una instancia concreta.

class Conversor
{
    public static function kilometrosAMillas(float $km): float
    {
        return $km * 0.621371;
    }
}

Se utiliza así:

$millas = Conversor::kilometrosAMillas(10);

Constantes de clase

class Pedido
{
    public const ESTADO_PENDIENTE = 'pendiente';
    public const ESTADO_PAGADO = 'pagado';
}

Podemos acceder mediante:

Pedido::ESTADO_PAGADO

Namespaces

Cuando un proyecto crece, los namespaces ayudan a organizar clases y evitar conflictos de nombres.

<?php

namespace App\Models;

class Usuario
{
}

?>

Luego podemos importar la clase:

use App\Models\Usuario;

$usuario = new Usuario();

Ejemplo práctico: clase Producto

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

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

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

    public function tieneStock(): bool
    {
        return $this->stock > 0;
    }

    public function descontarStock(int $cantidad): void
    {
        if ($cantidad <= 0 || $cantidad > $this->stock) {
            throw new InvalidArgumentException(
                'Cantidad inválida'
            );
        }

        $this->stock -= $cantidad;
    }
}

Ventajas de la programación orientada a objetos

  • organiza mejor proyectos grandes;
  • permite reutilizar código;
  • facilita separar responsabilidades;
  • mejora el mantenimiento;
  • permite crear componentes reemplazables;
  • facilita las pruebas automatizadas;
  • es la base conceptual de frameworks como Laravel.

No todo necesita ser una clase

La POO es una herramienta, no una obligación.

Crear clases sin una responsabilidad clara puede hacer que el código sea más complejo en lugar de mejorarlo.

Una buena clase debería representar una responsabilidad concreta y mantener relacionados sus datos y comportamientos.

Errores comunes

  • hacer todas las propiedades públicas;
  • crear clases que solamente agrupan funciones sin una responsabilidad clara;
  • abusar de la herencia;
  • crear jerarquías demasiado complejas;
  • utilizar métodos static para todo;
  • confundir una clase con un objeto;
  • no utilizar tipos de datos cuando ayudan a detectar errores;
  • crear clases gigantes que hacen demasiadas cosas.

Ejercicio práctico

Creá un pequeño sistema de biblioteca utilizando POO.

Definí:

  • una clase Libro con título, autor y disponibilidad;
  • una clase Usuario;
  • un método para prestar un libro;
  • un método para devolverlo;
  • encapsulamiento para impedir cambios inválidos;
  • una interfaz Notificable para informar cuando se realiza un préstamo.

¿Qué sigue?

En la próxima guía vamos a organizar un proyecto PHP utilizando una arquitectura más clara: MVC (Modelo, Vista y Controlador).

Veremos cómo separar:

  • acceso a datos;
  • lógica de negocio;
  • controladores;
  • vistas;
  • rutas.

Esto nos va a acercar mucho más a la estructura que utilizan frameworks como Laravel.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Descubre más desde Club Programador

Suscríbete y recibe las últimas entradas en tu correo electrónico.

Deja un comentario

Descubre más desde Club Programador

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo