我正在开发一个Minecraft服务器仪表板,其中一个功能是备份和恢复您的世界(目录)。我已经有了一个函数(见下文),但是,正如你可能看到的那样,代码非常糟糕。谁知道更好,更清洁的功能?
function backupOrRestoreWorld($source,$target){
foreach(glob($target.'*.*')as$v){
unlink($v);
}
if(is_dir($source)){
@mkdir($target);
$d=dir($source);
while(FALSE!==($entry=$d->read())){
if($entry=='.'||$entry=='..'){
continue;
}
$Entry=$source.'/'.$entry;
if(is_dir($Entry)){
backupOrRestoreWorld($Entry,$target.'/'.$entry);
continue;
}
copy($Entry,$target.'/'.$entry);
}
$d->close();
}
else{
copy($source,$target);
}
if($source == "server/world"){
return "World backed up.";
}
else {
return "World restored from backup.";
}
}
答案 0 :(得分:6)
我不会在PHP中这样做。只需使用system("cp -a $source $dest")
即可。 (并确保用户无法以任何方式控制$source
和$dest
的内容,否则您将被黑客入侵。)
答案 1 :(得分:2)
我会创建更多的功能,每个人都做一个独特的工作,可能封装成一个类,比如
empty_directory
copy_directory
您仍然可以维护单个函数,然后使用子例程/对象为应用程序提供外观,例如处理错误处理的异常等。
接下来你的代码并不是很糟糕。它是一种递归函数,用于复制可能会对文件系统造成一定压力的数据 - 这可能是可以忽略的。如果将正常的功能移动到它自己的单元中,如果遇到实际问题,可以随时更改。
但第一个好处是在我认为的子程序中使用异常:
function backupOrRestoreWorld($source, $target)
{
empty_directory($target);
copy_directory($source, $target);
if($source == "server/world"){
return "World backed up.";
}
else {
return "World restored from backup.";
}
}
function empty_directory($path)
{
$path = rtrim($path, '/');
if (!is_dir($path))
{
throw new InvalidArgumentException(sprintf('Not a directory ("%s").', $path));
}
if (!is_writable($path))
{
throw new InvalidArgumentException(sprintf('Directory ("%s") is not a writeable.', $path));
}
$paths = glob($path.'/*.*');
if (false === $paths)
{
throw new Exception(sprintf('Unable to get path list on path "%s" (glob failed).', $path));
}
foreach ($paths as $v)
{
unlink($v);
}
}
function copy_directory($source, $target)
{
$source = rtrim($source, '/');
$target = rtrim($target, '/');
if (!is_dir($source))
{
throw new InvalidArgumentException(sprintf('Source ("%s") is not a valid directory.', $source));
}
if (!is_readable($source))
{
throw new InvalidArgumentException(sprintf('Source ("%s") is not readable.', $source));
}
if (!is_dir($target))
$r = mkdir($target);
if (!is_dir($target))
{
throw new InvalidArgumentException(sprintf('Target ("%s") is not a valid directory.', $target));
}
if (!is_writable($target))
{
throw new InvalidArgumentException(sprintf('Target ("%s") is not a writeable.', $target));
}
$dirs = array('');
while(count($dirs))
{
$dir = array_shift($dirs)
$base = $source.'/'.$dir;
$d = dir($base);
if (!$d)
{
throw new Exception(sprintf('Unable to open directory "%s".', $base));
}
while(false !== ($entry = $d->read()))
{
// skip self and parent directories
if (in_array($entry, array('.', '..'))
{
continue;
}
// put subdirectories on stack
if (is_dir($base.'/'.$entry))
{
$dirs[] = $dir.'/'.$entry;
continue;
}
// copy file
$from = $base.'/'.$entry;
$to = $target.'/'.$dir.'/'.$entry;
$result = copy($from, $to);
if (!$result)
{
throw new Exception(sprintf('Failed to copy file (from "%s" to "%s").', $from, $to);
}
}
$d->close();
}
}
这个例子基本上引入了两个函数,一个用于清空目录,另一个用于将一个目录的内容复制到另一个目录。这两个函数现在都会抛出异常,或者说有用或多或少有用的描述。我试图尽早揭示错误,这是通过检查输入参数和执行一些额外的测试。
empty_directory
函数可能有点短。我不知道,例如,如果有一个子目录,那些不是空的,如果unlink
可行。我把它留给你练习。
copy_directory
函数是非递归的。这是通过提供一堆要处理的目录来完成的。如果有子目录,则将目录放在堆栈上并在复制当前目录的所有文件后进行处理。这有助于防止过于频繁地切换目录,并且通常更快。但正如您所看到的,它与您的代码非常相似。
因此,这些功能专注于文件系统工作。由于它们清楚地知道它们是什么以及它们是什么,因此您可以将功能集中在主要工作上,例如确定复制工作方向的逻辑。由于辅助函数现在抛出异常,您也可以捕获它们。此外,您可以验证$source
和$target
实际上是否包含您明确允许的值。例如,您可能不希望在其中包含..
或/
,而只是a-z
中的字符。
这也可以帮助您找到其他错误原因,例如覆盖尝试等等:
function backupOrRestoreWorld($source, $target)
{
$basePath = 'path/to/server';
$world = 'world';
$pattern = '[a-z]+';
try
{
if (!preg_match("/^{$pattern}\$/", $source))
{
throw new InvalidArgumentException('Invalid source path.');
}
if (!preg_match("/^{$pattern}\$/", $target))
{
throw new InvalidArgumentException('Invalid target path.');
}
if ($source === $target)
{
throw new InvalidArgumentException('Can not backup or restore to itself.');
}
$targetPath = $basePath.'/'.$target;
if (is_dir($targetPath))
empty_directory($targetPath);
copy_directory($basePath.'/'.$source, $targetPath);
if($source === $world)
{
return "World backed up.";
}
else
{
return "World restored from backup.";
}
}
catch(Exception $e)
{
return 'World not backed up. Error: '.$e->getMessage();
}
}
在示例backupOrRestoreWorld
中仍然充当原始函数,但现在返回错误消息,更具体地检查其自身逻辑中的错误条件。这有点过时了,因为它将异常转换为返回值,这是两种错误处理(您可能不需要),但是它与现有代码(façade)接口是一种妥协,同时用异常覆盖自身的输入验证。
此外,它所处理的值(参数)在函数顶部指定,而不是在函数代码中指定。
希望这会有所帮助。还剩什么?
copy_directory
函数可以检查目录是否已存在,是否为空。否则它不会真正复制一个世界,而是混合两个世界。empty_directory
函数实际上以故障安全方式清空目录,则应正确检查{{1}}函数,尤其是在处理子目录时。答案 2 :(得分:0)
0.1。使用@运算符会让您遇到麻烦。千万不要用它。如果有可能存在未存在的文件 - 首先检查它!
if (!file_exists($target)) mkdir($target);
0.2。我很惊讶为什么你在第一部分使用glob
而在第二部分不使用它。
0.3。您的“干净目标目录代码优先”代码不会清理子目录。
答案 3 :(得分:-1)
我从这里开始使用:http://codestips.com/php-copy-directory-from-source-to-destination/
<?
function copy_directory( $source, $destination ) {
if ( is_dir( $source ) ) {
mkdir( $destination );
$directory = dir( $source );
while ( FALSE !== ( $readdirectory = $directory->read() ) ) {
if ( $readdirectory == '.' || $readdirectory == '..' ) {
continue;
}
$PathDir = $source . '/' . $readdirectory;
if ( is_dir( $PathDir ) ) {
copy_directory( $PathDir, $destination . '/' . $readdirectory );
continue;
}
copy( $PathDir, $destination . '/' . $readdirectory );
}
$directory->close();
}else {
copy( $source, $destination );
}
}
?>
只要您不尝试复制自身内部的目录并进入无限循环就可以工作..
答案 4 :(得分:-1)
我有一些递归复制代码,但首先是一些想法。
与其他人的想法相反 - 我相信文件系统功能是@运营商唯一有意义的地方。它允许您引发自己的异常,而不必处理警告中内置的函数。对于所有文件系统调用,您应检查故障情况。
我不会做一些建议给你的事情:
if (!file_exists($target)) mkdir($target);
(假设mkdir会成功)。始终检查失败(file_exists检查与mkdir失败的整套可能性不匹配)。 (例如,破坏的sym链接,无法访问的安全模式文件)。
您必须始终决定如何处理异常,以及您的功能成功所需的初始条件是什么。我将任何因文件系统而失败的操作视为应该被捕获和处理的异常。
这是我使用的递归复制代码:
/** Copy file(s) recursively from source to destination.
* \param from \string The source of the file(s) to copy.
* \param to \string The desitination to copy the file(s) into.
* \param mode \int Octal integer to specify the permissions.
*/
public function copy($from, $to, $mode=0777)
{
if (is_dir($from))
{
// Recursively copy the directory.
$rDir = new RecursiveDirectoryIterator(
$from, FilesystemIterator::SKIP_DOTS);
$rIt = new RecursiveIteratorIterator(
$rDir, RecursiveIteratorIterator::SELF_FIRST);
// Make the directory - recursively creating the path required.
if (!@mkdir($to, $mode, true))
{
throw new Exception(
__METHOD__ .
'Unable to make destination directory: ' . var_export($to, true));
}
foreach ($rIt as $file)
{
$src = $file->getPathname();
$dest = $to . $rIt->getInnerIterator()->getSubPathname();
if (is_dir($src))
{
if (!@mkdir($dest, $mode))
{
throw new Exception(
__METHOD__ .
'From: ' . $from . ' To: ' . $to .
' Copying subdirectory from:' . $src . ' to: ' . $dest);
}
}
else
if (!@copy($src, $dest))
{
throw new Exception(
__METHOD__ .
'From: ' . $from . ' To: ' . $to .
' Copying file from: ' . $src . ' to: ' . $dest);
}
}
}
}
else
{
if (!@copy($from, $to))
{
throw new Exception(
__METHOD__ .
'Copying single file from: ' . $from . ' to: ' . $to);
}
}
}
答案 5 :(得分:-1)
在复制所有文件之前,您必须制作&#34; / destination&#34;允许为0777
$dst = '/destination'; // path for past
$src = '/source'; // path for copy
$files = glob($src.'/*.*');
foreach($files as $file){
$file_to_go = str_replace($src, $dst, $file);
copy($file, $file_to_go);
}
$dir_array = array();
$dir_array[] = $src;
while ($dir_array != null) {
$newDir = array ();
foreach ($dir_array as $d) {
$results = scandir($d);
foreach ($results as $r) {
if ($r == '.' or $r == '..') continue;
if (is_file($d . '/' . $r)) {
} else {
$path = $d.'/'.$r;
$newDir[] = $path;
$new = str_replace($src, $dst, $path);
mkdir($new, 0777, true);
$files = glob($path.'/*.*');
foreach($files as $file) {
$file_to_go = str_replace($src, $dst, $file);
copy($file, $file_to_go);
}
continue;
}
}
}
$dir_array = $newDir;
}
一切都完成了。
感谢。