获取父类中子类的名称(静态上下文)


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

显然,这不是我所期望的行为(尽管实际行为也很有意义)。因此,我的问题是,你们是否知道在父类中获得子类名称的意思。

Answers:


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();
贾斯汀C

这是慌乱!
bdalina

子类可能没有构造函数
shmnff

19

我知道这个问题确实很老,但是对于那些寻求比在每个包含类名称的类中定义属性更实用的解决方案的人:

您可以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_drawn_class(),则可以使用后期静态绑定(PHP 5.3+)的其他技巧。但是在这种情况下,缺点是每个模型都需要有getClass()方法。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

看来您可能正在尝试将单例模式用作工厂模式。我建议评估您的设计决策。如果一个单身真的是合适的,我也只是建议使用,其中继承静态方法希望的。

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或其他)。我将更新问题
。.– saalaa

好的,已更新。当然,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('User',24);。因为它允许同时进行多个连接,并且在语义上也更正确。但是你有一点:)。
saalaa

array_shift似乎很慢,因为它将不得不更改数组的每个键...只需$ args [0]即可;
Xesau 2015年

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

注意:以_开头的属性名称实际上是一个约定,即“我知道我已公开此属性,但它确实应该受到保护,但我无法做到这一点并实现我的目标”

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.