2009-05-30 8 views
34

Dado que cada archivo PHP en nuestro proyecto contiene una única definición de clase, ¿cómo puedo determinar qué clase o clases se definen dentro del archivo?Determinando qué clases están definidas en un archivo de clase PHP

Sé que podría simplemente volver a generar el archivo para las declaraciones class, pero preferiría hacer algo que sea más eficiente.

+0

¿Cuál es el propósito de obtener el nombre de clase de cada archivo? La mejor solución debe adaptarse para adaptarse a su espacio problemático. Tal como está, siento que probablemente haya una mejor solución dependiendo de lo que estás buscando hacer. –

+4

Ha pasado un tiempo, pero aún así: puedes llamar a 'get_declared_classes', guardarlo, incluir el archivo de clase y llamar a' get_declared_classes' nuevamente. La diferencia está en ese archivo. Sencillo. – Rudie

Respuesta

56

que necesitaba algo como esto para un proyecto que estoy trabajando, y aquí están las funciones que escribí:

function file_get_php_classes($filepath) { 
    $php_code = file_get_contents($filepath); 
    $classes = get_php_classes($php_code); 
    return $classes; 
} 

function get_php_classes($php_code) { 
    $classes = array(); 
    $tokens = token_get_all($php_code); 
    $count = count($tokens); 
    for ($i = 2; $i < $count; $i++) { 
    if ( $tokens[$i - 2][0] == T_CLASS 
     && $tokens[$i - 1][0] == T_WHITESPACE 
     && $tokens[$i][0] == T_STRING) { 

     $class_name = $tokens[$i][1]; 
     $classes[] = $class_name; 
    } 
    } 
    return $classes; 
} 
+0

No todos los tokens son matrices, así que esto puede dar algunas advertencias. – hakre

+0

@hakre: No sería porque si '$ tokens [$ i]' es una cadena, la sintaxis '$ tokens [i] [0]' aún está permitida. – netcoder

+0

Correcto, pero sugiero usar '===' para las comparaciones en ese caso. – hakre

2

Utilice la función de PHP get_declared_classes(). Esto devuelve una matriz de clases definidas en el script actual.

+0

Entonces, necesitaría comparar esto con la lista de clases anteriores al include ... ¿o puede pensar en algo más eficiente? –

+0

Si está cargando los archivos mediante una inclusión, ese es el método más eficiente que se me ocurre. –

+1

Si quiere saber qué clases hay en un archivo, esto no lo hará. No puede comparar get_declared_classes() antes y después de una inclusión porque es posible que el archivo ya esté incluido/cargado automáticamente/requerido. – cletus

15

Si lo que desea es comprobar un archivo sin cargarla utilizar token_get_all():

<?php 
header('Content-Type: text/plain'); 
$php_file = file_get_contents('c2.php'); 
$tokens = token_get_all($php_file); 
$class_token = false; 
foreach ($tokens as $token) { 
    if (is_array($token)) { 
    if ($token[0] == T_CLASS) { 
     $class_token = true; 
    } else if ($class_token && $token[0] == T_STRING) { 
     echo "Found class: $token[1]\n"; 
     $class_token = false; 
    } 
    }  
} 
?> 

Básicamente, se trata de un simple máquina de estados finitos. En PHP la secuencia de tokens será:

  • T_CLASS: palabra clave 'clase';
  • T_WHITESPACE: espacio (s) después de 'clase';
  • T_STRING: nombre de la clase.

Por lo tanto, este código manejará cualquier espaciado extraño o saltos nuevos que obtenga muy bien porque está usando el mismo analizador que PHP usa para ejecutar el archivo. Si token_get_all() no puede analizarlo, tampoco PHP.

Por cierto, usa token_name() para convertir un número de token en su nombre de constante.

Aquí es mi c2.php:

<?php 
class MyClass { 
    public __construct() { 
    } 
} 

class MyOtherClass { 
    public __construct() { 
    } 
} 
?> 

Salida:

Found class: MyClass 
Found class: MyOtherClass 
+0

Tenga en cuenta que algo como 'User :: class' es una sintaxis válida para recuperar la cadena FQCN (como" App \ Model \ User ") desde PHP 5.5. Entonces, o necesita verificar explícitamente el T_WHITESPACE (y nada más) en el medio, o puede verificar la ausencia de T_COLON antes de T_CLASS. – Fx32

1

