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