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

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

Очевидно, это не то поведение, которого я ожидал (хотя фактическое поведение также имеет смысл). Итак, мой вопрос, знаете ли вы, ребята, способ получить в родительском классе имя дочернего класса.

Saalaa
источник

Ответы:

98

короче говоря. это невозможно. в php4 вы можете реализовать ужасный взлом (изучите debug_backtrace()), но этот метод не работает в PHP5. Ссылки:

edit : пример позднего статического связывания в 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

милая да?

jrmgx
источник
11
Если вы используете абстрактные классы, это необходимо знать.
Леви Моррисон
1
Думаю $x = new Parent();должно быть $x = new Child();.
Justin C
это панцит!
bdalina
Дочерний класс может быть без конструктора
shmnff 08
21

Я знаю, что этот вопрос действительно старый, но для тех, кто ищет более практичное решение, чем определение свойства в каждом классе, содержащем имя класса:

Вы можете использовать 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
Joas
источник
1
Спасибо! Боролся с ReflectionClass, но вызов static()- это путь!
godbout
17

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

Протестировано с 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);

Это, вероятно , сломаться , если вы тогда подклассы пользователя, но я полагаю , вы могли бы заменить get_parent_class(__CLASS__)с 'BaseModel'в том случае ,

Том Хей
источник
На самом деле да. Это ограничение PHP имело огромное преимущество, заставив меня пересмотреть свой дизайн. Это будет больше похоже на $ connection-> get ('User', 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);
    }
}

Примечание: начало имени свойства с _ - это соглашение, которое в основном означает: «Я знаю, что сделал это общедоступным, но оно действительно должно было быть защищено, но я не смог этого сделать и достичь своей цели»

lo_fye
источник