Antarmuka PHP 7, tipe pengembalian mengisyaratkan dan diri sendiri

89

PEMBARUAN : PHP 7.4 sekarang mendukung kovariansi dan pelanggaran yang membahas masalah utama yang diangkat dalam pertanyaan ini.


Saya telah mengalami sesuatu masalah dengan menggunakan tipe return yang mengisyaratkan di PHP 7. Pemahaman saya adalah bahwa mengisyaratkan : selfberarti Anda bermaksud agar kelas pelaksana mengembalikan dirinya sendiri. Oleh karena itu saya menggunakan : selfantarmuka saya untuk menunjukkan itu, tetapi ketika saya mencoba untuk benar-benar menerapkan antarmuka saya mendapat kesalahan kompatibilitas.

Berikut ini adalah demonstrasi sederhana dari masalah yang saya alami:

interface iFoo
{
    public function bar (string $baz) : self;
}

class Foo implements iFoo
{

    public function bar (string $baz) : self
    {
        echo $baz . PHP_EOL;
        return $this;
    }
}

(new Foo ()) -> bar ("Fred") 
    -> bar ("Wilma") 
    -> bar ("Barney") 
    -> bar ("Betty");

Output yang diharapkan adalah:

Fred Wilma Barney Betty

Apa yang sebenarnya saya dapatkan adalah:

PHP Fatal error: Deklarasi Foo :: bar (int $ baz): Foo harus kompatibel dengan iFoo :: bar (int $ baz): iFoo di test.php pada baris 7

Masalahnya adalah Foo adalah implementasi dari iFoo, sejauh yang saya tahu implementasinya harus kompatibel dengan antarmuka yang diberikan. Saya mungkin dapat memperbaiki masalah ini dengan mengubah antarmuka atau kelas pelaksana (atau keduanya) untuk mengembalikan petunjuk antarmuka dengan nama daripada menggunakan self, tetapi pemahaman saya adalah bahwa secara semantik selfberarti "mengembalikan instance kelas yang baru saja Anda panggil metode ini ". Oleh karena itu mengubahnya ke antarmuka berarti dalam teori bahwa saya dapat mengembalikan contoh apa pun dari sesuatu yang mengimplementasikan antarmuka ketika maksud saya adalah untuk contoh yang dipanggil adalah apa yang akan dikembalikan.

Apakah ini kekeliruan di PHP atau apakah ini keputusan desain yang disengaja? Jika yang pertama, apakah ada kemungkinan melihatnya diperbaiki di PHP 7.1? Jika tidak, lalu apa cara yang benar untuk kembali yang mengisyaratkan bahwa antarmuka Anda mengharapkan Anda untuk mengembalikan contoh yang baru saja Anda panggil metode untuk merangkai?

GordonM
sumber
Saya pikir ini adalah kesalahan dalam PHP return type-hinting, mungkin Anda harus mengangkatnya sebagai bug ; tetapi perbaikan apa pun tidak mungkin masuk ke PHP 7.1 pada tahap akhir ini
Mark Baker
Karena versi beta terakhir 7.1 telah online beberapa hari yang lalu, sangat tidak mungkin perbaikan apa pun akan masuk ke 7.1.
Charlotte Dunois
Karena tertarik, di mana Anda membaca interpretasi Anda tentang bagaimana selfjenis pengembalian seharusnya bekerja?
Adam Cameron
@Adam: Sepertinya logis untuk selfmengartikan "Kembalikan contoh yang Anda panggil ini, dan bukan contoh lain yang mengimplementasikan antarmuka yang sama". Saya sepertinya ingat Java memiliki tipe pengembalian yang serupa (meskipun sudah lama sejak saya melakukan pemrograman Java)
GordonM
1
Hai Gordon. Yah kecuali itu didokumentasikan untuk bekerja di suatu tempat, saya tidak akan mengandalkan apa yang mungkin logis menjadi kenyataan. TBH dengan situasi yang akan saya gambarkan, saya akan sedeklaratif mungkin dan menggunakan iFoo sebagai tipe kembalian. Apakah ada situasi di mana itu tidak benar-benar berfungsi? (Saya menyadari ini adalah "nasihat" / pendapat daripada "jawaban", yang mengatakan.
Adam Cameron

Jawaban:

94

selftidak mengacu pada instance, itu mengacu pada kelas saat ini. Tidak ada cara bagi antarmuka untuk menentukan bahwa contoh yang sama harus dikembalikan - menggunakan selfcara yang Anda coba hanya akan memaksakan bahwa contoh yang dikembalikan berada dari kelas yang sama.

Karena itu, deklarasi tipe kembalian dalam PHP harus invarian sementara yang Anda coba adalah kovarian.

Penggunaan Anda selfsetara dengan:

interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : Foo  {...}
}

yang tidak diperbolehkan.


The Kembali Jenis Deklarasi RFC memiliki ini untuk mengatakan :

