2009-05-30 5 views
34

Учитывая, что каждый PHP-файл в нашем проекте содержит определение одного класса, как определить, какой класс или классы определены в файле?Определение классов, определенных в файле класса PHP

Я знаю, что я мог бы просто повторно создать файл для операторов class, но я бы предпочел сделать что-то более эффективное.

+0

Какова цель получения имени класса для каждого файла? Лучшее решение должно быть адаптировано под ваше проблемное пространство. В его нынешнем виде я чувствую, что, вероятно, лучшее решение в зависимости от того, что вы хотите сделать. –

+4

Прошло некоторое время, но все же: вы могли бы вызвать 'get_declared_classes', сохранить его, включить файл класса и снова вызвать' get_declared_classes'. Разница в этом файле. Просто. – Rudie

ответ

56

мне нужно было что-то вроде этого для проекта я работаю, а вот функции я писал:

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

Не все маркеры - это массивы, поэтому это может дать некоторые предупреждения. – hakre

+0

@hakre: Это не потому, что если '$ tokens [$ i]' является строкой, синтаксис '$ tokens [i] [0]' все еще разрешен. – netcoder

+0

Правильно, но я предлагаю использовать '===' для сравнения в этом случае. – hakre

2

Использовать функцию PHP get_declared_classes(). Это возвращает массив классов, определенных в текущем скрипте.

+0

Итак, мне нужно сравнить это со списком классов от до включения ... или вы можете подумать о чем-то более эффективном? –

+0

Если вы загружаете файлы через include, это самый эффективный метод, о котором я могу думать. –

+1

Если вы хотите узнать, какие классы находятся в файле, это не будет сделано. Вы не можете сравнивать get_declared_classes() до и после включения, потому что файл уже может быть включен/автозагружен/необходим. – cletus

15

Если вы просто хотите проверить файл без загрузки его использования 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; 
    } 
    }  
} 
?> 

В принципе, это просто конечный автомат. В PHP последовательность tokens будет:

  • T_CLASS: 'класс' ключевое слово;
  • T_WHITESPACE: space (s) after 'class';
  • T_STRING: название класса.

Так что этот код будет обрабатывать любые странные интервалы или новые строки, которые вы получите просто отлично, потому что он использует тот же парсер, который использует PHP для выполнения файла. Если token_get_all() не может его разобрать, ни PHP не может.

Кстати, вы используете token_name(), чтобы превратить номер токена в его постоянное имя.

Вот мой c2.php:

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

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

Выход:

Found class: MyClass 
Found class: MyOtherClass 
+0

Обратите внимание: что-то вроде 'User :: class' является допустимым синтаксисом для извлечения строки FQCN (например,« App \ Model \ User ») с PHP 5.5. Таким образом, вам либо нужно явно проверить T_WHITESPACE (и ничего больше) между ними, либо вы можете проверить отсутствие T_COLON до T_CLASS. – Fx32

1

Я продлил ответ Venkat D, чтобы включить возврат методов и поиск по каталогу. (Этот конкретный пример построен для CodeIgniter, который будет возвращать все методы в ./system/application/controller файлов - другими словами, каждый публичный URL, который вы можете позвонить через систему.)

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

Вы может игнорировать абстрактные классы, как (обратите внимание на маркер 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

Это вызовет недопустимое смещение, так как число начинается с 2, а отрицательные числа - это не левые массивы. (2-4 = -2) ... Вы можете исправить это, если перед тестированием T_ABSTRACT вы добавите условие $ i-4> = 0. – Tivie

5

мне нужно классы разбора из файла с пространствами имен, поэтому я изменил код. Если кому-то тоже нужно, то здесь:

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

Не удалось заставить ваш фрагмент работать с файлами с несколькими пространствами имен – Tivie

3

Мой сниппет тоже. Может анализировать файлы с несколькими классами, интерфейсами, массивами и пространствами имен. Возвращает массив с классами + типы (класс, интерфейс, реферат), разделенные пространствами имен.

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

Выдает что-то вроде этого ...

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) 

Помогите кому-нибудь!

4

Или вы могли бы легко использовать AnnotationsParser из (устанавливаемым с помощью композитора):

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

выход будет потом что-то вроде этого:

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

parsePhp() method в основном делает что-то подобное в качестве примеров в других ответы, но вам не нужно объявлять и не анализировать синтаксический анализ.

Смежные вопросы