Blog / PHP / Herencia en Traits: ¿Por qué tenemos acceso a todo?
Los Traits PHP permiten reutilizar código y simular "herencia múltiple", pero,
cuando empezamos a anidar Traits y las clases que los usan, el comportamiento de la visibilidad de
las propiedades y métodos es confuso.
Ejemplo
<?php
trait One
{
private string $name = 'Foo';
protected int $age = 23;
private function tellSomething(): void
{
echo 'My name is ' . $this->name . PHP_EOL;
}
protected function tellYourName(): void
{
echo $this->name . PHP_EOL;
}
}
trait Two
{
use One;
private function tellYourNameFirstLetter(): void
{
echo $this->name[0] . PHP_EOL;
}
private function tellYourAge(): void
{
echo $this->age . PHP_EOL;
}
}
class TestTraits
{
use Two;
public function __construct()
{
$this->tellYourName();
$this->tellYourNameFirstLetter();
$this->tellYourAge();
$this->tellSomething();
echo 'Its OK ' . $this->name . PHP_EOL;
}
}
class Fofi extends TestTraits
{
public function __construct()
{
parent::__construct();
}
}
new TestTraits();
new Fofi();
/** Result for PHP versions 7.4.33, 8.0.30, 8.1.34, 8.2.31, 8.3.30, 8.4.19, 8.5.6:
Foo
F
23
My name is Foo
Its OK Foo
Foo
F
23
My name is Foo
Its OK
*/
Una propiedad / método private de un Trait es
accesible desde la clase que lo consume y sus descendientes.
Al ejecutar este código, todo funciona perfectamente:
- Se accede a métodos
private del trait One.
- Se accede a propiedades
private del trait One desde el trait Two y desde
las clases TestTraits y Fofi.
El concepto de "Aplanamiento" (Flattening).
La gran diferencia entre la herencia de clases y el uso de Traits es cómo el motor de PHP
interpreta el código.
Cuando una clase usa un Trait, el código del Trait se "copia y pega"
virtualmente dentro de la clase en tiempo de compilación.
No existe una relación de jerarquía vertical (padre-hijo),
sino una composición horizontal.
En el ejemplo:
- El trait
Two incorpora todo el contenido del trait One.
- La clase
TestTraits incorpora todo el contenido del trait Two (que ya incluía a One).
- La clase
Fofi hereda todo el contenido incorporado por la clase
padre TestTraits.
Es como si la clase TestTraits hubiera declarado todas esas
propiedades y métodos de los traits incorporados, pero ocurre lo mismo con las clases hijas,
incorporan el contenido de los traits de la clase padre, de otro modo no podrían acceder a
propiedades y métodos private.
¿Por qué private no bloquea el acceso?
En la herencia de clases tradicional:
private Solo es accesible desde la clase que lo define.
Ni siquiera las clases hijas pueden verlo.
protected: Es accesible por la clase y sus descendientes.
En los traits, debido al "Aplanamiento", a efectos prácticos, cada propiedad
y método de un trait pasa a ser una propiedad o método, con la misma visibilidad
declarada, de la clase que usa ese trait.
Por eso, el método tellSomething() (que también se ha "inyectado" en
la clase)
puede leer $this->name sin problemas, y por eso desde el
constructor
de la clase podemos llamar a $this->tellSomething()
aunque fuera privado en el trait original.
Diferencias clave con las Clases
|
Herencia de Clases (extends) |
Composición de Traits (use) |
| Naturaleza |
Jerarquía vertical |
Inserción horizontal |
| Visibilidad Private |
El hijo no tiene acceso a lo que es privado en el
padre |
La clase tiene acceso total porque el código "se
mueve" a su scope |
| Conflictos |
El hijo sobrescribe (override) al padre |
Se deben resolver manualmente (aliasing/insteadof) si
dos traits tienen el mismo método |
Conclusión
Las clases que usan un trait, y sus descendientes, tienen acceso total a propiedades y métodos del
trait, porque los Traits no heredan, sino que expanden la definición de la clase.
Un Trait rompe las barreras de encapsulación que tendrías con una jerarquía de
clases, ya que todo el código del Trait pasa a formar parte del mismo ámbito (scope) que la
clase que lo utiliza y sus descendientes.
9-05-2026
Entradas relacionadas: