Obtener el nombre de una clase secundaria en la clase principal (contexto estático)

93

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. Tenga en cuenta 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 conocen un medio para obtener, en la clase principal, el nombre de la clase secundaria.

saalaa
fuente

Respuestas:

98

en breve. esto no es posible. en php4 podría implementar un truco terrible (examine el debug_backtrace()) pero ese método no funciona en PHP5. referencias:

editar : un ejemplo de enlace estático tardío en PHP 5.3 (mencionado en los comentarios). tenga en cuenta que existen 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"
Owen
fuente
Sí, acabo de leer sobre debug_backtrace()... Una posible solución sería utilizar un enlace estático tardío de PHP 5.3, pero esa no es una posibilidad en mi caso. Gracias.
saalaa
187

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 principal, 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?

jrmgx
fuente
11
Si está utilizando clases abstractas, esto es imprescindible.
Levi Morrison
1
Creo que $x = new Parent();debería serlo $x = new Child();.
Justin C
esto es pancit!
bdalina
La clase
secundaria
20

Sé que esta pregunta es realmente antigua, pero para aquellos que buscan una solución más práctica que definir una propiedad en cada clase que contiene el nombre de la clase:

Puede utilizar la staticpalabra clave para esto.

Como se explica en esta nota de colaborador en la documentación de php

la staticpalabra clave se puede utilizar dentro de una superclase para acceder a la subclase desde la que se llama a 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
Joas
fuente
1
¡Gracias! Tenía problemas con ReflectionClass, ¡pero llamar static()es el camino a seguir!
Godbout
16

Sé que es una publicación anterior, pero quiero compartir la solución que encontré.

Probado con PHP 7+ Use el enlace de funciónget_class()

<?php
abstract class bar {
    public function __construct()
    {
        var_dump(get_class($this));
        var_dump(get_class());
    }
}

class foo extends bar {
}

new foo;
?>

El ejemplo anterior dará como resultado:

string(3) "foo"
string(3) "bar"
Ing. Syed Rowshan Ali
fuente
6

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 debe tener el método getClass () en cada modelo. Lo cual no es gran cosa en mi opinión.

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

fuente
2

Parece que está intentando utilizar un patrón singleton como patrón de fábrica. Recomendaría evaluar sus decisiones de diseño. Si un singleton realmente es apropiado, también recomendaría usar solo métodos estáticos donde no se desee 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

fuente
.. No. No entendiste lo que estaba tratando de hacer, pero eso es porque no lo expliqué bien :). En realidad, solo estoy tratando de proporcionar un método estático (implementado en BaseModel) para obtener () una instancia de un Modelo dado (puede ser Usuario, Rol o lo que sea). Actualizaré la pregunta ..
saalaa
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 cambiado, etc. Pero gracias de todos modos :).
saalaa
2

Tal vez esto en realidad no esté respondiendo a la pregunta, pero podría agregar un parámetro para get () especificando el tipo. entonces puedes llamar

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

en lugar de llamar a User :: get (). Puede agregar lógica en BaseModel :: get () para verificar si existe un método get en la subclase y luego llamarlo 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 luego subclasificara Usuario, pero supongo que podría reemplazarlo get_parent_class(__CLASS__)con 'BaseModel'en ese caso

Tom Haigh
fuente
Actualmente, si. 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 además es semánticamente más correcto. Pero tienes un punto :).
saalaa
array_shift parece lento, porque tendrá que cambiar todas las claves de la matriz ... Solo $ args [0] en su lugar;
Xesau
0

El problema no es una limitación de idioma, es tu diseño. No importa que tengas clases; los métodos estáticos desmienten un diseño procedimental más que orientado a objetos. También estás usando el estado global de alguna forma. (¿Cómo get_row_from_db_as_array()sabe dónde encontrar la base de datos?) Y finalmente parece muy difícil realizar pruebas unitarias.

Intente algo en este sentido.

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

Dos variaciones de 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 el nombre de una propiedad con _ es una convención que básicamente significa "sé que hice esto público, pero realmente debería haber estado protegido, pero no pude hacer eso y lograr mi objetivo".

lo_fye
fuente