/ devlog / devscripts / check_model_vars.php
<?php
/* A method that asks a model for something without building one first
   dies where it asks. Yioop crashed on the query statistics screen that
   way: one arm of a switch built the model and returned, and the arm
   that drew the screen used the name it had left behind.

   This reads every method in the tree for a model variable used where
   nothing in that method built it. A variable a method is handed as an
   argument, or one built from anything at all, counts as built.

   php devlog/devscripts/check_model_vars.php
*/
$paths = [];
foreach (["src", "tests"] as $folder) {
    $walk = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($folder));
    foreach ($walk as $file) {
        if ($file->isDir() || substr($file->getFilename(), -4) !== ".php") {
            continue;
        }
        $paths[] = $file->getPathname();
    }
}
$missing = [];
foreach ($paths as $path) {
    $lines = file($path);
    $starts = [];
    foreach ($lines as $at => $one) {
        if (preg_match('/^\s*(?:public|private|protected|static|\s)*' .
            'function\s+(\w+)/', $one, $found)) {
            $starts[] = [$at, $found[1]];
        }
    }
    $starts[] = [count($lines), ""];
    for ($which = 0; $which < count($starts) - 1; $which++) {
        $from = $starts[$which][0];
        $to = $starts[$which + 1][0];
        $body = implode("", array_slice($lines, $from, $to - $from));
        /* A docblock before the next method is not part of this one,
           and reading it counted names that method is handed. */
        $doc_at = strpos($body, "\n    /**");
        if ($doc_at !== false) {
            $body = substr($body, 0, $doc_at);
        }
        /* A name that stands in a comment is not a use of it. */
        $body = preg_replace('/\/\*.*?\*\//s', "", $body);
        $body = preg_replace('/\/\/[^\n]*/', "", $body);
        if (!preg_match_all('/\$(\w*_model)\b/', $body, $found)) {
            continue;
        }
        foreach (array_unique($found[1]) as $name) {
            /* Given as an argument, or given a value anywhere. */
            if (preg_match('/function\s+\w+\s*\([^)]*\$' . $name . '\b/s',
                $body) || preg_match('/\$' . $name . '\s*=[^=]/', $body) ||
                preg_match('/\$' . $name . '\s*\)/', $body)) {
                continue;
            }
            $missing[] = basename($path) . "::" . $starts[$which][1] .
                " uses \$" . $name;
        }
    }
}
foreach ($missing as $one) {
    echo "  " . $one . "\n";
}
echo "check: " . (count($missing) ?
    count($missing) . " method(s) use a model nothing built" :
    "every model a method uses is built there") . "\n";
exit(count($missing) ? 1 : 0);
X