Traits php что это

Traits в php 5.4. Разбираем детали реализации

Совсем недавно вышла первая beta php 5.4, а пока я писал топик подоспела и вторая. Одно из нововведений в 5.4 – это traits (типажи). Предлагаю разобраться во всех деталях в том, что же типажи из себя представляют в php.

Простой пример типажа, чтобы не заглядывать в Википедею:

//определение типажа trait Pprint < public function whoAmI() < return get_class($this) . ': ' . (string) $this; >> class Human < use Pprint; //подключаем типаж, ключевое слово use protected $_name = 'unknown'; public function __construct($name) < $this->_name = $name; > public function __toString() < return (string) $this->_name; > > $a = new Human('Nikita'); echo $a->whoAmI(), PHP_EOL; //=> Human: Nikita 

Как видно, к классу Human было добавлено поведение из типажа Pprint .

Но во всём есть свои детали.

Синтаксис

В общем и целом всё просто. Типажей можно подключить к классу неограниченное кол-во через одну или несколько конструкций use внутри определения класса. use может быть указан в любом месте класса.

  • назначить alias’ы к методам типажа ( Trait::method as myMethod – method из Trait будет дополнительно доступен, как myMethod );
  • указать перекрытие метода одного типажа, методом другого, если у них совпали названия ( TraitA::method insteadof TraitB – будет использован метод TraitA вместо одноимённого метода TraitB );
  • повысить или понизить доступ к методу из типажа, за исключение перевода метода в статический ( Trait::publicMethod as protected ), можно сразу с переименованием ( Trait::publicMethod as protected _myProtectedMethod ).
trait Pprint < public function whoAmI() < return get_class($this) . ': ' . (string) $this; >> trait Namer < //использование одного типажа в другом use Pprint; public function getMyName() < return $this->whoAmI(); > public function getMyLastName() < return 'Unknown =('; >public function getMyNickname() < return preg_replace('/[^a-z]+/i', '_', strtolower($this->getMyName())); > > trait SuperNamer < public function getMyLastName() < return 'Ask me'; >> class Human < use SuperNamer; use Namer < SuperNamer::getMyLastName insteadof Namer; Namer::getMyNickname as protected _getMyLogin; >protected $_name = 'unknown'; public function __construct($name) < $this->_name = $name; > public function __toString() < return (string) $this->_name; > public function getLogin() < return $this->_getMyLogin(); > > $a = new Human('Nikita'); echo join(', ', get_class_methods($a)), PHP_EOL; //__construct, __toString, getLogin, getMyLastName, //getMyName, getMyNickname, whoAmI echo $a->getMyName(), PHP_EOL; //Human: Nikita echo $a->getMyLastName(), PHP_EOL; //Ask me echo $a->getLogin(), PHP_EOL; //human_nikita echo $a->getMyNickname(), PHP_EOL; //human_nikita 

Тут важно обратить внимание на два момента. Во-первых, блок после use кажется связанным с типажом около которого он описан, но это не так. Правила в блоке глобальные и могут быть объявлены в любом месте.

Чтобы не возникало путаницы, хорошей практикой будет записать сначала все типажи через запятую, а затем на отдельной строке правила перекрытия и alias. Либо описывать все правила для типажа рядом с его подключением. Выбор за вами.

//так use SuperNamer, Namer, Singleton, SomeOther < SuperNamer::getMyLastName insteadof Namer; SomeOther::getSomething as private; >//либо так use Namer; use Singleton; use SuperNamer < SuperNamer::getMyLastName insteadof Namer; >use SomeOther

Во-вторых, обратите внимание на список методов, в списке остался getMyNickname , а _getMyLogin просто его alias с пониженным доступом. Можно исключить исходный метод совсем, но об этом ниже в разделе магии.

Читайте также:  How to minimize css

Типажи инициализируются, как и классы, динамически. При большом желании можно писать так:

Свойства в типажах

До этого, я оперировал методами, но типаж может включать в себя и свойства, которые будут добавлены в класс. В этом плане «типажи» в php – это скорее mixin.

trait WithId < protected $_id = null; public function getId() < return $this->_id; > public function setId($id) < $this->_id = $id; > > 

Сразу предлагаю хорошую практику, чтобы однажды не оказалось, что свойство _id в типаже конфликтует с используемым в классе или его потомках, свойства типажей записывать с префиксами:

trait WithId < protected $_WithId_id = null; protected $_WithId_checked = false; //. public function getId() < return $this->_WithId_id; > public function setId($id) < $this->_WithId_id = $id; > > 

