'Determining what classes are defined in a PHP class file
Given that each PHP file in our project contains a single class definition, how can I determine what class or classes are defined within the file?
I know I could just regex the file for class statements, but I'd prefer to do something that's more efficient.
Solution 1:[1]
If you just want to check a file without loading it use token_get_all():
<?php
header('Content-Type: text/plain');
$php_file = file_get_contents('c2.php');
$tokens = token_get_all($php_file);
$class_token = false;
foreach ($tokens as $token) {
if (is_array($token)) {
if ($token[0] == T_CLASS) {
$class_token = true;
} else if ($class_token && $token[0] == T_STRING) {
echo "Found class: $token[1]\n";
$class_token = false;
}
}
}
?>
Basically, this is a simple finite state machine. In PHP the sequence of tokens will be:
T_CLASS: 'class' keyword;T_WHITESPACE: space(s) after 'class';T_STRING: name of class.
So this code will handle any weird spacing or newlines you get just fine because it's using the same parser PHP uses to execute the file. If token_get_all() can't parse it, neither can PHP.
By the way, you use token_name() to turn a token number into it's constant name.
Here is my c2.php:
<?php
class MyClass {
public __construct() {
}
}
class MyOtherClass {
public __construct() {
}
}
?>
Output:
Found class: MyClass
Found class: MyOtherClass
Solution 2:[2]
Or you could easily use AnnotationsParser from Nette\Reflection (installable using composer):
use Nette\Reflection\AnnotationsParser;
$classes = AnnotationsParser::parsePhp(file_get_contents($fileName));
var_dump($classes);
Output will be then something like this:
array(1) {
["Your\Class\Name"] =>
array(...) {
// property => comment
},
["Your\Class\Second"] =>
array(...) {
// property => comment
},
}
The parsePhp() method basically does something similar as examples in other answers, but you don't have to declare nor test the parsing yourselves.
Solution 3:[3]
I needed parse classes from file with namespaces, so I modified code. If somebody need too, here is it:
public function getPhpClasses($phpcode) {
$classes = array();
$namespace = 0;
$tokens = token_get_all($phpcode);
$count = count($tokens);
$dlm = false;
for ($i = 2; $i < $count; $i++) {
if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] == "phpnamespace" || $tokens[$i - 2][1] == "namespace")) ||
($dlm && $tokens[$i - 1][0] == T_NS_SEPARATOR && $tokens[$i][0] == T_STRING)) {
if (!$dlm) $namespace = 0;
if (isset($tokens[$i][1])) {
$namespace = $namespace ? $namespace . "\\" . $tokens[$i][1] : $tokens[$i][1];
$dlm = true;
}
}
elseif ($dlm && ($tokens[$i][0] != T_NS_SEPARATOR) && ($tokens[$i][0] != T_STRING)) {
$dlm = false;
}
if (($tokens[$i - 2][0] == T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] == "phpclass"))
&& $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING) {
$class_name = $tokens[$i][1];
if (!isset($classes[$namespace])) $classes[$namespace] = array();
$classes[$namespace][] = $class_name;
}
}
return $classes;
}
Solution 4:[4]
My snippet too. Can parse files with multiple classes, interfaces, arrays and namespaces. Returns an array with classes+types (class, interface, abstract) divided by namespaces.
<?php
/**
*
* Looks what classes and namespaces are defined in that file and returns the first found
* @param String $file Path to file
* @return Returns NULL if none is found or an array with namespaces and classes found in file
*/
function classes_in_file($file)
{
$classes = $nsPos = $final = array();
$foundNS = FALSE;
$ii = 0;
if (!file_exists($file)) return NULL;
$er = error_reporting();
error_reporting(E_ALL ^ E_NOTICE);
$php_code = file_get_contents($file);
$tokens = token_get_all($php_code);
$count = count($tokens);
for ($i = 0; $i < $count; $i++)
{
if(!$foundNS && $tokens[$i][0] == T_NAMESPACE)
{
$nsPos[$ii]['start'] = $i;
$foundNS = TRUE;
}
elseif( $foundNS && ($tokens[$i] == ';' || $tokens[$i] == '{') )
{
$nsPos[$ii]['end']= $i;
$ii++;
$foundNS = FALSE;
}
elseif ($i-2 >= 0 && $tokens[$i - 2][0] == T_CLASS && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING)
{
if($i-4 >=0 && $tokens[$i - 4][0] == T_ABSTRACT)
{
$classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'ABSTRACT CLASS');
}
else
{
$classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'CLASS');
}
}
elseif ($i-2 >= 0 && $tokens[$i - 2][0] == T_INTERFACE && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING)
{
$classes[$ii][] = array('name' => $tokens[$i][1], 'type' => 'INTERFACE');
}
}
error_reporting($er);
if (empty($classes)) return NULL;
if(!empty($nsPos))
{
foreach($nsPos as $k => $p)
{
$ns = '';
for($i = $p['start'] + 1; $i < $p['end']; $i++)
$ns .= $tokens[$i][1];
$ns = trim($ns);
$final[$k] = array('namespace' => $ns, 'classes' => $classes[$k+1]);
}
$classes = $final;
}
return $classes;
}
Outputs something like this...
array
'namespace' => string 'test\foo' (length=8)
'classes' =>
array
0 =>
array
'name' => string 'bar' (length=3)
'type' => string 'CLASS' (length=5)
1 =>
array
'name' => string 'baz' (length=3)
'type' => string 'INTERFACE' (length=9)
array
'namespace' => string 'this\is\a\really\big\namespace\for\testing\dont\you\think' (length=57)
'classes' =>
array
0 =>
array
'name' => string 'yes_it_is' (length=9)
'type' => string 'CLASS' (length=5)
1 =>
array
'name' => string 'damn_too_big' (length=12)
'type' => string 'ABSTRACT CLASS' (length=14)
2 =>
array
'name' => string 'fogo' (length=6)
'type' => string 'INTERFACE' (length=9)
Might help someone!
Solution 5:[5]
Use PHP's function get_declared_classes(). This returns an array of classes defined in the current script.
Solution 6:[6]
I've extended Venkat D's answer a bit to include returning the methods, and to search through a directory. (This specific example is built for CodeIgniter, which will return all the methods in the ./system/application/controller files - in other words, every public url that you can call through the system.)
function file_get_php_classes($filepath,$onlypublic=true) {
$php_code = file_get_contents($filepath);
$classes = get_php_classes($php_code,$onlypublic);
return $classes;
}
function get_php_classes($php_code,$onlypublic) {
$classes = array();
$methods=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];
$methods[$class_name] = array();
}
if ($tokens[$i - 2][0] == T_FUNCTION
&& $tokens[$i - 1][0] == T_WHITESPACE
&& $tokens[$i][0] == T_STRING) {
if ($onlypublic) {
if ( !in_array($tokens[$i-4][0],array(T_PROTECTED, T_PRIVATE))) {
$method_name = $tokens[$i][1];
$methods[$class_name][] = $method_name;
}
} else {
$method_name = $tokens[$i][1];
$methods[$class_name][] = $method_name;
}
}
}
return $methods;
}
function mapSystemClasses($controllerdir="./system/application/controllers/",$onlypublic=true) {
$result=array();
$dh=opendir($controllerdir);
while (($file = readdir($dh)) !== false) {
if (substr($file,0,1)!=".") {
if (filetype($controllerdir.$file)=="file") {
$classes=file_get_php_classes($controllerdir.$file,$onlypublic);
foreach($classes as $class=>$method) {
$result[]=array("file"=>$controllerdir.$file,"class"=>$class,"method"=>$method);
}
} else {
$result=array_merge($result,mapSystemClasses($controllerdir.$file."/",$onlypublic));
}
}
}
closedir($dh);
return $result;
}
Solution 7:[7]
You can ignore abstract classes like this (note the T_ABSTRACT token):
function get_php_classes($php_code)
{
$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 && !($tokens[$i - 3] && $i - 4 >= 0 && $tokens[$i - 4][0] == T_ABSTRACT))
{
$class_name = $tokens[$i][1];
$classes[] = $class_name;
}
}
return $classes;
}
Solution 8:[8]
Nowadays (2022) you can use the Composer package roave/better-reflection.
To get all classes defined in a file you can use this code with version 5 of the package:
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\Reflector\DefaultReflector;
use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator;
$astLocator = (new BetterReflection())->astLocator();
$reflector = new DefaultReflector(new SingleFileSourceLocator('path/to/file.php', $astLocator));
$classes = $reflector->reflectAllClasses();
$classNames = [];
foreach ($classes as $class) {
$classNames[] = $class->getName();
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jeremy Harris |
| Solution 2 | Ondrej Machulda |
| Solution 3 | frosty22 |
| Solution 4 | |
| Solution 5 | James Skidmore |
| Solution 6 | Jason Norwood-Young |
| Solution 7 | |
| Solution 8 | Michael D. |