He extendido la respuesta de Venkat D un poco para incluir la devolución de los métodos, y para buscar a través de un directorio. (Este ejemplo específico se construye para CodeIgniter, que devolverá todos los métodos en los archivos ./system/application/controller - en otras palabras, cada URL pública que se puede llamar a través del sistema.)

function file_get_php_classes($filepath,$onlypublic=true) { 
    $php_code = file_get_contents($filepath); 
    $classes = get_php_classes($php_code,$onlypublic); 
    return $classes; 
} 

function get_php_classes($php_code,$onlypublic) { 
    $classes = array(); 
    $methods=array(); 
    $tokens = token_get_all($php_code); 
    $count = count($tokens); 
    for ($i = 2; $i < $count; $i++) { 
     if ($tokens[$i - 2][0] == T_CLASS 
     && $tokens[$i - 1][0] == T_WHITESPACE 
     && $tokens[$i][0] == T_STRING) { 
      $class_name = $tokens[$i][1]; 
      $methods[$class_name] = array(); 
     } 
     if ($tokens[$i - 2][0] == T_FUNCTION 
     && $tokens[$i - 1][0] == T_WHITESPACE 
     && $tokens[$i][0] == T_STRING) { 
      if ($onlypublic) { 
       if (!in_array($tokens[$i-4][0],array(T_PROTECTED, T_PRIVATE))) { 
        $method_name = $tokens[$i][1]; 
        $methods[$class_name][] = $method_name; 
       } 
      } else { 
       $method_name = $tokens[$i][1]; 
       $methods[$class_name][] = $method_name; 
      } 
     } 
    } 
    return $methods; 
} 

function mapSystemClasses($controllerdir="./system/application/controllers/",$onlypublic=true) { 
    $result=array(); 
    $dh=opendir($controllerdir); 
    while (($file = readdir($dh)) !== false) { 
     if (substr($file,0,1)!=".") { 
      if (filetype($controllerdir.$file)=="file") { 
       $classes=file_get_php_classes($controllerdir.$file,$onlypublic); 
       foreach($classes as $class=>$method) { 
        $result[]=array("file"=>$controllerdir.$file,"class"=>$class,"method"=>$method); 

       } 
      } else { 
       $result=array_merge($result,mapSystemClasses($controllerdir.$file."/",$onlypublic)); 
      } 
     } 
    } 
    closedir($dh); 
    return $result; 
} 
0

Usted puede ignorar las clases abstractas como esto (tenga en cuenta el token T_ABSTRACT):

function get_php_classes($php_code) 
{ 
    $classes = array(); 
    $tokens = token_get_all($php_code); 
    $count = count($tokens); 
    for ($i = 2; $i < $count; $i++) 
    { 
     if ($tokens[$i - 2][0] == T_CLASS && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING && !($tokens[$i - 3] && $i - 4 >= 0 && $tokens[$i - 4][0] == T_ABSTRACT)) 
     { 
      $class_name = $tokens[$i][1]; 
      $classes[] = $class_name; 
     } 
    } 
    return $classes; 
} 
+1

Esto aumentará la compensación ilegal ya que para el recuento comienza en 2 y los números negativos no son claves de matriz legales. (2-4 = -2) ... Puede corregir esto si agrega la condición $ i-4> = 0 antes de probar para T_ABSTRACT. – Tivie

5

que necesitaba clases de análisis sintáctico de archivo con espacios de nombres, por lo que he modificado el código. Si alguien necesita también, aquí se encuentra:

public function getPhpClasses($phpcode) { 
    $classes = array(); 

    $namespace = 0; 
    $tokens = token_get_all($phpcode); 
    $count = count($tokens); 
    $dlm = false; 
    for ($i = 2; $i < $count; $i++) { 
     if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] == "phpnamespace" || $tokens[$i - 2][1] == "namespace")) || 
      ($dlm && $tokens[$i - 1][0] == T_NS_SEPARATOR && $tokens[$i][0] == T_STRING)) { 
      if (!$dlm) $namespace = 0; 
      if (isset($tokens[$i][1])) { 
       $namespace = $namespace ? $namespace . "\\" . $tokens[$i][1] : $tokens[$i][1]; 
       $dlm = true; 
      } 
     }  
     elseif ($dlm && ($tokens[$i][0] != T_NS_SEPARATOR) && ($tokens[$i][0] != T_STRING)) { 
      $dlm = false; 
     } 
     if (($tokens[$i - 2][0] == T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] == "phpclass")) 
       && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING) { 
      $class_name = $tokens[$i][1]; 
      if (!isset($classes[$namespace])) $classes[$namespace] = array(); 
      $classes[$namespace][] = $class_name; 
     } 
    } 
    return $classes; 
} 
+0

