Skip to content

Instantly share code, notes, and snippets.

@cwhite92
Last active August 26, 2024 16:17
Show Gist options
  • Save cwhite92/f0aaf008e1679b27768fbb8c884df6f7 to your computer and use it in GitHub Desktop.
Save cwhite92/f0aaf008e1679b27768fbb8c884df6f7 to your computer and use it in GitHub Desktop.
Get the FQCN from a PHP file specified by file path
<?php
public static function fqcnFromPath(string $path): string
{
$namespace = $class = $buffer = '';
$handle = fopen($path, 'r');
while (!feof($handle)) {
$buffer .= fread($handle, 512);
// Suppress warnings for cases where `$buffer` ends in the middle of a PHP comment.
$tokens = @token_get_all($buffer);
// Filter out whitespace and comments from the tokens, as they are irrelevant.
$tokens = array_filter($tokens, fn($token) => $token[0] !== T_WHITESPACE && $token[0] !== T_COMMENT);
// Reset array indexes after filtering.
$tokens = array_values($tokens);
foreach ($tokens as $index => $token) {
// The namespace is a `T_NAME_QUALIFIED` that is immediately preceded by a `T_NAMESPACE`.
if ($token[0] === T_NAMESPACE && isset($tokens[$index + 1]) && $tokens[$index + 1][0] === T_NAME_QUALIFIED) {
$namespace = $tokens[$index + 1][1];
continue;
}
// The class name is a `T_STRING` which makes it unreliable to match against, so check if we have a
// `T_CLASS` token with a `T_STRING` token ahead of it.
if ($token[0] === T_CLASS && isset($tokens[$index + 1]) && $tokens[$index + 1][0] === T_STRING) {
$class = $tokens[$index + 1][1];
}
}
if ($namespace && $class) {
// We've found both the namespace and the class, we can now stop reading and parsing the file.
break;
}
}
fclose($handle);
return $namespace.'\\'.$class;
}
@cwhite92
Copy link
Author

cwhite92 commented Sep 5, 2023

Based on my testing this works for the following examples:

namespace Foo\Bar;

use Bin\Qux;

class YourClass extends SomeOtherClass
{
    //
}
namespace Foo\Bar {
    use Bin\Qux;

    class YourClass extends SomeOtherClass
    {
        //
    }
};

and even some wonky whitespace or comments:

namespace Foo\Bar;

use Bin\Qux;

class

// I am a comment

YourClass
{
    //
}

@jlesueur
Copy link

There can be problems when a multiple of 512 lands in the middle of the class name. you can resolve it by making sure the class name was not the last token parsed by the token_get_all. Simplest way I found was to decrement a counter on every token, and set the counter to 2 when you find the class name. Then do if ($namespace && $class && $lastTokenIsClassNameCounter !== 1) {. That way if your class name was the last token in the buffer, and it may have gotten split, you do at least one more read from the file. Won't work if your php file is malformed, but then your php file is malformed :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment