- Determining what classes are defined in a PHP class file
- Solution 2
- Solution 3
- Solution 4
- Solution 5
- Saved searches
- Use saved searches to filter your results more quickly
- License
- violet-php/class-scanner
- Name already in use
- Sign In Required
- Launching GitHub Desktop
- Launching GitHub Desktop
- Launching Xcode
- Launching Visual Studio Code
- Latest commit
- Git stats
- Files
- README.md
- About
- Determining what classes are defined in a PHP class file
- Solution 2
- Solution 3
- Solution 4
- Solution 5
Determining what classes are defined in a PHP class file
I needed something like this for a project I am working on, and here are the functions I wrote:
function file_get_php_classes($filepath) < $php_code = file_get_contents($filepath); $classes = get_php_classes($php_code); return $classes; >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) < $class_name = $tokens[$i][1]; $classes[] = $class_name; >> return $classes; >
Solution 2
If you just want to check a file without loading it use token_get_all() :
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.
Found class: MyClass Found class: MyOtherClass
Solution 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
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 5
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.
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)
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
PHP Library for finding classes in PHP files by parsing them
License
violet-php/class-scanner
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
The Class Scanner library provides a convenient interface for finding classes defined in PHP source code. The purpose of this library is to be able to find classes within a class hierarchy without the need of actually executing the code and loading the classes into memory.
Normally if you want to find, for example, all child classes for a specific class in PHP, you would probably first need to include all the files that contain all the possible classes and then construct a hierarchy using class reflection by inspect all defined classes. Sometimes, however, including every file may not be possible, or may even be dangerous.
This library offers an alternative by allowing you to scan files for class definitions without the need of executing them in order to find classes based on their hierarchy or just for determining what classes are defined in which files.
Installation of this library is supported via composer. To install this library in your project, follow these steps:
- Install composer by following composer download instructions
- Add this library to your project by running composer require violet/class-scanner
The basic question this library intends to help answer, is which classes in your codebase extend a specific class.
Let’s say, for example, you want to find all classes in your project that extend that class Application\Controller . In order to do this, you could do:
require 'vendor/autoload.php'; $scanner = new Violet\ClassScanner\Scanner(); $scanner->scanDirectory('src'); foreach ($scanner->getSubClasses(Application\Controller::class) as $name) < echo $name; >
The scanner class provides the basic functionality of the library. For scanning class definitions from source code, it provides the following methods:
- scanFile(string $filename) — Scans this given filename for definitions
- scanDirectory(string $directory) — Scans all the files in the directory for definitions, but does not travers directories recursively
- scan(iterable $files) — Takes an iterable of file paths or instances of SplFileObject to scan.
- parse(string $code) — Parses the given string as PHP code to scan for class definitions.
All the scan* functions returns the scanner itself, so you can use it like a fluent interface, e.g.
require 'vendor/autoload.php'; $classes = (new Violet\ClassScanner\Scanner()) ->scanDirectory('controllers') ->scanDirectory('reporting') ->getClasses();
The parse() function, however, returns list of all the TypeDefinition instances from the parsed code.
To get the scanned classes, the scanner has two methods:
- getClasses(int $filter = TypeDefinition::TYPE_ANY) — Returns the names of all classes from the parsed files. The filter indicates the types of definitions to return.
- getSubClasses(string $class, int $filter = TypeDefinition::TYPE_CLASS) — Returns the names of all the child classes (inspected recursively) for the given class name. The second parameter allows to filter the types of returned names.
By default, the getClasses() returns all type definitions from the files. This includes classes, abstract classes, interfaces and traits. Both getClasses() and getSubClasses() have a filter parameter to return only specific kinds of types. The allowed types are:
- Violet\Scanner\TypeDefinition::TYPE_CLASS — Filters only instantiable classes and does not include abstract classes
- Violet\Scanner\TypeDefinition::TYPE_ABSTRACT — Filters for abstract class definitions
- Violet\Scanner\TypeDefinition::TYPE_INTERFACE — Filters for interfaces
- Violet\Scanner\TypeDefinition::TYPE_TRAIT — Filters for traits
- Violet\Scanner\TypeDefinition::TYPE_ANY — Filters for any type
The types can be combined with binary or operator, e.g. TypeDefinition::TYPE_CLASS | TypeDefinition::TYPE_ABSTRACT
If you need to implement more complex logic, you may want to use the TypeDefintion objects created from the parsed code. You can use the method getDefinitions(array $classes) to get definitions for the listed classes.
To get all definition from the scanned files you can use, for example:
$definitions = $scanner->getDefinitions($scanner->getClasses());
Note that even if you provide only one name, the array may contain multiple type definitions if the same type is defined multiple times in the scanned files.
Scanning all sub directories and excluding specific paths
About
PHP Library for finding classes in PHP files by parsing them
Determining what classes are defined in a PHP class file
I needed something like this for a project I am working on, and here are the functions I wrote:
function file_get_php_classes($filepath) < $php_code = file_get_contents($filepath); $classes = get_php_classes($php_code); return $classes; >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) < $class_name = $tokens[$i][1]; $classes[] = $class_name; >> return $classes; >
Solution 2
If you just want to check a file without loading it use token_get_all() :
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.
Found class: MyClass Found class: MyOtherClass
Solution 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
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 5
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.
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)