Область видимости

Важно понимать, как будут разрешаться различные вызовы внутри типажа. В этом поможет правило думать о подключении типажа, как о «copy-paste» кода в целевой класс. В самом первом примере, интерпретатор как бы сделал «copy-paste» метода whoAmI в класс Human , соответственно все вызовы к parent , self , $this будут работать также, как и вызов в методах класса. Исключение будут составлять некоторые магические константы, например внутри whoAmI __METHOD__ === ‘Pprint::whoAmI’.

Внутри методов типажа доступны все свойства объекта для обращения напрямую, никаких дополнительных областей видимости не добавляется. Можно было бы получить просто $this->_name , вместо вызова __toString . Однако стоит несколько раз подумать, прежде чем делать это, так как на сложных реализациях это внесёт не мало путаницы. Я бы рекомендовал всегда использовать понятные методы, при необходимости даже описать их в интерфейсе и «заставлять» классы его имплементировать.

Статические методы и свойства

В типаже можно объявлять статические методы, но нельзя объявлять статические свойства. Внутри статических методов можно использовать, как статическое связывание (self::), так и динамическое (static::), всё будет работать так, как будто вызвано из метода класса («copy-paste»).

Ограничение на хранение статических свойств обойти можно, как именно покажу позже с обращением к магии.

Совпадение методов типажей между собой и с методами класса

Метод описанный в классе перекрывает метод из типажа. Но если какой-то метод описан в родительском классе, а в дочернем классе подключён типаж с таким же методом, он перекроет метод из родительского (снова вспоминаем «copy-paste»).

Если в нескольких, указанных у класса типажах, используются одинаковые методы, php выдаст ошибку на этапе инициализации класса:

trait A < public function abc() <>> trait B < public function abc() <>> class C < use A, B; >//Fatal error: Trait method abc has not been applied, //because there are collisions with other trait methods //on C in %FILE% on line %line% 

На помощь приходит insteadof , с помощью которого нужно будет разрешить все коллизии.

Хитрая ошибка может быть в случае, когда в классе тоже определён метод, вызвавший коллизию, в таком случае php пропустит эту проверку, т.к. он проверяет только «выжившие» методы типажа:

trait A < public function abc() <>> trait B < public function abc() <>> class C < use A, B; public function abc() <>> //OK 

Когда-нибудь потом, перенеся метод abc в родительский класс, получим странную ошибку по коллизии методов типажей, которая может сбить с толку. Так что, коллизии лучше разрешить заранее. (С другой стороны, если в коде методы типажа и класса совпадают, возможно что-то уже не так.)

Совпадение свойств типажа со свойствами другого типажа и свойствами класса

trait WithId < protected $_id = false; //protected $_var = 'a'; public function getId() < return $this->_id; > //. > trait WithId2 < protected $_id = null; //protected $_var = null; //. >class A < use WithId, WithId2; >class B < use WithId2, WithId; >class C < use WithId; protected $_id = '0'; >// $a = new A(); var_dump($a->getId()); //NULL $b = new B(); var_dump($b->getId()); //false $c = new C(); var_dump($c->getId()); //false (!) //Если раскомментировать $_var // WithId and WithId2 define the same property ($_var) // in the composition of A. However, the definition differs // and is considered incompatible. Class was composed // in %FILE% on line %LINE% 

Поясняю. В общем случае при пересечении свойств типажей между собой или свойств типажа и класса выдаётся ошибка. Но зачем-то для «совместимых» свойств делается исключение и они работают по принципу «кто последний, тот и прав». Поэтому в классе A в getId получилось NULL, а в классе B – false. При этом свойства класса считаются ниже, чем свойство типажа (с методами равно наоборот) и в C вместо ожидаемого ‘0’ получим false.

Читайте также:  Html code mime type

Совместимыми считаются значения нестрогое сравнение которых даёт true, а так как в php при этом много неявных преобразований, могут быть неприятные ошибки при использовании строго сравнения возвращаемых значений.

var_dump(null == false); //true var_dump('0' == false); //true var_dump('a' == null); //false 

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

Ошибки и исключения в типажах

Если следовать мнемоническому правилу trait == «copy-paste», с ошибками становится сразу всё понятно:

