PHP 实现代码复用的一个方法 traits新特性
在阅读yii2源码的时候接触到了trait,就学习了一下,写下博客记录一下。
自PHP5.4.0起,PHP实现了代码复用的一个方法,称为traits。
Traits是一种为类似PHP的单继承语言而准备的代码复用机制。Trait为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用方法集。Traits和类组合的语义是定义了一种方式来减少复杂性,避免传统多继承和混入类(Mixin)相关的典型问题。
Trait和一个类相似,但仅仅旨在用细粒度和一致的方式来组合功能。Trait不能通过它自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用类的成员不需要继承。
Trait示例
<?php traitezcReflectionReturnInfo{ functiongetReturnType(){/*1*/} functiongetReturnDescription(){/*2*/} } classezcReflectionMethodextendsReflectionMethod{ useezcReflectionReturnInfo; /*...*/ } classezcReflectionFunctionextendsReflectionFunction{ useezcReflectionReturnInfo; /*...*/ } ?>
优先级
从基类继承的成员被trait插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了trait的方法,而trait则覆盖了被继承的方法。
优先顺序示例
<?php classBase{ publicfunctionsayHello(){ echo'Hello'; } } traitSayWorld{ publicfunctionsayHello(){ parent::sayHello(); echo'World!'; } } classMyHelloWorldextendsBase{ useSayWorld; } $o=newMyHelloWorld(); $o->sayHello(); ?>
以上例程会输出:HelloWorld!
从基类继承的成员被插入的SayWorldTrait中的sayHello方法所覆盖。其行为MyHelloWorld类中定义的方法一致。优先顺序是当前类中的方法会覆盖trait方法,而trait方法又覆盖了基类中的方法。
另一个优先级顺序的例子
<?php traitHelloWorld{ publicfunctionsayHello(){ echo'HelloWorld!'; } } classTheWorldIsNotEnough{ useHelloWorld; publicfunctionsayHello(){ echo'HelloUniverse!'; } } $o=newTheWorldIsNotEnough(); $o->sayHello(); ?>
以上例程会输出:HelloUniverse!
多个trait
通过逗号分隔,在use声明列出多个trait,可以都插入到一个类中。
多个trait的用法的例子
<?php traitHello{ publicfunctionsayHello(){ echo'Hello'; } } traitWorld{ publicfunctionsayWorld(){ echo'World'; } } classMyHelloWorld{ useHello,World; publicfunctionsayExclamationMark(){ echo'!'; } } $o=newMyHelloWorld(); $o->sayHello(); $o->sayWorld(); $o->sayExclamationMark(); ?>
以上例程会输出:HelloWorld!
冲突的解决
如果两个trait都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。
为了解决多个trait在同一个类中的命名冲突,需要使用insteadof操作符来明确指定使用冲突方法中的哪一个。
以上方式仅允许排除掉其它方法,as操作符可以将其中一个冲突的方法以另一个名称来引入。
冲突解决的例子
<?php traitA{ publicfunctionsmallTalk(){ echo'a'; } publicfunctionbigTalk(){ echo'A'; } } traitB{ publicfunctionsmallTalk(){ echo'b'; } publicfunctionbigTalk(){ echo'B'; } } classTalker{ useA,B{ B::smallTalkinsteadofA; A::bigTalkinsteadofB; } } classAliased_Talker{ useA,B{ B::smallTalkinsteadofA; A::bigTalkinsteadofB; B::bigTalkastalk; } } ?>
在本例中Talker使用了traitA和B。由于A和B有冲突的方法,其定义了使用traitB中的smallTalk以及traitA中的bigTalk。
Aliased_Talker使用了as操作符来定义了talk来作为B的bigTalk的别名。
修改方法的访问控制
使用as语法还可以用来调整方法的访问控制。
修改方法的访问控制的例子
<?php traitHelloWorld{ publicfunctionsayHello(){ echo'HelloWorld!'; } } //修改sayHello的访问控制 classMyClass1{ useHelloWorld{sayHelloasprotected;} } //给方法一个改变了访问控制的别名 //原版sayHello的访问控制则没有发生变化 classMyClass2{ useHelloWorld{sayHelloasprivatemyPrivateHello;} } ?>
从trait来组成trait
正如类能够使用trait一样,其它trait也能够使用trait。在trait定义时通过使用一个或多个trait,它能够组合其它trait中的部分或全部成员。
从trait来组成trait的例子
<?php traitHello{ publicfunctionsayHello(){ echo'Hello'; } } traitWorld{ publicfunctionsayWorld(){ echo'World!'; } } traitHelloWorld{ useHello,World; } classMyHelloWorld{ useHelloWorld; } $o=newMyHelloWorld(); $o->sayHello(); $o->sayWorld(); ?>
以上例程会输出:HelloWorld!
Trait的抽象成员
为了对使用的类施加强制要求,trait支持抽象方法的使用。
表示通过抽象方法来进行强制要求的例子
<?php traitHello{ publicfunctionsayHelloWorld(){ echo'Hello'.$this->getWorld(); } abstractpublicfunctiongetWorld(); } classMyHelloWorld{ private$world; useHello; publicfunctiongetWorld(){ return$this->world; } publicfunctionsetWorld($val){ $this->world=$val; } } ?>
Trait的静态成员
Traits可以被静态成员静态方法定义。
静态变量的例子
<?php traitCounter{ publicfunctioninc(){ static$c=0; $c=$c+1; echo"$c\n"; } } classC1{ useCounter; } classC2{ useCounter; } $o=newC1();$o->inc();//echo1 $p=newC2();$p->inc();//echo1 ?>
静态方法的例子
<?php traitStaticExample{ publicstaticfunctiondoSomething(){ return'Doingsomething'; } } classExample{ useStaticExample; } Example::doSomething(); ?>
静态变量和静态方法的例子
<?php traitCounter{ publicstatic$c=0; publicstaticfunctioninc(){ self::$c=self::$c+1; echoself::$c."\n"; } } classC1{ useCounter; } classC2{ useCounter; } C1::inc();//echo1 C2::inc();//echo1 ?>
属性
Trait同样可以定义属性。
定义属性的例子
<?php traitPropertiesTrait{ public$x=1; } classPropertiesExample{ usePropertiesTrait; } $example=newPropertiesExample; $example->x; ?>
如果trait定义了一个属性,那类将不能定义同样名称的属性,否则会产生一个错误。如果该属性在类中的定义与在trait中的定义兼容(同样的可见性和初始值)则错误的级别是E_STRICT,否则是一个致命错误。
冲突的例子
<?php traitPropertiesTrait{ public$same=true; public$different=false; } classPropertiesExample{ usePropertiesTrait; public$same=true;//StrictStandards public$different=true;//致命错误 } ?>
Use的不同
不同use的例子
<?php namespaceFoo\Bar; useFoo\Test; //means\Foo\Test-theinitial\isoptional ?> <?php namespaceFoo\Bar; classSomeClass{ useFoo\Test; //means\Foo\Bar\Foo\Test } ?>
第一个use是用于namespace的useFoo\Test,找到的是\Foo\Test,第二个use是使用一个trait,找到的是\Foo\Bar\Foo\Test。
__CLASS__和__TRAIT__
__CLASS__返回usetrait的classname,__TRAIT__返回traitname
示例如下
<?php traitTestTrait{ publicfunctiontestMethod(){ echo"Class:".__CLASS__.PHP_EOL; echo"Trait:".__TRAIT__.PHP_EOL; } } classBaseClass{ useTestTrait; } classTestClassextendsBaseClass{ } $t=newTestClass(); $t->testMethod(); //Class:BaseClass //Trait:TestTrait
Trait单例
实例如下
<?php traitsingleton{ /** *privateconstruct,generallydefinedbyusingclass */ //privatefunction__construct(){} publicstaticfunctiongetInstance(){ static$_instance=NULL; $class=__CLASS__; return$_instance?:$_instance=new$class; } publicfunction__clone(){ trigger_error('Cloning'.__CLASS__.'isnotallowed.',E_USER_ERROR); } publicfunction__wakeup(){ trigger_error('Unserializing'.__CLASS__.'isnotallowed.',E_USER_ERROR); } } /** *ExampleUsage */ classfoo{ usesingleton; privatefunction__construct(){ $this->name='foo'; } } classbar{ usesingleton; privatefunction__construct(){ $this->name='bar'; } } $foo=foo::getInstance(); echo$foo->name; $bar=bar::getInstance(); echo$bar->name;
调用trait方法
虽然不很明显,但是如果Trait的方法可以被定义为在普通类的静态方法,就可以被调用
实例如下
<?php traitFoo{ functionbar(){ return'baz'; } } echoFoo::bar(),"\\n"; ?>
小伙伴们对于traits的新特性是否熟悉了呢,希望本文能对大家有所帮助。