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.

saalaa
sumber

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"
Owen
sumber
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?

jrmgx
sumber
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
Joas
sumber
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"
Engr Syed Rowshan Ali
sumber
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)

sumber
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

sumber
.. 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

Tom Haigh
sumber
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);
Preston
sumber
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"

lo_fye
sumber