專案遷移、舊系統整合、跨團隊協作常面臨 GBK/UTF-8 混雜的檔案編碼問題。本文提供生產級 EncodingConverter 類,支援單檔/目錄、遞迴掃描、編碼自動偵測驗證、可讀寫檢查、異常處理,避免重複轉碼導致亂碼。
<?php
class EncodingConverter {
private string $fromEncoding;
private string $toEncoding;
private string $path;
private bool $isDirectory;
private bool $recursive;
private array $files = [];
public function __construct() {
if (!function_exists('mb_convert_encoding')) {
throw new RuntimeException('mbstring extension required');
}
}
public function setPath(string $path, bool $isDirectory = false, bool $recursive = false): self {
$this->path = $path;
$this->isDirectory = $isDirectory;
$this->recursive = $recursive;
return $this;
}
public function setEncoding(string $from, string $to): self {
$this->fromEncoding = $from;
$this->toEncoding = $to;
return $this;
}
public function convert(): bool {
return $this->isDirectory ? $this->convertDirectory() : $this->convertFile();
}
private function convertFile(): bool {
$realPath = $this->validatePath($this->path, false);
$content = file_get_contents($realPath);
if ($this->isValidEncoding($content, $this->fromEncoding)) {
$converted = mb_convert_encoding($content, $this->toEncoding, $this->fromEncoding);
file_put_contents($realPath, $converted);
}
return true;
}
private function convertDirectory(): bool {
$realPath = $this->validatePath($this->path, true);
$this->scanFiles($realPath);
if (empty($this->files)) {
throw new RuntimeException("Directory empty: $realPath");
}
foreach ($this->files as $file) {
$content = file_get_contents($file);
if ($this->isValidEncoding($content, $this->fromEncoding)) {
$converted = mb_convert_encoding($content, $this->toEncoding, $this->fromEncoding);
file_put_contents($file, $converted);
}
}
return true;
}
private function validatePath(string $path, bool $isDir): string {
if (!file_exists($path)) throw new RuntimeException("Path not found: $path");
if ($isDir && !is_dir($path)) throw new RuntimeException("Not a directory: $path");
if (!$isDir && !is_file($path)) throw new RuntimeException("Not a file: $path");
if (!is_readable($path) || !is_writable($path)) {
throw new RuntimeException("Path not readable/writable: $path");
}
return realpath($path);
}
private function isValidEncoding(string $content, string $encoding): bool {
// 往返驗證:編碼 -> 目標 -> 原編碼,MD5 不變即為有效
$converted = mb_convert_encoding($content, $encoding, $encoding);
return md5($converted) === md5($content);
}
private function scanFiles(string $dir): void {
$entries = scandir($dir);
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..' || $entry[0] === '.') continue;
$fullPath = $dir . DIRECTORY_SEPARATOR . $entry;
if (is_dir($fullPath)) {
if ($this->recursive) $this->scanFiles($fullPath);
} elseif (is_file($fullPath)) {
$this->files[] = $fullPath;
}
}
}
}
class EncodingException extends Exception {}
<?php
require_once 'EncodingConverter.php';
try {
$converter = new EncodingConverter();
// 單檔案
$converter->setPath('config.php')
->setEncoding('GBK', 'UTF-8')
->convert();
// 目錄遞迴
$converter->setPath('src/', true, true)
->setEncoding('GBK', 'UTF-8')
->convert();
echo "轉換完成\n";
} catch (EncodingException $e) {
echo "錯誤: " . $e->getMessage() . "\n";
}
mb_convert_encoding($str, $enc, $enc))配合 MD5 比對,精準判斷檔案是否屬於源編碼,**避免已是 UTF-8 的檔案被誤判為 GBK 再次轉碼導致亂碼**。EncodingConverter 類以「驗證後轉換」為核心原則,解決批量編碼轉換中最怕的「重複轉碼亂碼」問題。結合異常處理、鏈式 API、遞迴掃描,可直接投入生產環境作為遷移工具或 CI/CD 流水線步驟。