2010-08-17 16 views
19

J'ai une super-classe PHP abstraite, qui contient du code qui doit connaître la sous-classe dans laquelle elle s'exécute.PHP get_called_class() alternative

class Foo { 
    static function _get_class_name() { 
     return get_called_class(); 
     //works in PHP 5.3.*, but not in PHP 5.2.* 
    } 

    static function other_code() { 
     //needs to know 
     echo self::_get_class_name(); 
    } 
} 

class Bar extends Foo { 
} 

class FooBar extends Foo { 
} 

Bar::other_code(); // i need 'Bar' 
FooBar::other_code(); // i need 'FooBar' 

Cela fonctionnerait si j'ai appelé la fonction get_called_class() - cependant, ce code va être exécuté dans la version PHP 5.2 *, de sorte que la fonction est disponible..

Il y a quelques implémentations PHP personnalisé de get_called_class() là-bas, mais ils dépendent tous d'aller à travers le debug_backtrack(), l'analyse d'un nom de fichier & numéro de la ligne et l'exécution d'un regex (comme le codeur ne sait pas que PHP 5.2 a la réflexion) à trouve le nom de la classe. Ce code doit pouvoir être exécuté avec php, ie. non seulement à partir d'un fichier .php. (Il doit fonctionner à partir d'un shell php -a ou d'une instruction eval().)

Idéalement, une solution fonctionnerait sans nécessiter l'ajout de code aux sous-classes ... La seule solution potentielle que je puisse voir est d'ajouter le code suivant à chaque sous-classe, ce qui est évidemment un hack dégoûtant:

class FooBar extends Foo { 
    static function _get_class_name() { 
     return 'FooBar'; 
    } 
} 

EDIT: Attendez, cela ne semble même pas travailler. Ça aurait été mon dernier recours. Quelqu'un peut-il penser à quelque chose de similaire à cette solution qui me donnerait la fonctionnalité requise. C'est à dire, je suis prêt à accepter une solution qui m'oblige à ajouter une fonction ou une variable à chaque sous-classe en lui disant ce que son nom de classe est. Malheureusement, il semble que l'appel self::_get_class_name() de la superclasse appelle l'implémentation de la classe parente, même si la sous-classe l'a surchargée.

+3

Votre meilleure solution, honnêtement, est la mise à niveau 5.3. – Charles

+0

Je sais, mais malheureusement, ce n'est pas une option. –

+0

et comment faisons-nous cela en Java? –

Répondre

10

En réalité, c'est souvent utile de connaître la classe (sub) appelée réelle lors de l'exécution d'une méthode de super-classe, et je ne suis pas d'accord qu'il n'y a rien de mal à vouloir résoudre ce problème.Par exemple, mes objets ont besoin de connaître le nom de classe, mais ce qu'ils font avec cette information est toujours le même et pourrait être extrait dans une méthode de superclasse SI J'ai pu obtenir le nom de la classe appelée. Même l'équipe de PHP a pensé que cela était assez utile pour l'inclure dans PHP 5.3. La réponse correcte et sans prêche, pour autant que je sache, est qu'avant 5.3, vous devez soit faire quelque chose de haineux (par exemple, backtrace,) ou simplement inclure du code dupliqué dans chacune des sous-classes.

3

Ceci n'est pas possible.

Le concept de "classe appelée" a été introduit en PHP 5.3. Cette information n'a pas été suivie dans les versions précédentes. Comme une solution de travail laide, vous pouvez éventuellement utiliser debug_backtrace pour regarder dans la pile des appels, mais ce n'est pas équivalent. Par exemple, en PHP 5.3, l'utilisation de ClassName::method() ne transfère pas l'appel statique; vous n'avez aucun moyen de dire cela avec debug_backtrace. En outre, debug_backtrace est relativement lent.

+2

debug_backtrace, cependant, répertorie la classe en tant que superclasse au lieu de la sous-classe. Il liste le fichier et le numéro de ligne où il est appelé, donc je pourrais théoriquement aller là-bas et regex la ligne pour trouver la classe appelée, en supposant qu'elle soit appelée avec un format Bar :: mais ce n'est pas vraiment une solution. Et cela ne fonctionnerait pas dans le contexte d'un shell où le nom du fichier est bêtement listé comme 'code shell php' –

+0

@Ken Oui, 'debug_backtrace' est nul. Malheureusement, vos seules options restantes sont 1) upgrade, 2) remplacez la méthode statique 'other_code' (' _get_class_name' ne fonctionnera pas, car l'appeler depuis une implémentation 'other_code' dans la superclasse entraînera toujours l'appel de la version de la superclasse) dans chaque sous-classe. – Artefacto

+0

Ceci est possible. Essayez eval;) –

0

J'ai posé une question comme ça avant, parce que je voulais un parent d'avoir une méthode de fabrication qui était quelque chose comme ça

public static function factory() { 
    return new __CLASS__; 
} 

Mais il revient toujours la classe parente, non celle héritée.

On m'a dit que ce n'est pas possible sans liaison statique tardive. Il a été introduit en PHP 5.3. Vous pouvez lire le documentation.

1

La solution est:

get_class($this); 

Cependant, je ne sais pas si cette phrase fonctionne dans les fonctions statiques. Essayez-le et dites-moi vos commentaires.

+7

cela ne fonctionne pas sur les fonctions statiques, malheureusement ... –

+0

C'est essentiellement la définition des méthodes statiques: "no' $ this' ". –

