Obtendo o nome de uma classe filha na classe pai (contexto estático)

93

Estou construindo uma biblioteca ORM com reutilização e simplicidade em mente; tudo vai bem, exceto que fiquei preso por uma limitação de herança estúpida. Considere o código abaixo:

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, esse não é o comportamento que eu esperava (embora o comportamento real também faça sentido). Minha pergunta é se vocês sabem de um meio de obter, na classe pai, o nome da classe filha.

Saalaa
fonte

Respostas:

98

em resumo. isso não é possível. no php4 você poderia implementar um hack terrível (examine o debug_backtrace()), mas esse método não funciona no PHP5. referências:

editar : um exemplo de vinculação estática tardia no PHP 5.3 (mencionado nos comentários). observe que há problemas potenciais em sua implementação atual ( 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
fonte
Sim, acabei de ler sobre debug_backtrace().. Uma possível solução seria usar vinculação estática tardia do PHP 5.3, mas isso não é uma possibilidade no meu caso. Obrigado.
saalaa
187

Você não precisa esperar pelo PHP 5.3 se for capaz de conceber uma maneira de fazer isso fora de um contexto estático. No php 5.2.9, em um método não estático da classe pai, você pode fazer:

get_class($this);

e ele retornará o nome da classe filha como uma string.

ie

class Parent() {
    function __construct() {
        echo 'Parent class: ' . get_class() . "\n" . 'Child class: ' . get_class($this);
    }
}

class Child() {
    function __construct() {
        parent::construct();
    }
}

$x = new Child();

isso resultará em:

Parent class: Parent
Child class: Child

doce hein?

jrmgx
fonte
11
Se você estiver usando classes abstratas, isso é obrigatório.
Levi Morrison
1
Eu acho que $x = new Parent();deveria ser $x = new Child();.
Justin C de
é isso pancit!
bdalina
A classe
filha
19

Eu sei que essa questão é muito antiga, mas para quem procura uma solução mais prática do que definir uma propriedade em cada classe contendo o nome da classe:

Você pode usar a staticpalavra - chave para isso.

Conforme explicado nesta nota de contribuição na documentação php

a staticpalavra - chave pode ser usada dentro de uma superclasse para acessar a subclasse a partir da qual um método é chamado.

Exemplo:

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
fonte
1
Obrigado! Estava tendo problemas com o ReflectionClass, mas ligar static()é o caminho a percorrer!
quase
16

Eu sei que é um post antigo, mas quero compartilhar a solução que encontrei.

Testado com PHP 7+ Use o link de funçãoget_class()

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

class foo extends bar {
}

new foo;
?>

O exemplo acima resultará em:

string(3) "foo"
string(3) "bar"
Engr Syed Rowshan Ali
fonte
6

Caso você não queira usar get_called_class (), você pode usar outros truques de vinculação estática tardia (PHP 5.3+). Mas a desvantagem, neste caso, é o método getClass () em cada modelo. O que não é grande coisa, 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)

fonte
2

Parece que você está tentando usar um padrão singleton como padrão de fábrica. Eu recomendaria avaliar suas decisões de design. Se um singleton realmente for apropriado, eu também recomendaria usar apenas métodos estáticos onde a herança não é desejada.

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

fonte
.. não. Você não entendeu o que eu estava tentando fazer, mas é porque eu não expliquei bem :). Na verdade, estou apenas tentando fornecer um método estático (implementado no BaseModel) para get () uma instância de um determinado Model (pode ser User, Role ou qualquer outro). Vou atualizar a pergunta ..
saalaa
Ok, atualizado. Claro que a classe BaseModel tem muito mais métodos, incluindo alguns para controlar o que foi alterado no objeto e ATUALIZAR apenas o que foi alterado, etc ... Mas obrigado mesmo assim :).
saalaa
2

Talvez isso não esteja realmente respondendo à pergunta, mas você pode adicionar um parâmetro para get () especificando o tipo. então você pode ligar

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

em vez de chamar User :: get (). Você poderia adicionar lógica em BaseModel :: get () para verificar se um método get existe na subclasse e então chamá-lo se quiser permitir que a subclasse o substitua.

Caso contrário, a única maneira que consigo pensar é, obviamente, adicionando coisas a cada subclasse, o que é 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);

Isso provavelmente seria interrompido se você subclasse User, mas suponho que você possa substituir get_parent_class(__CLASS__)por 'BaseModel'nesse caso

Tom Haigh
fonte
Na verdade sim. Essa limitação do PHP teve a grande vantagem de me forçar a revisar meu design. Ele se parecerá muito mais com $ connection-> get ('User', 24); uma vez que permite várias conexões ao mesmo tempo e também é semanticamente mais correto. Mas você tem razão :).
saalaa
array_shift parece lento, porque terá que alterar todas as chaves do array ... Apenas $ args [0];
Xesau
0

O problema não é uma limitação de idioma, é seu design. Não importa se você tem aulas; os métodos estáticos desmentem um projeto procedural em vez de orientado a objetos. Você também está usando o estado global de alguma forma. (Como get_row_from_db_as_array()saber onde encontrar o banco de dados?) E, finalmente, parece muito difícil fazer um teste de unidade.

Experimente algo nesse sentido.

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

Duas variações da resposta 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);
    }
}

Observação: iniciar um nome de propriedade com _ é uma convenção que basicamente significa "eu sei que tornei isso público, mas realmente deveria ter sido protegido, mas eu não poderia fazer isso e atingir meu objetivo"

lo_fye
fonte