PHP特性:是否有一种正确的方法来确保使用特征的类扩展包含某种方法的超类?

时间:2012-10-21 21:58:22

标签: php class inheritance superclass traits

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

另一方面,如果特征需要使用某种方法我可以将此方法编写为抽象,这很好,因为它确保在编译时正确使用特征。但这不适用于父类:

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

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

}

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

4 个答案:

答案 0 :(得分:4)

不,没有。实际上,这个例子非常糟糕,因为引入特征的目的是在不依赖继承的情况下将相同的功能引入到许多类中,并且使用parent不仅要求类具有父级,而且还应该具有特定的方法

另一方面,parent调用在编译时没有被检查,你可以定义一个简单的类,它不会在它的方法中使用父调用扩展任何东西,但是它会工作直到调用其中一个方法

答案 1 :(得分:3)

您可以检查$ 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();
}

答案 2 :(得分:1)

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

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

但在run

时会爆炸
$b = new B;

因此,您在编译时严格无法进行parent检查。您可以做的最好的事情是尽早将其推迟到运行时间。正如其他答案所示,可以使用instanceof在特征方法中执行此操作。当我知道特质方法需要特定合同时,我个人更喜欢使用类型提示。

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

事实上,first academic discussion of traits提出了这样一种观念,即特质应该是纯粹的&#34;因为他们不了解周围的物体:

  

trait本质上是一组纯方法,用作类的构建块,是代码重用的原始单元。在这个模型中,类由一组特征组成,通过指定将特征连接在一起的粘合代码并访问必要的状态。

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

答案 3 :(得分:0)

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

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