Подтвердить что ты не робот

Определение классов, определенных в файле класса PHP

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

Если вы просто хотите проверить файл без загрузки, используйте 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: ключевое слово '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

Ответ 3

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

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

Ответ 4

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

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() в основном делает что-то похожее на примеры в других ответах, но вам не нужно объявлять и не анализировать синтаксический анализ.

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

Я добавил 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;
}

Ответ 8

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