Mengubah metode tanda tangan untuk mengimplementasikan kelas di PHP


9

Apakah ada pekerjaan yang layak untuk kurangnya PHP Generics yang memungkinkan pemeriksaan kode statis untuk mendeteksi konsistensi jenis?

Saya memiliki kelas abstrak, yang ingin saya sub-kelas dan juga menegakkan bahwa salah satu metode berubah dari mengambil parameter dari satu jenis, untuk mengambil parameter yang merupakan sub-kelas dari parameter itu.

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

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

Ini tidak diizinkan dalam PHP karena itu mengubah metode tanda tangan yang tidak diizinkan. Dengan generik gaya Java, Anda dapat melakukan sesuatu seperti:

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

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

Tapi jelas PHP tidak mendukung itu.

Google untuk masalah ini, orang menyarankan menggunakan instanceofuntuk memeriksa kesalahan pada saat run-time misalnya

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

Tapi itu hanya berfungsi saat runtime, itu tidak memungkinkan Anda untuk memeriksa kode Anda untuk kesalahan menggunakan analisis statis - jadi apakah ada cara yang masuk akal untuk menangani ini di PHP?

Contoh masalah yang lebih lengkap adalah:

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

Karena saya melewati hanya Itemke new ProcessedWoodItem($item)dan mengharapkan WoodItem sebagai parameter, pemeriksaan kode menunjukkan ada kesalahan.


1
Mengapa Anda tidak menggunakan Antarmuka? Anda dapat menggunakan warisan antarmuka untuk mencapai apa yang Anda inginkan, saya percaya.
RibaldEddie

Karena kedua kelas berbagi 80% dari kode mereka, yang ada di kelas abstrak. Menggunakan antarmuka akan berarti menduplikasi kode, atau refactor besar untuk memindahkan kode bersama ke kelas lain yang dapat digabungkan dengan dua kelas.
Danack

Saya cukup yakin Anda bisa menggunakan keduanya bersama-sama.
RibaldEddie

Ya - tetapi itu tidak mengatasi masalah bahwa saya) metode bersama perlu menggunakan kelas dasar 'Item' ii) Jenis metode khusus ingin menggunakan sub-kelas "WoodItem" tetapi itu memberikan kesalahan seperti "Deklarasi dari WoodProcessor :: bar () harus kompatibel dengan AbstractProcessor :: bar (Item $ item) "
Danack

Saya tidak punya waktu untuk benar-benar menguji perilaku tetapi bagaimana jika Anda mengisyaratkan antarmuka (buat IItem dan IWoodItem dan minta IWoodItem mewarisi dari IItem)? Kemudian beri tanda IItem pada tanda tangan fungsi untuk kelas dasar dan IWoodItem pada anak. Mungkin bisa. Mungkin tidak.
RibaldEddie

Jawaban:


3

Anda dapat menggunakan metode tanpa argumen, mendokumentasikan parameter dengan blok-doc sebagai gantinya:

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

Saya tidak akan mengatakan saya pikir ini adalah ide yang bagus.

Masalah Anda adalah, Anda memiliki konteks dengan jumlah variabel anggota - alih-alih mencoba memaksa mereka sebagai argumen, ide yang lebih baik dan lebih banyak bukti di masa depan adalah memperkenalkan tipe konteks untuk membawa semua argumen yang mungkin, sehingga argumen daftar tidak perlu diubah.

Seperti itu:

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

Metode pabrik statis tentu saja opsional - tetapi bisa berguna, jika hanya kombinasi anggota tertentu yang menghasilkan konteks yang bermakna. Jika demikian, Anda mungkin ingin mendeklarasikan __construct()sebagai dilindungi / pribadi juga.


Lingkaran harus dilewati untuk mensimulasikan OOP di PHP.
Tulains Córdova

4
@ user61852 Saya tidak mengatakan ini untuk mempertahankan PHP (percayalah pada saya) tetapi, tetapi sebagian besar bahasa tidak akan membiarkan Anda mengubah tanda tangan metode yang diwariskan - secara historis, ini dimungkinkan dalam PHP, tetapi karena berbagai alasan memutuskan untuk (secara artifisial) membatasi pemrogram dari melakukan ini, karena menyebabkan masalah mendasar dengan bahasa; perubahan ini dibuat untuk membawa bahasa lebih selaras dengan bahasa lain, jadi saya tidak yakin bahasa mana yang Anda bandingkan. Pola yang ditunjukkan pada bagian kedua dari jawaban saya berlaku dan berguna dalam bahasa lain seperti C # atau Java juga.
mindplay.dk
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.