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

我正在构建一个考虑到重用和简单性的 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();

Obviously, this is not the behavior I was expecting (although the actual behavior also makes sense).. So my question is if you guys know of a mean to get, in the parent class, the name of child class.

99303 次浏览

in short. this is not possible. in php4 you could implement a terrible hack (examine the debug_backtrace()) but that method does not work in PHP5. references:

edit: an example of late static binding in PHP 5.3 (mentioned in comments). note there are potential problems in it's current implementation (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"

It appears you might be trying to use a singleton pattern as a factory pattern. I would recommend evaluating your design decisions. If a singleton really is appropriate, I would also recommend only using static methods where inheritance is not desired.

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

Maybe this isn't actually answering the question, but you could add a parameter to get() specifing the type. then you can call

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

instead of calling User::get(). You could add logic in BaseModel::get() to check whether a get method exists in the subclass and then call that if you want to allow the subclass to override it.

Otherwise the only way I can think of obviously is by adding stuff to each subclass, which is stupid:

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

This would probably break if you then subclassed User, but I suppose you could replace get_parent_class(__CLASS__) with 'BaseModel' in that case

The problem is not a language limitation, it is your design. Never mind that you have classes; the static methods belie a procedural rather than object-oriented design. You're also using global state in some form. (How does get_row_from_db_as_array() know where to find the database?) And finally it looks very difficult to unit test.

Try something along these lines.

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

You don't need to wait for PHP 5.3 if you're able to conceive of a way to do this outside of a static context. In php 5.2.9, in a non-static method of the parent class, you can do:

get_class($this);

and it will return the name of the child class as a string.

i.e.

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


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


$x = new Child();

this will output:

Parent class: Parent
Child class: Child

sweet huh?

In case you don't want to use get_called_class() you can use other tricks of late static binding (PHP 5.3+). But the downside in this case you need to have getClass() method in every model. Which is not a big deal 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)

Two variations on Preston's answer:

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

Note: starting a property name with _ is a convention that basically means "i know i made this public, but it really should have been protected, but i couldn't do that and achieve my goal"

I know this question is really old, but for those looking for a more practical solution than defining a property in every class containing the class name:

You can use the static keyword for this.

As explained in this contributor note in the php documentation

the static keyword can be used inside a super class to access the sub class from which a method is called.

Example:

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

I know its old post but want to share the solution I have found.

Tested with PHP 7+ Use the function 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;
?>

The above example will output:

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

you can use class_basename($this) this will print classname of the file