IronWoods.es

Desarrollo web

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