Properti abstrak PHP

126

Apakah ada cara untuk mendefinisikan properti kelas abstrak di PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}
Tamás Pap
sumber

Jawaban:

154

Tidak ada yang mendefinisikan properti.

Anda hanya dapat mendeklarasikan properti karena itu adalah wadah data yang disimpan dalam memori saat inisialisasi.

Fungsi di sisi lain dapat dideklarasikan (tipe, nama, parameter) tanpa didefinisikan (fungsi tubuh hilang) dan dengan demikian, dapat dibuat abstrak.

"Abstrak" hanya menunjukkan bahwa sesuatu dideklarasikan tetapi tidak didefinisikan dan karena itu sebelum menggunakannya, Anda perlu mendefinisikannya atau menjadi tidak berguna.

Mathieu Dumoulin
sumber
58
Tidak ada alasan yang jelas bahwa kata 'abstrak' tidak dapat digunakan pada properti statis - tetapi dengan makna yang sedikit berbeda. Misalnya itu bisa menunjukkan bahwa subclass harus memberikan nilai untuk properti.
frodeborli
2
Di TypeScript ada properti abstrak dan accessor . Sangat menyedihkan bahwa dalam php tidak mungkin.
Илья Зеленько
52

Tidak, tidak ada cara untuk menegakkan itu dengan kompiler, Anda harus menggunakan pemeriksaan run-time (katakanlah, dalam konstruktor) untuk $tablenamevariabel, misalnya:

class Foo_Abstract {
  public final function __construct(/*whatever*/) {
    if(!isset($this->tablename))
      throw new LogicException(get_class($this) . ' must have a $tablename');
  }
}

Untuk menegakkan ini untuk semua kelas turunan Foo_Abstract Anda harus membuat konstruktor Foo_Abstract final, mencegah pengesampingan.

Anda bisa mendeklarasikan pengambil abstrak:

abstract class Foo_Abstract {
  abstract public function get_tablename();
}

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}
konek
sumber
Fitur yang bagus, saya suka bagaimana Anda menerapkan properti abstrak.
Mathieu Dumoulin
4
Ini akan mengharuskan Anda untuk membuat konstruktor final di kelas dasar abstrak.
hakre
3
Beberapa penjelasan: Jika Anda melakukan pemeriksaan di dalam konstruktor dan jika harus, Anda harus memastikan bahwa itu dieksekusi pada setiap instance instance. Oleh karena itu Anda perlu mencegah agar itu dihapus, misalnya dengan memperluas kelas dan mengganti konstruktor. Kata kunci terakhir yang akan Anda izinkan melakukannya.
hakre
1
Saya suka solusi "pengambil abstrak". Saat Anda mendeklarasikan fungsi dalam abstrak kelas, Anda harus mendeklarasikan abstrak kelas itu sendiri. Ini berarti kelas tidak dapat digunakan kecuali diperpanjang dan diimplementasikan sepenuhnya. Saat memperluas kelas itu, Anda harus menyediakan implementasi untuk fungsi "pengambil". Ini berarti bahwa Anda juga harus membuat properti terkait di dalam kelas extending, karena fungsi harus memiliki sesuatu untuk dikembalikan. Mengikuti pola ini Anda mendapatkan hasil yang sama seperti jika Anda menyatakan properti abstrak, itu juga merupakan pendekatan yang bersih dan jelas. Begitulah sebenarnya itu dilakukan.
Salivan
1
Menggunakan pengambil abstrak juga memungkinkan Anda untuk mengimplementasikannya dengan menghasilkan nilai, sebagai lawan mengembalikan nilai konstan, ketika masuk akal untuk melakukannya. Properti abstrak tidak akan memungkinkan Anda melakukannya, terutama properti statis.
Tobia
27

Bergantung pada konteks properti jika saya ingin memaksakan deklarasi properti objek abstrak di objek anak, saya suka menggunakan konstanta dengan statickata kunci untuk properti di konstruktor objek abstrak atau metode setter / pengambil. Anda dapat menggunakan secara opsional finaluntuk mencegah metode ditimpa di kelas-kelas yang diperluas.

