本文目录导读:

我来为您介绍几种PHP项目目录同步的脚本编写方法:
基础文件同步脚本
<?php
class DirectorySync {
private $sourceDir;
private $targetDir;
private $logFile;
public function __construct($source, $target, $log = 'sync.log') {
$this->sourceDir = rtrim($source, '/') . '/';
$this->targetDir = rtrim($target, '/') . '/';
$this->logFile = $log;
}
public function sync() {
$this->log("开始同步: {$this->sourceDir} -> {$this->targetDir}");
$this->syncDirectory($this->sourceDir, $this->targetDir);
$this->log("同步完成");
}
private function syncDirectory($source, $target) {
if (!is_dir($target)) {
mkdir($target, 0755, true);
$this->log("创建目录: {$target}");
}
$items = scandir($source);
foreach ($items as $item) {
if ($item == '.' || $item == '..') continue;
$sourcePath = $source . $item;
$targetPath = $target . $item;
if (is_file($sourcePath)) {
$this->syncFile($sourcePath, $targetPath);
} elseif (is_dir($sourcePath)) {
if (!$this->shouldExclude($item)) {
$this->syncDirectory($sourcePath, $targetPath);
}
}
}
}
private function syncFile($source, $target) {
// 检查文件是否存在或是否需要更新
if (!file_exists($target) ||
md5_file($source) !== md5_file($target)) {
copy($source, $target);
$this->log("同步文件: {$source}");
}
}
private function shouldExclude($name) {
$exclude = ['.git', 'node_modules', '.idea', 'vendor'];
return in_array($name, $exclude);
}
private function log($message) {
$time = date('Y-m-d H:i:s');
file_put_contents($this->logFile, "[{$time}] {$message}\n", FILE_APPEND);
echo "[{$time}] {$message}\n";
}
}
// 使用示例
$sync = new DirectorySync('/path/to/source', '/path/to/target');
$sync->sync();
?>
实时监控同步脚本(使用inotify)
<?php
class RealtimeSync {
private $sourceDir;
private $targetDir;
private $watchProcess;
public function __construct($source, $target) {
$this->sourceDir = $source;
$this->targetDir = $target;
}
public function watch() {
// 使用 inotifywait (需要安装 inotify-tools)
$command = sprintf(
'inotifywait -m -r -e modify,create,delete,move %s --format "%%w%%e%%f"',
escapeshellarg($this->sourceDir)
);
$descriptors = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
];
$this->watchProcess = proc_open($command, $descriptors, $pipes);
if (is_resource($this->watchProcess)) {
while ($line = fgets($pipes[1])) {
$this->handleEvent(trim($line));
}
}
}
private function handleEvent($event) {
// 解析事件并执行同步
$parts = explode(' ', $event);
$action = $parts[0] ?? '';
$file = $parts[1] ?? '';
switch ($action) {
case 'CREATE':
case 'MODIFY':
$this->copyFile($file);
break;
case 'DELETE':
$this->deleteFile($file);
break;
case 'MOVED_FROM':
case 'MOVED_TO':
$this->moveFile($file);
break;
}
}
private function copyFile($file) {
$sourcePath = $this->sourceDir . '/' . $file;
$targetPath = $this->targetDir . '/' . $file;
$targetDir = dirname($targetPath);
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
if (is_file($sourcePath)) {
copy($sourcePath, $targetPath);
echo "已同步: {$file}\n";
}
}
}
?>
基于MD5校验的增量同步脚本
<?php
class IncrementalSync {
private $source;
private $target;
private $checksumFile = '.sync_checksum.json';
public function __construct($source, $target) {
$this->source = rtrim($source, '/');
$this->target = rtrim($target, '/');
}
public function smartSync() {
// 读取上一次的校验和
$oldChecksums = $this->loadChecksums();
$newChecksums = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->source)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$relativePath = str_replace($this->source . '/', '', $file->getPathname());
$md5 = md5_file($file->getPathname());
$newChecksums[$relativePath] = $md5;
// 检查是否需要同步
if (!isset($oldChecksums[$relativePath]) ||
$oldChecksums[$relativePath] !== $md5) {
$this->syncFile($relativePath);
}
}
}
// 检测删除的文件
foreach ($oldChecksums as $file => $checksum) {
if (!isset($newChecksums[$file])) {
$this->deleteFile($file);
}
}
// 保存新的校验和
$this->saveChecksums($newChecksums);
}
private function syncFile($relativePath) {
$sourcePath = $this->source . '/' . $relativePath;
$targetPath = $this->target . '/' . $relativePath;
$targetDir = dirname($targetPath);
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
copy($sourcePath, $targetPath);
echo "同步: {$relativePath}\n";
}
private function deleteFile($relativePath) {
$targetPath = $this->target . '/' . $relativePath;
if (file_exists($targetPath)) {
unlink($targetPath);
echo "删除: {$relativePath}\n";
}
}
private function loadChecksums() {
$file = $this->target . '/' . $this->checksumFile;
if (file_exists($file)) {
return json_decode(file_get_contents($file), true) ?: [];
}
return [];
}
private function saveChecksums($checksums) {
$file = $this->target . '/' . $this->checksumFile;
file_put_contents($file, json_encode($checksums, JSON_PRETTY_PRINT));
}
}
?>
简单的命令行同步工具
#!/usr/bin/env php
<?php
// sync.php - 命令行同步工具
if ($argc < 3) {
echo "使用方法: php sync.php <源目录> <目标目录> [选项]\n";
echo "选项:\n";
echo " --exclude=dir1,dir2 排除目录\n";
echo " --delete 删除目标中不存在的文件\n";
echo " --dry-run 模拟运行\n";
exit(1);
}
$source = $argv[1];
$target = $argv[2];
// 解析选项
$options = [
'exclude' => [],
'delete' => false,
'dry-run' => false
];
for ($i = 3; $i < $argc; $i++) {
$arg = $argv[$i];
if (strpos($arg, '--exclude=') === 0) {
$options['exclude'] = explode(',', substr($arg, 10));
} elseif ($arg == '--delete') {
$options['delete'] = true;
} elseif ($arg == '--dry-run') {
$options['dry-run'] = true;
}
}
// 执行同步
syncDirectories($source, $target, $options);
function syncDirectories($source, $target, $options) {
if (!is_dir($source)) {
echo "错误: 源目录不存在\n";
exit(1);
}
if (!$options['dry-run'] && !is_dir($target)) {
mkdir($target, 0755, true);
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($files as $file) {
$relativePath = str_replace($source . '/', '', $file->getPathname());
// 检查排除
foreach ($options['exclude'] as $exclude) {
if (strpos($relativePath, $exclude) === 0) {
continue 2;
}
}
$targetPath = $target . '/' . $relativePath;
if ($file->isDir()) {
if (!$options['dry-run'] && !is_dir($targetPath)) {
mkdir($targetPath, 0755, true);
echo "创建目录: {$relativePath}\n";
}
} else {
if (!file_exists($targetPath) ||
filemtime($file) > filemtime($targetPath)) {
if ($options['dry-run']) {
echo "[模拟] 同步: {$relativePath}\n";
} else {
copy($file, $targetPath);
echo "同步: {$relativePath}\n";
}
}
}
}
// 处理删除
if ($options['delete']) {
deleteRemovedFiles($source, $target, $options);
}
}
?>
使用Rsync的PHP封装
<?php
class RsyncSync {
private $source;
private $target;
private $options = '-avz --delete';
public function __construct($source, $target) {
$this->source = $source;
$this->target = $target;
}
public function sync() {
$command = sprintf(
'rsync %s %s/ %s/',
$this->options,
escapeshellarg($this->source),
escapeshellarg($this->target)
);
$output = [];
$returnVar = 0;
exec($command, $output, $returnVar);
if ($returnVar === 0) {
echo "同步成功完成\n";
return $output;
} else {
echo "同步失败\n";
return false;
}
}
}
?>
使用建议
-
选择合适的同步方式:
- 小项目:使用基础的PHP文件操作
- 大项目:使用rsync或inotify
- 需要实时同步:使用inotify监控
-
安全考虑:
- 添加错误处理
- 设置适当的文件权限
- 记录日志
-
性能优化:
- 使用文件时间戳或MD5校验
- 排除不需要同步的目录
- 考虑使用多线程
根据您的具体需求选择合适的脚本,并根据实际情况进行调整。