Curso de PHP

Encapsulamiento en PHP: public, private y protected

Por Víctor Peña · Publicado el

Encapsulamiento de objetos en PHP

Hola, ¿cómo están? Continuando con el curso de PHP, hoy veremos el encapsulamiento: el pilar de la POO que protege los datos de tus objetos.

¡Empecemos!

Qué es el encapsulamiento

El encapsulamiento consiste en ocultar el estado interno de un objeto y permitir el acceso solo a través de métodos controlados.

La analogía de siempre: un cajero automático. Usas los botones y la pantalla, no metes la mano en la máquina para sacar el dinero. La máquina decide qué operaciones permite y bajo qué condiciones.

El problema sin encapsulamiento

Mira esta clase con todo público:

<?php
class CuentaBancaria
{
    public float $saldo = 0;
}

$cuenta = new CuentaBancaria();
$cuenta->saldo = -50000;   // nada lo impide

Un saldo negativo de cincuenta mil. La clase no puede evitarlo porque cualquiera puede escribir directamente en la propiedad.

Y el problema no es solo el valor absurdo: es que si mañana aparece un saldo incorrecto, el culpable puede estar en cualquiera de los cien archivos que tocan esa propiedad.

Los tres modificadores de acceso

Modificador Desde la clase Desde una hija Desde fuera
public
protected No
private No No
<?php
class Ejemplo
{
    public string $publica = "accesible desde todos lados";
    protected string $protegida = "solo la clase y sus hijas";
    private string $privada = "solo esta clase";
}

$obj = new Ejemplo();
echo $obj->publica;     // funciona
echo $obj->protegida;   // Error
echo $obj->privada;     // Error

Se aplican tanto a propiedades como a métodos.

La regla práctica

Empieza haciendo todo privado y abre solo lo necesario.

Es mucho más fácil hacer pública una propiedad que hacía falta que quitar el acceso a una que ya está siendo usada en veinte sitios.

Getters y setters

Si las propiedades son privadas, se accede a ellas mediante métodos:

<?php
declare(strict_types=1);

class CuentaBancaria
{
    private float $saldo = 0;

    public function __construct(
        private readonly string $titular,
    ) {}

    public function getTitular(): string
    {
        return $this->titular;
    }

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

    public function depositar(float $monto): void
    {
        if ($monto <= 0) {
            throw new InvalidArgumentException("El monto debe ser positivo");
        }

        $this->saldo += $monto;
    }

    public function retirar(float $monto): void
    {
        if ($monto <= 0) {
            throw new InvalidArgumentException("El monto debe ser positivo");
        }

        if ($monto > $this->saldo) {
            throw new RuntimeException("Saldo insuficiente");
        }

        $this->saldo -= $monto;
    }
}

Ahora es imposible dejar la cuenta en un estado inválido. No existe forma de tocar $saldo sin pasar por las validaciones.

<?php
$cuenta = new CuentaBancaria("Víctor Peña");
$cuenta->depositar(1000);
$cuenta->retirar(300);

echo $cuenta->getSaldo();    // 700
// $cuenta->saldo = 99999;   // Error

No todo necesita getter y setter

Aquí hay un malentendido muy extendido. Mucha gente aprende encapsulamiento y escribe esto:

<?php
class Producto
{
    private string $nombre;

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

    public function setNombre(string $nombre): void
    {
        $this->nombre = $nombre;
    }
}

Eso no es encapsulamiento: es una propiedad pública con dos métodos de por medio. Cualquiera puede leer y escribir sin ninguna restricción, igual que antes, pero con el triple de código.

El encapsulamiento real aparece cuando el método hace algo:

  • Valida (depositar rechaza montos negativos).
  • Calcula (getSalarioTotal() suma base y comisiones).
  • Mantiene la coherencia (al cambiar el estado de un pedido, registra la fecha).

Si un setter solo asigna, plantéate si la propiedad no debería ser pública, o readonly si no debe cambiar.

Métodos privados

El encapsulamiento también aplica al comportamiento. Un método privado es un paso interno que nadie de fuera necesita conocer:

<?php
class Pedido
{
    private array $lineas = [];
    private float $descuento = 0;

    public function calcularTotal(): float
    {
        $subtotal = $this->calcularSubtotal();
        $conDescuento = $this->aplicarDescuento($subtotal);

        return $this->aplicarImpuesto($conDescuento);
    }

    private function calcularSubtotal(): float
    {
        return array_reduce(
            $this->lineas,
            fn($total, $linea) => $total + ($linea['precio'] * $linea['cantidad']),
            0
        );
    }

    private function aplicarDescuento(float $monto): float
    {
        return $monto - ($monto * $this->descuento);
    }

    private function aplicarImpuesto(float $monto): float
    {
        return round($monto * 1.13, 2);
    }
}

Desde fuera solo existe calcularTotal(). Los tres pasos internos están ocultos, y eso significa que puedes cambiarlos cuando quieras sin romper a nadie: nadie los está llamando.

Esa es la ventaja menos evidente y más valiosa del encapsulamiento. Lo público es un compromiso; lo privado es libertad para cambiar.

Encapsulamiento con readonly

Desde PHP 8.1, cuando un dato debe leerse pero nunca cambiar, readonly es más directo que un getter:

<?php
class Factura
{
    public function __construct(
        public readonly string $numero,
        public readonly DateTimeImmutable $fecha,
        private array $lineas = [],
    ) {}
}

$f = new Factura("F-001", new DateTimeImmutable());
echo $f->numero;          // se lee sin problema
// $f->numero = "F-002";  // Error

Menos código y la misma protección.

protected: para las clases hijas

protected es el punto intermedio, y su uso natural es la herencia:

<?php
abstract class Empleado
{
    public function __construct(
        protected string $nombre,
        protected float $salarioBase,
    ) {}

    protected function calcularBonos(): float
    {
        return 0;
    }

    public function calcularSalario(): float
    {
        return $this->salarioBase + $this->calcularBonos();
    }
}

class Vendedor extends Empleado
{
    public function __construct(
        string $nombre,
        float $salarioBase,
        private float $ventas,
    ) {
        parent::__construct($nombre, $salarioBase);
    }

    protected function calcularBonos(): float
    {
        return $this->ventas * 0.05;   // accede a lo protegido
    }
}

calcularBonos() es un detalle interno que las hijas necesitan redefinir, pero que nadie debe llamar desde fuera.

Un aviso sobre las constantes de clase

Las constantes también admiten modificadores:

<?php
class Pedido
{
    public const ESTADO_PAGADO = 'pagado';
    private const CLAVE_INTERNA = 'xyz';
}

Errores comunes

  • Poner todo público «por comodidad». El sistema queda sin defensas.
  • Generar getters y setters automáticamente para cada propiedad. Es encapsulamiento aparente.
  • Usar private donde hacía falta protected. Las clases hijas no van a poder acceder.
  • Devolver arrays u objetos internos desde un getter: quien lo reciba puede modificarlos y estás filtrando el estado interno.

Para cerrar

El encapsulamiento no va de escribir getters: va de que un objeto nunca pueda quedar en un estado inválido. Si tus métodos públicos validan y mantienen la coherencia, el objeto se protege solo.

Y recuerda la libertad que da: todo lo privado puedes reescribirlo mañana sin avisar a nadie.

En la siguiente lección veremos las clases abstractas.

Saludos y éxitos.

Norvic Software

Desarrollamos el software que tu empresa necesita

Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp