/ devlog / devscripts / check_model_calls.php
<?php
/* Every call made on the group, wiki or feed model, checked against what
   that model holds. Splitting GroupModel into three left calls pointing
   at whichever model no longer held the method, and each one was found
   by Chris running the site rather than by a check. The arrow may end a
   line, which is how isThreadSubscribed went unseen twice.

   php devlog/devscripts/check_model_calls.php
*/
$holds = [];
$models = ["wiki" => "src/models/WikiModel.php",
    "feed" => "src/models/FeedModel.php",
    "group" => "src/models/GroupModel.php"];
foreach ($models as $which => $path) {
    preg_match_all('/function\s+(\w+)\s*\(/', file_get_contents($path), $m);
    $holds[$which] = $m[1];
}
preg_match_all('/function\s+(\w+)\s*\(/',
    file_get_contents("src/models/Model.php"), $m);
$base = $m[1];
$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();
    }
}
$paths[] = "index.php";
$missing = [];
foreach ($paths as $path) {
    if (str_contains($path, "/models/")) {
        continue;
    }
    $text = file_get_contents($path);
    foreach (array_keys($models) as $which) {
        $reachable = array_merge($holds[$which], $base);
        if ($which !== "group") {
            $reachable = array_merge($reachable, $holds["group"]);
        }
        $patterns = ['/\$\w*' . $which . '_model->\s*(\w+)\s*\(/s',
            '/model\([\'"]' . $which . '[\'"]\)->\s*(\w+)\s*\(/s'];
        foreach ($patterns as $pattern) {
            if (!preg_match_all($pattern, $text, $found)) {
                continue;
            }
            foreach (array_unique($found[1]) as $name) {
                if (in_array($name, $reachable)) {
                    continue;
                }
                $missing[] = $which . " model asked for " . $name . " in " .
                    basename($path);
            }
        }
    }
}
foreach ($missing as $one) {
    echo "  " . $one . "\n";
}
echo "check: " . (count($missing) ?
    count($missing) . " call(s) on a model that does not hold the method" :
    "every model call reaches a method that exists") . "\n";
exit(count($missing) ? 1 : 0);
X