a; //5 > public function someMethod() < $this->error(); > public function testExc() < throw new Exception('Test'); //16 >> class Brain < use Slug; public function plurk() < $this->testExc(); //25 > > error_reporting(E_ALL); $b = new Brain(); $b->someMethod(); //Notice: Undefined property: Brain::$a in %FILE% on line 5 try < $b->plurk(); //35 > catch(Exception $e) < echo $e; >// exception 'Exception' with message 'Test' in %FILE%:16 // Stack trace: // #0 %FILE%(25): Brain->testExc() // #1 %FILE%(35): Brain->plurk() // #2

Объект уже не знает, откуда у него взялся метод в котором был Notice или Exception, но это можно узнать в stack trace по строкам кода, в которых были вызовы. Если хранить типажи в отдельных файлах определить будет ещё проще.

Немного белой чёрной магии

Покажу пару грязных приёмов с типажами, используйте их на свой страх и риск.

Удаление метода типажа

trait A < public function a() <>public function b() <> > trait B < public function d() < $this->e(); > public function e() <> > class C < use A < //удаляем и переименовываем A::b insteadof A; A::b as c; >use B < //удаляем метод совсем B::e insteadof B; >> echo join(", ", get_class_methods('C')), PHP_EOL; //a, c, d 

Но в таком подходе таится большая опасность, т.к. одни методы типажа потенциально могут вызывать другие методы:

$c = new C(); $c->d(); //Fatal error: Call to undefined method C::e() 

При переименовании типаж ничего не знает о том, что метод был переименован. Поэтому по-умолчанию при указании alias’а сохраняется оригинальный метод.

Читайте также:  Рамка вокруг таблицы

«Наследование» в типажах

С помощью похожего трюка можно реализовать «наследование» в типажах c возможностью вызова «родительских» методов.

trait Namer < public function getName() < return 'Name'; >> trait Namer2 < public function getName() < return 'Name2'; >> trait Supernamer < use Namer, Namer2 < Namer::getName insteadof Namer; Namer::getName as protected _Namer_getName_; Namer2::getName insteadof Namer2; Namer2::getName as protected _Namer2_getName_; >public function getName() < return $this->_Namer_getName_() . $this->_Namer2_getName_(); > > 

Два способа реализовать Singleton с помощью типажей

Чтобы сгладить это магическое безобразие покажу один полезный пример. Часто в виде типажа приводят Singleton, хотя без возможности задания в типаже статической переменной сделать его будет не так просто, как кажется на первый взгляд. Можно воспользоваться двумя хитростями.

Первая – получить внутри вызываемого метода название класса, к которому он был вызван, а затем в качестве хранилища воспользоваться отдельным классом со статическим методом, примерно так:

trait Singleton < static public function getInstance() < $class = get_called_class(); //работает аналогично static:: if (!Storage::hasInstance($class)) < $new = new static(); Storage::setInstance($class, $new); >return Storage::getInstance($class); > > 

Вторая – воспользоваться толи фичей, толи багой php, которая связана с использованием ключевого слова static при объявлении переменной. Эти переменные должны сохранять своё значение при вызовах метода, но видимо структура для хранения этих переменных инициализируется в каждом месте использования метода. В итоге получается такая схема:

trait Singleton < static public function getInstance() < static $instance = null; if ($instance === null) < $instance = new static(); >return $instance; > > class MyClass < use Singleton; >class MyExtClass extends MyClass <> echo get_class(MyClass::getInstance()), PHP_EOL; //MyClass echo get_class(MyExtClass::getInstance()), PHP_EOL; //MyExtClass 

Источник

Traits php что это

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

Traits определяются с помощью ключевого словва trait , после которого идет название трейта:

Traits могут содержать только статические и нестатические методы:

trait Printer < public function printSimpleText($text) < echo "$text
"; > public function printHeaderText($text) < echo "$text"; > >

Для применения классов трейта применяется оператор use , после которого указывается добавляемый трейт:

trait Printer < public function printSimpleText($text) < echo "$text
"; > public function printHeaderText($text) < echo "$text"; > > class Message < use Printer; >$myMessage = new Message(); $myMessage->printSimpleText("Hello World!"); $myMessage->printHeaderText("Hello PHP 8");

После добавления трейта с помощью оператора use класс может использовать его методы, как-будто они определены в самом этом классе.

Следует учитывать, что при наследовании методы трейта переопределяют унаследованные методы с тем же именем:

class Data < function print() < echo "Print from Data"; >> trait Printer < function print() < echo "Print from Printer"; >> class Message extends Data < use Printer; >$myMessage = new Message(); $myMessage->print(); // Print from Printer

Источник

Оцените статью