rootDir = rtrim($rootDir, '/'); $this->onlyViewVars = $onlyViewVars; } public function findDeclarations() { $this->scanDirectory($this->rootDir); if ($this->onlyViewVars) { return $this->viewVariables; } return $this->variables; } private function scanDirectory($dir) { $items = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($items as $item) { if ($item->isFile() && $item->getExtension() === 'php') { // Skip scanning this finder script itself if (basename($item->getPathname()) === basename($_SERVER['SCRIPT_FILENAME'])) { continue; } $this->extractDeclarationsFromFile($item->getPathname()); } } } private function extractDeclarationsFromFile($filePath) { $content = file_get_contents($filePath); // Remove comments to avoid false positives $content = preg_replace('!/\*.*?\*/!s', '', $content); $content = preg_replace('#//.*#', '', $content); $relativePath = str_replace($this->rootDir . '/', '', $filePath); // If we're looking for view variables specifically if ($this->onlyViewVars) { $this->findViewVariables($content, $relativePath); return; } // Otherwise proceed with normal variable declaration extraction $this->findAllVariableDeclarations($content, $relativePath); // Additionally find view variables but store them separately $this->findViewVariables($content, $relativePath); } private function findAllVariableDeclarations($content, $filePath) { // Patterns to match variable declarations $patterns = [ // Variable assignments '/\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*=(?!=)/', // Function parameters '/function\s+\w+\s*\(([^)]*)\)/', // foreach loops '/foreach\s*\(\s*\$\w+\s+as\s+(\$[^=)]+)/', // Class properties '/(?:public|private|protected|var)\s+(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/', // Global declarations '/global\s+(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\s*,\s*\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)/', // Static declarations '/static\s+(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\s*,\s*\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)/', // Catch exception blocks '/catch\s*\(\s*\w+\s+(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*\)/' ]; $declarations = []; // Process regular variable assignments if (preg_match_all($patterns[0], $content, $matches)) { foreach ($matches[1] as $var) { $declarations[] = '$' . $var; } } // Process function parameters if (preg_match_all($patterns[1], $content, $matches)) { foreach ($matches[1] as $paramList) { preg_match_all('/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $paramList, $params); $declarations = array_merge($declarations, $params[0]); } } // Process foreach variables if (preg_match_all($patterns[2], $content, $matches)) { foreach ($matches[1] as $foreachVars) { preg_match_all('/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $foreachVars, $vars); $declarations = array_merge($declarations, $vars[0]); } } // Process class properties if (preg_match_all($patterns[3], $content, $matches)) { $declarations = array_merge($declarations, $matches[1]); } // Process global declarations if (preg_match_all($patterns[4], $content, $matches)) { foreach ($matches[1] as $globalVars) { preg_match_all('/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $globalVars, $vars); $declarations = array_merge($declarations, $vars[0]); } } // Process static declarations if (preg_match_all($patterns[5], $content, $matches)) { foreach ($matches[1] as $staticVars) { preg_match_all('/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $staticVars, $vars); $declarations = array_merge($declarations, $vars[0]); } } // Process catch block variables if (preg_match_all($patterns[6], $content, $matches)) { $declarations = array_merge($declarations, $matches[1]); } if (!empty($declarations)) { $this->variables[$filePath] = array_unique($declarations); } } private function findViewVariables($content, $filePath) { // Patterns specifically for $view declarations $viewPatterns = [ // $view = "..." '/\$view\s*=\s*([^;]+)/', // $view .= "..." '/\$view\s*\.=\s*([^;]+)/' ]; $viewVars = []; foreach ($viewPatterns as $pattern) { if (preg_match_all($pattern, $content, $matches)) { foreach ($matches[1] as $viewContent) { // Find variables within the view content // Look for PHP variable pattern with braces like {$variable} if (preg_match_all('/\{\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/', $viewContent, $varMatches)) { foreach ($varMatches[1] as $var) { $viewVars[] = '$' . $var; } } // Look for PHP variable pattern without braces in double-quoted strings if (strpos($viewContent, '"') !== false && preg_match_all('/"\s*\.\s*\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*\.\s*"/', $viewContent, $varMatches)) { foreach ($varMatches[1] as $var) { $viewVars[] = '$' . $var; } } // Look for variables in double-quoted strings if (preg_match_all('/"[^"]*\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[^"]*"/', $viewContent, $varMatches)) { foreach ($varMatches[1] as $var) { $viewVars[] = '$' . $var; } } } } } if (!empty($viewVars)) { $this->viewVariables[$filePath] = array_unique($viewVars); } } public function getHtmlResults() { $resultData = $this->onlyViewVars ? $this->viewVariables : $this->variables; $totalVars = 0; $totalFiles = 0; $html = '
Total files with $view variables: ' . $totalFiles . '
'; $html .= 'Total variables found in $view declarations: ' . $totalVars . '
'; } else { $html .= 'Total files with variable declarations: ' . $totalFiles . '
'; $html .= 'Total variable declarations found: ' . $totalVars . '
'; // Show view variables summary if not in view-only mode if (!empty($this->viewVariables)) { $viewVarCount = 0; $viewFileCount = count($this->viewVariables); foreach ($this->viewVariables as $vars) { $viewVarCount += count($vars); } $html .= 'Additionally found ' . $viewVarCount . ' variables in $view declarations across ' . $viewFileCount . ' files
'; $html .= '