PHP复制目录功能

时间:2011-10-02 08:14:41

标签: php directory

我正在开发一个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.";
    }
}

6 个答案:

答案 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;
        }
一切都完成了。 感谢。