Buscar..
Rasgos para facilitar la reutilización de código horizontal.
Digamos que tenemos una interfaz para el registro:
interface Logger {
function log($message);
}
Ahora digamos que tenemos dos implementaciones concretas del Logger
interfaz: la FileLogger
y la ConsoleLogger
.
class FileLogger implements Logger {
public function log($message) {
// Append log message to some file
}
}
class ConsoleLogger implements Logger {
public function log($message) {
// Log message to the console
}
}
Ahora, si define alguna otra clase de Foo
que también desee poder realizar tareas de registro, podría hacer algo como esto:
class Foo implements Logger {
private $logger;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function log($message) {
if ($this->logger) {
$this->logger->log($message);
}
}
}
Foo
es ahora también un Logger
, pero su funcionalidad depende del Logger
la aplicación que se le pasa a través de setLogger()
. Si ahora queremos que la clase Bar
tenga también este mecanismo de registro, tendríamos que duplicar esta lógica en la clase Bar
.
En lugar de duplicar el código, se puede definir un rasgo:
trait LoggableTrait {
protected $logger;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function log($message) {
if ($this->logger) {
$this->logger->log($message);
}
}
}
Ahora que hemos definido la lógica en un rasgo, podemos usar el rasgo para agregar la lógica a las clases Foo
y Bar
:
class Foo {
use LoggableTrait;
}
class Bar {
use LoggableTrait;
}
Y, por ejemplo, podemos usar la clase Foo
como esta:
$foo = new Foo();
$foo->setLogger( new FileLogger() );
//note how we use the trait as a 'proxy' to call the Logger's log method on the Foo instance
$foo->log('my beautiful message');
La resolución de conflictos
Tratar de usar varios rasgos en una clase podría resultar en problemas que involucren métodos conflictivos. Necesita resolver tales conflictos manualmente.
Por ejemplo, vamos a crear esta jerarquía:
trait MeowTrait {
public function say() {
print "Meow \n";
}
}
trait WoofTrait {
public function say() {
print "Woof \n";
}
}
abstract class UnMuteAnimals {
abstract function say();
}
class Dog extends UnMuteAnimals {
use WoofTrait;
}
class Cat extends UnMuteAnimals {
use MeowTrait;
}
Ahora, vamos a tratar de crear la siguiente clase:
class TalkingParrot extends UnMuteAnimals {
use MeowTrait, WoofTrait;
}
El intérprete php devolverá un error fatal:
Error grave : el método del rasgo dice que no se ha aplicado, porque hay colisiones con otros métodos del rasgo en TalkingParrot
Para resolver este conflicto, podríamos hacer esto:
- utilizar palabras clave
insteadof
utilizar el método de un rasgo en lugar de método desde otro rasgo - cree un alias para el método con una construcción como
WoofTrait::say as sayAsDog;
class TalkingParrotV2 extends UnMuteAnimals {
use MeowTrait, WoofTrait {
MeowTrait::say insteadof WoofTrait;
WoofTrait::say as sayAsDog;
}
}
$talkingParrot = new TalkingParrotV2();
$talkingParrot->say();
$talkingParrot->sayAsDog();
Este código producirá la siguiente salida:
maullar
Guau
Uso de múltiples rasgos
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World';
}
}
class MyHelloWorld {
use Hello, World;
public function sayExclamationMark() {
echo '!';
}
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
El ejemplo anterior dará como resultado:
Hello World!
Modificación de la visibilidad del método
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// Change visibility of sayHello
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
Ejecutando este ejemplo:
(new MyClass1())->sayHello();
// Fatal error: Uncaught Error: Call to protected method MyClass1::sayHello()
(new MyClass2())->myPrivateHello();
// Fatal error: Uncaught Error: Call to private method MyClass2::myPrivateHello()
(new MyClass2())->sayHello();
// Hello World!
Entonces, tenga en cuenta que en el último ejemplo en MyClass2
el método sin alias original de la trait HelloWorld
permanece accesible tal como está.
¿Qué es un rasgo?
PHP solo permite herencia individual. En otras palabras, una clase solo puede extend
otra clase. Pero, ¿qué sucede si necesita incluir algo que no pertenece a la clase principal? Antes de PHP 5.4, tendría que ser creativo, pero en 5.4 se introdujeron rasgos. Los rasgos te permiten básicamente "copiar y pegar" una parte de una clase en tu clase principal
trait Talk {
/** @var string */
public $phrase = 'Well Wilbur...';
public function speak() {
echo $this->phrase;
}
}
class MrEd extends Horse {
use Talk;
public function __construct() {
$this->speak();
}
public function setPhrase($phrase) {
$this->phrase = $phrase;
}
}
Así que aquí tenemos a MrEd
, que ya está extendiendo Horse
. Pero no todos los caballos Talk
, así que tenemos un rasgo para eso. Notemos lo que esto está haciendo
En primer lugar, definimos nuestro rasgo. Podemos usarlo con la carga automática y los espacios de nombres (consulte también Cómo hacer referencia a una clase o función en un espacio de nombres ). Luego lo incluimos en nuestra clase MrEd
con el use
palabras clave.
Notará que MrEd
utiliza las funciones y variables de Talk
sin definirlas. ¿Recuerdas lo que dijimos sobre copiar y pegar ? Estas funciones y variables están todas definidas dentro de la clase ahora, como si esta clase las hubiera definido.
Los rasgos están más estrechamente relacionados con las clases abstractas, ya que puede definir variables y funciones. Tampoco puede crear una instancia de un rasgo directamente (es decir, un new Trait()
). Los rasgos no pueden obligar a una clase a definir implícitamente una función como una clase abstracta o una interfaz puede. Los rasgos son solo para definiciones explícitas (ya que puede implement
tantas Interfaces como desee, consulte Interfaces ).
¿Cuándo debo usar un rasgo?
Lo primero que debe hacer, al considerar un Rasgo, es hacerse esta pregunta importante
¿Puedo evitar usar un Rasgo reestructurando mi código?
La mayoría de las veces, la respuesta será Sí . Los rasgos son casos de borde causados por herencia única. La tentación de mal uso o uso excesivo de los rasgos puede ser alta. Pero tenga en cuenta que un Rasgo introduce otra fuente para su código, lo que significa que hay otra capa de complejidad. En el ejemplo aquí, solo estamos tratando con 3 clases. Pero Rasgos significa que ahora puedes lidiar con mucho más que eso. Para cada Rasgo, su clase se vuelve mucho más difícil de tratar, ya que ahora debe consultar cada Rasgo para averiguar qué define (y potencialmente dónde ocurrió una colisión, vea Resolución de conflictos ). Idealmente, debes mantener la menor cantidad posible de Rasgos en tu código.
Rasgos para mantener las clases limpias
Con el tiempo, nuestras clases pueden implementar más y más interfaces. Cuando estas interfaces tienen muchos métodos, el número total de métodos en nuestra clase será muy grande.
Por ejemplo, supongamos que tenemos dos interfaces y una clase que las implementa:
interface Printable {
public function print();
//other interface methods...
}
interface Cacheable {
//interface methods
}
class Article implements Cachable, Printable {
//here we must implement all the interface methods
public function print(){ {
/* code to print the article */
}
}
En lugar de implementar todos los métodos de interfaz dentro de la clase de Article
, podríamos usar Rasgos separados para implementar estas interfaces, manteniendo la clase más pequeña y separando el código de la implementación de la interfaz de la clase.
A partir del ejemplo, para implementar la interfaz de Printable
, podríamos crear este rasgo:
trait PrintableArticle {
//implements here the interface methods
public function print() {
/* code to print the article */
}
}
y hacer que la clase use el rasgo:
class Article implements Cachable, Printable {
use PrintableArticle;
use CacheableArticle;
}
Los beneficios principales serían que nuestros métodos de implementación de la interfaz se separarán del resto de la clase y se almacenarán en un rasgo que tiene la responsabilidad exclusiva de implementar la interfaz para ese tipo de objeto en particular.
Implementando un Singleton usando Rasgos
Descargo de responsabilidad : de ninguna manera este ejemplo aboga por el uso de singletons. Los Singletons se deben usar con mucho cuidado.
En PHP hay una forma bastante estándar de implementar un singleton:
public class Singleton {
private $instance;
private function __construct() { };
public function getInstance() {
if (!self::$instance) {
// new self() is 'basically' equivalent to new Singleton()
self::$instance = new self();
}
return self::$instance;
}
// Prevent cloning of the instance
protected function __clone() { }
// Prevent serialization of the instance
protected function __sleep() { }
// Prevent deserialization of the instance
protected function __wakeup() { }
}
Para evitar la duplicación de código, es una buena idea extraer este comportamiento en un rasgo.
trait SingletonTrait {
private $instance;
protected function __construct() { };
public function getInstance() {
if (!self::$instance) {
// new self() will refer to the class that uses the trait
self::$instance = new self();
}
return self::$instance;
}
protected function __clone() { }
protected function __sleep() { }
protected function __wakeup() { }
}
Ahora, cualquier clase que quiera funcionar como un singleton puede simplemente usar el rasgo:
class MyClass {
use SingletonTrait;
}
// Error! Constructor is not publicly accessible
$myClass = new MyClass();
$myClass = MyClass::getInstance();
// All calls below will fail due to method visibility
$myClassCopy = clone $myClass; // Error!
$serializedMyClass = serialize($myClass); // Error!
$myClass = deserialize($serializedMyclass); // Error!
Aunque ahora es imposible serializar un singleton, también es útil no permitir el método de deserialización.