Отримання імені дочірнього класу в батьківському класі (статичний контекст)


93

Я будую бібліотеку ORM з урахуванням повторного використання та простоти; все йде нормально, за винятком того, що я застряг через дурне обмеження спадщини. Будь ласка, розгляньте код нижче:

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

Очевидно, це не та поведінка, яку я очікував (хоча фактична поведінка також має сенс) .. Тож моє питання полягає в тому, чи знаєте ви, хлопці, про засіб отримати в батьківському класі ім’я дочірнього класу.

Відповіді:


98

коротко. це неможливо. у php4 ви можете реалізувати жахливий хак (вивчіть debug_backtrace()), але цей метод не працює в PHP5. посилання:

редагувати : приклад пізнього статичного прив'язки в PHP 5.3 (згаданий у коментарях). зверніть увагу, що в його поточній реалізації ( 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"

Так, я щойно прочитав про це debug_backtrace(). Можливим рішенням буде використання пізнього статичного прив'язки з PHP 5.3, але в моєму випадку це неможливо. Дякую.
saalaa

187

Вам не потрібно чекати PHP 5.3, якщо ви можете уявити спосіб зробити це поза статичним контекстом. У php 5.2.9 у нестатичному методі батьківського класу ви можете зробити:

get_class($this);

і він поверне ім'я дочірнього класу у вигляді рядка.

тобто

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

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

$x = new Child();

це виведе:

Parent class: Parent
Child class: Child

солодке так?


11
Якщо ви використовуєте абстрактні класи, це потрібно знати.
Леві Моррісон,

1
Я думаю, що так $x = new Parent();повинно бути $x = new Child();.
Justin C

це це панцит!
bdalina

Клас дитини може бути без конструктора
shmnff

20

Я знаю, що це питання справді давнє, але для тих, хто шукає більш практичного рішення, ніж визначення властивості в кожному класі, що містить назву класу:

Для цього можна використовувати staticключове слово.

Як пояснюється у цій примітці до співпраці в документації на php

staticключове слово може бути використане в супер клас для доступу до класу південь від який називається методом.

Приклад:

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
Дякую! Боровся з ReflectionClass, але дзвінок static()- це шлях!
godbout

16

Я знаю його старий пост, але хочу поділитися знайденим рішенням.

Перевірено на PHP 7+ Використовуйте посилання на функціюget_class()

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

class foo extends bar {
}

new foo;
?>

Наведений вище приклад виведе:

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

6

Якщо ви не хочете використовувати get_called_class (), ви можете скористатися іншими прийомами пізнього статичного прив'язки (PHP 5.3+). Але мінусом у цьому випадку потрібно мати метод getClass () у кожній моделі. Що не є великою проблемою для ІМО.

<?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

Здається, ви, можливо, намагаєтесь використовувати однофакторний шаблон як заводський. Я б порадив оцінити ваші дизайнерські рішення. Якщо синглтон дійсно підходить, я б також рекомендував використовувати лише статичні методи, коли успадкування не є бажаним.

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

.. немає. Ви не розуміли, про що я намагаюся здивуватись, але це тому, що я погано це пояснив :). Насправді, я просто намагаюся надати статичний метод (реалізований в BaseModel), щоб отримати () екземпляр даної моделі (може це бути User, Role чи будь-яка інша). Я
оновлю

Гаразд, оновлено. Звичайно, клас BaseModel має набагато більше методів, включаючи деякі, щоб відстежувати, що змінилося в об'єкті, та ОНОВЛЮВАТИ лише те, що сталося, тощо ... Але все одно дякую :).
saalaa

2

Можливо, це насправді не відповідає на питання, але ви можете додати параметр, щоб get () вказав тип. тоді ви можете зателефонувати

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

замість виклику User :: get (). Ви можете додати логіку в BaseModel :: get (), щоб перевірити, чи існує метод get у підкласі, а потім викликати це, якщо ви хочете дозволити підкласу його замінити.

Інакше єдиний спосіб, про який я можу думати, - це додавання матеріалів до кожного підкласу, що є дурним:

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

Це, мабуть, зіпсується, якщо ви потім зробите підклас User, але я гадаю, ви могли б замінити get_parent_class(__CLASS__)на 'BaseModel'в такому випадку


Власне, так. Це обмеження PHP мало велику перевагу, змусивши мене переглянути свій дизайн. Це буде набагато більше виглядати як $ connection-> get ('Користувач', 24); оскільки це дозволяє одночасно здійснювати кілька з’єднань, а також є семантично більш правильним. Але ви зрозуміли :).
saalaa

array_shift здається повільним, тому що йому доведеться змінити кожен ключ масиву ... Натомість просто $ args [0];
Xesau

0

Проблема не в мовному обмеженні, це у вашому дизайні. Не забувайте, що у вас є заняття; статичні методи вважають процедурним, а не об'єктно-орієнтованим дизайном. Ви також використовуєте глобальний стан в якійсь формі. (Звідки get_row_from_db_as_array()знати, де знайти базу даних?) І нарешті, модульний тест виглядає дуже важко.

Спробуйте щось у цьому напрямку.

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

0

Дві варіації відповіді Престона:

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

Примітка. Починати назву властивості з _ - це домовленість, яка в основному означає "я знаю, що зробив це загальнодоступним, але це дійсно мало б бути захищеним, але я не зміг цього зробити і досягти своєї мети"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.