Trait en PHP
Ya vimos en Rimorsoft un artículo completo sobre la herencia (dentro del artículo de programación orientada a objetos), de esa manera podemos extender nuestras clases y compartir funcionalidad. El problema básico con extender extends
clases, es que solo puedes extender de una, solo una. Créeme, esto limita y mucho.
Traits en PHP
Ejemplo real
<?php
namespace App\Entities;
use App\Classes\Permission;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable, Permission;
// ...
}
Esta es la entidad User
que viene por defecto al instalar Laravel:
- Permission: Es una clase personal, es un Traits.
- Notifiable: Es un Traits de Laravel
-
Authenticatable: Es un alias asignado a la clase
Illuminate\Foundation\Auth\User
, y la entidadUser
la usa para heredar.
¿Este ejemplo que dice realmente? muestra que extendemos de una (1) clase y usamos dos (2) traits.
- Al extender usamos
extends Authenticatable
- Los traits tienen efecto al colocar dentro de la clase la línea
use Notifiable, Permission;
Es una gran ventaja, un Trait es un código que reutilizamos y podemos incluirlo en cualquier clase... Piensa en esto "código reutilizable"
Definición: Con los traits es posible que nuestras clases de PHP hereden métodos y propiedades. Es la solución directa al problema de herencia única.
Estructura
<?php
trait TagTrait
{
// ...
}
Un trait se parece mucho a una clase pero aquí usamos la palabra clave trait. No es obligatorio pero podemos seguir la convención de colocar el sufijo Trait en el nombre, ejemplo TagTrait
.
Notas
- En un Trait no puedes usar
extends
niimplements
, tampoco puedes crear un objeto a partir de él (no puedes instanciar un trait). - Un Trait no reemplaza a una Clase, el único propósito es dar apoyo...
<?php
trait TagTrait
{
/**
* @param $tags string or array
*/
public function tags($tags)
{
// Code here
}
/**
* @param $tag string
*/
private function addTag($tag)
{
// Code here
}
// ...
}
Con este simple Trait aislamos toda la lógica sobre las etiquetas Tag
y es la mejor manera de hacerlo, porque así tendremos un único código y el resto de nuestro sistema tendrá la función de etiquetas. De esta manera los usuarios, productos, artículos, etc tendrán la opción de ser segmentados por etiquetas y cada clase solo deberá incluir el archivo TagTrait
.
Es decir, puede la clase User
extender de una clase muy relacionada con ella, lo mismo Product
, Post
y así, pero lo interesante aquí es que pueden todas incluir el Trait TagTrait
.
Recuerda: El sufijo Trait no es obligatorio, es una convención y aquí es muy apropiado usarlo en la explicación
Usa traits para extender y mejorar la funcionalidad de tus clases, esto es todo... Extensión de código, *aislar código útil*.
Mi primer Trait
Veamos ejemplos: Archivo public/index.php
<?php
require '../vendor/autoload.php';
use App\User;
$user = new User;
echo $user->greet();
Archivo app/User.php
<?php
namespace App;
use App\Traits\Test;
/**
* this class represents a user
*/
class User
{
use Test;
}
Archivo app/Traits/Test.php
<?php
namespace App\Traits;
/**
* this trait represents a Test
*/
trait Test
{
function greet()
{
return "Mi primer Trait";
}
}
La lógica de greet()
está en el archivo Trait llamado Test y User
lo puede usar porque incluye el Trait.
Prioridad
Seguramente te has preguntado ¿Qué pasaría si extendemos de una clase, usamos un trait y ambos tienen un método con el mismo nombre?
Imagina: Mi clase User
extiende, es decir, hereda de la clase Person
y además incluye el trait TagTrait
, entonces, ¿Qué pasa si Person
y TagTrait
tienen un método con el mismo nombre?
La respuesta es: El método del trait tiene prioridad sobre el método de la clase padre. En otras palabras, con los traits podemos reemplazar la funcionalidad heredada.
Otra pregunta ¿Qué pasa si mi clase padre, el Trait y mi clase actual tienen un método con el mismo nombre?, ejemplo:
- Clase
User
métodogetName()
- Clase padre
Person
métodogetName()
- Trait
TagTrait
métodogetName()
Gana la clase; Se revisa de esta manera: (1) clase, (2) trait, (3) clase padre.
Ejemplo: Archivo public/index.php
<?php
require '../vendor/autoload.php';
use App\User;
$user = new User;
echo $user->greet();
Aquí creamos un objeto y usamos el método greet()
Archivo app/User.php
<?php
namespace App;
use App\Traits\Test;
/**
* this class represents a user
*/
class User extends Person
{
use Test;
function greet()
{
return "Hola, soy la clase User";
}
}
Nuestra clase User
tiene el método greet()
y al mismo tiempo extendemos o heredamos de Person
y ademas hacemos uso del Trait Test.php
Archivo app/Person.php
<?php
namespace App;
/**
* this class represents a person
*/
class Person
{
function greet()
{
return "Hola, soy la clase padre";
}
}
Archivo app/Traits/Test.php
<?php
namespace App\Traits;
/**
* this trait represents a Test
*/
trait Test
{
function greet()
{
return "Hola, soy un Trait";
}
}
Se ve claramente que nuestra clase User
, nuestra clase padre Person
y nuestro trait Test
usan el método greet()
¿Quien gana?
¿Quien gana ahí?, vamos a simplificarlo de la siguiente manera:
- El método de un Trait sobreescribe un método heredado.
- Un métodos propio (de mi clase actual) sobreescribe los métodos de un trait.
En otras palabras, los métodos propios tienen la última palabra
Conflictos, cuando dos (2) o más Trait tienen métodos con el mismo nombre
Cuando esto sucede te saldrá un error Fatal
Fatal error: Trait method greet has not been applied, because there are collisions with other trait methods on ... in ... on line 10
Archivo public/index.php
<?php
require '../vendor/autoload.php';
use App\User;
$user = new User;
echo $user->greetOne();
echo '<br>';
echo $user->greetTwo();
Archivo app/User.php
<?php
namespace App;
use App\Traits\TestOne;
use App\Traits\TestTwo;
/**
* this class represents a user
*/
class User
{
use TestOne, TestTwo {
TestOne::greet insteadof TestTwo;
// Usa el TestOne en lugar de TestTwo
// esto resuelve el conflicto
TestOne::greet as greetOne; // Usando alias
TestTwo::greet as greetTwo; // Usando alias
}
}
Aquí la clave está en la palabra insteadof
, básicamente traduce a "Usa esta clase en lugar de esta otra"
Archivo app/Traits/TestOne.php
<?php
namespace App\Traits;
/**
* this trait represents a Test
*/
trait TestOne
{
function greet()
{
return "Hola, soy un TraitOne";
}
}
Archivo app/Traits/TestTwo.php
<?php
namespace App\Traits;
/**
* this trait represents a Test
*/
trait TestTwo
{
function greet()
{
return "Hola, soy un TraitTwo";
}
}
¿Qué podemos hacer? conclusión
Los Traits inyectan *flexibilidad*, esta es la palabra clave
- Es posible usar varios traits en una clase.
- Un Trait puede tener dentro otro trait (Esto es frecuente cuando la aplicación crece).