Penegakan tipe pengembalian yang dideklarasikan selama pewarisan adalah invariant; ini berarti bahwa ketika sub-tipe mengganti metode induk maka tipe kembalian dari anak harus sama persis dengan induk dan tidak boleh dihilangkan. Jika orang tua tidak mendeklarasikan tipe yang dikembalikan maka anak diperbolehkan untuk mendeklarasikannya.

...

RFC ini awalnya mengusulkan jenis kembalian kovarian tetapi diubah menjadi invarian karena beberapa masalah. Dimungkinkan untuk menambahkan jenis pengembalian kovarian di beberapa titik di masa mendatang.


Untuk saat ini, setidaknya yang terbaik yang dapat Anda lakukan adalah:

interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : iFoo  {...}
}
pengguna3942918
sumber
19
Yang mengatakan, saya akan mengharapkan petunjuk jenis kembali staticuntuk bekerja, tetapi itu bahkan tidak dikenali
Mark Baker
Saya pikir itu akan menjadi masalahnya, dan itulah cara saya akan menyelesaikan masalah. Saya akan, bagaimanapun, lebih suka menggunakan: self jika memungkinkan karena jika kelas mengimplementasikan antarmuka maka nilai kembali diri secara implisit juga merupakan nilai kembali dari sebuah contoh antarmuka juga.
GordonM
19
Paul, menghapus komentar yang telah Anda hapus di sini sebenarnya berbahaya karena (A) menghilangkan informasi penting, dan (B) mengganggu aliran diskusi terkait dengan komentar lainnya. Saya tidak dapat melihat alasan mengapa komentar Anda yang mengandalkan Mark dan Gordon perlu dihapus. Faktanya, Anda melakukan ini di semua tempat, dan itu harus dihentikan. Sama sekali tidak ada alasan yang baik untuk kembali ke pertanyaan lama dan menghapus semua komentar Anda, benar-benar menghancurkan aliran diskusi. Faktanya, itu berbahaya dan mengganggu.
Cody Grey
Ada kata pengantar penting untuk bagian kutipan Anda yang terlihat di sini: " Jenis kembalian kovarian dianggap jenis suara dan digunakan dalam banyak bahasa lain (C ++ dan Java, tetapi saya tidak yakin C #). RFC ini awalnya mengusulkan jenis kembalian kovarian tetapi diubah menjadi invarian karena beberapa masalah. ". Saya ingin tahu masalah apa yang dimiliki PHP. Pilihan desain mereka menyebabkan beberapa batasan yang juga menyebabkan masalah aneh dengan ciri-ciri, menjadikannya agak tidak berguna dalam banyak kasus kecuali Anda melonggarkan batasan tipe pengembalian (seperti yang terlihat pada beberapa jawaban di bawah). Sangat membuat frustasi.
John Pancoast
1
@ MarkBaker, tipe pengembalian staticditambahkan ke PHP 8.
Ricardo Boss
16

Ini juga bisa menjadi solusi, bahwa Anda tidak mendefinisikan secara eksplisit jenis kembalian di Antarmuka, hanya di PHPDoc dan kemudian Anda dapat menentukan jenis kembalian tertentu dalam implementasi:

interface iFoo
{
    public function bar (string $baz);
}

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}
Gabor
sumber
2
Atau alih-alih Foohanya digunakan self.
alih
2

Dalam kasus, ketika Anda ingin memaksa dari antarmuka, metode itu akan mengembalikan objek, tetapi tipe objek bukan tipe antarmuka, tetapi kelas itu sendiri, maka Anda dapat menulisnya seperti ini:

interface iFoo {
    public function bar (string $baz) : object;
}

class Foo implements iFoo {
    public function bar (string $baz) : self  {...}
}

Ini berfungsi sejak PHP 7.4.

sebagai gantinya
sumber
0

Ini terlihat seperti perilaku yang diharapkan bagi saya.

Ubah saja Foo::barmetode Anda untuk kembali, iFoobukan selfdan selesai dengannya.

Penjelasan:

selfseperti yang digunakan dalam antarmuka berarti "objek bertipe iFoo".
selfseperti yang digunakan dalam implementasi berarti "objek bertipe Foo".

Oleh karena itu, jenis kembalian di antarmuka dan implementasinya jelas tidak sama.

Salah satu komentar menyebutkan Java dan apakah Anda akan mengalami masalah ini. Jawabannya adalah ya, Anda akan memiliki masalah yang sama jika Java mengizinkan Anda menulis kode seperti itu - padahal sebenarnya tidak. Karena Java mengharuskan Anda menggunakan nama jenis alih-alih selfpintasan PHP , Anda tidak akan pernah benar-benar melihat ini. (Lihat di sini untuk diskusi tentang masalah serupa di Java.)

Moshe Katz
sumber
Jadi apakah mendeklarasikan self, mirip dengan mendeklarasikan MyClass::class?
peterchaula
1
@ Laser Ya, benar.
Moshe Katz
4
Tetapi jika Foo mengimplementasikan iFoo maka Foo menurut definisi tipe iFoo
GordonM