Unlike inheritance; if a trait has static properties, each class using that trait has independent instances of those properties.
Example using parent class:
<?php
class TestClass {
public static $_bar;
}
class Foo1 extends TestClass { }
class Foo2 extends TestClass { }
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo Foo1::$_bar . ' ' . Foo2::$_bar; // Prints: World World
?>
Example using trait:
<?php
trait TestTrait {
public static $_bar;
}
class Foo1 {
use TestTrait;
}
class Foo2 {
use TestTrait;
}
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo Foo1::$_bar . ' ' . Foo2::$_bar; // Prints: Hello World
?>
Traits
Desde PHP 5.4.0, PHP implementa una metodología de reutilización de código llamada Traits.
Los traits (rasgos) son un mecanismo de reutilización de código en lenguajes de herencia simple, como PHP. El objetivo de un trait es el de reducir las limitaciones propias de la herencia simple permitiendo que los desarrolladores reutilicen a voluntad conjuntos de métodos sobre varias clases independientes y pertenecientes a clases jerárquicas distintas. La semántica a la hora combinar Traits y clases se define de tal manera que reduzca su complejidad y se eviten los problemas típicos asociados a la herencia múltiple y a los Mixins.
Un Trait es similar a una clase, pero con el único objetivo de agrupar funcionalidades muy específicas y de una manera coherente. No se puede instanciar directamente un Trait. Es por tanto un añadido a la herencia tradicional, y habilita la composición horizontal de comportamientos; es decir, permite combinar miembros de clases sin tener que usar herencia.
Ejemplo #1 Ejemplo de Trait
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
?>
Precedencia
Los miembros heredados de una clase base se sobrescriben cuando se inserta otro miembro homónimo desde un Trait. De acuerdo con el orden de precedencia, los miembros de la clase actual sobrescriben los métodos del Trait, que a su vez sobrescribe los métodos heredados.
Ejemplo #2 Ejemplo de Orden de Precedencia
Se sobrescribe un miembro de la clase base con el método insertado en MiHolaMundo a partir del Trait DecirMundo. El comportamiento es el mismo para los métodos definidos en la clase MiHolaMundo. Según el orden de precedencia los métodos de la clase actual sobrescriben los métodos del Trait, a la vez que el Trait sobrescribe los métodos de la clase base.
<?php
class Base {
public function decirHola() {
echo '¡Hola ';
}
}
trait DecirMundo {
public function decirHola() {
parent::decirHola();
echo 'Mundo!';
}
}
class MiHolaMundo extends Base {
use DecirMundo;
}
$o = new MiHolaMundo();
$o->decirHola();
?>
El resultado del ejemplo sería:
¡Hola Mundo!
Ejemplo #3 Ejemplo de Orden de Precedencia #2
<?php
trait HolaMundo {
public function decirHola() {
echo '¡Hola Mundo!';
}
}
class ElMundoNoEsSuficiente {
use HolaMundo;
public function decirHola() {
echo '¡Hola Universo!';
}
}
$o = new ElMundoNoEsSuficiente();
$o->decirHola();
?>
El resultado del ejemplo sería:
¡Hola Universo!
Multiples Traits
Se pueden insertar múltiples Traits en una clase, mediante una lista separada por comas en la sentencia use.
Ejemplo #4 Uso de Múltiples Traits
<?php
trait Hola {
public function decirHola() {
echo 'Hola ';
}
}
trait Mundo {
public function decirMundo() {
echo 'Mundo';
}
}
class MiHolaMundo {
use Hola, Mundo;
public function decirAdmiración() {
echo '!';
}
}
$o = new MiHolaMundo();
$o->decirHola();
$o->decirMundo();
$o->decirAdmiración();
?>
El resultado del ejemplo sería:
Hola Mundo!
Resolución de Conflictos
Si dos Traits insertan un método con el mismo nombre, se produce un error fatal, siempre y cuando no se haya resuelto explicitamente el conflicto.
Para resolver los conflictos de nombres entre Traits en una misma clase, se debe usar el operador insteadof para elejir unívocamente uno de los métodos conflictivos.
Como esto sólo permite excluir métodos, se puede usar el operador as para permitir incluir uno de los métodos conflictivos bajo otro nombre.
Ejemplo #5 Resolución de Conflictos
En este ejemplo, Talker utiliza los traits A y B. Como A y B tienen métodos conflictos, se define el uso de la variante de smallTalk del trait B, y la variante de bigTalk del traita A.
Aliased_Talker hace uso del operador as para poder usar la implementación de bigTalk de B, bajo el alias adicional talk.
<?php
trait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}
trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}
class Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
?>
Modificando la Visibilidad de los Métodos
Al usar el operador as, se puede también ajustar la visibilidad del método en la clase exhibida.
Ejemplo #6 Modificar la Visibilidad de un Método
<?php
trait HolaMundo {
public function decirHola() {
echo 'Hola Mundo!';
}
}
// Cambiamos visibilidad de decirHola
class MiClase1 {
use HolaMundo { decirHola as protected; }
}
// Método alias con visibilidad cambiada
// La visibilidad de decirHola no cambia
class MiClase2 {
use HolaMundo { decirHola as private miPrivadoHola; }
}
?>
Traits Compuestos de Traits
Al igual que las clases, los Traits también pueden hacer uso de otros Traits. Al usar uno o más traits en la definición de un trait, éste puede componerse parcial o completamente de miembros de finidos en esos otros traits.
Ejemplo #7 Traits Compuestos de Traits
<?php
trait Hola {
public function decirHola() {
echo 'Hola ';
}
}
trait Mundo {
public function decirMundo() {
echo 'Mundo!';
}
}
trait HolaMundo {
use Hola, Mundo;
}
class MiHolaMundo {
use HolaMundo;
}
$o = new MiHolaMundo();
$o->decirHola();
$o->decirMundo();
?>
El resultado del ejemplo sería:
Hola Mundo!
Miembros Abstractos de Traits
Los traits soportan el uso de métodos abstractos para imponer requisitos a la clase a la que se exhiban.
Ejemplo #8 Expresar Resquisitos con Métodos Abstractos
<?php
trait Hola {
public function decirHolaMundo() {
echo 'Hola'.$this->obtenerMundo();
}
abstract public function obtenerMundo();
}
class MiHolaMundo {
private $mundo;
use Hola;
public function obtenerMundo() {
return $this->mundo;
}
public function asignarMundo($val) {
$this->mundo = $val;
}
}
?>
Miembros Estáticos en Traits
Los métodos de un trait pueden referenciar a variables estáticas, pero no puede ser definido en el trait. Los traits, sin embargo, pueden definir método estáticos a la clase a la que se exhiben.
Ejemplo #9 Variables estáticas
<?php
trait Contador {
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
}
class C1 {
use Contador;
}
class C2 {
use Contador;
}
$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>
Ejemplo #10 Métodos Estáticos
<?php
trait EjemploEstatico {
public static function hacerAlgo() {
return 'Hacer algo';
}
}
class Ejemplo {
use EjemploEstatico;
}
Ejemplo::hacerAlgo();
?>
Propiedades
Los traits también pueden definir propiedades.
Ejemplo #11 Definir Propiedades
<?php
trait PropiedadesTrait {
public $x = 1;
}
class EjemploPropiedades {
use PropiedadesTrait;
}
$ejemplo = new EjemploPropiedades;
$ejemplo->x;
?>
Si un trait define una propiedad una clase no puede definir una propiedad con
el mismo nombre, si no se emitirá un error.Éste de tipo
E_STRICT si la definición de la clase es compatible (misma
visibilidad y valor inicial) o de otro modo un error fatal.
Ejemplo #12 Resolución de Conflictos
<?php
trait PropiedadesTrait {
public $misma = true;
public $diferente = false;
}
class EjemploPropiedades {
use PropiedadesTrait;
public $misma = true; // Estándares estrictos
public $diferente = true; // Error fatal
}
?>
It may be worth noting here that the magic constant __CLASS__ becomes even more magical - __CLASS__ will return the name of the class in which the trait is being used.
for example
<?php
trait sayWhere {
public function whereAmI() {
echo __CLASS__;
}
}
class Hello {
use sayWHere;
}
class World {
use sayWHere;
}
$a = new Hello;
$a->whereAmI(); //Hello
$b = new World;
$b->whereAmI(); //World
?>
The magic constant __TRAIT__ will giev you the name of the trait
Another difference with traits vs inheritance is that methods defined in traits can access methods and properties of the class they're used in, including private ones.
For example:
<?php
trait MyTrait
{
protected function accessVar()
{
return $this->var;
}
}
class TraitUser
{
use MyTrait;
private $var = 'var';
public function getVar()
{
return $this->accessVar();
}
}
$t = new TraitUser();
echo $t->getVar(); // -> 'var'
?>
Another useful property of traits:
<?php
namespace Traits;
trait Properties{
public function __get($var){
$var = '_' . $var;
$getter = '_get' . $var;
if(method_exists($this, $getter)){
try{
$val = $this->$getter();
}catch(\Exception $e){
throw new \Exception($e);
}
return $val;
}
throw new \Exception('Can not get property: ' . $var . ', method ' . $getter . ' not exists');
}
public function __set($var, $val){
$var = '_' . $var;
$setter = '_set' . $var;
if(method_exists($this->$setter) && isset($this->$var)){
try{
$setval = $this->$setter($val);
}catch(\Exception $e){
throw new \Exception($e);
}
$this->$var = ($setval === NULL) ? $this->$var : $setval;
}else{
throw new \Exception('Can not set property: ' . $var . ', method ' . $setter . ' not exists');
}
}
}
class Some{
use \Chidori\Traits\Properties;
// Magic begin
protected $_var = 42;
protected function _get_var(){ return $this->_var; }
protected function _set_var($val){ return NULL; }
}
$s = new Some();
$s->var = 23; \\ set value
echo $s->var; \\ return 42? where is my 23? =)
?>
Using AS on a __construct method (and maybe other magic methods) is really, really bad. The problem is that is doesn't throw any errors, at least in 5.4.0. It just sporadically resets the connection. And when I say "sporadically," I mean that arbitrary changes in the preceding code can cause the browser connection to reset or not reset *consistently*, so that subsequent page refreshes will continue to hang, crash, or display perfectly in the same fashion as the first load of the page after a change in the preceding code, but the slightest change in the code can change this state. (I believe it is related to precise memory usage.)
I've spent a good part of the day chasing down this one, and weeping every time commenting or even moving a completely arbitrary section of code would cause the connection to reset. It was just by luck that I decided to comment the
"__construct as primitiveObjectConstruct"
line and then the crashes went away entirely.
My parent trait constructor was very simple, so my fix this time was to copy the functionality into the child __construct. I'm not sure how I'll approach a more complicated parent trait constructor.
Simple singleton trait.
<?php
trait singleton {
/**
* private construct, generally defined by using class
*/
//private function __construct() {}
public static function getInstance() {
static $_instance = NULL;
$class = __CLASS__;
return $_instance ?: $_instance = new $class;
}
public function __clone() {
trigger_error('Cloning '.__CLASS__.' is not allowed.',E_USER_ERROR);
}
public function __wakeup() {
trigger_error('Unserializing '.__CLASS__.' is not allowed.',E_USER_ERROR);
}
}
/**
* Example Usage
*/
class foo {
use singleton;
private function __construct() {
$this->name = 'foo';
}
}
class bar {
use singleton;
private function __construct() {
$this->name = 'bar';
}
}
$foo = foo::getInstance();
echo $foo->name;
$bar = bar::getInstance();
echo $bar->name;
The best way to understand what traits are and how to use them is to look at them for what they essentially are: language assisted copy and paste.
If you can copy and paste the code from one class to another (and we've all done this, even though we try not to because its code duplication) then you have a candidate for a trait.
A (somewhat) practical example of trait usage.
Without traits:
<?php
class Controller {
/* Controller-specific methods defined here. */
}
class AdminController extends Controller {
/* Controller-specific methods inherited from Controller. */
/* Admin-specific methods defined here. */
}
class CrudController extends Controller {
/* Controller-specific methods inherited from Controller. */
/* CRUD-specific methods defined here. */
}
class AdminCrudController extends CrudController {
/* Controller-specific methods inherited from Controller. */
/* CRUD-specific methods inherited from CrudController. */
/* (!!!) Admin-specific methods copied and pasted from AdminController. */
}
?>
With traits:
<?php
class Controller {
/* Controller-specific methods defined here. */
}
class AdminController extends Controller {
/* Controller-specific methods inherited from Controller. */
/* Admin-specific methods defined here. */
}
trait CrudControllerTrait {
/* CRUD-specific methods defined here. */
}
class AdminCrudController extends AdminController {
use CrudControllerTrait;
/* Controller-specific methods inherited from Controller. */
/* Admin-specific methods inherited from AdminController. */
/* CRUD-specific methods defined by CrudControllerTrait. */
}
?>
The magic method __call works as expected using traits.
<?php
trait Call_Helper{
public function __call($name, $args){
return count($args);
}
}
class Foo{
use Call_Helper;
}
$foo = new Foo();
echo $foo->go(1,2,3,4); // echoes 4
Traits are useful for strategies, when you want the same data to be handled (filtered, sorted, etc) differently.
For example, you have a list of products that you want to filter out based on some criteria (brands, specs, whatever), or sorted by different means (price, label, whatever). You can create a sorting trait that contains different functions for different sorting types (numeric, string, date, etc). You can then use this trait not only in your product class (as given in the example), but also in other classes that need similar strategies (to apply a numeric sort to some data, etc).
<?php
trait SortStrategy {
private $sort_field = null;
private function string_asc($item1, $item2) {
return strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]);
}
private function string_desc($item1, $item2) {
return strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]);
}
private function num_asc($item1, $item2) {
if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
return ($item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 );
}
private function num_desc($item1, $item2) {
if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
return ($item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 );
}
private function date_asc($item1, $item2) {
$date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
$date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
if ($date1 == $date2) return 0;
return ($date1 < $date2 ? -1 : 1 );
}
private function date_desc($item1, $item2) {
$date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
$date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
if ($date1 == $date2) return 0;
return ($date1 > $date2 ? -1 : 1 );
}
}
class Product {
public $data = array();
use SortStrategy;
public function get() {
// do something to get the data, for this ex. I just included an array
$this->data = array(
101222 => array('label' => 'Awesome product', 'price' => 10.50, 'date_added' => '2012-02-01'),
101232 => array('label' => 'Not so awesome product', 'price' => 5.20, 'date_added' => '2012-03-20'),
101241 => array('label' => 'Pretty neat product', 'price' => 9.65, 'date_added' => '2012-04-15'),
101256 => array('label' => 'Freakishly cool product', 'price' => 12.55, 'date_added' => '2012-01-11'),
101219 => array('label' => 'Meh product', 'price' => 3.69, 'date_added' => '2012-06-11'),
);
}
public function sort_by($by = 'price', $type = 'asc') {
if (!preg_match('/^(asc|desc)$/', $type)) $type = 'asc';
switch ($by) {
case 'name':
$this->sort_field = 'label';
uasort($this->data, array('Product', 'string_'.$type));
break;
case 'date':
$this->sort_field = 'date_added';
uasort($this->data, array('Product', 'date_'.$type));
break;
default:
$this->sort_field = 'price';
uasort($this->data, array('Product', 'num_'.$type));
}
}
}
$product = new Product();
$product->get();
$product->sort_by('name');
echo '<pre>'.print_r($product->data, true).'</pre>';
?>
Not very obvious but trait methods can be called as if they were defined as static methods in a regular class
<?php
trait Foo {
function bar() {
return 'baz';
}
}
echo Foo::bar(),"\\n";
?>
Traits can not implement interfaces.
(should be obvious, but tested is tested)
The difference between Traits and multiple inheritance is in the inheritance part. A trait is not inherited from, but rather included or mixed-in, thus becoming part of "this class". Traits also provide a more controlled means of resolving conflicts that inevitably arise when using multiple inheritance in the few languages that support them (C++). Most modern languages are going the approach of a "traits" or "mixin" style system as opposed to multiple-inheritance, largely due to the ability to control ambiguities if a method is declared in multiple "mixed-in" classes.
Also, one can not "inherit" static member functions in multiple-inheritance.
Trait can not have the same name as class because it will show: Fatal error: Cannot redeclare class
Just in case someone was wondering, traits can be inside namespaces too! Cool!!
Traits can be useful to create Fluent API
ex:
trait StaticMake
{
public static function make()
{
return new static();
}
}
class HelloWorld
{
use StaticMake;
public function getHello()
{
return "Hello World";
}
}
//now instead:
$theHello = new HelloWorld;
echo $theHello -> getHello();
//one may just use
echo HelloWorld:make() -> getHello();
Hi! Just a quick solution to use class-identical static property. I use it to store SQL descriptions, which is always same for the same class, but may change for an extended class.
<?php
interface hasO{ // just for forcing basic functionalities
static function O($use_as);
function myObj($append = "nothing");
function inc();
static function getName();
}
class O{ // the target class to access
var $val, $num = 0;
function setVAl($val){$this->val = $val;}
function inc(){$this->num++;}
function prt($append){echo "hello, i'm {$this->val} with number: {$this->num} and append a string: {$append}<br>"; }
}
trait T{ // the trait to access O
private static $objs = [];
private static $preference = [];
static function setPreference($class){
self::$preference[get_called_class()] = $class; // we can hack even polymorphism
}
static function O($use_as = null){
/* we use $use_as, preference or the current calling class in this order. */
$cc = ($use_as ? $use_as : (($cc2 = @self::$preference[get_called_class()])!= null ? $cc2 : get_called_class()));
if (!@self::$objs[$cc]){ // you have to use 'self::' to access a private method.
self::$objs[$cc] = new O();
self::$objs[$cc]->setVal(static::getName()); // but you can use 'static::' to access class method.
}
return self::$objs[$cc];
}
}
class A implements hasO{ // A is independent from the other classes
use T;
static function getName() {return 'A';}
function myObj($append = "i'm A",$use_as = null){
if (!$append && $append !== false) $append = "i'm ".get_called_class();
self::O($use_as)->prt($append);
}
function inc(){self::O()->inc();}
}
class B implements hasO{
use T;
function myObj($append = null){
if (!$append && $append !== false) $append = "i'm ".get_called_class();
static::O()->prt($append);
}
function inc(){static::O()->inc();}
static function getName() {return get_called_class();}
}
class C extends B{} // not relevant inheritence.
class D extends B{
use T; // you won't get errors if you accidently reuse the same trait
static function getName() {return 'a child of B ';} // we can use current object static methods, so inheritence is intact
function inc(){
static::O()->inc(); // both self::O and static::O is usable to preserve further inherits
self::O()->inc();
}
}
class E extends B{
const CLSS = 'E';
function myObj($append="EEEE!", $use_as = null){
if (!$append && $append !== false) $append = "i'm ".get_called_class();
self::O($use_as)->prt($append);
}
}
$a = new A;$b = new B;$c = new C;$d = new D;$e = new E; // create all objects...
$b->myObj(); $b->inc(); $b->myObj();
$c->myObj(); $c->inc(); $c->myObj(); // C comes before A, so C's object is already instantized before A tries to access it
$a->myObj(); $a->inc(); $a->myObj();
$a->myObj(null,'C'); // this isn't works, because it's protected private
$d->myObj(); $d->inc(); $d->myObj();
$e->myObj(); $e->inc(); $e->myObj("i'm E again");
$e->myObj(0,'C'); // but this IS working, even if it's private NOT protected.
$e::setPreference('B'); // we want to morph to B
$e->myObj();
$e->inc(); // we incement B's object now as seen bellow
$e->myObj(); $b->myObj();
?>
<?php
trait first_trait
{
function first_function()
{
echo "From First Trait";
}
}
trait second_trait
{
function first_function()
{
echo "From Second Trait";
}
}
class first_class
{
use first_trait, second_trait
{
// This class will now call the method
// first function from first_trait only
first_trait::first_function insteadof second_trait;
// first_function of second_traits can be
// accessed with second_function
second_trait::first_function as second_function;
}
}
$obj = new first_class();
// Output: From First Trait
$obj->first_function();
// Output: From Second Trait
$obj->second_function();
?>
the iranian php programmer
writer: farhad zand
farhad.peb@gmail.com
php_engineer_bk@yahoo.com
