piątek, 4 października 2019

Trait




  • Wprowadzone do PHP w wersji 5.4.0.
  • krótko mówiąc jest to 'language assisted copy/paste',
  • importujemy do klasy za pośrednictwem słowa kluczowego use
    • w tej deklaracji możemy importować wiele Trait'ów po przecinku,
    • w klasie, miejsce na import Trait'ów jest takie samo jak dla deklarowania pól, stałych i metod - próba importu w ciele metody wyrzuci FATAL ERROR,
    • tak naprawdę słowo kluczowe use w przypadku importu Trait'a do klasy, a to wykorzystane do importu klas/interfejsów z innej przestrzeni nazw ma trochę inne zachowanie - ale o tym w innym wpisie,
  • nazwę Trait'a można uzyskać w jego metodach dzięki magicznej stałej __TRAIT__ zwracającą jego nazwę wraz z przestrzenią nazw (tak jak został zadeklarowany w use, wewnątrz klasy), 
  • dostępne mamy słowo kluczowe as które umożliwia zmianę:
    • identyfikatora dostępu metody Trait'a,
    • nazwę metody Trait'a,
  • Trait nie może: 
    • dziedziczyć po innych Trait'ach, 
    • implementować interfejsów,
    • posiadać const'ów (stałych jak w klasach),
    • być instancjonowany,
  • Trait może:
    • posiadać pola:
      • publiczne,
      • protected,
      • prywatne,
        • do takiego pola możemy bez przeszkód odwołać się w metodach klasy która importuje takiego Trait'a,
      • statyczne, 
        • możemy się do nich odwoływać dwojako: 
          • MyClass::$field 
          • MyTrait::$field,
        • każda klasa która implementuje tego samego Trait'a z polem statycznym, ma jego indywidualną - niezależną kopie, 
      • Trait i klasa mogą mieć pola o takiej samej nazwie i takiej samej sygnaturze, poniżej niewalidowalne przykłady wyrzucające FATAL ERROR :
        • private static $a;     - public static $a;
        • private static $a;     - private $a;
        • private static $a = 1; - private static $a = [];
        • private static $a;     - private static $a = 1;
        • private $a;            - private $a = 1;
        • private $a;            - public $a;
        • public $a = true;      - public $a = false;
    • posiadać metody:
      • publiczne,
      • protected,
      • prywatne,
      • statyczne, możemy się do nich odwoływać dwojako:
        • MyClass::getValue(),
        • MyTrait::getValue(),
      • abstrakcyjne,
        • sygnatura metody w Trait np. public abstract function getValue(int $param): string nie musi być dokładnie taka sama jak w klasie która musi spełnić ten kontrakt, gdzie może wyglądać np. tak: public function getValue(int $param, array $list): string {...} - dokładnie takie same muszą być jedynie nazwy tych metod,
      • magiczne (tylko te sprawdziłem):
        • __invoke(),
        • __toString(),
        • __call(),
    • odwoływać się do $this,
    • używać innych Trait'sów w ten sam sposób co klasy,
    • mieć zadeklarowany typ zwracany self oraz TypeHint'ować po nim,
    • używać w swoich metodach magicznej stałej __CLASS__ 
      • zwraca wtedy nazwę klasy która go używa,
      • chyba że jest to metoda statyczna i odwołujemy się do niej tak: MyTrait::getMagicConstant__CLASS__Value() to wtedy zwraca nazwę Trait'a xD,
    • używać ::class na Trait'cie,
  • konflikt wystąpi gdy klasa klienta zaimportuje przynajmniej dwa Trait'y gdzie nazwa metody się powtarza,
    • Różne sygnatury metod np: getValue(int $option): int {...} oraz getValue(string $option): string {...} nie zmieniają sytuacji,
  • pierwszeństwo - gdy klasa i Trait posiada metode getName() - uznana jest metoda klasy,
  • precedens - gdy klasa A posiada metodę getName(), ale posiada ją w wyniku dziedziczenia po klasie B oraz (klasa A) importuje Trait'a z metodą getName() - pierwszeństwo ma metoda z Trait'a.


KONFLIKTY NAZW

Gdy klasa korzysta z dwóch Trait'ów które posiadają takie same sygnatury metod. Konflikty nie są rozwiązywane automatycznie i gdy sami się o to nie zatroszczymy to interpreter wywali FATAL ERROR. Konflikty można rozwiązywać za pomocą operatora insteadof - jest to konstrukt językowy, słowo kluczowe charakterystyczne tylko dla Trait'ów np:

use TraitA, TraitB, TraitC {
    TraitC::getValue insteadof TraitA, TraitC;
}

------------------------                 ------------------------
------------------------    ERROR'sy     ------------------------
------------------------                 ------------------------

FATAL ERROR - Gdy normalna klasa będzie rozszerzać Traita.
trait Something {}
class Example extends Something {}
//FATAL ERROR Class Example cannot extend from trait Something on line number x

FATAL ERROR - Gdy dodamy do klasy nieistniejącego Traita.
class Example { use UnknownTrait }
//FATAL ERROR Trait 'UnknownTrait ' not found on line number x

FATAL ERROR - Dwa Trait'y będą miały takie same nazwy metod.
trait Something1 {function getName() { return 'Robert'; }}
trait Something2 {function getName() { return 'Stanisław'; }}
class Client { use Something1, Something2; }
//FATAL ERROR Trait method getName has not been applied, because there are collisions with other trait methods on Client on line number x

FATAL ERROR - Gdy dziedziczy po innym Trait'cie.
//FATAL ERROR syntax error, unexpected 'extends' (T_EXTENDS), expecting '{' on line number x

FATAL ERROR - Podczas importu Trait'a do interfejsu (użycia use jak w klasach).
//FATAL ERROR Cannot use traits inside of interfaces. MyTrait1 is used in MyInterface on line number x

FATAL ERROR - Gdy zadeklarujemy const'a wewnątrz Trait'a.
//FATAL ERROR Traits cannot have constants on line number x

FATAL ERROR - Gdy spróbujemy stworzyć instancje Trait'a.
//FATAL ERROR Uncaught Error: Cannot instantiate trait MyTrait

FATAL ERROR - Gdy klasa i Trait będą miały pole o takiej samej nazwie ale innej sygnaturze.
//FATAL ERROR Client and MyTrait define the same property ($a) in the composition of Client. However, the definition differs and is considered incompatible. Class was composed on line number x




Brak komentarzy:

Prześlij komentarz