Modifica della firma del metodo per l'implementazione delle classi in PHP

9

Esiste un lavoro decente intorno alla mancanza di Generics di PHP che consente l'ispezione del codice statico per rilevare la coerenza del tipo?

Ho una classe astratta, che voglio sottoclasse e impone anche che uno dei metodi passi dal prendere un parametro di un tipo, a un parametro che è una sottoclasse di quel parametro.

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

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

Questo non è permesso in PHP perché sta cambiando la firma dei metodi che non è consentita. Con i generici di stile Java puoi fare qualcosa di simile:

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

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

Ma ovviamente PHP non supporta quelli.

Google per questo problema, le persone suggeriscono di utilizzare instanceof per controllare gli errori in fase di esecuzione, ad es.

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");
        } 
    }
}

Ma funziona solo in fase di runtime, non ti permette di ispezionare il tuo codice per errori usando l'analisi statica - quindi c'è un modo ragionevole di gestirlo in PHP?

Un esempio più completo del problema è:

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
    }
}

Poiché sto passando solo un Item a new ProcessedWoodItem($item) e si aspetta un WoodItem come parametro, l'ispezione del codice suggerisce che c'è un errore.

    
posta Danack 05.02.2014 - 04:36
fonte

1 risposta

4

Puoi usare metodi senza argomenti, documentando invece i parametri con i blocchi doc:

<?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');

Non dirò che penso sia comunque una buona idea.

Il tuo problema è che hai un contesto con un numero variabile di membri - piuttosto che cercare di forzare quelli come argomenti, un'idea migliore e più a prova di futuro è introdurre un tipo di contesto per portare tutti gli argomenti possibili, quindi che l'elenco degli argomenti non deve mai cambiare.

Così:

<?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'));

I metodi di produzione statici sono ovviamente facoltativi, ma potrebbero essere utili, se solo determinate combinazioni specifiche di membri producono un contesto significativo. Se è così, potresti dichiarare __construct() come protetto / privato.

    
risposta data 23.02.2015 - 12:49
fonte

Leggi altre domande sui tag