PHP 8.4.2 Released!

Трейты

Способ, которым в языке PHP один и тот же код внедряют в несвязанные иерархии классов, называется трейтами (англ. Traits).

Трейты — механизм, который разрешает повторно использовать код в языках с одиночным наследованием наподобие PHP. Задача трейта — уменьшить ограничения одиночного наследования, разрешая разработчику легко переиспользовать наборы методов в нескольких независимых классах, которые находятся в разных иерархиях наследования. Семантику комбинации трейтов и классов определили так, чтобы снизить уровень сложности и избежать типичных проблем, свойственных множественному наследованию и примесям (англ. Mixins).

Трейт похож на класс, но предназначается только для группировки функциональности тонко контролируемым и согласованным образом. Нельзя создать отдельный экземпляр трейта. Трейт дополняет традиционное наследование и разрешает выстраивать горизонтальную композицию поведения; другими словами, трейт играет роль приложения к членам класса, которое не требует наследования.

Пример #1 Пример трейта

<?php

trait TraitA {
public function
sayHello()
{
echo
'Hello';
}
}

trait
TraitB {
public function
sayWorld()
{
echo
'World';
}
}

class
MyHelloWorld
{
use
TraitA, TraitB; // Класс разрешает внедрять несколько трейтов

public function sayHelloWorld()
{
$this->sayHello();
echo
' ';

$this->sayWorld();
echo
"!\n";
}
}

$myHelloWorld = new MyHelloWorld();
$myHelloWorld->sayHelloWorld();

?>

Результат выполнения приведённого примера:

Hello World!

Приоритет

Член, который класс унаследовал из базового класса, переопределяется членом, который внедрился трейтом. Порядок приоритета выстраивается так, что методы трейта переопределяются методами текущего класса, а методы, которые класс унаследовал из базового класса, переопределяются методами трейта.

Пример #2 Пример того, в каком порядке выстраивается приоритет

Метод, который класс унаследовал из базового класса, переопределяется методом, который внедрился в класс MyHelloWorld из трейта SayWorld. Методы трейта ведут себя как методы класса MyHelloWorld. Порядок приоритета такой: методами текущего класса переопределяются методы трейта, которыми переопределяются методы базового класса.

<?php

class Base
{
public function
sayHello()
{
echo
'Hello ';
}
}

trait
SayWorld
{
public function
sayHello()
{
parent::sayHello();
echo
'World!';
}
}

class
MyHelloWorld extends Base
{
use
SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();

?>

Результат выполнения приведённого примера:

Hello World!

Пример #3 Пример альтернативного порядка приоритета

<?php

trait HelloWorld
{
public function
sayHello()
{
echo
'Hello World!';
}
}

class
TheWorldIsNotEnough
{
use
HelloWorld;

public function
sayHello()
{
echo
'Hello Universe!';
}
}

$o = new TheWorldIsNotEnough();
$o->sayHello();

?>

Результат выполнения приведённого примера:

Hello Universe!

Несколько трейтов

Названия трейтов перечисляют через запятую в инструкции use, чтобы добавить в класс несколько трейтов.

Пример #4 Пример внедрения нескольких трейтов

<?php

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();

?>

Результат выполнения приведённого примера:

Hello World!

Разрешение конфликтов

При добавлении двумя трейтами метода с одним и тем же названием возникает фатальная ошибка, если конфликт явно не разрешили.

Оператор insteadof разрешает конфликты имён и указывает, метод какого трейта исключить из класса, когда название метода одного трейта совпадает с названием метода другого трейта, который включили в этот же класс.

Для добавления псевдонима методу трейта указывают оператор as, поскольку предыдущий оператор только исключает методы. Обратите внимание, оператор as не переименовывает метод и не влияет ни на какие другие методы.

Пример #5 Пример разрешения конфликтов

В этом примере в класс Talker включили трейты A и B. Поскольку в трейтах A и B содержатся методы, которые вступают в конфликт, класс использует вариант метода smallTalk из трейта B, а вариант метода bigTalk из трейта A.

Оператор as в классе Aliased_Talker разрешает вызывать метод bigTalk трейта B по псевдониму 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;
}
}