1

Ce hack comprend l'utilisation odieux debug_backtrace ... pas joli, mais il fait le travail:

<?php 
function callerName($functionName=null) 
{ 
    $btArray = debug_backtrace(); 
    $btIndex = count($btArray) - 1; 
    while($btIndex > -1) 
    { 
     if(!isset($btArray[$btIndex]['file'])) 
     { 
      $btIndex--; 
      if(isset($matches[1])) 
      { 
       if(class_exists($matches[1])) 
       { 
        return $matches[1]; 
       } 
       else 
       { 
        continue; 
       } 
      } 
      else 
      { 
       continue; 
      } 
     } 
     else 
     { 
      $lines = file($btArray[$btIndex]['file']); 
      $callerLine = $lines[$btArray[$btIndex]['line']-1]; 
      if(!isset($functionName)) 
      { 
       preg_match('/([a-zA-Z\_]+)::/', 
       $callerLine, 
       $matches); 
      } 
      else 
      { 
       preg_match('/([a-zA-Z\_]+)::'.$functionName.'/', 
        $callerLine, 
        $matches); 
      } 
      $btIndex--; 
      if(isset($matches[1])) 
      { 
       if(class_exists($matches[1])) 
       { 
        return $matches[1]; 
       } 
       else 
       { 
        continue; 
       } 
      } 
      else 
      { 
       continue; 
      } 
     } 
    } 
    return $matches[1]; 
} 
6

Solution de travail:

function getCalledClass(){ 
    $arr = array(); 
    $arrTraces = debug_backtrace(); 
    foreach ($arrTraces as $arrTrace){ 
     if(!array_key_exists("class", $arrTrace)) continue; 
     if(count($arr)==0) $arr[] = $arrTrace['class']; 
     else if(get_parent_class($arrTrace['class'])==end($arr)) $arr[] = $arrTrace['class']; 
    } 
    return end($arr); 
} 
+1

tout à fait horrifiant, +1 pour l'effort de fournir une solution d'exécution. Je suppose que si quelqu'un a désespérément besoin de support php <= 5.2 .. eh –

+0

Malheureusement, votre implémentation ne fonctionne pas comme prévu. Considérons l'exemple suivant: 'classe A {public static function foo() {return getCalledClass(); }} La classe B étend A {} echo B :: foo(); '. Le résultat attendu est 'B' (c'est ce que vous obtenez, quand vous utilisez 'get_called_class()'), mais ici nous obtenons 'A' à la place. – Xemlock

0

Cette fonction fait le même travail, mais travaille avec instances aussi:

if (!function_exists('get_called_class')) { 

    function get_called_class() { 

     $bt = debug_backtrace(); 

     /* 
      echo '<br><br>'; 
      echo '<pre>'; 
      print_r($bt); 
      echo '</pre>'; 
     */ 

     if (self::$fl == $bt[1]['file'] . $bt[1]['line']) { 
      self::$i++; 
     } else { 
      self::$i = 0; 
      self::$fl = $bt[1]['file'] . $bt[1]['line']; 
     } 

     if ($bt[1]['type'] == '::') { 

      $lines = file($bt[1]['file']); 
      preg_match_all('/([a-zA-Z0-9\_]+)::' . $bt[1]['function'] . '/', $lines[$bt[1]['line'] - 1], $matches); 
      $result = $matches[1][self::$i]; 

     } else if ($bt[1]['type'] == '->') { 

      $result = get_class($bt[1]['object']); 
     } 

     return $result; 
    } 
} 
2

L'alternative PHP/5.2 à la liaison statique tardive qui conserve la duplication c ode au minimum tout en évitant hacks étranges serait de créer une seule ligne sur les classes d'enfants qui passent le nom de classe comme argument:

abstract class Transaction{ 
    public $id; 

    public function __construct($id){ 
     $this->id = $id; 
    } 

    protected static function getInstanceHelper($class_name, $id){ 
     return new $class_name($id); 
    } 
} 

class Payment extends Transaction{ 
    public static function getInstance($id){ 
     return parent::getInstanceHelper(__CLASS__, $id); 
    } 
} 

class Refund extends Transaction{ 
    public static function getInstance($id){ 
     return parent::getInstanceHelper(__CLASS__, $id); 
    } 
} 

var_dump(Payment::getInstance(1), Refund::getInstance(2)); 
object(Payment)#1 (1) { 
    ["id"]=> 
    int(1) 
} 
object(Refund)#2 (1) { 
    ["id"]=> 
    int(2) 
} 
+0

Pas exactement ce que j'avais en tête, mais cela pourrait fonctionner. Après tout, devoir écrire '__CLASS__' pour chaque classe n'est pas un si gros travail. Ensuite, une autre logique peut être construite autour de cela. – XedinUnknown

0
<?php 

class Foo { 

    private static $instance; 

    static function _get_class_name() { 
     return self::myNameIs(); 
    } 

    static function other_code() { 
     //needs to know 
     echo self::_get_class_name(); 
    } 

} 

class Bar extends Foo { 

    public static function myNameIs() { 
     self::$instance = new Bar(); 
     return get_class(self::$instance); 
    } 

} 

class FooBar extends Foo { 

    public static function myNameIs() { 
     self::$instance = new FooBar(); 
     return get_class(self::$instance); 
    } 

} 

Bar::other_code(); // i need 'Bar' 
FooBar::other_code(); // i need 'FooBar'