Получение имени дочернего класса в родительском классе (статический контекст)
Я создаю библиотеку 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();
Очевидно, что это не то поведение, которое я ожидал (хотя реальное поведение также имеет смысл). Поэтому мой вопрос: знаете ли вы, что вы знаете, что в родительском классе вы получите имя дочернего класса.
Ответы
Ответ 1
короче. это невозможно. в 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"
Ответ 2
Вам не нужно ждать 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
сладкий ха?
Ответ 3
Если вы не хотите использовать get_called_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)
Ответ 4
Кажется, вы можете использовать шаблон singleton в качестве шаблона factory. Я бы рекомендовал оценить ваши дизайнерские решения. Если одноэлемент действительно подходит, я бы также рекомендовал использовать только статические методы, где наследование нежелательно.
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
Ответ 5
Возможно, это не отвечает на этот вопрос, но вы можете добавить параметр, чтобы 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'
в этом случае
Ответ 6
Проблема не в языковых ограничениях, это ваш дизайн. Неважно, что у вас есть занятия; статические методы опровергают процедурный, а не объектно-ориентированный дизайн. Вы также используете глобальное состояние в той или иной форме. (Как get_row_from_db_as_array()
знает, где найти базу данных?) И, наконец, это очень сложно для unit test.
Попробуйте что-нибудь в этом направлении.
$db = new DatabaseConnection('dsn to database...');
$userTable = new UserTable($db);
$user = $userTable->get(24);
Ответ 7
Два варианта ответа Престона:
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);
}
}
Примечание: запуск имени свойства с помощью _ - это соглашение, которое в основном означает "я знаю, что я сделал это публично, но он действительно должен был быть защищен, но я не мог этого сделать и достичь своей цели"