Mendapatkan nama kelas anak di kelas induk (konteks statis)


93

Saya sedang membangun perpustakaan ORM dengan menggunakan kembali dan kesederhanaan; semuanya berjalan baik-baik saja kecuali bahwa saya terjebak oleh batasan warisan yang bodoh. Harap pertimbangkan kode di bawah ini:

class BaseModel {
    /*
     * Return an instance of a Model from the database.
     */
    static public function get (/* varargs */) {
        // 1. Notice we want an instance of User
        $class = get_class(parent); // value: bool(false)
        $class = get_class(self);   // value: bool(false)
        $class = get_class();       // value: string(9) "BaseModel"
        $class =  __CLASS__;        // value: string(9) "BaseModel"

        // 2. Query the database with id
        $row = get_row_from_db_as_array(func_get_args());

        // 3. Return the filled instance
        $obj = new $class();
        $obj->data = $row;
        return $obj;
    }
}

class User extends BaseModel {
    protected $table = 'users';
    protected $fields = array('id', 'name');
    protected $primary_keys = array('id');
}
class Section extends BaseModel {
    // [...]
}

$my_user = User::get(3);
$my_user->name = 'Jean';

$other_user = User::get(24);
$other_user->name = 'Paul';

$my_user->save();
$other_user->save();

$my_section = Section::get('apropos');
$my_section->delete();

Jelas, ini bukan perilaku yang saya harapkan (meskipun perilaku sebenarnya juga masuk akal) .. Jadi pertanyaan saya adalah jika kalian tahu maksud untuk mendapatkan, di kelas orang tua, nama kelas anak.

Jawaban:


98

pendeknya. ini tidak mungkin. di php4 Anda dapat menerapkan peretasan yang buruk (periksa debug_backtrace()) tetapi metode itu tidak berfungsi di PHP5. referensi:

edit : contoh pengikatan statis akhir di PHP 5.3 (disebutkan dalam komentar). perhatikan ada potensi masalah dalam implementasi saat ini ( src ).

class Base {
    public static function whoAmI() {
        return get_called_class();
    }
}

class User extends Base {}

print Base::whoAmI(); // prints "Base"
print User::whoAmI(); // prints "User"

Ya, saya baru saja membaca tentang debug_backtrace().. Solusi yang mungkin adalah menggunakan pengikatan statis terlambat dari PHP 5.3 tetapi itu bukan kemungkinan dalam kasus saya. Terima kasih.
saalaa

187

Anda tidak perlu menunggu PHP 5.3 jika Anda dapat membayangkan cara untuk melakukan ini di luar konteks statis. Di php 5.2.9, dalam metode non-statis kelas induk, Anda dapat melakukan:

get_class($this);

dan itu akan mengembalikan nama kelas anak sebagai string.

yaitu

class Parent() {
    function __construct() {
        echo 'Parent class: ' . get_class() . "\n" . 'Child class: ' . get_class($this);
    }
}

class Child() {
    function __construct() {
        parent::construct();
    }
}

$x = new Child();

ini akan menghasilkan:

Parent class: Parent
Child class: Child

manis ya?


11
Jika Anda menggunakan kelas abstrak, ini harus diketahui.
Levi Morrison

1
Saya pikir $x = new Parent();harus $x = new Child();.
Justin C

ini pancit!
bdalina

Kelas anak mungkin tanpa konstruktor
shmnff

20

Saya tahu pertanyaan ini benar-benar kuno, tetapi bagi mereka yang mencari solusi yang lebih praktis daripada menentukan properti di setiap kelas yang berisi nama kelas:

Anda dapat menggunakan statickata kunci untuk ini.

Seperti yang dijelaskan dalam catatan kontributor ini di dokumentasi php

yang statickata kunci dapat digunakan di dalam kelas super untuk mengakses sub kelas dari yang metode disebut.

Contoh:

class Base
{
    public static function init() // Initializes a new instance of the static class
    {
        return new static();
    }

    public static function getClass() // Get static class
    {
        return static::class;
    }

    public function getStaticClass() // Non-static function to get static class
    {
        return static::class;
    }
}

class Child extends Base
{

}

$child = Child::init();         // Initializes a new instance of the Child class

                                // Output:
var_dump($child);               // object(Child)#1 (0) {}
echo $child->getStaticClass();  // Child
echo Child::getClass();         // Child

1
Terima kasih! Sedang berjuang dengan ReflectionClass tetapi menelepon static()adalah cara terbaik!
Godbout

16

Saya tahu postingan lamanya tetapi ingin membagikan solusi yang saya temukan.

Diuji dengan PHP 7+ Gunakan tautan fungsiget_class()

<?php
abstract class bar {
    public function __construct()
    {
        var_dump(get_class($this));
        var_dump(get_class());
    }
}

class foo extends bar {
}

new foo;
?>

Contoh di atas akan menampilkan:

string(3) "foo"
string(3) "bar"

6

Jika Anda tidak ingin menggunakan get_called_class (), Anda dapat menggunakan trik lain dari pengikatan statis terbaru (PHP 5.3+). Tetapi sisi negatifnya dalam hal ini Anda perlu memiliki metode getClass () di setiap model. Yang bukan masalah besar IMO.

<?php

class Base 
{
    public static function find($id)
    {
        $table = static::$_table;
        $class = static::getClass();
        // $data = find_row_data_somehow($table, $id);
        $data = array('table' => $table, 'id' => $id);
        return new $class($data);
    }