?>

Изменение видимости метода

Синтаксис с оператором as умеет также настраивать видимость метода в классе.

Пример #6 Пример изменения видимости метода

<?php

trait HelloWorld
{
public function
sayHello()
{
echo
'Привет, мир!';
}
}

// Изменим видимость метода sayHello
class MyClass1
{
use
HelloWorld {
sayHello as protected;
}
}

// Создадим псевдоним метода и изменим видимость этого метода.
// Видимость метода sayHello не изменилась
class MyClass2
{
use
HelloWorld {
sayHello as private myPrivateHello;
}
}

?>

Трейты, которые состоят из трейтов

Аналогично внедрению в классы трейты разрешается внедрять в другие трейты. Трейт будет состоять из отдельных или всех членов других трейтов при внедрении одного или нескольких трейтов в определении трейта.

Пример #7 Пример трейтов, которые состоят из трейтов

<?php

trait Hello
{
public function
sayHello()
{
echo
'Hello ';
}
}

trait
World
{
public function
sayWorld()
{
echo
'World!';
}
}

trait
HelloWorld
{
use
Hello, World;
}

class
MyHelloWorld
{
use
HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();

?>

Результат выполнения приведённого примера:

Hello World!

Абстрактные члены трейтов

Трейты поддерживают абстрактные методы, чтобы установить требования к классу, в который внедрится трейт. Поддерживаются общедоступные, защищённые и закрытые методы. До PHP 8.0.0 поддерживались только общедоступные и защищённые абстрактные методы.

Предостережение

Начиная с PHP 8.0.0 выдаётся фатальная ошибка, если сигнатура конкретного метода не следует правилам совместимости сигнатур. Раньше при несовпадении сигнатуры метода ошибка не выдавалась.

Пример #8 Пример установки требований к классу через абстрактный метод трейта

<?php

trait Hello
{
public function
sayHelloWorld()
{
echo
'Hello' . $this->getWorld();
}

abstract public function
getWorld();
}

class
MyHelloWorld
{
private
$world;

use
Hello;

public function
getWorld()
{
return
$this->world;
}

public function
setWorld($val)
{
$this->world = $val;
}
}

?>

Статические члены трейта

В трейтах разрешается определять статические переменные, статические методы и статические свойства.

Замечание:

Начиная с PHP 8.1.0 прямой вызов статического метода или прямой доступ к статическому свойству в трейте устарели. Доступ к статическим методам и свойствам получают только в классе, в который внедрили трейт.

Пример #9 Статические переменные

<?php

trait Counter
{
public function
inc()
{
static
$c = 0;
$c = $c + 1;
echo
"$c\n";
}
}

class
C1
{
use
Counter;
}

class
C2
{
use
Counter;
}

$o = new C1();
$o->inc();

$p = new C2();
$p->inc();

?>

Результат выполнения приведённого примера:

1
1

Пример #10 Статические методы

<?php

trait StaticExample
{
public static function
doSomething()
{
return
'Делаем что-нибудь';
}
}

class
Example
{
use
StaticExample;
}

echo
Example::doSomething();

?>

Результат выполнения приведённого примера:

Doing something

Пример #11 Статические свойства

Предостережение

До PHP 8.3.0 дочерние классы наследовали статические свойства, которые родительские классы получали из трейта, даже если трейт явно внедрялся в дочерний класс. Начиная с PHP 8.3.0 статическое свойство трейта переопределяет в дочернем классе статическое свойство, которое дочерний класс унаследовал из родительского.

<?php

trait T
{
public static
$counter = 1;
}

class
A
{
use
T;

public static function
incrementCounter()
{
static::
$counter++;
}
}

class
B extends A
{
use
T;
}

A::incrementCounter();

echo
A::$counter, "\n";
echo
B::$counter, "\n";

?>

Результат выполнения приведённого примера в PHP 8.3:

2
1

Свойства

В трейтах доступно определение свойств.

Пример #12 Пример определения свойств в трейте

<?php

trait PropertiesTrait
{
public
$x = 1;
}

class
PropertiesExample
{
use
PropertiesTrait;
}

$example = new PropertiesExample();
$example->x;

?>

В классе нельзя определять свойство с названием как у свойства трейта, если свойство класса несовместимо со свойством трейта по области видимости и типу, модификатору readonly и начальному значению, иначе PHP выдаёт фатальную ошибку.

Пример #13 Разрешение конфликтов

<?php

trait PropertiesTrait
{
public
$same = true;
public
$different1 = false;
public
bool $different2;
public
bool $different3;
}

class
PropertiesExample
{
use
PropertiesTrait;

public
$same = true;
public
$different1 = true; // Фатальная ошибка
public string $different2; // Фатальная ошибка
readonly protected bool $different3; // Фатальная ошибка
}

?>

Константы

Начиная с версии PHP 8.2.0 в трейтах разрешили также определять константы.

Пример #14 Определение констант

<?php

trait ConstantsTrait
{
public const
FLAG_MUTABLE = 1;
final public const
FLAG_IMMUTABLE = 5;
}

class
ConstantsExample
{
use
ConstantsTrait;
}

$example = new ConstantsExample;
echo
$example::FLAG_MUTABLE;

?>

Результат выполнения приведённого примера:

1

В классе нельзя определять константу с названием как у константы трейта, если константа класса несовместима с константой трейта по области видимости, начальному значению и модификатору final, иначе PHP выдаёт фатальную ошибку.

Пример #15 Разрешение конфликтов

<?php

trait ConstantsTrait
{
public const
FLAG_MUTABLE = 1;
final public const
FLAG_IMMUTABLE = 5;
}

class
ConstantsExample
{
use
ConstantsTrait;

public const
FLAG_IMMUTABLE = 5; // Фатальная ошибка
}

?>

Окончательные методы

Начиная с PHP 8.3.0 методы, которые импортировали из трейтов, разрешили делать окончательными через оператор as и модификатор final. Определение метода трейта окончательным запрещает дочерним классам переопределять метод. Но самому классу, в который импортировали трейт и в котором сделали метод окончательным, по-прежнему доступно переопределение метода.

Пример #16 Определение метода окончательным путём добавления модификатора final при внедрении трейта

<?php

trait CommonTrait
{
public function
method()
{
echo
'Привет';
}
}

class
FinalExampleA
{
use
CommonTrait {
CommonTrait::method as final; // Начиная с PHP 8.3.0 модификатор final
// запретит переопределять метод в дочерних классах
}
}

class
FinalExampleB extends FinalExampleA
{
public function
method() {}
}

?>

Вывод приведённого примера будет похож на:

Fatal error: Cannot override final method FinalExampleA::method() in ...
Добавить

Примечания пользователей 25 notes

up
656
Safak Ozpinar / safakozpinar at gmail
12 years ago
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
?>
up
454
greywire at gmail dot com
12 years ago
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.
up
248
Stefan W
11 years ago
Note that the "use" operator for traits (inside a class) and the "use" operator for namespaces (outside the class) resolve names differently. "use" for namespaces always sees its arguments as absolute (starting at the global namespace):

<?php
namespace Foo\Bar;
use
Foo\Test; // means \Foo\Test - the initial \ is optional
?>

On the other hand, "use" for traits respects the current namespace:

<?php
namespace Foo\Bar;
class
SomeClass {
use
Foo\Test; // means \Foo\Bar\Foo\Test
}
?>

Together with "use" for closures, there are now three different "use" operators. They all mean different things and behave differently.
up
11
JustAddingSomeAdditionalUseCase
1 year ago
I have not seen this specific use case:

"Wanting to preserve action of parent class method, the trait one calling ::parent & also the child class mehod action".

// Child class.
use SuperTrait {
initialize as initializeOr;
}
public function initialize(array &$element) {
...
$this->initializeOr($element);
}
// Trait.
public function initialize(array &$element) {
...
parent::initialize($element);
}
// Parent class.
public function initialize(array &$element) {
...
}
up
105
t8 at AT pobox dot com
12 years ago
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'

?>
up
99
chris dot rutledge at gmail dot com
13 years ago
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
up
62
qeremy (!) gmail
9 years ago
Keep in mind; "final" keyword is useless in traits when directly using them, unlike extending classes / abstract classes.

<?php
trait Foo {
final public function
hello($s) { print "$s, hello!"; }
}
class
Bar {
use
Foo;
// Overwrite, no error
final public function hello($s) { print "hello, $s!"; }
}

abstract class
Foo {
final public function
hello($s) { print "$s, hello!"; }
}
class
Bar extends Foo {
// Fatal error: Cannot override final method Foo::hello() in ..
final public function hello($s) { print "hello, $s!"; }
}
?>

But this way will finalize trait methods as expected;

<?php
trait FooTrait {
final public function
hello($s) { print "$s, hello!"; }
}
abstract class
Foo {
use
FooTrait;
}
class
Bar extends Foo {
// Fatal error: Cannot override final method Foo::hello() in ..
final public function hello($s) { print "hello, $s!"; }
}
?>
up
11
yeu_ym at yahoo dot com
5 years ago
Here is an example how to work with visiblity and conflicts.

<?php

trait A
{
private function
smallTalk()
{
echo
'a';
}

private function
bigTalk()
{
echo
'A';
}
}

trait
B
{
private function
smallTalk()
{
echo
'b';
}

private function
bigTalk()
{
echo
'B';
}
}

trait
C
{
public function
smallTalk()
{
echo
'c';
}

public function
bigTalk()
{
echo
'C';
}
}

class
Talker
{
use
A, B, C {
//visibility for methods that will be involved in conflict resolution
B::smallTalk as public;
A::bigTalk as public;

//conflict resolution
B::smallTalk insteadof A, C;
A::bigTalk insteadof B, C;

//aliases with visibility change
B::bigTalk as public Btalk;
A::smallTalk as public asmalltalk;

//aliases only, methods already defined as public
C::bigTalk as Ctalk;
C::smallTalk as cmallstalk;
}

}

(new
Talker)->bigTalk();//A
(new Talker)->Btalk();//B
(new Talker)->Ctalk();//C

(new Talker)->asmalltalk();//a
(new Talker)->smallTalk();//b
(new Talker)->cmallstalk();//c
up
32
canufrank
8 years ago
A number of the notes make incorrect assertions about trait behaviour because they do not extend the class.

So, while "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
?>"

shows a correct example, simply adding
<?php
require_once('above');
class
Foo3 extends Foo2 {
}
Foo3::$_bar = 'news';
echo
Foo1::$_bar . ' ' . Foo2::$_bar . ' ' . Foo3::$_bar;

// Prints: Hello news news

I think the best conceptual model of an incorporated trait is an advanced insertion of text, or as someone put it "language assisted copy and paste." If Foo1 and Foo2 were defined with $_bar, you would not expect them to share the instance. Similarly, you would expect Foo3 to share with Foo2, and it does.

Viewing this way explains away a lot of the 'quirks' that are observed above with final, or subsequently declared private vars,
up
15
rawsrc
6 years ago
About the (Safak Ozpinar / safakozpinar at gmail)'s great note, you can still have the same behavior than inheritance using trait with this approach :
<?php

trait TestTrait {
public static
$_bar;
}

class
FooBar {
use
TestTrait;
}

class
Foo1 extends FooBar {

}
class
Foo2 extends FooBar {

}
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo
Foo1::$_bar . ' ' . Foo2::$_bar; // Prints: World World
up
10
balbuf
8 years ago
(It's already been said, but for the sake of searching on the word "relative"...)

The "use" keyword to import a trait into a class will resolve relative to the current namespace and therefore should include a leading slash to represent a full path, whereas "use" at the namespace level is always absolute.
up
18
marko at newvibrations dot net
8 years ago
As already noted, static properties and methods in trait could be accessed directly using trait. Since trait is language assisted c/p, you should be aware that static property from trait will be initialized to the value trait property had in the time of class declaration.

Example:

<?php

trait Beer {
protected static
$type = 'Light';
public static function
printed(){
echo static::
$type.PHP_EOL;
}
public static function
setType($type){
static::
$type = $type;
}
}

class
Ale {
use
Beer;
}

Beer::setType("Dark");

class
Lager {
use
Beer;
}

Beer::setType("Amber");

header("Content-type: text/plain");

Beer::printed(); // Prints: Amber
Ale::printed(); // Prints: Light
Lager::printed(); // Prints: Dark

?>
up
12
qschuler at neosyne dot com
10 years ago
Note that you can omit a method's inclusion by excluding it from one trait in favor of the other and doing the exact same thing in the reverse way.

<?php

trait A {
public function
sayHello()
{
echo
'Hello from A';
}

public function
sayWorld()
{
echo
'World from A';
}
}

trait
B {
public function
sayHello()
{
echo
'Hello from B';
}

public function
sayWorld()
{
echo
'World from B';
}
}

class
Talker {
use
A, B {
A::sayHello insteadof B;
A::sayWorld insteadof B;
B::sayWorld insteadof A;
}
}

$talker = new Talker();
$talker->sayHello();
$talker->sayWorld();

?>

The method sayHello is imported, but the method sayWorld is simply excluded.
up
18
Edward
12 years ago
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.
up
44
ryan at derokorian dot com
12 years ago
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;
up
6
katrinaelaine6 at gmail dot com
7 years ago
Adding to "atorich at gmail dot com":

The behavior of the magic constant __CLASS__ when used in traits is as expected if you understand traits and late static binding (http://php.net/manual/en/language.oop5.late-static-bindings.php).

<?php

$format
= 'Class: %-13s | get_class(): %-13s | get_called_class(): %-13s%s';

trait
TestTrait {
public function
testMethod() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}

public static function
testStatic() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}
}

trait
DuplicateTrait {
public function
duplMethod() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}

public static function
duplStatic() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}
}

