У меня есть php файл, содержащий только один класс. как я могу узнать, какой класс существует, зная имя файла? Я знаю, что могу сделать что-то с регулярным выражением, но есть ли стандартный способ php? (файл уже включен в страницу, которая пытается определить имя класса).
Получить имя класса из файла
Ответ 1
Существует несколько возможных решений этой проблемы, каждая из которых имеет свои преимущества и недостатки. Вот они, это знать, чтобы решить, какой из них вы хотите.
Tokenizer
Этот метод использует токенизатор и считывает части файла до тех пор, пока не найдет определение класса.
<сильные > Преимущества
- Не нужно полностью анализировать файл
- Быстро (читает только начало файла)
- Мало ли шансов на ложные срабатывания
Недостатки
- Самое длинное решение
код
$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];
}
}
}
}
}
Регулярные выражения
Используйте регулярные выражения для синтаксического анализа начала файла, пока не будет найдено определение класса.
<сильные > Преимущества
- Не нужно полностью анализировать файл
- Быстро (читает только начало файла)
Недостатки
- Высокие шансы на ложные срабатывания (например:
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
Вы также можете использовать описанное выше, чтобы определить пространство имен, которое объявляет файл, независимо от того, содержит ли он класс или нет.
Ответ 2
Вы можете заставить PHP выполнить эту работу, просто включив файл и получив последний объявленный класс:
$file = 'class.php'; # contains class Foo
include($file);
$classes = get_declared_classes();
$class = end($classes);
echo $class; # Foo
Если вам нужно изолировать это, заверните его в командную строку script и выполните его с помощью shell_exec
:
$file = 'class.php'; # contains class Foo
$class = shell_exec("php -r \"include('$file'); echo end(get_declared_classes());\"");
echo $class; # Foo
Если вам не нравятся скрипты командной строки, вы можете сделать это как этот вопрос, однако этот код не отражает пространства имен.
Ответ 3
Вы можете получить все объявленные классы, прежде чем включать файл, используя get_declared_classes
. Сделайте то же самое после того, как вы включили его, и сравните их с чем-то вроде array_diff
, и у вас есть новый добавленный класс.
Ответ 4
Благодаря некоторым людям из 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];
}
Ответ 5
Я изменил 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;
}
}
Ответ 6
Вы можете сделать это двумя способами:
- сложное решение: откройте файл и через regex извлеките имя класса (например,
/class ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/
) - простое решение: укажите все ваши php файлы с содержащимся именем класса (например: class
TestFoo
в файлеTestFoo.php
илиTestFoo.class.php
)
Ответ 7
$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] ...)
Ответ 8
Этот образец возвращает все классы. Если вы ищете класс, который является производным от определенного, используйте 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;
Ответ 9
Возможно, вы сможете использовать функцию автозагрузки.
function __autoload($class_name) {
include "special_directory/" .$class_name . '.php';
}
И вы можете echo $class_name. Но для этого требуется каталог с одним файлом.
Но стандартная практика - иметь один класс в каждом файле в PHP. Итак, Main.class.php будет содержать основной класс. Вы можете использовать этот стандарт, если вы являетесь одной кодировкой.
Ответ 10
Я потратил много времени на то, чтобы найти способ обойти это. Из решения @netcoder очевидно, что во всех решениях есть много недостатков.
Поэтому я решил сделать это вместо этого. Поскольку большинство классов PHP имеют имя класса, такое же, как имя файла, мы могли бы получить имя класса из имени файла. В зависимости от вашего проекта вы также можете иметь соглашение об именах. NB: этот класс accept не имеет пространства имен
<?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