Mengapa PHP 5.2+ melarang metode kelas statis abstrak?

121

Setelah mengaktifkan peringatan ketat di PHP 5.2, saya melihat banyak peringatan standar ketat dari proyek yang awalnya ditulis tanpa peringatan ketat:

Standar Ketat : Static function Program :: getSelectSQL () tidak boleh abstrak di Program.class.inc

Fungsi yang dimaksud adalah milik Program kelas induk abstrak dan dinyatakan statis abstrak karena harus diimplementasikan dalam kelas anaknya, seperti TVProgram.

Saya menemukan referensi untuk perubahan ini di sini :

Menghilangkan fungsi kelas statis abstrak. Karena pengawasan, PHP 5.0.x dan 5.1.x mengizinkan fungsi statis abstrak di kelas. Pada PHP 5.2.x, hanya antarmuka yang dapat memilikinya.

Pertanyaan saya adalah: dapatkah seseorang menjelaskan dengan jelas mengapa seharusnya tidak ada fungsi statis abstrak di PHP?

Artem Russakovskii
sumber
12
Pembaca baru harus memperhatikan bahwa batasan irasional ini telah dihapus di PHP 7.
Mark Amery

Jawaban:

76

metode statis milik kelas yang mendeklarasikannya. Saat memperluas kelas, Anda dapat membuat metode statis dengan nama yang sama, tetapi sebenarnya Anda tidak mengimplementasikan metode abstrak statis.

Hal yang sama berlaku untuk memperluas kelas apa pun dengan metode statis. Jika Anda memperluas kelas itu dan membuat metode statis dari tanda tangan yang sama, Anda sebenarnya tidak menimpa metode statis superclass