abstract class
AbstractClass {

use
DuplicateTrait;

public function
absMethod() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}

public static function
absStatic() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}
}

class
BaseClass extends AbstractClass {
use
TestTrait;
}

class
TestClass extends BaseClass { }

$t = new TestClass();

$t->testMethod();
TestClass::testStatic();

$t->absMethod();
TestClass::absStatic();

$t->duplMethod();
TestClass::duplStatic();

?>

Will output:

Class: BaseClass | get_class(): BaseClass | get_called_class(): TestClass
Class: BaseClass | get_class(): BaseClass | get_called_class(): TestClass
Class: AbstractClass | get_class(): AbstractClass | get_called_class(): TestClass
Class: AbstractClass | get_class(): AbstractClass | get_called_class(): TestClass
Class: AbstractClass | get_class(): AbstractClass | get_called_class(): TestClass
Class: AbstractClass | get_class(): AbstractClass | get_called_class(): TestClass

Since Traits are considered literal "copying/pasting" of code, it's clear how the methods defined in DuplicateTrait give the same results as the methods defined in AbstractClass.
up
15
D. Marti
12 years ago
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>';
?>
up
4
bscheshirwork at gmail dot com
7 years ago
https://3v4l.org/mFuQE

1. no deprecate if same-class-named method get from trait
2. replace same-named method ba to aa in C

