【问题标题】:PHP trait: is there a proper way to ensure that class using a trait extends a super class which contains certain method?PHP trait:是否有适当的方法来确保使用 trait 的类扩展包含特定方法的超类?
【发布时间】:2012-10-11 17:47:38
【问题描述】:

来自 PHP 手册 http://php.net/manual/en/language.oop5.traits.php 状态的示例 #2

<?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() 方法之前,此代码不会产生任何错误。这很糟糕。

另一方面,如果 trait 需要使用某个方法 我可以把这个方法写成抽象的,这样很好,因为它可以确保在编译时正确使用 trait。但这不适用于父类:

<?php
trait SayWorld
{
    public function sayHelloWorld()
    {
        $this->sayHello();
        echo 'World!';
    }

    public abstract function sayHello(); // compile-time safety

}

所以我的问题是:有没有办法确保(在编译时,而不是在运行时)使用特定特征的类将具有 parent::sayHello() 方法?

【问题讨论】:

  • 您可以做的是添加一个抽象方法来强制使用该特征的类具有您需要的方法。这样它就变得独立于类层次结构(特征背后的想法:P)

标签: php class inheritance superclass traits


【解决方案1】:

PHP 编译阶段只是创建字节码。其他一切都是在运行时完成的,包括多态决策。因此,编译如下代码:

class A {}
class B extends A {
    public function __construct() {
        parent::__construct();
    }
}

但是当run时爆炸:

$b = new B;

因此,您绝对不能在编译时检查parent。您能做的最好的事情就是尽早将其推迟到运行时。正如其他答案所示,可以使用instanceof 在您的特征方法中执行此操作。当我知道 trait 方法需要特定契约时,我个人更喜欢使用类型提示。

所有这些都归结为特征的基本目的:编译时复制和粘贴。而已。 Traits 对合同一无所知。对多态一无所知。它们只是提供了一种重用机制。与接口结合使用时,它们非常强大,虽然有些冗长。

事实上,first academic discussion of traits 揭示了特征意味着“纯粹”的想法,因为它们不了解周围的对象:

特征本质上是一组纯方法,它们充当类的构建块,并且是代码重用的基本单元。在此模型中,类由一组特征组成,通过指定将特征连接在一起并访问必要状态的粘合代码。

"If Traits Weren't Evil, They'd Be Funny" 很好地总结了要点、陷阱和选项。我不会分享作者对特征的刻薄:我会在有意义的时候使用它们,并且总是与 interface 配对。不幸的是,PHP 文档中的示例鼓励了不良行为。

【讨论】:

  • 自从我问这个问题以来,我实际上完全停止使用特征。我会尽可能地重写我的旧代码以删除特征。你可以说我长大了。它们有时很有用,但只在极少数情况下有用。
【解决方案2】:

您可以检查 $this 是否扩展了特定的类或实现了特定的接口:

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

【讨论】:

    【解决方案3】:

    我认为有一种完全没有特质的方法:

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

    【讨论】:

      【解决方案4】:

      不,没有。其实这个例子很糟糕,因为引入 Trait 的目的是在不依赖继承的情况下为多个类引入相同的功能,而使用parent 不仅要求类有父类,还需要有特定的方法。

      附带说明,parent 调用在编译时不会被检查,您可以定义简单的类,它不会在其方法中使用父调用扩展任何内容,它会一直工作直到调用其中一个方法。

      【讨论】:

      • 谢谢。我知道简单类在编译时不会检查父调用,但它仍然是正确的代码。我非常聪明的 IDE 可以找出父调用,这意味着当我从 PHP 切换到强类型语言时,它仍然是正确的代码。这让我晚上睡觉。但是当涉及到多重继承时,我不知道有任何其他语言不能做到这一点。 Scala 中的特征概念允许以两种方式继承。 C++和Python类可以继承多个parents
      • 所以我想我正在寻找一种绕过 PHP 限制的方法,这将使我的代码更加类型安全。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-15
      • 2015-06-25
      • 1970-01-01
      • 2020-08-27
      • 1970-01-01
      • 2013-11-26
      相关资源
      最近更新 更多