FTP (File Transfer Protocol) 是服務器間傳輸文件的常用協議。PHP 內置 ftp_* 系列函數,無需第三方擴展即可實現連接、上傳、下載、目錄操作。本文提供一個封裝良好的上傳類與常見問題處理。
| 函數 | 用途 |
|---|---|
| ftp_connect() | 建立連接 |
| ftp_login() | 用戶名密碼登錄 |
| ftp_pasv() | 設置被動模式(穿透防火牆/NAT 必須) |
| ftp_put() | 上傳文件 (FTP_ASCII / FTP_BINARY) |
| ftp_fput() | 從文件指針上傳(大文件流式) |
| ftp_mkdir() / ftp_chdir() | 創建/切換遠程目錄 |
| ftp_close() | 關閉連接 |
<?php
/**
* PHP FTP 客戶端封裝
* 支持鏈式調用、自動創建遠程目錄、錯誤異常拋出
*/
class FtpUploader {
private $conn;
private $config;
public function __construct(array $config) {
$this->config = array_merge([
'host' => 'ftp.example.com',
'port' => 21,
'user' => '',
'pass' => '',
'pasv' => true, // 默認被動模式
'timeout' => 30,
'ssl' => false, // true 使用 ftp_ssl_connect (FTPS)
], $config);
}
/** 連接並登錄 */
public function connect(): self {
$func = $this->config['ssl'] ? 'ftp_ssl_connect' : 'ftp_connect';
$this->conn = @$func($this->config['host'], $this->config['port'], $this->config['timeout']);
if (!$this->conn) {
throw new RuntimeException("FTP 連接失敗: {$this->config['host']}:{$this->config['port']}");
}
if (!@ftp_login($this->conn, $this->config['user'], $this->config['pass'])) {
throw new RuntimeException("FTP 登錄失敗: 用戶 {$this->config['user']}");
}
ftp_pasv($this->conn, $this->config['pasv']);
return $this;
}
/** 確保遠程目錄存在,遞歸創建 */
public function ensureDir(string $remotePath): self {
$dirs = explode('/', trim($remotePath, '/'));
$current = '';
foreach ($dirs as $dir) {
if (!$dir) continue;
$current .= "/$dir";
if (!@ftp_chdir($this->conn, $current)) {
if (!@ftp_mkdir($this->conn, $current)) {
throw new RuntimeException("無法創建遠程目錄: $current");
}
ftp_chdir($this->conn, $current);
}
}
return $this;
}
/**
* 上傳單個文件
* @param string $localPath 本地絕對路徑
* @param string $remotePath 遠程相對路徑 (含文件名)
* @param int $mode FTP_BINARY (默認) 或 FTP_ASCII
*/
public function upload(string $localPath, string $remotePath, int $mode = FTP_BINARY): bool {
if (!is_file($localPath)) {
throw new InvalidArgumentException("本地文件不存在: $localPath");
}
// 確保遠程目錄存在
$remoteDir = dirname($remotePath);
if ($remoteDir !== '.') $this->ensureDir($remoteDir);
$result = @ftp_put($this->conn, $remotePath, $localPath, $mode);
if (!$result) {
throw new RuntimeException("FTP 上傳失敗: $localPath => $remotePath");
}
return true;
}
/** 大文件流式上傳 (內存友好) */
public function uploadStream(string $localPath, string $remotePath, int $mode = FTP_BINARY): bool {
if (!is_file($localPath)) throw new InvalidArgumentException("文件不存在: $localPath");
$remoteDir = dirname($remotePath);
if ($remoteDir !== '.') $this->ensureDir($remoteDir);
$fp = fopen($localPath, 'rb');
if (!$fp) throw new RuntimeException("無法打開本地文件: $localPath");
$result = @ftp_fput($this->conn, $remotePath, $fp, $mode);
fclose($fp);
if (!$result) throw new RuntimeException("FTP 流式上傳失敗: $remotePath");
return true;
}
/** 關閉連接 */
public function close(): void {
if ($this->conn) {
@ftp_close($this->conn);
$this->conn = null;
}
}
/** 析構函數保證關閉 */
public function __destruct() {
$this->close();
}
}
// ===== 使用示例 =====
try {
$ftp = new FtpUploader([
'host' => '192.168.1.100',
'user' => 'backup_user',
'pass' => 'secret123',
'pasv' => true,
])->connect();
// 小文件直接上傳
$ftp->upload('/data/export/report_202405.csv', 'reports/report_202405.csv');
// 大文件 (如 > 100MB) 用流式
$ftp->uploadStream('/data/backup/db_dump.sql.gz', 'backups/db_dump_202405.sql.gz');
echo "所有文件上傳成功!\n";
} catch (Exception $e) {
echo "上傳異常: " . $e->getMessage() . "\n";
// 記錄日志、告警
}
?>
ftp_pasv(true),否則數據連接無法建立(表現為連接成功但上傳卡死或超時)。FTP_BINARY。僅純文本文件且跨系統 (Windows↔Linux) 換行符需轉換時用 FTP_ASCII。圖片、壓縮包、視頻必須二進制。ftp_connect 的 timeout,並設置 PHP set_time_limit(0) 或 ini_set('max_execution_time', 0)。ftp_ssl_connect() 並確保服務器支持隱式/顯式 FTPS。證書驗證問題可暫時關閉 stream_context_set_option(不推薦生產)。ftp_put 返回 false 時,調用 error_get_last() 或檢查 FTP 服務器日志排查具體原因 (磁盤滿、配額超限、權限拒絕)。REST 指令 (PHP 無原生封裝)。需用 ftp_raw($conn, "REST $offset") 然後 ftp_fput 從指定偏移追加。配合本地文件 fseek 實現。pcntl_fork、swoole、或 Guzzle + FTP 協議適配器 實現異步併發。PHP FTP 操作核心在於:被動模式、二進制傳輸、目錄預創建、異常處理、資源關閉。封裝為可復用類並結合日志監控,即可構建穩健的文件分發、備份同步系統。