web-dev-qa-db-fra.com

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

Je construis une bibliothèque ORM dans un esprit de réutilisation et de simplicité; tout se passe 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 que j’attendais (bien que le comportement lui-même ait également un sens). Ma question est donc de savoir si vous connaissez un moyen d’obtenir, dans la classe parent, le nom de la classe enfant.

79
saalaa

en bref. ce n'est pas possible. en php4, vous pourriez mettre en œuvre un piratage terrible (examiner la debug_backtrace()) mais cette méthode ne fonctionne pas en PHP5. références:

edit: un exemple de liaison statique tardive dans PHP 5.3 (mentionné dans les commentaires). notez qu'il y a 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"
82
Owen

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

ceci produira:

Parent class: Parent
Child class: Child

chérie hein?

165
Jon47

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

Vous pouvez utiliser le mot clé static pour cela.

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

le mot clé static 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
10
Joas

Au cas où vous ne voudriez pas utiliser get_called_class (), vous pouvez utiliser d'autres astuces de dernière liaison statique (PHP 5.3+). Mais l'inconvénient dans ce cas, vous devez avoir la méthode getClass () dans chaque modèle. Ce qui n’est pas une grosse affaire à l’OMI.

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

Il semble que vous essayiez peut-être d'utiliser un motif singleton comme motif 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 est pas désiré.

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
2
Rabbit

Peut-être que cela ne répond pas vraiment à la question, mais vous pouvez ajouter un paramètre pour obtenir () spécifiant le type. alors vous pouvez 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 appeler cela si vous souhaitez autoriser la sous-classe à la remplacer.

Sinon, la seule façon dont je puisse 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 casserait probablement si vous sous-classiez ensuite Utilisateur, mais je suppose que vous pourriez remplacer get_parent_class(__CLASS__) par 'BaseModel' dans ce cas

2
Tom Haigh

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

Testé avec PHP 7 + Utilisez la fonction get_class()link

<?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"
2

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 que orientée objet. Vous utilisez également l'état global sous une forme quelconque. (Comment get_row_from_db_as_array() sait-il où trouver la base de données?) Enfin, il semble très difficile d'effectuer des tests unitaires.

Essayez quelque chose dans ce sens.

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

Deux variations sur 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 par un nom de propriété avec _ est une convention qui signifie "je sais que j'ai rendu ce public public, mais que cela aurait dû être protégé, mais je ne pouvais pas le faire et atteindre mon objectif"

0
lo_fye