PHP Ковариация с унаследованным классом - объявления несовместимы - PullRequest
2 голосов
/ 24 апреля 2020

Я хотел бы создать абстрактный класс с абстрактным методом, который допускает абстрактный тип в возвращаемом типе. В моем последнем классе я хотел бы, чтобы тип переопределения возвращался с типом, который реализует абстрактный тип, первоначально объявленный.

<?php

abstract class A {
    abstract public function test(A $foo): self;
}

class B extends A {
    public function test(B $foo): self
    {
        return $this;
    }
}

Выдается эта ошибка компиляции:

Fatal error: Declaration of B::test(B $foo): B must be compatible with A::test(A $foo): A in ... on line 8

В документации, ковариация объясняется интерфейсом. Но не абстрактным классом. Подробнее о реализации PHP в документации говорится:

В PHP 7.2.0 введена частичная контрастность путем удаления ограничений типа для параметров в дочернем методе. По состоянию на PHP 7.4.0 добавлена ​​полная поддержка ковариации и контравариантности.

Я использую PHP 7.4.

1 Ответ

1 голос
/ 24 апреля 2020

Довольно основным принципом объектно-ориентированного программирования является принцип подстановки Лискова , который сводится к следующему:

, если S является подтипом T,, тогда объекты типа T в программе можно заменить объектами типа S без изменения каких-либо желательных свойств этой программы

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

Вам нужна ковариация аргументов типа, которая нарушает этот принцип. Причину этого можно увидеть, рассмотрев приведенный ниже пример:

abstract class A {
    abstract public function test(A $foo): self;
}

class C extends A {
    public function test(C $foo): self {
        return $this;
    }
}

class B extends A {
    public function test(B $foo): self {
        return $this;
    }
}

$b = new B();
$c = new C();

$b->test($c); // Does not work
((A)$b)->test((A)$c); // Works


В приведенном выше примере вы не разрешаете B::test принимать любой тип, отличный от B, в качестве аргумента типа. Однако, поскольку B сам по себе является дочерним элементом A, а C также является дочерним по A по простому понижению (что допускается), ограничение обойдено. Вы всегда можете отключить понижающее преобразование, но это почти говорит о том, что вы отключаете наследование, что является основным принципом OOP.

Теперь, конечно, есть веские причины разрешить ковариацию аргументов типа, поэтому некоторые языки (например, Eiffel ) допускают это, однако это признается проблемой, и даже было дано имя CATcalling (CAT означает измененную доступность или тип).

В PHP вы можете попытаться выполнить проверки во время выполнения, чтобы исправить эту ситуацию:

abstract class A {
    abstract public function test(A $foo) {
         // static keyword resolve to the current object type at runtime 
         if (!$foo instanceof static) { throw new Exception(); }  
    }
}

class C extends A {
    public function test(A $foo): self {
        parent::test($foo);
        return $this;
    }
}

class B extends A {
    public function test(A $foo): self {
        parent::test($foo);
        return $this;
    }
}

Однако это немного грязно и, возможно, не нужно.

...