2016-06-17 8 views
4

Phpstorm hat Inspektion: Invocation parameter types are not compatible with declared.Aufrufparameter sind nicht kompatibel mit deklarierten

Ich war überrascht, dass PHP verwenden Basistyp als Subtyp zulassen.

interface Base 
{ 
    public function getId(); 
} 

interface Child extends Base 
{ 

} 

interface SecondChildType extends Base 
{ 

} 

class ChildImpl implements Child 
{ 
    public function getId() 
    { 
     return 1; 
    } 
} 

class SecondChildTypeImpl implements SecondChildType 
{ 
    public function getId() 
    { 
     return 2; 
    } 
} 

class BaseService 
{ 
    public function process(Base $base) 
    { 
     $childService = new ChildService($base); 

     return $childService->process($base); //Invocation parameter types are not compatible with declared 
    } 
} 

class ChildService 
{ 
    public function process(Child $child) 
    { 
     return $child->getId(); 
    } 
} 

class InheritanceTest extends \PHPUnit_Framework_TestCase 
{ 
    public function testInterfacesCanUsesAsSubstitute() 
    { 
     $baseService = new BaseService(); 
     $this->assertEquals(1, $baseService->process(new ChildImpl())); 
    } 

    /** 
    * @expectedException \TypeError 
    */ 
    public function testInterfacesCanUsesAsSubstitute_Exception() 
    { 
     $baseService = new BaseService(); 
     $baseService->process(new SecondChildTypeImpl()); 
    } 
} 

Warum erster Test bestanden? Warum php erlaubt es?

Antwort

4

PhpStorm warnt Sie, dass Ihr Code möglicherweise für eine Instanz von Base-BaseService::process erlaubt, die nicht eine gültige Child Instanz ist, und kann daher nicht auf ChildService::process weitergegeben werden.

In Ihrem ersten Komponententest haben Sie eine Instanz Child zur Verfügung gestellt, die Base erweitert, so dass es funktioniert.

In Ihrem zweiten Unit-Test beweisen Sie tatsächlich, dass es möglich ist, einen PHP-Fehler zu verursachen. PhpStorm warnt Sie einfach im Voraus, dass Ihre Typhints dieses Problem ermöglichen.

Wenn BaseService::process wird immer Anruf ChildService::process wie Sie jetzt haben, dann BaseService::process ihr Argument typehint sollte auch mit ChildService::process kompatibel zu sein.


Ich habe Ihren Code ein bisschen geändert, einige der Klassennamen neu geschrieben einfacher zu sein, und entfernt die getId Methode. Ich möchte das so einfach wie möglich zeigen, damit Sie verstehen, was vor sich geht.

interface Base {} 
interface Child extends Base {} 
interface Base2 extends Base {} 

// This class implements Child, which extends Base. So this will meet either requirement. 
class Class1 implements Child {} 

// This class implements Base2, which extends Base. 
// So this will meet any Base requirement, but NOT a Child requirement 
class Class2 implements Base2 {} 


class BaseService 
{ 
    /** 
    * Problem! We are requiring Base here, but then we pass the same argument to 
    * ChildService->process, which requires Child. 
    * 
    * 1) Class1 WILL work, since it implements Child which extends Base. 
    * 
    * 2) Class2 WILL NOT work. Or at least, we can't pass it to ChildService->process 
    * since it only implements Base2 which extends Base. It doesn't implement Child, 
    * therefore ChildService->process won't accept it. 
    */ 
    public function process(Base $base) 
    { 
     $childService = new ChildService($base); 

     return $childService->process($base); 
    } 
} 

class ChildService 
{ 
    /** 
    * I will ONLY receive an instance that implements Child. 
    * Class1 will work, but not Class2. 
    */ 
    public function process(Child $child) 
    { 
     return $child->getId(); 
    } 
} 

$service = new BaseService(); 

// I can do this! I'm passing in Child1, which implements Child, which extends Base. 
// So it fulfills the initial Base requirement, and the secondary Child requirement. 
$service->process(new Child1()); 

// I can't do this. While BaseService will initially accept it, ChildService will refuse 
// it because this doesn't implement the Child interface as required. 
$service->process(new Child2()); 
+0

Warum? Warum Implementierung ist wichtig? –

+2

@ Onedev.Link Ich verstehe nicht, was Sie fragen – jszobody

+0

Bei BaseService :: Prozess können wir nur "Base" -Objekt übergeben. Und wir übergeben "Base" -Objekt als "Child" in "ChildService :: process". Ich möchte nur "Child" oder "Child" -Subtypen in "ChildService :: process" übergeben. Aber warum kann ich 'Base' als' Kind' verwenden, verstehe ich nicht. –

1

Ich glaube, du bist ein Liskov Substitution Principle violation erwartet, aber das ist hier nicht der Fall: ChildService nicht aus BaseService nicht ableiten.

Wie Sie wissen, erfüllen abgeleitete Klassen Basisklassen-Hints, aber abgeleitete Klassenmethoden können die API der Basisklassenmethoden nicht stärken. Sie können also eine Child in eine Methode übergeben, die eine Base akzeptiert, aber Sie können nicht die Signatur einer Methode stärken, die zwischen Child und Base geteilt wird.

Der folgende klassische Code demonstriert den Versuch LSP zu verletzen, und wirft eine fatale „Erklärung vereinbar sein muß“:

abstract class AbstractService { } 
abstract class AbstractFactory { abstract function make(AbstractService $s); } 

class ConcreteService extends AbstractService { } 
class ConcreteFactory extends AbstractFactory { function make(ConcreteService $s) {} } 

Sie tun etwas sehr ähnliches in Ihrem Code, mit dem entscheidenden Unterschied, dass ChildService nicht erben von einem abstrakten BaseService. Somit kann ChildService beliebige Argumente annehmen und es gibt keinen fatalen Fehler von PHP. Wenn Sie den Basisdienst in Abstrakt ändern und den untergeordneten Dienst daraus ableiten, erhalten Sie eine LSP-Verletzung. (Siehe auch this question.)

Nun BaseService::process() nimmt ein ChildImpl weil ChildImplist-einBase. Es akzeptiert auch alles, was Child implementiert, und irgendetwas, das Base implementiert.Das bedeutet, dass der folgende Code gültig ist:

class BaseImpl implements Base { 
    public function getId() { return 0; } 
} 
(new BaseService)->process(new BaseImpl); 

Aber das wird die Luft zu sprengen, weil BaseService::process Hände weg zu ChildService::process, die eine Child nimmt - und BaseImpl ist kein Child. PHPSstorm hat diese statische Analyse durchgeführt und warnt Sie vor den möglichen Laufzeitkonsequenzen des Designs.

Verwandte Themen