4

I'm looping over all the files in a directory. Now I want to get all the functions and classes defined in each of them. From there, I can examine them further using the ReflectionClass. I can't figure out how to get all the functions and classes defined in a file though.

ReflectionExtension looks the closest to what I want, except my files aren't part of an extension. Is there some class or function I'm overlooking?

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Are you having php parse the files via include/require or are you trying to parse the text yourself? – webbiedave Oct 03 '11 at 22:27
  • Reflection requires the files to be loaded in to the script. It sounds as though you want a tool more like [`PHP_Token_Stream`](https://github.com/sebastianbergmann/php-token-stream). – salathe Oct 03 '11 at 22:41
  • @salathe: Does `PHP_Token_Stream` have a bit more documentation than what's on the front page there? Doesn't really tell me what it does. – mpen Oct 03 '11 at 22:44
  • @webbiedave: Not quite sure what you're trying to ask. I'm not including/requiring the files at all. And I don't exactly want to "parse the text myself" -- I want to have the reflection classes do that for me, because parsing it myself would be a little bit insane :D – mpen Oct 03 '11 at 22:45

2 Answers2

2

Great question. get_declared_classes and get_defined_functions could be a good starting point. You would have to take note of what classes / functions are already defined when trying to determine what's in a given file.

Also, not sure what your end goal is here, but tools such as PHP Depend or PHP Mess Detector may do something similar to what you want. I'd recommend checking them out as well.

mfonda
  • 7,873
  • 1
  • 26
  • 30
  • `get_declared_classes` does exactly that though...gets only the declared ones. This means I'd have to include each of the files first. Problem with that is that many of these files are meant to be included in a specific way (are not self-contained/need other files to have been included first). Also, I don't want to run the files (some of them have little scripts outside of the functions/classes). – mpen Oct 03 '11 at 22:42
  • @Mark you'll have to load the classes if you want to use reflection. Perhaps check out the PHP_Token_Steam tool salathe linked to, or maybe try putting something together using php.net/token_get_all. – mfonda Oct 03 '11 at 22:54
  • Oh...I guess you're right. I would have to include them. Bummer. I still don't know what `PHP_Token_Stream` tool does. – mpen Oct 03 '11 at 22:59
0

This is the best I could come up with (courtesy):

function trimds($s) {
    return rtrim($s,DIRECTORY_SEPARATOR);
}

function joinpaths() {
    return implode(DIRECTORY_SEPARATOR, array_map('trimds', func_get_args()));
}

$project_dir = '/path/to/project/';

$ds = array($project_dir);

$classes = array();

while(!empty($ds)) {
    $dir = array_pop($ds);
    if(($dh=opendir($dir))!==false) {
        while(($file=readdir($dh))!==false) {
            if($file[0]==='.') continue;
            $path = joinpaths($dir,$file);
            if(is_dir($path)) {
                $ds[] = $path;
            } else {
                $contents = file_get_contents($path);
                $tokens = token_get_all($contents);
                for($i=0; $i<count($tokens); ++$i) {
                    if(is_array($tokens[$i]) && $tokens[$i][0] === T_CLASS) {
                        $i += 2;
                        $classes[] = $tokens[$i][1];
                    }
                }
            }
        }
    } else {
        echo "ERROR: Could not open directory '$dir'\n";
    }
}

print_r($classes);

Wish I didn't have to parse out the files and loop over all the tokens like this.


Forgot the former solutions prevents me from using reflection as I wanted. New solution:

$project_dir = '/path/to/project/';

$ds = array($project_dir);

while(!empty($ds)) {
    $dir = array_pop($ds);
    if(($dh=opendir($dir))!==false) {
        while(($file=readdir($dh))!==false) {
            if($file[0]==='.') continue;
            $path = joinpaths($dir,$file);
            if(is_dir($path)) {
                $ds[] = $path;
            } else {
                try{
                    include_once $path;
                }catch(Exception $e) {
                    echo 'EXCEPTION: '.$e->getMessage().PHP_EOL;
                }
            }
        }
    } else {
        echo "ERROR: Could not open directory '$dir'\n";
    }
}

foreach(get_declared_classes() as $c) {
    $class = new ReflectionClass($c);
    $methods = $class->getMethods();
    foreach($methods as $m) {
        $dc = $m->getDocComment();
        if($dc !== false) {
            echo $class->getName().'::'.$m->getName().PHP_EOL;
            echo $dc.PHP_EOL;
        }
    }
}
Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Unfortunately, this won't cover namespaced classes. – rich remer Jun 09 '14 at 20:39
  • @richremer You sure? There should be something within `ReflectionClass` to give you the fully qualified name, no? – mpen Jun 09 '14 at 22:54
  • I was looking at the first block, which doesn't use ReflectionClass. The second block will work with namespaces, but it appears to identify all classes, not just the ones defined in a particular file. – rich remer Jun 09 '14 at 23:19
  • I think the first solution is closer, it just needs to look for T_NAMESPACE tokens as well as T_CLASS tokens, and keep track of what namespace the parser would be in when encountering the class. It would still have potential issues with namespace aliases, though, I think. – rich remer Jun 09 '14 at 23:20
  • @richremer Yeah...the first solution is definitely preferable. If you fix it up, post it as an answer :-) – mpen Jun 09 '14 at 23:38
  • @richremer: Post it as a new answer. Editing someone else's code (except for whitespace formatting) will usually get rejected. – mpen Jun 10 '14 at 01:46