trait ATrait {
public function a(){
return 'Aa';
}
}

trait BTrait {
public function a(){
return 'Ba';
}
}

class C {
use ATrait{
a as aa;
}
use BTrait{
a as ba;
}

public function a() {
return static::aa() . static::ba();
}
}

$o = new C;
echo $o->a(), "\n";

class D {
use ATrait{
ATrait::a as aa;
}
use BTrait{
BTrait::a as ba;
}

public function a() {
return static::aa() . static::ba();
}
}

$o = new D;
echo $o->a(), "\n";

class E {
use ATrait{
ATrait::a as aa;
ATrait::a insteadof BTrait;
}
use BTrait{
BTrait::a as ba;
}

public function e() {
return static::aa() . static::ba();
}
}

$o = new E;
echo $o->e(), "\n";

class F {
use ATrait{
a as aa;
}
use BTrait{
a as ba;
}

public function f() {
return static::aa() . static::ba();
}
}

$o = new F;
echo $o->f(), "\n";

AaAa
AaBa

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; E has a deprecated constructor in /in/mFuQE on line 48
AaBa

Fatal error: Trait method a has not been applied, because there are collisions with other trait methods on F in /in/mFuQE on line 65
up
13
Kristof
10 years ago
don't forget you can create complex (embedded) traits as well

