示例#2来自php手册http://php.net/manual/enual/en/language.oop.5.traits.php states
<?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();
?>
这是正确的代码,但是在这种情况下使用parent::
并不安全。假设我写了自己的" Hello World"课程,该课程没有继承其他类:
<?php
class MyOwnHelloWorld
{
use SayWorld;
}
?>
在我调用sayHello()
方法之前,此代码不会产生任何错误。这是不好的。
另一方面,如果特征需要使用某种方法,我可以将此方法写为Abstract ,这很好,因为它可以确保在编译时正确使用该性状。但这不适用于父类:
<?php
trait SayWorld
{
public function sayHelloWorld()
{
$this->sayHello();
echo 'World!';
}
public abstract function sayHello(); // compile-time safety
}
所以我的问题是:是否有一种方法可以确保使用某个特征的类(在运行时,而不是在运行时)将具有parent::sayHello()
方法?
不,没有。实际上,此示例非常糟糕,因为引入特征的目的是在不依赖继承的情况下向许多类引入相同的功能,并且使用parent
不仅需要类具有父级,而且还应具有特定的方法。
在旁注上,在编译时间未检查parent
调用,您可以定义简单的类,该类不会通过其方法中的父呼叫扩展任何内容,ant它将工作直到调用其中一种方法。
您可以检查$是否扩展特定类或实现特定接口:
interface SayHelloInterface {
public function sayHello();
}
trait SayWorldTrait {
public function sayHello() {
if (!in_array('SayHello', class_parents($this))) {
throw new LogicException('SayWorldTrait may be used only in classes that extends SayHello.');
}
if (!$this instanceof SayHelloInterface) {
throw new LogicException('SayWorldTrait may be used only in classes that implements SayHelloInterface.');
}
parent::sayHello();
echo 'World!';
}
}
class SayHello {
public function sayHello() {
echo 'Hello ';
}
}
class First extends SayHello {
use SayWorldTrait;
}
class Second implements SayHelloInterface {
use SayWorldTrait;
}
try {
$test = new First();
$test->sayHello(); // throws logic exception because the First class does not implements SayHelloInterface
} catch(Exception $e) {
echo $e->getMessage();
}
try {
$test = new Second();
$test->sayHello(); // throws logic exception because the Second class does not extends SayHello
} catch(Exception $e) {
echo $e->getMessage();
}
php汇编阶段仅创建字节码。其他一切都在运行时完成,包括多态性决策。因此,像下面的代码编译:
class A {}
class B extends A {
public function __construct() {
parent::__construct();
}
}
但是运行时会炸毁:
$b = new B;
因此,您严格无法在编译时检查parent
检查。您能做的最好的就是尽早将其推迟到运行时间。正如其他答案所表明的那样,可以使用instanceof
在您的性状方法中执行此操作。当我知道特征方法需要特定合同时,我个人更喜欢使用类型的饲养。
所有这些都归结为特征的基本目的:编译时间副本和粘贴。就是这样。特质一无所知。一无所知多态性。他们只是提供了重复使用的机制。结合接口时,它们非常强大,尽管有些冗长。
实际上,关于特征的第一个学术讨论揭示了特质的想法,即特征是"纯粹"的,因为他们不了解周围的对象:
特征本质上是一组纯方法,它是类的基础,并且是代码重用的原始单位。在此模型中,类是通过指定将特征连接在一起并访问必要状态的胶代码来组成的。
。
"如果特征不是邪恶的,那么它们会很有趣",可以很好地总结这些要点,陷阱和选择。我没有分享作者的特征硫酸:在有意义的情况下使用它们,并且总是与interface
搭配。PHP文档中的示例鼓励了不幸的行为。
我认为没有特质的方法是完全没有特质的方法:
class BaseClass
{
public function sayHello() {
echo 'Hello ';
}
}
class SayWorld
{
protected $parent = null;
function __construct(BaseClass $base) {
$this->parent = $base;
}
public function sayHelloWorld()
{
$this->parent->sayHello();
echo 'World!';
}
}
class MyHelloWorld extends Base {
protected $SayWorld = null;
function __construct() {
$this->SayWorld = new SayWorld($this);
}
public function __call ( string $name , array $arguments ) {
if(method_exists($this->SayWorld, $name)) {
$this->SayWorld->$name();
}
}
}
不幸的是,没有办法强制使用特定类别的特定类别的特定类别。但是在开发时间,您可以使用注释注释来访问父班的方法或属性。为此,您可以使用@extends
,@implements
,@method
和@property
注释。例如:
<?php
namespace AppModelsTraits;
use AppModelsUser;
use IlluminateDatabaseConcernsBuildsQueries;
use IlluminateDatabaseEloquentBuilder;
use IlluminateDatabaseEloquentModel;
/**
* @extends Model
* @method Model|Builder setRole(string $role)
*
* // for example
* @implements BuildsQueries
* @method someExampleMethod()
* @property $exampleProperty
*/
trait WithPermission
{
public function scopeSetRole(Builder $query, string $role): void
{
$query->join('model_has_roles', function ($join) {
$join
->on($this->getTable() . '.user_id', '=', 'model_has_roles.model_id')
->where('model_has_roles.model_type', User::class);
})
->join('roles', 'model_has_roles.role_id', '=', 'roles.id')
->where('roles.name', $role);
}
}
之后,您必须确定您在适当的类中使用了此特征。在此示例中,我使用Model
类的getTable()
方法,编辑器可以将方法带入自动使用。