EDIT (16 September 2009)
Pembaruan tentang ini. Menjalankan PHP 5.3, saya melihat statis abstrak kembali, baik atau buruk. (lihat http://php.net/lsb untuk info lebih lanjut)

KOREKSI (oleh philfreo)
abstract staticmasih tidak diperbolehkan di PHP 5.3, LSB terkait tetapi berbeda.

Jonathan Fingland
sumber
3
Oke, jadi bagaimana jika saya ingin menerapkan kebutuhan fungsi getSelectSQL () pada semua anak yang memperluas kelas abstrak saya? getSelectSQL () di kelas induk tidak memiliki alasan yang valid. Apa rencana tindakan terbaik? Alasan saya memilih abstrak statis adalah bahwa kode tidak akan dikompilasi sampai saya menerapkan getSelectSQL () di semua anak.
Artem Russakovskii
1
Kemungkinan besar, Anda harus mendesain ulang sesuatu sehingga getSelectSQL () adalah abstrak / instance / metode. Dengan begitu, / instance / dari setiap anak akan memiliki metode seperti itu.
Matthew Flaschen
7
Abstrak statis masih dilarang di PHP 5.3. Binding statis yang terlambat tidak ada hubungannya dengan itu. Lihat juga stackoverflow.com/questions/2859633
Artefacto
40
Menurut pendapat saya, peringatan ketat ini bodoh karena PHP memiliki "pengikatan statis terlambat", yang secara alami memberikan gagasan untuk menggunakan metode statis seolah-olah kelas itu sendiri telah menjadi objek (seperti, katakanlah, dalam ruby). Yang menyebabkan overloading metode statis dan abstract staticmungkin berguna dalam kasus ini.
dmitry
4
Jawaban ini samar-samar salah. "masih tidak diizinkan" hanya berarti Anda akan mendapatkan peringatan level E_STRICT, setidaknya di 5.3+ Anda dipersilakan untuk membuat fungsi statis abstrak, mengimplementasikannya dalam kelas yang diperluas, dan kemudian merujuknya melalui static :: keyword. Jelas versi statis kelas induk masih ada dan tidak dapat dipanggil secara langsung (melalui self :: atau static :: di dalam kelas itu) karena itu abstrak dan akan membuat kesalahan fatal seolah-olah Anda memanggil fungsi abstrak non-statis biasa. Secara fungsional ini berguna, saya setuju dengan sentimen @dmitry untuk efek itu.
ahoffner
79

Ini cerita yang panjang dan menyedihkan.

Ketika PHP 5.2 pertama kali memperkenalkan peringatan ini, binding statis terbaru belum ada dalam bahasa tersebut. Jika Anda tidak terbiasa dengan binding statis yang terlambat, perhatikan bahwa kode seperti ini tidak berfungsi seperti yang Anda harapkan:

<?php

abstract class ParentClass {
    static function foo() {
        echo "I'm gonna do bar()";
        self::bar();
    }

    abstract static function bar();
}

class ChildClass extends ParentClass {
    static function bar() {
        echo "Hello, World!";
    }
}

ChildClass::foo();

Mengesampingkan peringatan mode ketat, kode di atas tidak berfungsi. The self::bar()panggilan foo()eksplisit mengacu pada bar()metode ParentClass, bahkan ketika foo()disebut sebagai metode ChildClass. Jika Anda mencoba menjalankan kode ini dengan mode ketat nonaktif, Anda akan melihat " Kesalahan fatal PHP: Tidak dapat memanggil metode abstrak ParentClass :: bar () ".

Mengingat ini, metode statis abstrak di PHP 5.2 tidak berguna. The Seluruh titik menggunakan metode abstrak adalah bahwa Anda dapat menulis kode yang memanggil metode tanpa mengetahui apa implementasi itu akan menjadi menelepon - dan kemudian memberikan implementasi yang berbeda pada anak kelas yang berbeda. Tetapi karena PHP 5.2 tidak menawarkan cara yang bersih untuk menulis metode kelas induk yang memanggil metode statis kelas anak tempat ia dipanggil, penggunaan metode statis abstrak ini tidak dimungkinkan. Karenanya setiap penggunaan abstract staticdalam PHP 5.2 adalah kode yang buruk, mungkin terinspirasi oleh kesalahpahaman tentang cara kerja selfkata kunci. Sangat masuk akal untuk memberikan peringatan atas hal ini.

Tapi kemudian PHP 5.3 datang ditambahkan dalam kemampuan untuk merujuk ke kelas di mana metode dipanggil melalui statickata kunci (tidak seperti selfkata kunci, yang selalu mengacu pada kelas di mana metode itu didefinisikan ). Jika Anda mengubah self::bar()ke static::bar()dalam contoh saya di atas, ini berfungsi dengan baik di PHP 5.3 dan di atasnya. Anda dapat membaca lebih lanjut tentang selfvs staticat New self vs. new static .

Dengan kata kunci statis ditambahkan, argumen yang jelas untuk abstract staticmengeluarkan peringatan telah hilang. Tujuan utama pengikatan statis akhir adalah untuk memungkinkan metode yang didefinisikan dalam kelas induk untuk memanggil metode statis yang akan didefinisikan dalam kelas anak; memungkinkan metode statis abstrak tampaknya masuk akal dan konsisten mengingat adanya binding statis terlambat.

Anda masih bisa, saya kira, membuat alasan untuk menjaga peringatan itu. Misalnya, Anda dapat berargumen bahwa karena PHP memungkinkan Anda memanggil metode statis kelas abstrak, dalam contoh saya di atas (bahkan setelah memperbaikinya dengan menggantinya selfdengan static) Anda mengekspos metode publik ParentClass::foo()yang rusak dan Anda tidak benar-benar ingin melakukannya. membuka. Menggunakan kelas non-statis - yaitu, membuat semua metode contoh metode dan membuat turunan dari ParentClasssemua menjadi lajang atau sesuatu - akan menyelesaikan masalah ini, karena ParentClass, menjadi abstrak, tidak dapat dipakai dan metode contoh tidak bisa disebut. Saya pikir argumen ini lemah (karena saya pikir mengeksposParentClass::foo() bukan masalah besar dan menggunakan lajang daripada kelas statis sering kali tidak perlu bertele-tele dan jelek), tetapi Anda mungkin cukup tidak setuju - ini adalah panggilan yang agak subjektif.

Jadi berdasarkan argumen ini, pengembang PHP menyimpan peringatan dalam bahasa, bukan?

Uh, tidak juga .

Laporan bug PHP 53081, ditautkan di atas, meminta peringatan untuk dihapus karena penambahan static::foo()konstruksi telah membuat metode statis abstrak menjadi wajar dan berguna. Rasmus Lerdorf (pencipta PHP) memulai dengan memberi label permintaan palsu dan melewati rantai panjang alasan yang buruk untuk mencoba membenarkan peringatan tersebut. Kemudian, akhirnya, pertukaran ini terjadi:

Giorgio

saya tahu tapi:

abstract class cA
{
      //static function A(){self::B();} error, undefined method
      static function A(){static::B();} // good
      abstract static function B();
}

class cB extends cA
{
    static function B(){echo "ok";}
}

cB::A();

Rasmus

Benar, begitulah seharusnya cara kerjanya.

Giorgio

tapi tidak diperbolehkan :(

Rasmus

Apa yang tidak diperbolehkan?

abstract class cA {
      static function A(){static::B();}
      abstract static function B();
}

class cB extends cA {
    static function B(){echo "ok";}
}

cB::A();

Ini bekerja dengan baik. Anda jelas tidak bisa memanggil self :: B (), tapi static :: B () baik-baik saja.

Klaim Rasmus bahwa kode dalam contohnya "berfungsi dengan baik" adalah salah; seperti yang Anda ketahui, itu melontarkan peringatan mode ketat. Saya kira dia sedang menguji tanpa mode ketat dihidupkan. Terlepas dari itu, Rasmus yang bingung meninggalkan permintaan yang ditutup secara keliru sebagai "palsu".

Dan itulah mengapa peringatan itu masih dalam bahasa. Ini mungkin bukan penjelasan yang sepenuhnya memuaskan - Anda mungkin datang ke sini berharap ada pembenaran rasional dari peringatan itu. Sayangnya, di dunia nyata, terkadang pilihan lahir dari kesalahan duniawi dan penalaran yang buruk daripada dari pengambilan keputusan yang rasional. Ini hanyalah salah satu saat itu.

Untungnya, Nikita Popov telah menghapus peringatan dari bahasa di PHP 7 sebagai bagian dari PHP RFC: Reclassify E_STRICT notices . Pada akhirnya, kewarasan telah menang, dan begitu PHP 7 dirilis, kita semua dapat dengan senang hati menggunakannya abstract statictanpa menerima peringatan konyol ini.

Mark Amery
sumber
70

Ada solusi yang sangat sederhana untuk masalah ini, yang sebenarnya masuk akal dari sudut pandang desain. Seperti yang ditulis Jonathan:

Hal yang sama berlaku untuk memperluas kelas apa pun dengan metode statis. Jika Anda memperluas kelas itu dan membuat metode statis dari tanda tangan yang sama, Anda sebenarnya tidak menimpa metode statis superclass

Jadi, sebagai solusi Anda bisa melakukan ini:

<?php
abstract class MyFoo implements iMyFoo {

    public static final function factory($type, $someData) {
        // don't forget checking and do whatever else you would
        // like to do inside a factory method
        $class = get_called_class()."_".$type;
        $inst = $class::getInstance($someData);
        return $inst;
    }
}


interface iMyFoo {
    static function factory($type, $someData);
    static function getInstance();
    function getSomeData();
}
?>

Dan sekarang Anda memaksakan bahwa setiap kelas yang membuat subkelas MyFoo mengimplementasikan metode statis getInstance, dan metode getSomeData publik. Dan jika Anda tidak membuat subkelas MyFoo, Anda masih dapat mengimplementasikan iMyFoo untuk membuat kelas dengan fungsionalitas serupa.

Wouter Van Vliet
sumber
2
Apakah mungkin dengan pola ini membuat fungsi terlindungi . Ketika saya melakukannya, itu berarti kelas yang memperluas peringatan MyFoo melempar bahwa getInstance harus bersifat publik. Dan Anda tidak dapat menempatkan dilindungi dalam definisi antarmuka.
artfulrobot
3
beberapa kali static::bisa bermanfaat.
Seyed
3
Tidak bekerja dengan Sifat. Jika saja Ciri-ciri bisa memiliki abstract staticmetode, tanpa PHP menggerutu ....
Rudie
1
Penggunaan antarmuka mungkin merupakan solusi terbaik di sini. +1.
Juan Carlos Coto
Ini sebenarnya sangat elegan karena kesederhanaannya. +1
G. Stewart
12

Saya tahu ini sudah tua tapi ....

Mengapa tidak melontarkan pengecualian metode statis kelas induk itu, dengan cara itu jika Anda tidak menimpanya, pengecualian akan terjadi.

Petah
sumber
1
Itu tidak membantu, pengecualian akan terjadi pada panggilan metode statis - pada saat yang sama kesalahan 'metode tidak ada' akan muncul jika Anda tidak menimpanya.
BT
3
@BT Maksud saya, jangan mendeklarasikan metode abstrak, terapkan, tetapi cukup lempar pengecualian saat dipanggil, yang berarti ia tidak akan membuang jika telah diganti.
Petah
Ini sepertinya solusi paling elegan.
Alex S
Lebih baik melihat sesuatu seperti ini pada waktu kompilasi, daripada waktu proses. Lebih mudah menemukan masalah sebelum diproduksi karena Anda hanya perlu memuat file, bukan mengeksekusi kode untuk mengetahui apakah itu buruk atau tidak sesuai
Rahly
4

Saya berpendapat bahwa kelas / antarmuka abstrak dapat dilihat sebagai kontrak antara programmer. Ini lebih berkaitan dengan bagaimana sesuatu harus terlihat / berperilaku seperti dan tidak mengimplementasikan fungsionalitas yang sebenarnya. Seperti yang terlihat di php5.0 dan 5.1.x, ini bukanlah hukum alam yang mencegah pengembang php melakukannya, tetapi dorongan untuk mengikuti pola desain OO lainnya dalam bahasa lain. Pada dasarnya ide-ide ini mencoba untuk mencegah perilaku yang tidak diharapkan, jika seseorang sudah terbiasa dengan bahasa lain.

merkuro
sumber
Meskipun tidak terkait php, berikut adalah penjelasan bagus lainnya: stackoverflow.com/questions/3284/…
merkuro
3
Tuhanku! Dua orang yang tidak menyukai Anda sama sekali tidak waras! Ini adalah jawaban paling berwawasan di utas ini.
Theodore R. Smith
5
@ TheodoreR.SW Insightful? Ini mengandung kesalahan dan hampir tidak koheren. Klaim bahwa kelas abstrak "tidak mengimplementasikan fungsionalitas yang sebenarnya" tidak selalu benar, dan itulah inti dari mereka yang ada selain antarmuka. Dalam klaim bahwa "bukan hukum alam yang mencegah pengembang php untuk melakukannya" , saya tidak tahu apa "itu" . Dan dalam klaim bahwa "Pada dasarnya ide-ide ini mencoba untuk mencegah perilaku yang tidak terduga" , saya tidak tahu apa itu "ide-ide" atau "potensi perilaku yang tidak terduga" itu. Apa pun wawasan yang Anda ekstrak dari ini, itu hilang pada saya.
Mark Amery
2

Saya tidak melihat alasan untuk melarang fungsi abstrak statis. Argumen terbaik bahwa tidak ada alasan untuk melarangnya adalah, bahwa mereka diperbolehkan di Jawa. Pertanyaannya adalah: - Apakah secara teknis dapat dilakukan? - Ya, karena ada di PHP 5.2 dan ada di Java. Jadi, mana BISA melakukannya. HARUSkah kita melakukannya? - Apakah itu masuk akal? Iya. Masuk akal untuk mengimplementasikan bagian dari kelas dan menyerahkan bagian lain dari kelas kepada pengguna. Masuk akal dalam fungsi non-statis, mengapa tidak masuk akal untuk fungsi statis? Satu penggunaan fungsi statis adalah kelas di mana tidak boleh ada lebih dari satu instance (singletons). Misalnya mesin enkripsi. Ini tidak perlu ada dalam beberapa kasus dan ada alasan untuk mencegahnya - misalnya, Anda harus melindungi hanya satu bagian dari memori dari penyusup. Jadi sangat masuk akal untuk menerapkan satu bagian dari mesin dan menyerahkan algoritma enkripsi kepada pengguna. Ini hanya satu contoh. Jika Anda terbiasa menggunakan fungsi statis, Anda akan menemukan lebih banyak lagi.

pengguna2964021
sumber
3
Poin Anda tentang metode statis abstrak yang ada di 5.2 sangat menyesatkan. Pertama, peringatan mode ketat yang melarang mereka diperkenalkan di 5.2; Anda membuatnya terdengar seperti di 5.2 mereka diizinkan. Kedua, di 5.2 mereka tidak dapat dengan mudah digunakan "untuk mengimplementasikan bagian dari kelas dan meninggalkan bagian lain dari kelas kepada pengguna" karena Late Static Bindings belum ada.
Mark Amery
0

Di php 5.4+ gunakan sifat:

trait StaticExample {
    public static function instance () {
    return new self;
    }
}

dan di kelas Anda letakkan di awal:

use StaticExample;
kamil
sumber
1
Saya bisa memasukkan abstract public static function get_table_name();sifat dan menggunakan sifat itu dalam kelas abstrak saya tanpa peringatan E_STRICT lagi! Ini masih memaksakan pendefinisian metode statis pada anak-anak seperti yang saya harapkan. Fantastis!
Programster
-1

Perhatikan masalah 'Late Static Binding' PHP. Jika Anda menempatkan metode statis pada kelas abstrak, Anda mungkin akan menemukannya lebih cepat daripada nanti. Masuk akal jika peringatan ketat memberi tahu Anda untuk menghindari penggunaan fitur bahasa yang rusak.

Sean McSomething
sumber
4
Saya pikir yang dia maksud adalah peringatan "Standar Ketat".
Jacob Hume
2
Apa "masalah" yang Anda klaim bahwa binding statis terlambat? Anda menegaskan bahwa mereka "rusak", yang merupakan klaim yang berani, dibuat di sini tanpa bukti atau penjelasan. Fitur ini selalu berfungsi dengan baik untuk saya, dan menurut saya posting ini tidak masuk akal.
Mark Amery