PHPでクラスを実装するためのメソッドシグネチャの変更


9

PHPのGenericsの欠如に対して、静的コード検査で型の一貫性を検出できる適切な回避策はありますか?

サブクラスを作成する抽象クラスがあり、メソッドの1つが1つの型のパラメーターの取得から、そのパラメーターのサブクラスであるパラメーターの取得に変更されるように強制します。

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はこれらをサポートしていません。

この問題のためのグーグル、人々は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、2014

2つのクラスがコードの80%を共有しているため、これは抽象クラスにあります。インターフェースを使用することは、コードを複製すること、または2つのクラスと合成できる別のクラスに共有コードを移動するための大規模なリファクタリングを意味します。
Danack、2014

あなたは両方を一緒に使用できると確信しています。
RibaldEddie、2014

はい-しかし、それはi)共有メソッドが 'Item'の基本クラスを使用する必要があるという問題には対処していませんii)タイプ固有のメソッドはサブクラス「WoodItem」を使用したいが、それは「宣言of WoodProcessor :: bar()はAbstractProcessor :: bar(Item $ item)と互換性がある必要があります "
Danack

実際に動作をテストする時間はありませんが、インターフェイスを示唆した場合(IItemとIWoodItemを作成し、IWoodItemをIItemから継承させる)とどうなりますか?次に、基本クラスの関数シグネチャのIItemと子のIWoodItemにヒントを与えます。タットはうまくいくかもしれません。ではないかもしれない。
RibaldEddie、2014

回答:


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をシミュレートするには、ジャンプする必要があります。
TulainsCórdova、2015

4
@ user61852私はこれをPHPを守るために言っているわけではありません(私を信じてください)が、ほとんどの言語では継承されたメソッドシグネチャを変更できませんでした。言語に根本的な問題を引き起こすため、これを行うことから; この変更は、言語を他の言語とより一致させるために行われたので、どの言語と比較しているかわかりません。私の回答の2番目の部分で示したパターンは、C#やJavaなどの他の言語にも適用可能であり、有用です。
mindplay.dk 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.