Функция копирования каталога PHP - PullRequest
0 голосов
/ 02 октября 2011

Я работаю над панелью управления Minecraft Server, и одной из ее функций является резервное копирование и восстановление вашего мира (каталога). У меня уже есть функция (см. Ниже), но, как вы, вероятно, видите, это довольно плохой код. Кто-нибудь знает о лучшей, более чистой функции?

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 ]

6 голосов
/ 02 октября 2011

Я бы не стал делать это в PHP. Просто используйте system("cp -a $source $dest"). (И убедитесь, что пользователь никоим образом не может контролировать содержимое $source и $dest, иначе вас взломают.)

2 голосов
/ 02 октября 2011

Я бы создал из этого больше функций, каждая из которых выполняет отличительную работу, вероятно, инкапсулированную в класс, как

  • 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 по-прежнему действует как исходная функция, но теперь возвращает сообщение об ошибке и, в частности, проверяет условия ошибки в своей собственной логике.Это немного расточительно, потому что он преобразует исключения в возвращаемое значение, которые представляют собой два вида обработки ошибок (которые вы, возможно, не захотите), но это компромисс для взаимодействия с существующим кодом (фасадом), в то же время покрывая саму проверку входных данных исключениями.

Кроме того, значения, с которыми он работает (параметры), указываются в верхней части функции, а не позже в коде функции.

Надеюсь, это поможет.Что осталось?

  • Функция copy_directory может проверить, существует ли каталог, является ли он пустым.В противном случае он действительно не скопировал бы мир, но смешал бы два мира.
  • Функция empty_directory должна быть правильно проверена, если она действительно выполняет работу по очистке каталога в отказоустойчивой манере, особенно при работе сподкаталоги.
  • Вы можете принять решение об общем способе обработки ошибок в вашем приложении, чтобы вам было легче справляться с ошибками внутри приложения.
  • Вы можете подумать о создании объектовиметь дело с хранением и извлечением миров, чтобы в конечном итоге их можно было легко расширять.
0 голосов
/ 22 марта 2015

Прежде чем копировать все файлы, вы должны разрешить "/ destination" быть 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;
        }

все готовоСпасибо.

0 голосов
/ 02 октября 2011

У меня есть некоторый рекурсивный код копирования, который следует, но сначала несколько мыслей.

В противоположность тому, что думают некоторые другие - я считаю, что функции файловой системы - единственное место, где оператор @ имеет смысл.Это позволяет вам вызывать ваши собственные исключения, а не обрабатывать функции, встроенные в предупреждения.При всех вызовах файловой системы вы должны проверять условия сбоя.

Я бы не сделал то, что вам было предложено:

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);
         }
      }
   }
0 голосов
/ 02 октября 2011

0,1. Использование оператора @ приведет к неприятностям. НИКОГДА не используйте это. Если существует возможность несуществующего файла - сначала проверьте его!

if (!file_exists($target)) mkdir($target);

0,2. Я весьма удивлен, почему вы используете glob в первой части и не используете его во второй.

0,3. Ваш код «сначала очистите целевой каталог» не очистит подкаталоги.

0 голосов
/ 02 октября 2011

Я использовал отсюда: 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 );
    }
}
?>

Работает до тех пор, пока вы не попытаетесь скопировать каталог внутри себя и войти в бесконечный цикл ..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...