2008-11-12 26 views
67

Estoy creando una biblioteca ORM pensando en la reutilización y la simplicidad; todo va bien, excepto que me quedé atrapado por una estúpida limitación de herencia. Por favor, considere el siguiente código:Obteniendo el nombre de una clase hija en la clase padre (contexto estático)

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 yo esperaba (aunque el comportamiento real también tiene sentido) .. Así que mi pregunta es si ustedes saben de un medio para obtener, en el clase de padres, el nombre de la clase de niños.

Respuesta

72

en resumen. esto no es posible. en php4 puedes implementar un truco terrible (examina el debug_backtrace()) pero ese método no funciona en PHP5. referencias:

editar: un ejemplo de tarde de unión en PHP 5.3 estática (mencionado en 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" 
+0

Sí, acabo de leer sobre 'debug_backtrace()' .. Una posible solución sería usar * enlace estático tardío * de PHP 5.3, pero esa no es una posibilidad en mi caso. Gracias. – saalaa

2

Parece que está intentando utilizar 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 la herencia es no deseada.

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 
+0

.. no. No entendiste lo que estaba tratando de hacer, pero eso es porque no lo explicaba bien :). En realidad, solo intento proporcionar un método estático (implementado en BaseModel) para obtener() una instancia de un Modelo dado (ya sea Usuario, Rol o lo que sea). Actualizaré la pregunta ... – saalaa

+0

Ok, actualizado. Por supuesto, la clase BaseModel tiene muchos más métodos, incluidos algunos para realizar un seguimiento de lo que se ha cambiado en el objeto y ACTUALIZAR solo lo que ha marcado, etc ... Pero gracias de todos modos :). – saalaa

2

Tal vez esto no es realmente responder a la pregunta, pero se puede añadir un parámetro para obtener() especificando el tipo. luego puede llamar al

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.

lo contrario, la única manera que se me ocurre, obviamente, es mediante la adición de materia para cada subclase, que es estúpida:

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 a continuación, una subclase del usuario, pero supongo que podría reemplazar get_parent_class(__CLASS__) con 'BaseModel' en ese caso

+0

En realidad, sí. Esta limitación de PHP tenía la gran ventaja de obligarme a revisar mi diseño. Se parecerá mucho más a $ connection-> get ('User', 24); ya que permite múltiples conexiones al mismo tiempo y también es semánticamente más correcto. Pero tienes un punto :). – saalaa

+0

array_shift parece ser lento, porque tendrá que cambiar todas las claves de la matriz ... Simplemente $ args [0] en su lugar; – Xesau

0

El problema no es una limitación de idioma, es su 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.

Pruebe algo en esta línea.

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

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, que puede hacer:

get_class($this); 

y ha de devolver el nombre de la clase hija como una cadena.

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

esta es la salida:

Parent class: Parent 
Child class: Child 

dulce eh?

+10

Si está utilizando clases abstractas, este es un deber-saber. –

+0

precioso! ¡buen truco! –

+0

¡Esto merece más votos favorables! – Mattisdada

5

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

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: a partir de un nombre de propiedad con _ es una convención que significa básicamente " Sé que lo hice público, pero realmente debería haber sido protegido, pero no podría hacer eso y lograr mi objetivo "

Cuestiones relacionadas