php - sencillo - herencia simple en java
Obteniendo el nombre de una clase hija en la clase padre(contexto estático) (8)
Estoy construyendo una biblioteca ORM con la reutilización y la simplicidad en mente; todo va bien, excepto que me quedé atrapado por una estúpida limitación de herencia. Por favor considere el siguiente código:
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();
Obviamente, este no es el comportamiento que esperaba (aunque el comportamiento real también tiene sentido). Así que mi pregunta es si ustedes saben de un medio para obtener, en la clase de padres, el nombre de clase infantil.
Dos variaciones en la respuesta de Preston:
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);
}
}
Nota: comenzar un nombre de propiedad con _ es una convención que básicamente significa "sé que lo hice público, pero realmente debería haber sido protegido, pero no pude hacer eso y lograr mi objetivo"
El problema no es una limitación de idioma, es tu diseño. No importa que tengas clases; los métodos estáticos contradicen un diseño de procedimiento en lugar de orientado a objetos. También está usando estado global de alguna forma. (¿Cómo sabe get_row_from_db_as_array()
dónde encontrar la base de datos?) Y finalmente parece muy difícil realizar una prueba unitaria.
Intenta algo en esta línea.
$db = new DatabaseConnection(''dsn to database...'');
$userTable = new UserTable($db);
$user = $userTable->get(24);
En caso de que no desee utilizar get_called_class (), puede utilizar otros trucos de enlace estático tardío (PHP 5.3+). Pero la desventaja en este caso es que necesitas tener el método getClass () en cada modelo. Lo cual no es un gran problema 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)
No necesita esperar PHP 5.3 si puede concebir una forma de hacerlo fuera de un contexto estático. En php 5.2.9, en un método no estático de la clase padre, puede hacer:
get_class($this);
y devolverá el nombre de la clase secundaria como una cadena.
es decir
class Parent() {
function __construct() {
echo ''Parent class: '' . get_class() . "/n" . ''Child class: '' . get_class($this);
}
}
class Child() {
function __construct() {
parent::construct();
}
}
$x = new Child();
esto dará como resultado:
Parent class: Parent
Child class: Child
dulce ¿eh?
Parece que está intentando usar un patrón singleton como patrón de fábrica. Yo recomendaría evaluar sus decisiones de diseño. Si un singleton es realmente apropiado, también recomendaría usar solo métodos estáticos donde no se desea la herencia.
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
Sé que esta pregunta es muy antigua, pero para aquellos que buscan una solución más práctica que definir una propiedad en cada clase que contenga el nombre de clase:
Puede usar la palabra clave static
para esto.
Como se explica en esta nota del colaborador en la documentación de php
la palabra clave
static
se puede usar dentro de una superclase para acceder a la subclase desde la que se llama un método.
Ejemplo:
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
Tal vez esto no responda la pregunta, pero podrías agregar un parámetro para que get () especifique el tipo. entonces puedes llamar
BaseModel::get(''User'', 1);
en lugar de llamar a User :: get (). Puede agregar lógica en BaseModelo :: get () para verificar si existe un método get en la subclase y luego invocarlo si desea permitir que la subclase lo anule.
De lo contrario, la única forma en que puedo pensar obviamente es agregando cosas a cada subclase, lo cual es estúpido:
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);
Esto probablemente se rompería si usted subclasificó al usuario, pero supongo que podría reemplazar get_parent_class(__CLASS__)
con ''BaseModel''
en ese caso
en breve. esto no es posible. en php4 puedes implementar un hack terrible (examina el debug_backtrace()
) pero ese método no funciona en PHP5. referencias:
edit : un ejemplo de enlace estático tardío en PHP 5.3 (mencionado en los comentarios). tenga en cuenta que hay problemas potenciales en su implementación actual ( 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"