공분산과 반공분산

PHP 7.2.0에서 자식 메서드의 매개변수에 대한 유형 제한을 제거하여 부분 반공변성이 도입되었습니다. PHP 7.4.0부터 완전한 공분산(Covariance) 및 반공분산(contravariance) 지원이 추가되었습니다.

공분산(Covariance)을 통해 자식 메서드는 부모 메서드의 반환 유형보다 더 구체적인 형식을 반환할 수 있습니다. 반면 반공변성은 매개변수 유형이 상위 메소드보다 하위 메소드에서 덜 구체적일 수 있도록 합니다.

유형 선언은 다음과 같은 경우에 더 구체적인 것으로 간주됩니다.

반대의 경우 유형 클래스는 덜 구체적인 것으로 간주됩니다.


공분산(Covariance)

공분산이 어떻게 작동하는지 설명하기 위해 간단한 추상 상위 클래스인 Animal이 생성됩니다. Animal은 자식 클래스인 Cat, Dog로 확장됩니다.

                  
<?php

abstract class Animal
{
    protected string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    abstract public function speak();
}

class Dog extends Animal
{
    public function speak()
    {
        echo $this->name . " barks";
    }
}

class Cat extends Animal
{
    public function speak()
    {
        echo $this->name . " meows";
    }
}
                  
                

이 예제에서는 값을 반환하는 메서드가 없습니다. Animal, Cat 또는 Dog 클래스 유형의 새 개체를 반환하는 몇 가지 팩토리가 추가됩니다.

                  
<?php

interface AnimalShelter
{
    public function adopt(string $name): Animal;
}

class CatShelter implements AnimalShelter
{
    public function adopt(string $name): Cat // instead of returning class type Animal, it can return class type Cat
    {
        return new Cat($name);
    }
}

class DogShelter implements AnimalShelter
{
    public function adopt(string $name): Dog // instead of returning class type Animal, it can return class type Dog
    {
        return new Dog($name);
    }
}

$kitty = (new CatShelter)->adopt("Ricky");
$kitty->speak();
echo "\n";

$doggy = (new DogShelter)->adopt("Mavrick");
$doggy->speak();
                  
                

위의 예는 다음을 출력합니다.

Ricky meows
Mavrick barks
                

반공분산(Contravariance)

Animal, Cat, Dog 클래스가 있는 이전 예제에 계속해서 FoodAnimalFood라는 클래스가 포함되고 Eat(AnimalFood $food) 메서드가 Animal 추상 클래스에 추가됩니다.

                  
<?php

class Food {}

class AnimalFood extends Food {}

abstract class Animal
{
    protected string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function eat(AnimalFood $food)
    {
        echo $this->name . " eats " . get_class($food);
    }
}
                  
                

반공변성(contravariance)의 동작을 보기 위해 Dog 클래스에서 eat 메서드가 재정의되어 모든 Food 유형 개체를 허용합니다. Cat 클래스는 변경되지 않습니다.

                  
<?php

class Dog extends Animal
{
    public function eat(Food $food) {
        echo $this->name . " eats " . get_class($food);
    }
}
                  
                

다음 예제에서는 반공변성의 동작을 보여줍니다.

                  
<?php

$kitty = (new CatShelter)->adopt("Ricky");
$catFood = new AnimalFood();
$kitty->eat($catFood);
echo "\n";

$doggy = (new DogShelter)->adopt("Mavrick");
$banana = new Food();
$doggy->eat($banana);
                  
                

위의 예는 다음을 출력합니다.

Ricky eats AnimalFood
Mavrick eats Food
                

하지만 $kitty$banana를 먹으려 하면 어떻게 될까요?

$kitty->eat($banana);

위의 예는 다음을 출력합니다.

Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given