    public function __construct($data)
    {
        echo get_class($this) . ': ' . print_r($data, true) . PHP_EOL;
    }
}

class User extends Base
{
    protected static $_table = 'users';

    public static function getClass()
    {
        return __CLASS__;
    }
}

class Image extends Base
{
    protected static $_table = 'images';

    public static function getClass()
    {
        return __CLASS__;
    }
}

$user = User::find(1); // User: Array ([table] => users [id] => 1)  
$image = Image::find(5); // Image: Array ([table] => images [id] => 5)

2

Tampaknya Anda mungkin mencoba menggunakan pola tunggal sebagai pola pabrik. Saya akan merekomendasikan mengevaluasi keputusan desain Anda. Jika singleton benar-benar sesuai, saya juga akan merekomendasikan hanya menggunakan metode statis di mana warisan tidak diinginkan.

class BaseModel
{

    public function get () {
        echo get_class($this);

    }

    public static function instance () {
        static $Instance;
        if ($Instance === null) {
            $Instance = new self;

        }
        return $Instance;
    }
}

class User
extends BaseModel
{
    public static function instance () {
        static $Instance;
        if ($Instance === null) {
            $Instance = new self;

        }
        return $Instance;
    }
}

class SpecialUser
extends User
{
    public static function instance () {
        static $Instance;
        if ($Instance === null) {
            $Instance = new self;

        }
        return $Instance;
    }
}


BaseModel::instance()->get();   // value: BaseModel
User::instance()->get();        // value: User
SpecialUser::instance()->get(); // value: SpecialUser

.. tidak. Anda tidak mengerti apa yang saya coba bo, tapi itu karena saya tidak menjelaskannya dengan baik :). Sebenarnya, saya hanya mencoba menyediakan metode statis (diimplementasikan di BaseModel) untuk mendapatkan () sebuah instance dari Model tertentu (mungkin itu Pengguna, Peran atau apa pun). Saya akan memperbarui pertanyaannya ..
saalaa

Oke, diperbarui. Tentu saja kelas BaseModel memiliki lebih banyak metode, termasuk beberapa untuk melacak apa yang diubah dalam objek dan hanya UPDATE yang telah berubah, dll ... Tapi terima kasih :).
saalaa

2

Mungkin ini tidak benar-benar menjawab pertanyaan, tetapi Anda bisa menambahkan parameter untuk get () menentukan tipe. maka Anda dapat menelepon

BaseModel::get('User', 1);

alih-alih memanggil User :: get (). Anda bisa menambahkan logika di BaseModel :: get () untuk memeriksa apakah metode get ada di subkelas dan kemudian memanggilnya jika Anda ingin mengizinkan subkelas untuk menimpanya.

Kalau tidak, satu-satunya cara yang bisa saya pikirkan dengan jelas adalah dengan menambahkan barang ke setiap subclass, yang bodoh:

class BaseModel {
    public static function get() {
        $args = func_get_args();
        $className = array_shift($args);

        //do stuff
        echo $className;
        print_r($args);
    }
}

class User extends BaseModel {
    public static function get() { 
        $params = func_get_args();
        array_unshift($params, __CLASS__);
        return call_user_func_array( array(get_parent_class(__CLASS__), 'get'), $params); 
    }
}


User::get(1);

Ini mungkin akan pecah jika Anda kemudian subclassed Pengguna, tapi saya kira Anda bisa mengganti get_parent_class(__CLASS__)dengan 'BaseModel'dalam kasus itu


Sebenarnya ya. Batasan PHP ini memiliki keuntungan besar untuk memaksa saya meninjau desain saya. Ini akan lebih terlihat seperti $ connection-> get ('User', 24); karena memungkinkan banyak koneksi pada saat yang sama dan juga secara semantik lebih tepat. Tapi Anda ada benarnya :).
saalaa

array_shift tampaknya lambat, karena itu harus mengubah setiap kunci dari array ... Sebagai gantinya, cukup $ args [0];
Xesau

0

Masalahnya bukan pada batasan bahasa, ini adalah desain Anda. Tidak peduli Anda memiliki kelas; metode statis lebih percaya pada desain prosedural daripada berorientasi objek. Anda juga menggunakan status global dalam beberapa bentuk. (Bagaimana cara get_row_from_db_as_array()mengetahui di mana menemukan database?) Dan akhirnya terlihat sangat sulit untuk menguji unit.

Cobalah sesuatu di sepanjang garis ini.

$db = new DatabaseConnection('dsn to database...');
$userTable = new UserTable($db);
$user = $userTable->get(24);

0

Dua variasi jawaban Preston:

1)

class Base 
{
    public static function find($id)
    {
        $table = static::$_table;
        $class = static::$_class;
        $data = array('table' => $table, 'id' => $id);
        return new $class($data);
    }
}

class User extends Base
{
    public static $_class = 'User';
}

2)

class Base 
{
    public static function _find($class, $id)
    {
        $table = static::$_table;
        $data = array('table' => $table, 'id' => $id);
        return new $class($data);
    }
}

class User extends Base
{
    public static function find($id)
    {
        return self::_find(get_class($this), $id);
    }
}

Catatan: memulai nama properti dengan _ adalah konvensi yang pada dasarnya berarti "saya tahu saya telah membuat ini publik, tetapi seharusnya benar-benar dilindungi, tetapi saya tidak dapat melakukannya dan mencapai tujuan saya"

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.