Определение того, какие классы определены в файле классов PHP
Учитывая, что каждый файл PHP в нашем проекте содержит одно определение класса, как я могу определить, какой класс или классы определены в файле?
Я знаю, что могу просто пересмотреть файл для class
заявления, но я бы предпочел сделать что-то более эффективное.
9 ответов
Мне нужно что-то подобное для проекта, над которым я работаю, и вот функции, которые я написал:
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;
}
Если вы просто хотите проверить файл без загрузки, используйте 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 последовательность токенов будет:
T_CLASS
: ключевое слово "класс";T_WHITESPACE
: пробел (ы) после "класса";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
Или вы можете легко использовать AnnotationsParser из Nette \ Reflection (устанавливается с помощью composer):
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() в основном делает нечто похожее на примеры в других ответах, но вам не нужно ни объявлять, ни проверять синтаксический анализ самостоятельно.
Мне нужно было разобрать классы из файла с пространствами имен, поэтому я изменил код. Если кому-то тоже нужно, вот оно:
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;
}
Мой фрагмент тоже. Может анализировать файлы с несколькими классами, интерфейсами, массивами и пространствами имен. Возвращает массив с классами + типами (class, interface, abstract), разделенными на пространства имен.
<?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 'fogo' (length=6)
'type' => string 'INTERFACE' (length=9)
Может помочь кому-нибудь!
Используйте функцию PHP get_declared_classes (). Это возвращает массив классов, определенных в текущем скрипте.
Я немного расширил ответ 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;
}
В настоящее время (2022 год) вы можете использовать пакет Composer roave/better-reflection .
Чтобы получить все классы, определенные в файле, вы можете использовать этот код с версией 5 пакета:
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\Reflector\DefaultReflector;
use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator;
$astLocator = (new BetterReflection())->astLocator();
$reflector = new DefaultReflector(new SingleFileSourceLocator('path/to/file.php', $astLocator));
$classes = $reflector->reflectAllClasses();
$classNames = [];
foreach ($classes as $class) {
$classNames[] = $class->getName();
}
Вы можете игнорировать абстрактные классы, как это (обратите внимание на токен 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;
}