功能特色
- 遞迴掃描目錄,統計 `.php`(可自訂副檔名)檔案總數與行數
- 自動排除:單行註解(`//` `#`)、多行註解開頭(`/*` `*`)、空白行
- 支援跳過特定前綴檔案(如 `I_*.php`、`A_*.php` 介面/抽象類)
- 支援跳過目錄(`.svn`、`.git`、`.idea` 等)
- 可選擇是否顯示每檔詳細統計
核心類別
<?php
class CodeStat {
private $ext = '.php';
private $showEach = true;
private $fileSkip = []; // 檔名前綴
private $lineSkip = ['*', '/*', '//', '#'];
private $dirSkip = ['.', '..', '.svn', '.git', '.idea', 'vendor'];
public function setExt(string $ext) { $this->ext = strtolower($ext); }
public function setShowEach(bool $f) { $this->showEach = $f; }
public function setFileSkip(array $prefixes) { $this->fileSkip = $prefixes; }
public function setLineSkip(array $tags) { $this->lineSkip = array_merge($this->lineSkip, $tags); }
public function setDirSkip(array $dirs) { $this->dirSkip = $dirs; }
public function run(string $dir) {
if (!is_dir($dir)) throw new InvalidArgumentException("Not a dir: $dir");
$result = $this->scanDir($dir);
$this->output($dir, $result);
}
private function scanDir(string $dir): array {
$total = ['files' => 0, 'lines' => 0, 'allLines' => 0];
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
if (in_array($file, $this->dirSkip, true)) continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
$sub = $this->scanDir($path);
$total['files'] += $sub['files'];
$total['lines'] += $sub['lines'];
$total['allLines'] += $sub['allLines'];
} elseif ($this->validFile($file)) {
list($all, $valid) = $this->countFile($path);
$total['files']++;
$total['allLines'] += $all;
$total['lines'] += $valid;
}
}
closedir($dh);
return $total;
}
private function validFile(string $file): bool {
if (strtolower(pathinfo($file, PATHINFO_EXTENSION)) !== ltrim($this->ext, '.')) return false;
foreach ($this->fileSkip as $prefix) {
if (strpos($file, $prefix) === 0) return false;
}
return true;
}
private function countFile(string $file): array {
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$all = count(file($file)); // 含空行
$valid = 0;
foreach ($lines as $line) {
$trim = trim($line);
if ($trim === '') continue;
$skip = false;
foreach ($this->lineSkip as $tag) {
if (strpos($trim, $tag) === 0) { $skip = true; break; }
}
if (!$skip) $valid++;
}
if ($this->showEach) {
echo "$file\n 總行數: $all 有效行: $valid\n";
}
return [$all, $valid];
}
private function output(string $dir, array $r) {
echo str_repeat('=', 60) . "\n";
echo "$dir\n";
echo "總檔案數: {$r['files']}\n";
echo "總行數(含空行/註解): {$r['allLines']}\n";
echo "有效程式碼行數: {$r['lines']}\n";
}
}
使用範例
<?php
require 'CodeStat.php';
$stat = new CodeStat();
$stat->setShowEach(false); // 不顯示單檔細節
$stat->setFileSkip(['Test', 'Stub']); // 跳過 Test*、Stub* 開頭檔案
$stat->setDirSkip(['vendor', '.git', 'storage', 'bootstrap/cache']);
$stat->run(__DIR__ . '/../src');
輸出範例
============================================================
/var/www/project/src
總檔案數: 142
總行數(含空行/註解): 28,451
有效程式碼行數: 19,873
局限與改進
- 多行註解 `/* ... */` 中間不以 `*` 開頭的行無法偵測(需狀態機解析)。
- 大型專案建議用 **PHPLOC** (`phploc.phar`) 或 **SonarQube** 專業工具。
- 可擴充支援 `.inc`、`.module`、`.phtml` 等副檔名。