PHP에서 클래스를 구현하기위한 메소드 서명 변경


9

정적 코드 검사로 유형 일관성을 감지 할 수있는 PHP의 제네릭 부족에 대한 적절한 해결책이 있습니까?

나는 서브 클래스를 만들고 싶어하는 추상 클래스를 가지고 있으며 메소드 중 하나가 한 유형의 매개 변수를 취하는 것에서 해당 매개 변수의 하위 클래스 인 매개 변수를 취하는 것으로 변경하도록 강요합니다.

abstract class AbstractProcessor {
    abstract function processItem(Item $item);
}

class WoodProcessor extends AbstractProcessor {
    function processItem(WoodItem $item){}
}

허용되지 않는 메소드 서명을 변경하기 때문에 PHP에서는 허용되지 않습니다. Java 스타일 제네릭을 사용하면 다음과 같은 작업을 수행 할 수 있습니다.

abstract class AbstractProcessor<T> {
    abstract function processItem(T $item);
}

class WoodProcessor extends AbstractProcessor<WoodItem> {
    function processItem(WoodItem $item);
}

그러나 분명히 PHP는이를 지원하지 않습니다.

이 문제에 대한 Google의 경우 사람들 instanceof은 런타임시 오류를 확인 하는 데 사용 하는 것이 좋습니다.

class WoodProcessor extends AbstractProcessor {
    function processItem(Item $item){
        if (!($item instanceof WoodItem)) {
            throw new \InvalidArgumentException(
                "item of class ".get_class($item)." is not a WoodItem");
        } 
    }
}

그러나 런타임에만 작동하므로 정적 분석을 사용하여 코드에서 오류를 검사 할 수 없으므로 PHP에서이를 처리하는 합리적인 방법이 있습니까?

보다 완벽한 문제의 예는 다음과 같습니다.

class StoneItem extends Item{}
class WoodItem extends Item{}

class WoodProcessedItem extends ProcessedItem {
    function __construct(WoodItem $woodItem){}
}

class StoneProcessedItem extends ProcessedItem{
    function __construct(StoneItem $stoneItem){}
}

abstract class AbstractProcessor {
    abstract function processItem(Item $item);

    function processAndBoxItem(Box $box, Item $item) {
       $processedItem = $this->processItem($item);
       $box->insertItem($item);
    }

    //Lots of other functions that can call processItem
}

class WoodProcessor extends AbstractProcessor {
    function processItem(Item $item) {
        return new ProcessedWoodItem($item); //This has an inspection error
    }
}

class StoneProcessor extends AbstractProcessor {
    function processItem(Item $item) {
        return new ProcessedStoneItem($item);//This has an inspection error
    }
}

Itemto를 전달 new ProcessedWoodItem($item)하고 있으며 WoodItem을 매개 변수로 사용하기 때문에 코드 검사에서 오류가 있음을 나타냅니다.


1
왜 인터페이스를 사용하지 않습니까? 인터페이스 상속을 사용하여 원하는 것을 얻을 수 있다고 생각합니다.
RibaldEddie

두 클래스는 코드의 80 %를 공유하기 때문에 추상 클래스에 있습니다. 인터페이스를 사용한다는 것은 코드를 복제하거나 공유 코드를 두 클래스와 합성 할 수있는 다른 클래스로 이동하기위한 큰 리 팩터를 의미합니다.
Danack

둘 다 함께 사용할 수 있다고 확신합니다.
RibaldEddie

예-그러나 i) 공유 메소드가 'Item'의 기본 클래스를 사용해야 함 ii) 유형별 메소드는 서브 클래스 "WoodItem"을 사용하려고하지만 "선언"과 같은 오류를 발생시키는 문제를 해결하지 않습니다. of WoodProcessor :: bar ()는 AbstractProcessor :: bar (Item $ item) "과 호환되어야합니다"
Danack

실제로 동작을 테스트 할 시간이 없지만 인터페이스에 힌트를 주면 (IItem 및 IWoodItem을 만들고 IWoodItem이 IItem에서 상속 받도록) 어떻게해야합니까? 그런 다음 기본 클래스의 함수 시그니처에 IItem을, 하위의 IWoodItem에 힌트를 제공하십시오. 문신이 효과가있을 수 있습니다. 그렇지 않을 수 있습니다.
RibaldEddie

답변:


3

대신 doc-blocks로 매개 변수를 문서화하는 인수없이 메소드를 사용할 수 있습니다.

<?php

class Foo
{
    /**
     * @param string $world
     */
    public function hello()
    {
        list($world) = func_get_args();

        echo "Hello, {$world}\n";
    }
}

class Bar extends Foo
{
    /**
     * @param string $greeting
     * @param string $world
     */
    public function hello()
    {
        list($greeting, $world) = func_get_args();

        echo "{$greeting}, {$world}\n";
    }
}

$foo = new Foo();
$foo->hello('World');

$bar = new Bar();
$bar->hello('Bonjour', 'World');

나는 이것이 좋은 생각이라고 말하지 않을 것입니다.

문제는 가변적 인 수의 멤버가있는 컨텍스트를 가지고 있다는 것입니다. 인수를 인수로 강제하는 것이 아니라 더 나은 미래 지향적 인 아이디어는 가능한 모든 인수를 전달하는 컨텍스트 유형을 도입하여 인수가 목록을 변경할 필요가 없습니다.

이렇게 :

<?php

class HelloContext
{
    /** @var string */
    public $greeting;

    /** @var string */
    public $world;

    public static function create($world)
    {
        $context = new self;

        $context->world = $world;

        return $context;
    }

    public static function createWithGreeting($greeting, $world)
    {
        $context = new self;

        $context->greeting = $greeting;
        $context->world = $world;

        return $context;
    }
}

class Foo
{
    public function hello(HelloContext $context)
    {
        echo "Hello, {$context->world}\n";
    }
}

class Bar extends Foo
{
    public function hello(HelloContext $context)
    {
        echo "{$context->greeting}, {$context->world}\n";
    }
}

$foo = new Foo();
$foo->hello(HelloContext::create('World'));

$bar = new Bar();
$bar->hello(HelloContext::createWithGreeting('Bonjour', 'World'));

정적 팩토리 메소드는 물론 선택 사항이지만 특정 멤버 조합 만 의미있는 컨텍스트를 생성하는 경우 유용 할 수 있습니다. 그렇다면 __construct()보호 / 개인으로도 선언 할 수 있습니다.


후프는 PHP에서 OOP를 시뮬레이션하기 위해 뛰어야합니다.
Tulains Córdova

4
@ user61852 PHP를 옹호하기 위해 이것을 말하지는 않지만 대부분의 언어는 상속 된 메소드 서명을 변경할 수 없습니다. 역사적으로 PHP에서는 가능했지만 다양한 이유로 프로그래머를 (인공적으로) 제한하기로 결정했습니다. 언어에 근본적인 문제를 일으킴으로써 이것을하는 것으로부터; 이 변경으로 언어가 다른 언어와 일치하도록 변경되었으므로 비교할 언어가 확실하지 않습니다. 내 대답의 두 번째 부분에서 보여준 패턴은 C # 또는 Java와 같은 다른 언어에서도 적용 가능하고 유용합니다.
mindplay.dk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.