Pobieranie nazwy klasy podrzędnej w klasie nadrzędnej (kontekst statyczny)

93

Buduję bibliotekę ORM z myślą o ponownym wykorzystaniu i prostocie; wszystko idzie dobrze, z wyjątkiem tego, że utknąłem w głupim ograniczeniu dziedziczenia. Proszę wziąć pod uwagę poniższy kod:

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

Oczywiście nie jest to zachowanie, którego się spodziewałem (chociaż rzeczywiste zachowanie również ma sens). Więc moje pytanie brzmi: czy znacie sposób na uzyskanie w klasie rodzicielskiej nazwy klasy dziecięcej.

saalaa
źródło

Odpowiedzi:

98

w skrócie. To jest niemożliwe. w php4 możesz zaimplementować okropny hack (sprawdź debug_backtrace()), ale ta metoda nie działa w PHP5. Bibliografia:

edycja : przykład późnego statycznego wiązania w PHP 5.3 (wspomniany w komentarzach). zauważ, że istnieją potencjalne problemy w jego aktualnej implementacji ( 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"
piekarnik
źródło
Tak, właśnie przeczytałem o debug_backtrace()… Możliwym rozwiązaniem byłoby użycie późnego statycznego wiązania z PHP 5.3, ale w moim przypadku nie jest to możliwe. Dziękuję Ci.
saalaa
187

Nie musisz czekać na PHP 5.3, jeśli jesteś w stanie wymyślić sposób na zrobienie tego poza statycznym kontekstem. W php 5.2.9, w niestatycznej metodzie klasy nadrzędnej, możesz:

get_class($this);

i zwróci nazwę klasy potomnej jako ciąg.

to znaczy

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

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

$x = new Child();

to da:

Parent class: Parent
Child class: Child

słodki co?

jrmgx
źródło
11
Jeśli używasz klas abstrakcyjnych, musisz to wiedzieć.
Levi Morrison,
1
Myślę, że $x = new Parent();powinno $x = new Child();.
Justin C
to jest to naleśnik!
bdalina
Klasa dziecięca może być bez konstruktora
shmnff
21

Wiem, że to pytanie jest naprawdę stare, ale dla tych, którzy szukają bardziej praktycznego rozwiązania niż zdefiniowanie właściwości w każdej klasie zawierającej nazwę klasy:

Możesz użyć staticdo tego słowa kluczowego.

Jak wyjaśniono w tej uwadze współautora w dokumentacji php

statickluczowe mogą być stosowane wewnątrz super klasy, aby przejść do klasy sub, z którego wywoływana jest metoda.

Przykład:

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
źródło
1
Dziękuję Ci! Walczył z ReflectionClass, ale dzwonienie static()jest najlepszym rozwiązaniem!
o
17

Znam jego stary post, ale chcę się podzielić znalezionym rozwiązaniem.

Testowane w PHP 7+ Użyj łącza funkcjiget_class()

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

class foo extends bar {
}

new foo;
?>

Powyższy przykład wyświetli:

string(3) "foo"
string(3) "bar"
Engr Syed Rowshan Ali
źródło
6

Jeśli nie chcesz używać get_called_class (), możesz użyć innych sztuczek późnego statycznego wiązania (PHP 5.3+). Ale minusem w tym przypadku jest konieczność posiadania metody getClass () w każdym modelu. Co nie jest wielkim problemem 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)

źródło
2

Wygląda na to, że próbujesz użyć wzorca pojedynczego jako wzorca fabrycznego. Zalecałbym ocenę twoich decyzji projektowych. Jeśli singleton naprawdę jest odpowiedni, zalecałbym również używanie tylko metod statycznych, w których dziedziczenie nie jest pożądane.

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

źródło
.. nie. Nie rozumiałeś, o co mi chodzi, ale to dlatego, że nie wyjaśniłem tego dobrze :). Właściwie po prostu próbuję zapewnić metodę statyczną (zaimplementowaną w BaseModel), aby uzyskać () wystąpienie danego modelu (może to być użytkownik, rola lub cokolwiek). Zaktualizuję pytanie ...
saalaa
OK, zaktualizowane. Oczywiście klasa BaseModel ma znacznie więcej metod, w tym niektóre do śledzenia zmian w obiekcie i UPDATE tylko tego, co zostało zmienione, itp ... Ale mimo wszystko dziękuję :).
saalaa
2

Może to tak naprawdę nie odpowiada na pytanie, ale możesz dodać parametr do get () określający typ. wtedy możesz zadzwonić

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

zamiast wywoływać User :: get (). Możesz dodać logikę w BaseModel :: get (), aby sprawdzić, czy metoda get istnieje w podklasie, a następnie wywołać ją, jeśli chcesz zezwolić podklasie na zastąpienie jej.

W przeciwnym razie jedynym sposobem, o którym mogę pomyśleć, jest oczywiście dodanie rzeczy do każdej podklasy, co jest głupie:

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

To prawdopodobnie by się zepsuło, gdybyś następnie utworzył podklasę User, ale przypuszczam, że w takim przypadku możesz zastąpić get_parent_class(__CLASS__)go'BaseModel'

Tom Haigh
źródło
Aktualnie tak. To ograniczenie PHP miało tę wielką zaletę, że zmusiło mnie do przejrzenia mojego projektu. Bardziej będzie wyglądać jak $ connection-> get ('User', 24); ponieważ pozwala na wiele połączeń w tym samym czasie i jest również semantycznie bardziej poprawny. Ale masz rację :).
saalaa
array_shift wydaje się być powolny, ponieważ będzie musiał zmienić każdy klucz tablicy ... Zamiast tego po prostu $ args [0];
Xesau
0

Problem nie jest ograniczeniem językowym, to twój projekt. Nieważne, że masz zajęcia; metody statyczne zaprzeczają projektowi proceduralnemu, a nie obiektowemu. Używasz również stanu globalnego w jakiejś formie. (Jakget_row_from_db_as_array() wie, gdzie znaleźć bazę danych?) I w końcu test jednostkowy wygląda na bardzo trudny.

Spróbuj czegoś podobnego.

$db = new DatabaseConnection('dsn to database...');
$userTable = new UserTable($db);
$user = $userTable->get(24);
Preston
źródło
0

Dwie odmiany odpowiedzi Prestona:

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

Uwaga: rozpoczęcie nazwy nieruchomości od _ to konwencja, która zasadniczo oznacza „Wiem, że to upubliczniłem, ale naprawdę powinno być chronione, ale nie mogłem tego zrobić i osiągnąć celu”

lo_fye
źródło