No se pudo hacer que su fragmento funcione con archivos con varios espacios de nombres – Tivie

3

Mi fragmento también. Puede analizar archivos con múltiples clases, interfaces, matrices y espacios de nombres. Devuelve una matriz con clases + tipos (clase, interfaz, resumen) dividida por espacios de nombres.

<?php  
    /** 
    * 
    * Looks what classes and namespaces are defined in that file and returns the first found 
    * @param String $file Path to file 
    * @return Returns NULL if none is found or an array with namespaces and classes found in file 
    */ 
    function classes_in_file($file) 
    { 

     $classes = $nsPos = $final = array(); 
     $foundNS = FALSE; 
     $ii = 0; 

     if (!file_exists($file)) return NULL; 

     $er = error_reporting(); 
     error_reporting(E_ALL^E_NOTICE); 

     $php_code = file_get_contents($file); 
     $tokens = token_get_all($php_code); 
     $count = count($tokens); 

     for ($i = 0; $i < $count; $i++) 
     { 
      if(!$foundNS && $tokens[$i][0] == T_NAMESPACE) 
      { 
       $nsPos[$ii]['start'] = $i; 
       $foundNS = TRUE; 
      } 
      elseif($foundNS && ($tokens[$i] == ';' || $tokens[$i] == '{')) 
      { 
       $nsPos[$ii]['end']= $i; 
       $ii++; 
       $foundNS = FALSE; 
      } 
      elseif ($i-2 >= 0 && $tokens[$i - 2][0] == T_CLASS && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING) 
      { 
       if($i-4 >=0 && $tokens[$i - 4][0] == T_ABSTRACT) 
       { 
        $classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'ABSTRACT CLASS'); 
       } 
       else 
       { 
        $classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'CLASS'); 
       } 
      } 
      elseif ($i-2 >= 0 && $tokens[$i - 2][0] == T_INTERFACE && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING) 
      { 
       $classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'INTERFACE'); 
      } 
     } 
     error_reporting($er); 
     if (empty($classes)) return NULL; 

     if(!empty($nsPos)) 
     { 
      foreach($nsPos as $k => $p) 
      { 
       $ns = ''; 
       for($i = $p['start'] + 1; $i < $p['end']; $i++) 
        $ns .= $tokens[$i][1]; 

       $ns = trim($ns); 
       $final[$k] = array('namespace' => $ns, 'classes' => $classes[$k+1]); 
      } 
      $classes = $final; 
     } 
     return $classes; 
    } 

Produce algo como esto ...

array 
    'namespace' => string 'test\foo' (length=8) 
    'classes' => 
    array 
     0 => 
     array 
      'name' => string 'bar' (length=3) 
      'type' => string 'CLASS' (length=5) 
     1 => 
     array 
      'name' => string 'baz' (length=3) 
      'type' => string 'INTERFACE' (length=9) 
array 
    'namespace' => string 'this\is\a\really\big\namespace\for\testing\dont\you\think' (length=57) 
    'classes' => 
    array 
     0 => 
     array 
      'name' => string 'yes_it_is' (length=9) 
      'type' => string 'CLASS' (length=5) 
     1 => 
     array 
      'name' => string 'damn_too_big' (length=12) 
      'type' => string 'ABSTRACT CLASS' (length=14) 
     2 => 
     array 
      'name' => string 'fodass' (length=6) 
      'type' => string 'INTERFACE' (length=9) 

Might help someone!

4

O usted podría utilizar fácilmente AnnotationsParser de (instalable utilizando compositor):

use Nette\Reflection\AnnotationsParser; 
$classes = AnnotationsParser::parsePhp(file_get_contents($fileName)); 
var_dump($classes); 

de salida será entonces algo como esto:

array(1) { 
    ["Your\Class\Name"] => 
    array(...) { 
     // property => comment 
    }, 
    ["Your\Class\Second"] => 
    array(...) { 
     // property => comment 
    }, 
} 

El parsePhp() method, básicamente, hace algo similar como ejemplos en otra respuestas, pero no tiene que declarar ni probar el análisis sintáctico.

Cuestiones relacionadas