Подтвердить что ты не робот

PHP trait: есть ли способ обеспечить, чтобы класс с использованием признака расширил суперкласс, который содержит определенный метод?

Пример # 2 из руководства PHP http://php.net/manual/en/language.oop5.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(). Это плохо.

С другой стороны, если черта должна использовать определенный метод , я могу написать этот метод как абстрактный, и это хорошо, так как это гарантирует правильность использования признака во время компиляции. Но это не относится к родительским классам:

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

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

}

Итак, мой вопрос: есть ли способ гарантировать (во время компиляции, а не во время выполнения), что класс, который использует определенный признак, будет иметь метод parent::sayHello()?

4b9b3361

Ответ 1

Нет, нет. На самом деле этот пример очень плохой, поскольку цель введения черт заключалась в том, чтобы вводить такую ​​же функциональность многим классам, не полагаясь на наследование, а использование parent требует не только того, чтобы класс имел родительский элемент, но также должен иметь конкретный метод.

На стороне примечания, вызовы parent не проверяются во время компиляции, вы можете определить простой класс, который не расширяет ничего с родительскими вызовами в нем методами, ant он будет работать до тех пор, пока не будет вызван один из этих методов.

Ответ 2

Вы можете проверить, поддерживает ли $этот класс для определенного класса или реализует определенный интерфейс:

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

Этап компиляции PHP просто создает байт-код. Все остальное выполняется во время выполнения, включая принятие решений о полиморфизме. Таким образом, код, подобный приведенному ниже, компилируется:

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

но взорвется, когда запустить:

$b = new B;

Таким образом, вы строго не можете проверять parent во время компиляции. Лучшее, что вы можете сделать, это отложить это до времени выполнения как можно раньше. Как показали другие ответы, можно сделать это внутри вашего метода trait с помощью instanceof. Я лично предпочитаю использовать тип-намек, когда я знаю, что метод признака нуждается в конкретном контракте.

Все это сводится к основной цели черт: компиляция времени копирования и вставки. Это. Черты ничего не знают о контрактах. Не знаю ничего о полиморфизме. Они просто предоставляют механизм для повторного использования. В сочетании с интерфейсами они довольно мощные, хотя и несколько подробные.

Фактически, первое академическое обсуждение признаков раскрывает идею о том, что черты должны быть "чистыми", поскольку они не имеют знание объекта вокруг них:

Признак - это, по сути, группа чистых методов, которые служат в качестве строительного блока для классов и являются примитивной единицей повторного использования кода. В этой модели классы состоят из набора признаков, определяя код клея, который соединяет черты вместе и обращается к необходимому состоянию.

"Если черты не были злы, они были бы смешны" красиво суммирует точки, подводные камни и варианты. Я не разделяю автора vitriol для черт: я использую их, когда он имеет смысл и всегда находится в паре с interface. То, что пример в документации PHP поощряет плохое поведение, является неудачным.

Ответ 4

Я думаю, что есть путь полностью без признаков:

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