Abrufen des Namens einer untergeordneten Klasse in der übergeordneten Klasse (statischer Kontext)

93

Ich baue eine ORM-Bibliothek mit Blick auf Wiederverwendung und Einfachheit. Alles geht gut, außer dass ich von einer dummen Erbschaftsbeschränkung festgefahren bin. Bitte beachten Sie den folgenden Code:

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

Offensichtlich ist dies nicht das Verhalten, das ich erwartet hatte (obwohl das tatsächliche Verhalten auch Sinn macht). Meine Frage ist also, ob ihr ein Mittel kennt, um in der Elternklasse den Namen der Kinderklasse zu erhalten.

Saalaa
quelle

Antworten:

98

Zusamenfassend. das ist nicht möglich. in php4 könnten sie einen schrecklichen hack implementieren (untersuchen debug_backtrace()), aber diese methode funktioniert in PHP5 nicht. Verweise:

edit : ein Beispiel für eine späte statische Bindung in PHP 5.3 (in den Kommentaren erwähnt). Beachten Sie, dass die aktuelle Implementierung ( src ) potenzielle Probleme aufweist .

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
quelle
Ja, ich habe gerade darüber gelesen debug_backtrace(). Eine mögliche Lösung wäre die Verwendung einer späten statischen Bindung aus PHP 5.3, aber das ist in meinem Fall nicht möglich. Danke dir.
Saalaa
187

Sie müssen nicht auf PHP 5.3 warten, wenn Sie sich eine Möglichkeit vorstellen können, dies außerhalb eines statischen Kontexts zu tun. In PHP 5.2.9 können Sie in einer nicht statischen Methode der übergeordneten Klasse Folgendes tun:

get_class($this);

und es wird der Name der untergeordneten Klasse als Zeichenfolge zurückgegeben.

dh

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

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

$x = new Child();

Dies wird Folgendes ausgeben:

Parent class: Parent
Child class: Child

süß huh?

jrmgx
quelle
11
Wenn Sie abstrakte Klassen verwenden, ist dies ein Muss.
Levi Morrison
1
Ich denke $x = new Parent();sollte sein $x = new Child();.
Justin C
das ist es pancit!
Bdalina
Kinderklasse
19

Ich weiß, dass diese Frage wirklich alt ist, aber für diejenigen, die nach einer praktischeren Lösung suchen, als eine Eigenschaft in jeder Klasse zu definieren, die den Klassennamen enthält:

Sie können hierfür das staticSchlüsselwort verwenden.

Wie in diesem Beitrag in der PHP-Dokumentation erläutert

Das staticSchlüsselwort kann innerhalb einer Superklasse verwendet werden, um auf die Unterklasse zuzugreifen, von der aus eine Methode aufgerufen wird.

Beispiel:

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
quelle
1
Danke dir! Ich hatte Probleme mit ReflectionClass, aber das Anrufen static()ist der richtige Weg!
Godbout
16

Ich kenne den alten Beitrag, möchte aber die Lösung teilen, die ich gefunden habe.

Getestet mit PHP 7+ Mit der Funktion get_class()Link

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

class foo extends bar {
}

new foo;
?>

Das obige Beispiel gibt Folgendes aus:

string(3) "foo"
string(3) "bar"
Engr Syed Rowshan Ali
quelle
6

Falls Sie get_called_class () nicht verwenden möchten, können Sie andere Tricks der späten statischen Bindung (PHP 5.3+) verwenden. Der Nachteil in diesem Fall ist jedoch, dass in jedem Modell die Methode getClass () vorhanden sein muss. Welches ist keine große Sache 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)

quelle
2

Möglicherweise versuchen Sie, ein Singleton-Muster als Factory-Muster zu verwenden. Ich würde empfehlen, Ihre Entwurfsentscheidungen zu bewerten. Wenn ein Singleton wirklich geeignet ist, würde ich auch empfehlen, nur statische Methoden zu verwenden, bei denen keine Vererbung erwünscht ist.

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

quelle
.. Nein. Du hast nicht verstanden, was ich versucht habe zu bo, aber das liegt daran, dass ich es nicht gut erklärt habe :). Eigentlich versuche ich nur, eine statische Methode (in BaseModel implementiert) bereitzustellen, um () eine Instanz eines bestimmten Modells abzurufen (möglicherweise Benutzer, Rolle oder was auch immer). Ich werde die Frage aktualisieren ..
Saalaa
Ok, aktualisiert. Natürlich verfügt die BaseModel-Klasse über viel mehr Methoden, einschließlich einiger Methoden, um zu verfolgen, was im Objekt geändert wurde, und um nur das zu aktualisieren, was geändert wurde, usw. Aber trotzdem danke :).
Saalaa
2

Vielleicht beantwortet dies die Frage nicht wirklich, aber Sie könnten einen Parameter hinzufügen, um () den Typ anzugeben. dann kannst du anrufen

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

anstatt User :: get () aufzurufen. Sie können in BaseModel :: get () Logik hinzufügen, um zu überprüfen, ob eine get-Methode in der Unterklasse vorhanden ist, und diese dann aufrufen, wenn Sie möchten, dass die Unterklasse sie überschreibt.

Ansonsten kann ich mir nur vorstellen, dass ich jeder Unterklasse etwas hinzufüge, was dumm ist:

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

Dies würde wahrscheinlich brechen , wenn Sie dann auf Benutzer subclassed, aber ich nehme an, Sie ersetzen könnte get_parent_class(__CLASS__)mit 'BaseModel'in diesem Fall

Tom Haigh
quelle
Eigentlich ja. Diese PHP-Einschränkung hatte den großen Vorteil, dass ich gezwungen war, mein Design zu überprüfen. Es wird viel mehr wie $ connection-> get ('User', 24) aussehen; da es mehrere Verbindungen gleichzeitig zulässt und auch semantisch korrekter ist. Aber du hast einen Punkt :).
Saalaa
array_shift scheint langsam zu sein, da jeder Schlüssel des Arrays geändert werden muss ... Stattdessen nur $ args [0];
Xesau
0

Das Problem ist keine Sprachbeschränkung, sondern Ihr Design. Es ist egal, dass Sie Unterricht haben; Die statischen Methoden glauben eher an ein prozedurales als an ein objektorientiertes Design. Sie verwenden auch den globalen Status in irgendeiner Form. (Woher weiß man get_row_from_db_as_array(), wo man die Datenbank findet?) Und schließlich sieht es sehr schwierig aus, einen Unit-Test durchzuführen.

Versuchen Sie etwas in diese Richtung.

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

Zwei Variationen von Prestons Antwort:

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

Hinweis: Das Starten eines Eigenschaftsnamens mit _ ist eine Konvention, die im Grunde bedeutet: "Ich weiß, dass ich dies öffentlich gemacht habe, aber es hätte wirklich geschützt werden müssen, aber ich konnte das nicht tun und mein Ziel erreichen."

lo_fye
quelle