2010-08-17 9 views
19

Tengo una superclase PHP abstracta, que contiene un código que necesita saber en qué subclase se está ejecutando.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' 

Esto funcionaría si llamara a la función get_called_class() - sin embargo, este código se va a ejecutar en PHP versión 5.2 *, por lo que la función no está disponible..

Hay algunas implementaciones PHP personalizado de get_called_class() por ahí, pero todos se basan en ir a través de la debug_backtrack(), analizar un nombre de archivo & número de línea, y ejecuta una expresión regular (como el codificador no es consciente de que PHP 5.2 tiene reflexión) a encuentra el nombre de la clase. Este código debe poder ejecutarse con php, es decir. no solo desde un archivo .php (Necesita funcionar desde un shell php -a, o una instrucción eval().)

Idealmente, una solución funcionaría sin necesidad de agregar ningún código a las subclases ... La única solución potencial que puedo ver es agregar el siguiente código a cada subclase, lo que obviamente es un corte desagradable:

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

EDIT: Espera, esto ni siquiera parece funcionar. Hubiera sido mi último recurso. ¿Alguien puede pensar en algo similar a esta solución que me daría la funcionalidad requerida? Es decir, estoy dispuesto a aceptar una solución que requiera que agregue una función o variable a cada subclase diciéndole cuál es su nombre de clase. Desafortunadamente, parece que llamar a self::_get_class_name() desde la superclase llama a la implementación de la clase padre, incluso si la subclase lo ha anulado.

+3

La mejor solución, la verdad, es la actualización a 5.3. – Charles

+0

Lo sé, pero desafortunadamente, esa no es una opción. –

+0

y ¿cómo hacemos esto en Java? –

Respuesta

10

En realidad es menudo útil para saber la llamada (sub) clase real al ejecutar un método de superclase, y no estoy de acuerdo con que haya algún problema al querer resolver este problema.

Ejemplo, mis objetos necesitan saber el nombre de clase, pero lo que hacen con esa información es siempre el mismo y podría extraerse en un método de superclase IF Pude obtener el nombre de clase llamado. Incluso el equipo de PHP pensó que esto era lo suficientemente útil como para incluirlo en php 5.3.

La respuesta correcta e improvisada, hasta donde puedo decir, es que antes de 5.3, tienes que hacer algo atroz (por ejemplo, trazar atrás) o simplemente incluir código duplicado en cada una de las subclases.

3

Esto no es posible.

El concepto de "clase llamada" se introdujo en PHP 5.3. Esta información no fue rastreada en versiones anteriores.

Como un desagradable problema, podría usar debug_backtrace para examinar la pila de llamadas, pero no es equivalente. Por ejemplo, en PHP 5.3, usar ClassName::method() no reenvía la llamada estática; no tienes manera de decir esto con debug_backtrace. Además, debug_backtrace es relativamente lento.

+2

debug_backtrace, sin embargo, enumera la clase como la superclase en lugar de la subclase. Enumera el archivo y el número de línea a donde se llama, así que teóricamente podría entrar allí y volver a expresar la línea para encontrar la clase llamada, suponiendo que se llamó con un formato Bar ::, pero esa no es realmente una solución. Y no funcionaría en el contexto de un shell donde el nombre del archivo está estúpidamente listado como 'código de shell de php' –

+0

@Ken Sí, 'debug_backtrace' apesta. Desafortunadamente, tus únicas opciones restantes son 1) actualizar, 2) reemplazar el método estático 'other_code' (' _get_class_name' no funcionará, porque llamarlo desde una implementación 'other_code' en la superclase daría como resultado que siempre se llame a la versión de la superclase) en cada subclase. – Artefacto

+0

Esto es posible. Pruebe eval;) –

0

me han preguntado una pregunta como esto antes, porque quería un padre tener un método de fábrica que fue algo como esto

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

Pero siempre devuelto la clase padre, no el uno heredado.

Me dijeron que no es posible sin vinculación estática tardía. Fue introducido en PHP 5.3. Puedes leer el documentation.

1

La solución es:

get_class($this); 

Sin embargo, no sabemos si esta frase trabaja en funciones estáticas. Pruébalo y cuéntame tus comentarios.

+7

esto no funciona en funciones estáticas, desafortunadamente ... –

+0

Esa es básicamente la definición de métodos estáticos: "no' $ this' ". –

1

Este truco incluye el uso atroz de debug_backtrace ... no es bonito, pero hace el trabajo:

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

Solución de trabajo:

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

aunque es absolutamente horrible, +1 por el esfuerzo de proporcionar una solución de tiempo de ejecución. Supongo que si alguien necesita soporte desesperado php <= 5.2 .. eh –

+0

Desafortunadamente su implementación no funciona como debería. Considere el siguiente ejemplo: 'clase A {función estática pública foo() {return getCalledClass(); }} clase B extiende A {} echo B :: foo(); '. El resultado esperado es 'B' (que es lo que obtienes, cuando usas 'get_called_class()'), pero aquí obtenemos 'A'. – Xemlock

0

Esta función hace el mismo trabajo, pero trabaja con instancias también:

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

La alternativa PHP/5.2 a la vinculación estática tardía que mantiene duplicados c oda al mínimo, evitando los cortes extraños sería la creación de una sola línea en las clases hijas que pasan el nombre de clase como argumento:

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

No es exactamente lo que tenía en mente, pero podría funcionar. Después de todo, tener que escribir '__CLASS__' para cada clase no es un trabajo tan grande. Entonces, otra lógica puede ser construida alrededor de eso. – 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'