Traits en PHP 7

Mencionamos anteriormente que PHP es un lenguaje de herencia único. No podemos usar la palabra clave extends para extender varias clases en PHP. Esta característica es en realidad un producto raro que solo admite un puñado de lenguajes de programación, como C ++. Para bien o para mal, la herencia múltiple permite algunos ajustes interesantes con nuestras estructuras de código.

Los traits de PHP proporcionan un mecanismo por el cual podemos lograr estas estructuras, ya sea en el contexto de la reutilización de código o la agrupación de funcionalidades. La palabra clave trait se usa para declarar un rasgo, de la siguiente manera:

<?php
trait Formatter
{
// Trait body
}

El cuerpo de un trait puede ser prácticamente cualquier cosa que pondríamos en una clase. Si bien se parecen a las clases, no podemos crear una instancia de un trait en sí. Solo podemos usar el trait de otra clase. Para hacerlo, empleamos la palabra clave use dentro del cuerpo de la clase, como se muestra en siguiente ejemplo:

class Ups
{
use Formatter;
// Class body (properties & methods)
}

Para comprender mejor cómo los Traits pueden ser útiles, echemos un vistazo al siguiente ejemplo:

<?php
trait Formatter
{
public function formatPrice($price)
{
return sprintf('%.2F', $price);
}
}
class Ups
{
use Formatter;
private $price = 4.4999; // Base shipping price
public function getShippingPrice($formatted = false)
{
// Shipping cost calc… $this->price = XXX
if ($formatted) {
return $this->formatPrice($this->price);
}
return $this->price;
}
}
class Dhl
{
use Formatter;
private $price = 9.4999; // Base shipping price
public function getShippingPrice($formatted = false)
{
// Shipping cost calc… $this->price = XXX
if ($formatted) {
return $this->formatPrice($this->price);
}
return $this->price;
}
}
$ups = new Ups();
echo $ups->getShippingPrice(true); // 4.50
$dhl = new Dhl();
echo $dhl->getShippingPrice(true); // 9.50

El ejemplo anterior demuestra el uso de trait en un contexto de reutilización de código, donde dos clases de envío diferentes, Ups y Dhl, usan el mismo trait. El trait en sí envuelve un pequeño y agradable método auxiliar formatPrice() que formatea el precio dado a dos campos decimales.

Al igual que las clases, los traits tienen acceso a $ this, 1 lo que significa que podríamos reescribir fácilmente el método anterior formatPrice() del trait Formatter de la siguiente manera:

<?php
trait Formatter
{
public function formatPrice()
{
return sprintf(‘%.2F’, $this->price);
}
}

Sin embargo, esto limita severamente nuestro uso de traits, ya que su método formatPrice() ahora espera un miembro $price, que algunas de las clases que usan el trait Formatter podrían no tener.
Echemos un vistazo a otro ejemplo en el que usamos traits en una agrupación de contexto funcional:

<?php
trait SalesOrderCustomer
{
public function getCustomerFirstname()
{
/* body */
}
public function getCustomerEmail()
{
/* body */
}
public function getCustomerGender()
{
/* body */
}
}
trait SalesOrderActions
{
public function cancel()
{
/* body */
}
public function complete()
{
/* body */
}
public function hold()
{
/* body */
}
}
class SalesOrder
{
use SalesOrderCustomer;
use SalesOrderActions;
/* body */
}

Lo que hicimos aquí no fue más que cortar y pegar nuestro código de clase en dos traits diferentes. Agrupamos todos los métodos relacionados con posibles acciones de pedido en un solo trait SalesOrderActions, y todos los métodos relacionados con el pedido de cliente en el trait SalesOrderCustomer. Esto nos lleva de vuelta al posible-no-necesariamente-significa- Filosofía preferible.
El uso de múltiples traits a veces puede generar conflictos, donde se puede encontrar el mismo nombre de método en más de un trait. Podemos usar las palabras clave insteadof en lugar de as para mitigar este tipo de conflictos, como se muestra en el siguiente ejemplo:

<?php
trait CsvHandler
{
public function import()
{
echo 'CsvHandler > import' . PHP_EOL;
}
public function export()
{
echo 'CsvHandler > export' . PHP_EOL;
}
}
trait XmlHandler
{
public function import()
{
echo 'XmlHandler > import' . PHP_EOL;
}
public function export()
{
echo 'XmlHandler > export' . PHP_EOL;
}
}
class SalesOrder
{
use CsvHandler, XmlHandler {
XmlHandler::import insteadof CsvHandler;
CsvHandler::export insteadof XmlHandler;
XmlHandler::export as exp;
}
public function initImport()
{
$this->import();
}
public function initExport()
{
$this->export();
$this->exp();
}
}
$order = new SalesOrder();
$order->initImport();
$order->initExport();
//XmlHandler > import
//CsvHandler > export
//XmlHandler > export

La palabra clave as también se puede usar junto con las palabras clave public, protected o private para cambiar la visibilidad del método:

<?php
trait Message
{
private function hello()
{
return 'Hello!';
}
}
class User
{
use Message {
hello as public;
}
}
$user = new User();
echo $user->hello(); // Hello!

Para hacer las cosas aún más interesantes, los traits pueden estar compuestos de otros traits, incluso admitiendo los miembros abstract y static, como se muestra en el siguiente ejemplo:

<?php
trait A
{
public static $counter = 0;
public function theA()
{
return self::$counter;
}
}
trait B
{
use A;
abstract public function theB();
}
class C
{
use B;
public function theB()
{
return self::$counter;
}
}
$c = new C();
$c::$counter++;
echo $c->theA(); // 1
$c::$counter++;
$c::$counter++;
echo $c->theB(); // 3

Además de no ser instanciables, los traits comparten muchas características con las clases. Si bien nos proporcionan las herramientas para una estructuración de código interesante, también facilitan la violación del principio de responsabilidad única. La impresión general del uso del trait es a menudo el de extender las clases regulares, lo que dificulta encontrar el caso de uso correcto. Podemos usarlos para describir características que son comunes a muchos, pero no esenciales. Por ejemplo, los motores a reacción no son esenciales en todos los aviones, pero muchos aviones los tienen, mientras que otros pueden tener hélices.

Comparte