<?php
trait Name {
// ...
}
trait
Address {
// ...
}
trait
Telephone {
// ...
}
trait
Contact {
use
Name, Address, Telephone;
}
class
Customer {
use
Contact;
}
class
Invoce {
use
Contact;
}
?>
up
5
Carlos Alberto Bertholdo Carucce
8 years ago
If you want to resolve name conflicts and also change the visibility of a trait method, you'll need to declare both in the same line:

trait testTrait{

public function test(){
echo 'trait test';
}

}

class myClass{

use testTrait {
testTrait::test as private testTraitF;
}

public function test(){
echo 'class test';
echo '<br/>';
$this->testTraitF();
}

}

$obj = new myClass();
$obj->test(); //prints both 'trait test' and 'class test'
$obj->testTraitF(); //The method is not accessible (Fatal error: Call to private method myClass::testTraitF() )
up
4
Oddant
11 years ago
I think it's obvious to notice that using 'use' followed by the traits name must be seen as just copying/pasting lines of code into the place where they are used.
up
1
guidobelluomo at gmail dot com
4 years ago
If you override a method which was defined by a trait, calling the parent method will also call the trait's override. Therefore if you need to derive from a class which has a trait, you can extend the class without losing the trait's functionality:

<?php

trait ExampleTrait
{
public function
output()
{
parent::output();
echo
"bar<br>";
}
}

