Получить имя класса из файла - PullRequest
21 голосов
/ 23 августа 2011

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

Ответы [ 10 ]

48 голосов
/ 23 августа 2011

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

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 , измененное для включения переменной $ namespace , содержащей пространство имен класса, если применимо:

$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

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

6 голосов
/ 23 августа 2011

Вы можете заставить 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

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

4 голосов
/ 06 октября 2016

Благодаря некоторым людям из 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];
}
3 голосов
/ 20 июня 2017

Я изменил 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;
    }
}
2 голосов
/ 19 сентября 2012
$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] ...)
2 голосов
/ 23 августа 2011

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

1 голос
/ 23 ноября 2017

Я потратил много продуктивного времени, пытаясь найти способ обойти это. Из решения @ 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
1 голос
/ 10 января 2013

Этот образец возвращает все классы.Если вы ищете класс, производный от конкретного, используйте 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;
1 голос
/ 23 августа 2011

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

  • комплексное решение : откройте файл и с помощью регулярных выражений извлеките имя класса (например, /class ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/)
  • просто решение : присвойте всем вашим php-файлам имя класса (например: класс TestFoo в файле TestFoo.php или TestFoo.class.php)
0 голосов
/ 23 августа 2011

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...