Получить имя класса из файла

У меня есть php-файл, который содержит только один класс. как я могу узнать, какой класс существует, зная имя файла? Я знаю, что могу сделать что-то с регулярным выражением, но есть ли стандартный способ php? (файл уже включен в страницу, которая пытается определить имя класса).

Существует несколько возможных решений этой проблемы, каждая из которых имеет свои преимущества и недостатки. Вот они, это знать, чтобы решить, какой из них вы хотите.

Tokenizer

Этот метод использует токенизатор и считывает части файла до тех пор, пока не найдет определение класса.

преимущества

  • Не нужно полностью разбирать файл
  • Fast (читает только начало файла)
  • Довольно мало шансов на ложные срабатывания

Недостатки

  • Самое длинное решение

Код

$fp = fopen($file, 'r'); $class = $buffer = ''; $i = 0; while (!$class) { if (feof($fp)) break; $buffer .= fread($fp, 512); $tokens = token_get_all($buffer); if (strpos($buffer, '{') === false) continue; for (;$i<count($tokens);$i++) { if ($tokens[$i][0] === T_CLASS) { for ($j=$i+1;$j<count($tokens);$j++) { if ($tokens[$j] === '{') { $class = $tokens[$i+2][1]; } } } } } 

Обычные выражения

Используйте регулярные выражения для синтаксического анализа начала файла, пока не будет найдено определение класса.

преимущества

  • Не нужно полностью разбирать файл
  • Fast (читает только начало файла)

Недостатки

  • Высокие шансы ложных срабатываний (например: echo "class Foo {"; )

Код

 $fp = fopen($file, 'r'); $class = $buffer = ''; $i = 0; while (!$class) { if (feof($fp)) break; $buffer .= fread($fp, 512); if (preg_match('/class\s+(\w+)(.*)?\{/', $buffer, $matches)) { $class = $matches[1]; break; } } 

Примечание. Регулярное выражение, вероятно, может быть улучшено, но никакое регулярное выражение не может сделать это отлично.

Получить список объявленных классов

Этот метод использует get_declared_classes() и ищет первый класс, определенный после включения.

преимущества

  • Кратчайшее решение
  • Отсутствие ложных срабатываний

Недостатки

  • Необходимо загрузить весь файл
  • Необходимо дважды загрузить весь список классов в памяти
  • Необходимо загрузить определение класса в памяти

Код

 $classes = get_declared_classes(); include 'test2.php'; $diff = array_diff(get_declared_classes(), $classes); $class = reset($diff); 

Примечание. Вы не можете просто сделать end() как предложили другие. Если класс включает другой класс, вы получите неверный результат.


Это решение Tokenizer , модифицированное для включения переменной пространства имен, содержащей пространство имен классов, если это применимо:

 $fp = fopen($file, 'r'); $class = $namespace = $buffer = ''; $i = 0; while (!$class) { if (feof($fp)) break; $buffer .= fread($fp, 512); $tokens = token_get_all($buffer); if (strpos($buffer, '{') === false) continue; for (;$i<count($tokens);$i++) { if ($tokens[$i][0] === T_NAMESPACE) { for ($j=$i+1;$j<count($tokens); $j++) { if ($tokens[$j][0] === T_STRING) { $namespace .= '\\'.$tokens[$j][1]; } else if ($tokens[$j] === '{' || $tokens[$j] === ';') { break; } } } if ($tokens[$i][0] === T_CLASS) { for ($j=$i+1;$j<count($tokens);$j++) { if ($tokens[$j] === '{') { $class = $tokens[$i+2][1]; } } } } } 

Скажем, у вас есть этот класс:

 namespace foo\bar { class hello { } } 

… или альтернативный синтаксис:

 namespace foo\bar; class hello { } 

Вы должны иметь следующий результат:

 var_dump($namespace); // \foo\bar var_dump($class); // hello 

Вы также можете использовать описанное выше, чтобы определить пространство имен, которое объявляет файл, независимо от того, содержит ли он класс или нет.

Вы можете заставить PHP выполнять работу, просто включив файл и получив последний объявленный класс:

 $file = 'class.php'; # contains class Foo include($file); $classes = get_declared_classes(); $class = end($classes); echo $class; # Foo 

Если вам нужно изолировать это, заверните его в сценарий командной строки и выполните его через shell_exec :

 $file = 'class.php'; # contains class Foo $class = shell_exec("php -r \"include('$file'); echo end(get_declared_classes());\""); echo $class; # Foo 

Если вам не нравятся сценарии командной строки, вы можете сделать это, как в этом вопросе , однако этот код не отражает пространства имен.

Вы можете получить все объявленные классы, прежде чем включать файл, используя get_declared_classes . Сделайте то же самое после того, как вы включили его, и сравните их с чем-то вроде array_diff и у вас есть новый добавленный класс.