Selain itu objek anak menimpa properti objek induk dan metode jika didefinisikan ulang. Misalnya jika properti dinyatakan sebagai protecteddi induk dan didefinisikan ulang seperti publicdi anak, properti yang dihasilkan adalah publik. Namun, jika properti tersebut dinyatakan privatedalam induk, properti itu akan tetap privatedan tidak tersedia untuk anak.

http://www.php.net//manual/en/language.oop5.static.php

abstract class AbstractFoo
{
    public $bar;

    final public function __construct()
    {
       $this->bar = static::BAR;
    }
}

class Foo extends AbstractFoo
{
    //const BAR = 'foobar';
}

$foo = new Foo; //Fatal Error: Undefined class constant 'BAR' (uncomment const BAR = 'foobar';)
echo $foo->bar;
fyrye
sumber
4
Solusi paling elegan di sini
Jannie Theunissen
24

Sebagaimana dinyatakan di atas, tidak ada definisi yang tepat. Namun, saya menggunakan solusi sederhana ini untuk memaksa kelas anak untuk mendefinisikan properti "abstrak":

abstract class Father 
{
  public $name;
  abstract protected function setName(); // now every child class must declare this 
                                      // function and thus declare the property

  public function __construct() 
  {
    $this->setName();
  }
}

class Son extends Father
{
  protected function setName()
  {
    $this->name = "son";
  }

  function __construct(){
    parent::__construct();
  }
}
ulka
sumber
Elegan, tetapi tidak menyelesaikan masalah untuk staticproperti.
Robbert
1
Saya tidak berpikir Anda dapat memiliki metode abstrak pribadi.
Zorji
@ Phate01 seperti yang saya mengerti, dalam komentar itu sendiri menyatakan the only "safe" methods to have in a constructor are private and/or final ones, bukankah penyelesaian masalah saya seperti itu? Saya menggunakan
private
4
Ini terlihat bagus, tetapi tidak memaksa kelas anak untuk benar-benar diatur $name. Anda dapat mengimplementasikan setName()fungsi tanpa pengaturan yang sebenarnya $name.
JohnWE
3
Saya pikir menggunakan getNamebukan $namebekerja lebih baik. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid
7

Saya bertanya pada diri sendiri pertanyaan yang sama hari ini, dan saya ingin menambahkan dua sen saya.

Alasan kami ingin abstractproperti adalah untuk memastikan bahwa subclass mendefinisikannya dan membuang pengecualian ketika mereka tidak. Dalam kasus khusus saya, saya membutuhkan sesuatu yang bisa bekerja dengan staticsekutu.

Idealnya saya ingin sesuatu seperti ini:

abstract class A {
    abstract protected static $prop;
}

class B extends A {
    protected static $prop = 'B prop'; // $prop defined, B loads successfully
}

class C extends A {
    // throws an exception when loading C for the first time because $prop
    // is not defined.
}

Saya berakhir dengan implementasi ini

abstract class A
{
    // no $prop definition in A!

    public static final function getProp()
    {
        return static::$prop;
    }
}

class B extends A
{
    protected static $prop = 'B prop';
}

class C extends A
{
}

Seperti yang Anda lihat, di Asaya tidak mendefinisikan $prop, tapi saya menggunakannya dalam staticpengambil. Oleh karena itu, kode berikut berfungsi

B::getProp();
// => 'B prop'

$b = new B();
$b->getProp();
// => 'B prop'

Di C, di sisi lain, saya tidak mendefinisikan $prop, jadi saya mendapatkan pengecualian:

C::getProp();
// => Exception!

$c = new C();
$c->getProp();
// => Exception!

Saya harus memanggil getProp() metode untuk mendapatkan pengecualian dan saya tidak bisa mendapatkannya di pemuatan kelas, tetapi cukup dekat dengan perilaku yang diinginkan, setidaknya dalam kasus saya.

