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

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

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

4b9b3361

Ответ 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