Благодаря некоторым людям из Stackoverflow и Github, я смог написать это замечательное полностью работающее решение:

 /** * get the full name (name \ namespace) of a class from its file path * result example: (string) "I\Am\The\Namespace\Of\This\Class" * * @param $filePathName * * @return string */ public function getClassFullNameFromFile($filePathName) { return $this->getClassNamespaceFromFile($filePathName) . '\\' . $this->getClassNameFromFile($filePathName); } /** * build and return an object of a class from its file path * * @param $filePathName * * @return mixed */ public function getClassObjectFromFile($filePathName) { $classString = $this->getClassFullNameFromFile($filePathName); $object = new $classString; return $object; } /** * get the class namespace form file path using token * * @param $filePathName * * @return null|string */ protected function getClassNamespaceFromFile($filePathName) { $src = file_get_contents($filePathName); $tokens = token_get_all($src); $count = count($tokens); $i = 0; $namespace = ''; $namespace_ok = false; while ($i < $count) { $token = $tokens[$i]; if (is_array($token) && $token[0] === T_NAMESPACE) { // Found namespace declaration while (++$i < $count) { if ($tokens[$i] === ';') { $namespace_ok = true; $namespace = trim($namespace); break; } $namespace .= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i]; } break; } $i++; } if (!$namespace_ok) { return null; } else { return $namespace; } } /** * get the class name form file path using token * * @param $filePathName * * @return mixed */ protected function getClassNameFromFile($filePathName) { $php_code = file_get_contents($filePathName); $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]; } 

Я изменил Nette\Reflection\AnnotationsParser , так что он возвращает массив пространства имен + имя класса, которые определены в файле

 $parser = new PhpParser(); $parser->extractPhpClasses('src/Path/To/File.php'); class PhpParser { public function extractPhpClasses(string $path) { $code = file_get_contents($path); $tokens = @token_get_all($code); $namespace = $class = $classLevel = $level = NULL; $classes = []; while (list(, $token) = each($tokens)) { switch (is_array($token) ? $token[0] : $token) { case T_NAMESPACE: $namespace = ltrim($this->fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\'); break; case T_CLASS: case T_INTERFACE: if ($name = $this->fetch($tokens, T_STRING)) { $classes[] = $namespace . $name; } break; } } return $classes; } private function fetch(&$tokens, $take) { $res = NULL; while ($token = current($tokens)) { list($token, $s) = is_array($token) ? $token : [$token, $token]; if (in_array($token, (array) $take, TRUE)) { $res .= $s; } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], TRUE)) { break; } next($tokens); } return $res; } } 

Вы можете сделать это двумя способами:

  • сложное решение : откройте файл и через regex извлеките имя класса (например, /class ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/ )
  • простое решение : назовите все ваши php-файлы с содержащимся именем класса (например: класс TestFoo в файле TestFoo.php или TestFoo.class.php )
 $st = get_declared_classes(); include "classes.php"; //one or more classes in file, contains class class1, class2, etc... $res = array_values(array_diff_key(get_declared_classes(),$st)); print_r($res); # Array ([0] => class1 [1] => class2 [2] ...) 

Этот образец возвращает все классы. Если вы ищете класс, который является производным от определенного, используйте is_subclass_of

 $php_code = file_get_contents ( $file ); $classes = array (); $namespace=""; $tokens = token_get_all ( $php_code ); $count = count ( $tokens ); for($i = 0; $i < $count; $i ++) { if ($tokens[$i][0]===T_NAMESPACE) { for ($j=$i+1;$j<$count;++$j) { if ($tokens[$j][0]===T_STRING) $namespace.="\\".$tokens[$j][1]; elseif ($tokens[$j]==='{' or $tokens[$j]===';') break; } } if ($tokens[$i][0]===T_CLASS) { for ($j=$i+1;$j<$count;++$j) if ($tokens[$j]==='{') { $classes[]=$namespace."\\".$tokens[$i+2][1]; } } } return $classes; 

Вы можете использовать функцию автозагрузки.

 function __autoload($class_name) { include "special_directory/" .$class_name . '.php'; } 

И вы можете echo $ class_name. Но для этого требуется каталог с одним файлом.

Но стандартная практика заключается в том, чтобы иметь один класс в каждом файле в PHP. Итак, Main.class.php будет содержать основной класс. Вы можете использовать этот стандарт, если вы являетесь одной кодировкой.

Я потратил много времени на поиск пути. Из решения @ netcoder очевидно, что во всех решениях пока есть много недостатков.

Поэтому я решил сделать это вместо этого. Поскольку большинство классов PHP имеют имя класса, такое же, как имя файла, мы могли бы получить имя класса из имени файла. В зависимости от вашего проекта вы также можете иметь соглашение об именах. NB: этот класс допуска не имеет пространства имен

 <?php $path = '/path/to/a/class/file.php'; include $path; /*get filename without extension which is the classname*/ $classname = pathinfo(basename($path), PATHINFO_FILENAME); /* you can do all this*/ $classObj = new $classname(); /*dough the file name is classname you can still*/ get_class($classObj); //still return classname