Saya mendefinisikan getProp()sebagai finaluntuk menghindari bahwa beberapa orang pintar (alias sendiri dalam 6 bulan) tergoda untuk melakukan

class D extends A {
    public static function getProp() {
        // really smart
    }
}

D::getProp();
// => no exception...
Marco Pallante
sumber
Ini hack yang sangat cerdik. Semoga ini tidak perlu dilakukan di masa depan.
CMCDragonkai
6

Seperti yang Anda ketahui dengan hanya menguji kode Anda:

Kesalahan fatal: Properti tidak dapat dinyatakan abstrak di ... pada baris 3

Tidak, tidak ada. Properti tidak dapat dinyatakan abstrak dalam PHP.

Namun Anda dapat menerapkan fungsi getter / setter abstrak, ini mungkin yang Anda cari.

Properti tidak diimplementasikan (terutama properti publik), mereka hanya ada (atau tidak):

$foo = new Foo;
$foo->publicProperty = 'Bar';
hakre
sumber
6

Kebutuhan akan properti abstrak dapat mengindikasikan masalah desain. Sementara banyak jawaban menerapkan jenis pola metode Templat dan itu berfungsi, itu selalu terlihat agak aneh.

Mari kita lihat contoh aslinya:

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}

Menandai sesuatu abstractberarti menandainya sebagai sesuatu yang harus dimiliki. Nah, nilai yang harus dimiliki (dalam hal ini) adalah ketergantungan yang diperlukan, sehingga harus diteruskan ke konstruktor selama instantiasi :

class Table
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function name(): string
    {
        return $this->name;
    }
}

Maka jika Anda benar-benar menginginkan kelas bernama yang lebih konkret, Anda dapat mewarisi seperti:

final class UsersTable extends Table
{
    public function __construct()
    {
        parent::__construct('users');
    }
}

Ini dapat berguna jika Anda menggunakan wadah DI dan harus melewati tabel berbeda untuk objek yang berbeda.

sevavietl
sumber
3

PHP 7 membuatnya sedikit lebih mudah untuk membuat "properti" abstrak. Sama seperti di atas, Anda akan membuatnya dengan membuat fungsi abstrak, tetapi dengan PHP 7 Anda dapat menentukan tipe pengembalian untuk fungsi itu, yang membuat segalanya jauh lebih mudah ketika Anda membangun kelas dasar yang dapat diperluas oleh siapa pun.

<?php

abstract class FooBase {

  abstract public function FooProp(): string;
  abstract public function BarProp(): BarClass;

  public function foo() {
    return $this->FooProp();
  }

  public function bar() {
    return $this->BarProp()->name();
  }

}

class BarClass {

  public function name() {
    return 'Bar!';
  }

}

class FooClass extends FooBase {

  public function FooProp(): string {
    return 'Foo!';
  }

  public function BarProp(): BarClass {
    // This would not work:
    // return 'not working';
    // But this will!
    return new BarClass();
  }

}

$test = new FooClass();
echo $test->foo() . PHP_EOL;
echo $test->bar() . PHP_EOL;
Dropa
sumber
1

jika nilai tablename tidak akan pernah berubah selama masa hidup objek, mengikuti akan menjadi implementasi yang sederhana namun aman.

abstract class Foo_Abstract {
    abstract protected function getTablename();

    public function showTableName()
    {
        echo 'my table name is '.$this->getTablename();
    }
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' getTablename()
    protected function getTablename()
    {
        return 'users';
    }
}

kuncinya di sini adalah bahwa nilai string 'pengguna' ditentukan dan dikembalikan langsung di getTablename () dalam implementasi kelas anak. Fungsi ini meniru properti "readonly".

Ini cukup mirip dengan solusi yang diposting sebelumnya yang menggunakan variabel tambahan. Saya juga suka solusi Marco meskipun bisa sedikit lebih rumit.

ck.tan
sumber