class
Foo
{
public function
output()
{
echo
"foo<br>";
}
}

class
FooBar extends Foo
{
use
ExampleTrait;
}

class
FooBarBaz extends FooBar
{
use
ExampleTrait;
public function
output()
{
parent::output();
echo
"baz";
}
}

(new
FooBarBaz())->output();
?>

Output:
foo
bar
baz
up
3
84td84 at gmail dot com
9 years ago
A note to 'Beispiel #9 Statische Variablen'. A trait can also have a static property:

trait Counter {
static $trvar=1;

public static function stfunc() {
echo "Hello world!"
}
}

class C1 {
use Counter;
}

print "\nTRVAR: " . C1::$trvar . "\n"; //prints 1

$obj = new C1();
C1::stfunc(); //prints Hello world!
$obj->stfunc(); //prints Hello world!

A static property (trvar) can only be accessed using the classname (C1).
But a static function (stfunc) can be accessed using the classname or the instance ($obj).
up
2
cody at codysnider dot com
7 years ago
/*
DocBlocks pertaining to the class or trait will NOT be carried over when applying the trait.

Results trying a couple variations on classes with and without DocBlocks that use a trait with a DocBlock
*/

<?php

/**
* @Entity
*/
trait Foo
{
protected
$foo;
}

/**
* @HasLifecycleCallbacks
*/
class Bar
{
use
\Foo;

protected
$bar;
}

class
MoreBar
{
use
\Foo;

protected
$moreBar;
}

$w = new \ReflectionClass('\Bar');
echo
$w->getName() . ":\r\n";
echo
$w->getDocComment() . "\r\n\r\n";

$x = new \ReflectionClass('\MoreBar');
echo
$x->getName() . ":\r\n";
echo
$x->getDocComment() . "\r\n\r\n";

$barObj = new \Bar();
$y = new \ReflectionClass($barObj);
echo
$y->getName() . ":\r\n";
echo
$y->getDocComment() . "\r\n\r\n";

foreach(
$y->getTraits() as $traitObj) {
echo
$y->getName() . " ";
echo
$traitObj->getName() . ":\r\n";
echo
$traitObj->getDocComment() . "\r\n";
}

$moreBarObj = new \MoreBar();
$z = new \ReflectionClass($moreBarObj);
echo
$z->getName() . " ";
echo
$z->getDocComment() . "\r\n\r\n";

foreach(
$z->getTraits() as $traitObj) {
echo
$z->getName() . " ";
echo
$traitObj->getName() . ":\r\n";
echo
$traitObj->getDocComment() . "\r\n";
}
up
4
artur at webprojektant dot pl
12 years ago
Trait can not have the same name as class because it will show: Fatal error: Cannot redeclare class
To Top