Ottenere il nome di una classe figlia nella classe genitore (contesto statico)


93

Sto costruendo una libreria ORM pensando al riutilizzo e alla semplicità; va tutto bene tranne che sono rimasto bloccato da una stupida limitazione di eredità. Si prega di considerare il codice seguente:

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();

Ovviamente, questo non è il comportamento che mi aspettavo (sebbene anche il comportamento effettivo abbia senso) .. Quindi la mia domanda è se voi ragazzi conoscete un mezzo per ottenere, nella classe genitore, il nome della classe figlio.

Risposte:


98

in breve. non è possibile. in php4 potresti implementare un terribile hack (esamina il debug_backtrace()) ma quel metodo non funziona in PHP5. Riferimenti:

modifica : un esempio di associazione statica tardiva in PHP 5.3 (menzionato nei commenti). nota che ci sono potenziali problemi nella sua attuale implementazione ( 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"

Sì, ho appena letto di debug_backtrace().. Una possibile soluzione sarebbe quella di utilizzare l' associazione statica tardiva da PHP 5.3, ma nel mio caso non è possibile. Grazie.
saalaa

187

Non è necessario attendere PHP 5.3 se si è in grado di concepire un modo per farlo al di fuori di un contesto statico. In php 5.2.9, in un metodo non statico della classe genitore, puoi fare:

get_class($this);

e restituirà il nome della classe figlia come stringa.

cioè

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

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

$x = new Child();

questo produrrà:

Parent class: Parent
Child class: Child

dolce eh?


11
Se stai usando classi astratte, questo è un must.
Levi Morrison,

1
Penso che $x = new Parent();dovrebbe essere $x = new Child();.
Justin C

questo è pancit!
bdalina

La classe figlio potrebbe essere senza costruttore
shmnff

19

So che questa domanda è davvero vecchia, ma per chi cerca una soluzione più pratica rispetto alla definizione di una proprietà in ogni classe contenente il nome della classe:

Puoi usare la staticparola chiave per questo.

Come spiegato in questa nota del collaboratore nella documentazione di php

la staticparola chiave può essere utilizzata all'interno di una super classe per accedere alla sottoclasse da cui viene chiamato un metodo.

Esempio:

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
Grazie! Stavo lottando con ReflectionClass ma chiamare static()è la strada da percorrere!
Godbout

16

Conosco il suo vecchio post ma voglio condividere la soluzione che ho trovato.

Testato con PHP 7+ Usa il collegamento alla funzioneget_class()

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

class foo extends bar {
}

new foo;
?>

L'esempio precedente produrrà:

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

6

Nel caso in cui non si desideri utilizzare get_called_class (), è possibile utilizzare altri trucchi del binding statico tardivo (PHP 5.3+). Ma lo svantaggio in questo caso è che devi avere il metodo getClass () in ogni modello. Che non è un grosso problema 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

Sembra che tu stia cercando di utilizzare un pattern singleton come pattern di fabbrica. Consiglierei di valutare le tue decisioni di progettazione. Se un singleton è davvero appropriato, consiglierei anche di utilizzare solo metodi statici in cui l'ereditarietà non è desiderata.

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

.. no. Non hai capito cosa stavo cercando di fare, ma è perché non l'ho spiegato bene :). In realtà, sto solo cercando di fornire un metodo statico (implementato in BaseModel) per ottenere () un'istanza di un dato modello (che sia Utente, Ruolo o qualsiasi altra cosa). Aggiornerò la domanda ..
saalaa

Ok, aggiornato. Naturalmente la classe BaseModel ha molti più metodi, inclusi alcuni per tenere traccia di ciò che è cambiato nell'oggetto e AGGIORNARE solo ciò che è cambiato, ecc ... Ma grazie comunque :).
saalaa

2

Forse questo non sta effettivamente rispondendo alla domanda, ma potresti aggiungere un parametro per get () specificando il tipo. allora puoi chiamare

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

invece di chiamare User :: get (). È possibile aggiungere logica in BaseModel :: get () per verificare se esiste un metodo get nella sottoclasse e quindi chiamarlo se si desidera consentire alla sottoclasse di sovrascriverlo.

Altrimenti l'unico modo a cui posso pensare è ovviamente aggiungere cose a ciascuna sottoclasse, il che è stupido:

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);

Questo probabilmente si interromperà se si sottoclasse User, ma suppongo che in tal caso si potrebbe sostituire get_parent_class(__CLASS__)con'BaseModel'


Attualmente si. Questa limitazione PHP ha avuto il grande vantaggio di costringermi a rivedere il mio progetto. Assomiglierà molto di più a $ connection-> get ('User', 24); poiché consente più connessioni contemporaneamente ed è anche semanticamente più corretto. Ma hai ragione :).
saalaa

array_shift sembra essere lento, perché dovrà cambiare ogni chiave dell'array ... Solo $ args [0] invece;
Xesau

0

Il problema non è una limitazione della lingua, è il tuo design. Non importa se hai lezioni; i metodi statici smentiscono una progettazione procedurale piuttosto che orientata agli oggetti. Stai anche usando lo stato globale in qualche forma. (Come fa a get_row_from_db_as_array()sapere dove trovare il database?) E infine sembra molto difficile eseguire un test unitario.

Prova qualcosa in questo senso.

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

0

Due varianti sulla risposta di 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);
    }
}

Nota: iniziare un nome di proprietà con _ è una convenzione che in pratica significa "so di averlo reso pubblico, ma in realtà avrebbe dovuto essere protetto, ma non sono riuscito a farlo e raggiungere il mio obiettivo"

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.