Obtenir le nom d'une classe enfant dans la classe parent (contexte statique)

93

Je construis une bibliothèque ORM avec la réutilisation et la simplicité à l'esprit; tout va bien sauf que je suis coincé par une stupide limitation d'héritage. Veuillez considérer le code ci-dessous:

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

Évidemment, ce n'est pas le comportement auquel je m'attendais (bien que le comportement réel ait également du sens). Ma question est donc de savoir si vous connaissez un moyen d'obtenir, dans la classe parent, le nom de la classe enfant.

saalaa
la source

Réponses:

98

en bref. ce n'est pas possible. en php4, vous pourriez implémenter un terrible hack (examinez le debug_backtrace()) mais cette méthode ne fonctionne pas en PHP5. références:

edit : un exemple de liaison statique tardive en PHP 5.3 (mentionné dans les commentaires). notez qu'il existe des problèmes potentiels dans son implémentation actuelle ( 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
la source
Oui, je viens de lire à propos de debug_backtrace().. Une solution possible serait d'utiliser la liaison statique tardive de PHP 5.3 mais ce n'est pas une possibilité dans mon cas. Je vous remercie.
saalaa
187

Vous n'avez pas besoin d'attendre PHP 5.3 si vous êtes capable de concevoir un moyen de le faire en dehors d'un contexte statique. En php 5.2.9, dans une méthode non statique de la classe parente, vous pouvez faire:

get_class($this);

et il renverra le nom de la classe enfant sous forme de chaîne.

c'est à dire

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

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

$x = new Child();

cela produira:

Parent class: Parent
Child class: Child

doux hein?

jrmgx
la source
11
Si vous utilisez des classes abstraites, c'est un must.
Levi Morrison
1
Je pense que ça $x = new Parent();devrait être $x = new Child();.
Justin C
c'est ça pancit!
bdalina
La classe enfant pourrait être sans constructeur
shmnff
20

Je sais que cette question est vraiment ancienne, mais pour ceux qui recherchent une solution plus pratique que de définir une propriété dans chaque classe contenant le nom de la classe:

Vous pouvez utiliser le staticmot - clé pour cela.

Comme expliqué dans cette note du contributeur dans la documentation php

le staticmot - clé peut être utilisé dans une super classe pour accéder à la sous-classe à partir de laquelle une méthode est appelée.

Exemple:

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
la source
1
Je vous remercie! J'avais du mal avec ReflectionClass mais appeler static()est la voie à suivre!
godbout
16

Je connais son ancien poste mais je souhaite partager la solution que j'ai trouvée.

Testé avec PHP 7+ Utiliser le lien de fonctionget_class()

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

class foo extends bar {
}

new foo;
?>

L'exemple ci-dessus affichera:

string(3) "foo"
string(3) "bar"
Ingénieur Syed Rowshan Ali
la source
6

Si vous ne souhaitez pas utiliser get_called_class (), vous pouvez utiliser d'autres astuces de liaison statique tardive (PHP 5.3+). Mais l'inconvénient dans ce cas, vous devez avoir la méthode getClass () dans chaque modèle. Ce n'est pas un gros problème 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)

la source
2

Il semble que vous essayez d'utiliser un modèle singleton comme modèle d'usine. Je recommanderais d'évaluer vos décisions de conception. Si un singleton est vraiment approprié, je recommanderais également d'utiliser uniquement des méthodes statiques où l'héritage n'est pas souhaité.

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

la source
.. non. Vous n'avez pas compris ce que j'essayais de faire, mais c'est parce que je ne l'ai pas bien expliqué :). En fait, j'essaie juste de fournir une méthode statique (implémentée dans BaseModel) pour obtenir () une instance d'un modèle donné (que ce soit User, Role ou autre). Je vais mettre à jour la question ..
saalaa
Ok, mis à jour. Bien sûr, la classe BaseModel a beaucoup plus de méthodes, dont certaines pour garder une trace de ce qui est changé dans l'objet et UPDATE uniquement ce qui a chagé, etc ... Mais merci quand même :).
saalaa
2

Peut-être que cela ne répond pas réellement à la question, mais vous pouvez ajouter un paramètre pour get () spécifiant le type. alors tu peux appeler

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

au lieu d'appeler User :: get (). Vous pouvez ajouter une logique dans BaseModel :: get () pour vérifier si une méthode get existe dans la sous-classe, puis l'appeler si vous souhaitez autoriser la sous-classe à la remplacer.

Sinon, la seule façon dont je peux penser est évidemment d'ajouter des éléments à chaque sous-classe, ce qui est stupide:

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

Cela se casserait probablement si vous sous-classiez l'utilisateur, mais je suppose que vous pourriez remplacer get_parent_class(__CLASS__)par 'BaseModel'dans ce cas

Tom Haigh
la source
En fait, oui. Cette limitation PHP avait le grand avantage de me forcer à revoir ma conception. Cela ressemblera beaucoup plus à $ connection-> get ('User', 24); car il permet plusieurs connexions en même temps et est également sémantiquement plus correct. Mais vous avez un point :).
saalaa
array_shift semble lent, car il devra changer chaque clé du tableau ... Juste $ args [0] à la place;
Xesau
0

Le problème n'est pas une limitation de la langue, c'est votre conception. Peu importe que vous ayez des cours; les méthodes statiques démentent une conception procédurale plutôt qu'orientée objet. Vous utilisez également l'état global sous une forme ou une autre. (Comment get_row_from_db_as_array()savoir où trouver la base de données?) Et enfin, il semble très difficile de tester unitaire.

Essayez quelque chose de ce genre.

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

Deux variantes de la réponse 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);
    }
}

Remarque: commencer un nom de propriété par _ est une convention qui signifie essentiellement "Je sais que j'ai rendu ce contenu public, mais il aurait vraiment dû être protégé, mais je n'ai pas pu le faire et atteindre mon objectif"

lo_fye
la source