PHP'de sınıf uygulamak için yöntem imzasını değiştirme


9

PHP'nin statik kod denetiminin tür tutarlılığını algılamasına izin veren Generics eksikliğine yönelik iyi bir çalışma var mı?

Ben soyut bir sınıf var, ben alt sınıf ve aynı zamanda bir yöntem bir tür bir parametre alarak, bu parametrenin bir alt sınıf olan bir parametre almak için değişir zorlamak istiyorum.

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

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

PHP'de buna izin verilmez, çünkü izin verilmeyen yöntem imzasını değiştirir. Java tarzı jenerikler ile aşağıdakileri yapabilirsiniz:

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

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

Ama belli ki PHP bunları desteklemiyor.

Google bu sorun için, kullanıcılar instanceofçalışma zamanında hataları kontrol etmek için kullanmayı önerir.

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

Ancak bu sadece çalışma zamanında çalışır, statik analiz kullanarak kodunuzu kontrol etmenize izin vermez - bu yüzden PHP'de bunu ele almanın herhangi bir mantıklı yolu var mı?

Sorunun daha eksiksiz bir örneği:

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

Çünkü sadece bir Itemiçin new ProcessedWoodItem($item)geçiyorum ve parametre olarak bir WoodItem bekliyor, kod denetimi bir hata olduğunu gösterir.


1
Neden Arayüz kullanmıyorsunuz? Aradığınızı elde etmek için arayüz kalıtımını kullanabilirsiniz, inanıyorum.
RibaldEddie

Çünkü iki sınıf, soyut sınıftaki kodlarının% 80'ini paylaşır. Arabirimlerin kullanılması, kodun çoğaltılması veya paylaşılan kodu iki sınıfla birleştirilebilecek başka bir sınıfa taşımak için büyük bir refactor anlamına gelir.
Danack

İkisini birlikte kullanabileceğinizden eminim.
RibaldEddie

Evet - ancak bu, i) paylaşılan yöntemlerin 'Öğe' temel sınıfını kullanması gerektiği sorununu gidermiyor ii) Tipe özgü yöntemler, "WoodItem" alt sınıfını kullanmak istiyor, ancak "Beyanname" gibi bir hata veriyor of barProcessor :: bar () AbstractProcessor :: bar (Item $ item) ile uyumlu olmalıdır "
Danack

Aslında davranışı test etmek için zaman yok ama ne arabirim ima (bir IItem ve bir IWoodItem oluşturmak ve IWoodItem IItem miras var)? Sonra temel sınıf için işlev imzasında IItem ve alt öğede IWoodItem ipucu. Tat işe yarayabilir. Olmayabilir.
RibaldEddie

Yanıtlar:


3

Parametreleri doc-blokları ile belgeleyerek bağımsız değişken içermeyen yöntemleri kullanabilirsiniz:

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

Ama bunun iyi bir fikir olduğunu düşünüyorum demeyeceğim.

Sorununuz, değişken sayıda üyeye sahip bir bağlamınız var - bunları argüman olarak zorlamak yerine, daha iyi ve daha geleceğe yönelik bir fikir, olası tüm argümanları taşımak için bir bağlam türü tanıtmaktır, böylece argüman listenin asla değişmesi gerekmez.

Şöyle ki:

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

Statik fabrika yöntemleri elbette isteğe bağlıdır - ancak üyelerin yalnızca belirli belirli kombinasyonları anlamlı bir bağlam üretiyorsa yararlı olabilir. Öyleyse, __construct()korumalı / özel olarak beyan etmek isteyebilirsiniz .


Çemberler PHP OOP simüle etmek için atlamak zorunda.
Tulains Córdova

4
@ user61852 Bu PHP (inanın bana) savunmak için söylemiyorum ama, ama çoğu dil olurdu değil Devralınan yöntem-imza değiştirelim - tarihsel olarak, bu PHP mümkün, ama karar çeşitli nedenlerle (yapay) programcıları kısıtlamak dil ile ilgili temel sorunlara neden olduğu için bunu yapmaktan; bu değişiklik, dili diğer dillerle daha uyumlu hale getirmek için yapıldı, bu nedenle hangi dili karşılaştırdığınızdan emin değilim. Cevabımın ikinci bölümünde gösterilen örüntü C # veya Java gibi diğer dillerde de uygulanabilir ve kullanışlıdır.
mindplay.dk
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.