Номер телефона

Последнее обновление:

Как эффективнее копировать файл PHP или проблема функции copy()

Например, необходимо скопировать локальный файл, имеющий имя file1.html в локальный же файл, имеющий имя file2.html.

Казалось бы, нет ничего проще – вполне можно воспользоваться функцией copy().

Эта функция работает даже в РНР4, т.е., казалось бы, никакой особой проблемы нет. Однако, не стоит спешить с выводами...

Одно дело, если file2.html – это новый файл, которого на жестком диске компьютера еще нет

Тогда, и в самом деле, использование функции copy() никаких особых проблем вызвать не должно. Синтаксис соответствующей команды может иметь, к примеру, следующий вид:

if(copy(‘ПолныйПуть1’. ‘/’.file1.html, ‘ПолныйПуть2’. ‘/’.file2.html) !== false){

echo ‘Файл успешно скопирован’;

}

В этом отрывке программного кода на всякий случай делается проверка, скопирован ли файл file1.html в файл file2.html.

Кстати, вполне можно, начиная с PHP5, использовать и такой синтаксис:

if(copy(‘URL1’. ‘/’.file1.html, ‘URL2’. ‘/’.file2.html) !== false){

echo ‘Файл успешно скопирован’;

}

Т.е. вместо полных путей к файлам использовать их URL. Однако, при этом время копирования увеличится, так как серверу придется не только найти этот файл, но и осуществить к нему доступ по тому или иному протоколу (например, http или https). А это означает, в том числе, что сервер, как минимум, будет отвлекаться на подключение механизма, использующего тот или иной протокол (обертку) для этой функции.

Кроме того, и это самое главное, при использовании оберток сервер вначале будет СОБИРАТЬ файл в соответствии с правилами и языковыми конструкциями языка PHP и самого сервера (Apache, Ngnix и т.п.). Т.е. вначале файл будет динамически собираться, а потом уже(!) – копироваться в другой (конечный) файл. Это необходимо иметь в виду. Если цели динамической сборки файла не преследуется, то целесообразно указывать в качестве путей к файлам обычные полные пути к ним, т.е. без использования оберток.

А если конечный файл уже существует?

В таком случае функция copy() произведет ПЕРЕЗАПИСЬ этого файла. Так написано в мануалах и именно так обстоит дело на практике. Точнее, будет ПЫТАТЬСЯ произвести перезапись файла. А уж получится такая попытка или нет – это совсем другой вопрос.

И вот здесь могут возникнуть проблемы. В частности, в данном случае результат работы функции copy() может быть и отрицательным (при этом она вернет значение false). Т.е. функция завершит работу с ошибкой, выдав примерно такое предупреждение:

Warning: copy(Z:\home\site.ru\www\directory\file2.html) [function.copy]: failed to open stream: Invalid argument in Z:\home\site.ru\www\... \program.php on line 123

Кстати, любопытно, что нигде в интернете подобная ошибка толком не обсуждается. А если и обсуждается – то не в этом ключе, как правило, на форумах обсуждают более банальные ошибки, вызванные работой функции copy() языка PHP. При этом файл file2.html (имеющий полный путь Z:\home\site.ru\www\directory\file2.html) вполне себе существует и доступен как для чтения, так и для записи… В чем  же дело?

Когда может произойти такая ошибка?

Вообще говоря, это зависит от состояния сервера. Нет ли там вирусов или еще чего. Однако, если в этом плане все идеально, если программный код этой функции (в недрах установленного PHP) не поврежден и вполне работоспособен – вот тогда такая ошибка ставит в тупик.

Причем, она может возникать не постоянно, а периодически. А это существенно запутывает ситуацию и усложняет ее обнаружение и, главное – ее устранение. Понятное дело, что в т.н. мануалах об этом, конечно же, ничего нет.

А это означает, что те, кто использует функцию copy() в языке РНР «просто так» (ну, т.е. написал строчку кода в соответствии с мануалом и подумал, что все, мол, «должно работать») – те крайне заблуждаются в своей правоте. И вполне могут начать искать причины неправильной работы сайта там, где их вообще нет. Особенно, если сайт написан на современном движке (Yii или Laravel каком-нибудь), который только для того, чтобы просто отдать браузеру даже простейшую страницу html, КАЖДЫЙ РАЗ выполняет многие десятки вызовов различных функций PHP (как правило, 50…70, если не больше, вызовов для обслуживания одного запроса).

Данная ошибка может возникать, в частности, если конечный файл (file2.html) незадолго до вызова функции copy() уже использовался, например, для чтения или записи. Или хотя бы просто открывался (и закрывался).

Напомним, что для открытия файла используется функция fopen(). А для чтения его содержимого могут быть использованы функции fread(), fgets() (если до этого файл был открыт). Кроме того, можно прочитать файл «за один присест» (т.е. без использования fopen()), например, при помощи функций file() или file_get_contents().

Теперь – более подробно о причине ошибки

Так вот, по всей видимости, даже если после вызова fopen() вполне был вызов flcose() или эти вызовы вообще не использованы, а вместо них применялось что-то типа file_get_contents(‘Z:\home\site.ru\www\directory\file2.html’), тем не менее

Внимание!

операционная система (и/или PHP) сохраняет какое-то время дескриптор этого файла, не удаляет его. Хотя, вроде как, при операции чтения файл не меняется, соответственно, нет необходимости ставить файл в очередь на перезапись его содержимого на жесткий диск, к примеру. Тем не менее, практика показывает именно такую ситуацию.

Причем, наблюдается это как в Windows, так и в Linux.

Ведь дело в том, что даже если в программе PHP программист не использовал вызов fopen(), тем не менее, такой вызов все равно используется на «низком» уровне при работе функций вида file() или file_get_contents(). Ведь для того, чтобы что-то прочитать из файла, его непременно придется открыть.

По какой причине файловый дескриптор ранее открывавшегося файла сохраняется некоторое время после его (файла) закрытия – сложно сказать. Возможно, система резервирует файловые дескрипторы (точнее, не сами дескрипторы, а записи в таблицах дескрпиторов) в расчете не то, что в скором будущем файл может быть открыт снова, осуществляя их кэширование.

Кстати, это вполне может быть так, ибо, в конце концов, в КЭШе операционной системы сохраняются, в том числе, и фрагменты таблиц дескрипторов.

Может, есть и какая-то иная причина. Но, факт остается фактом: если к файлу недавно было обращение, он может быть перезаписан не сразу после этого, а через некоторое время.

И добро, если бы это время было равно нескольким десяткам-тысячам тактов процессора, не больше. В таком случае, если вызов copy() будет идти в программном коде не сразу после вызова file_get_contents() (к примеру), то программный код на PHP, в подавляющем большинстве случаев, попросту «не заметил» бы этой проблемы. Но, увы.

Наш практический опыт показал, что файл, к которому недавно был доступ, может быть недоступен для перезаписи, например, в течение 0,1 секунды и даже дольше.

Кстати, в этом смысле, использование современных PHP-движков, которые при своей работе плодят многочисленные вызовы различных функций PHP, на самом деле, очень скрашивает проблему, «спасает» ситуацию. Ведь если после доступа к файлу и до вызова функции copy(), которая должна его перезаписать, пройдет достаточно много времени (полсекунды или более), то данная ошибка не будет появляться и проблемы не будет. А вот в скоростных, быстродействующих PHP-кодах, использующих минимум ООП, минимум цепочек абстракций и вызовов – там такая проблема вполне может возникнуть. Собственно, это и побудило автора данной статьи изложить проблему и способ ее решения.

Как избавиться от проблемы?

Кардинальное решение – НЕ использовать функцию copy(), если она при копировании должна будет перезаписывать файл, к которому недавно осуществлялся доступ. В таком случае перезаписывать файл можно «в лоб», т.е. путем использования функции file_put_contents(), например. Это будет наиболее надежным решением, хотя в некоторых случаях – и более трудоемким и кажущимся «неоптимальным».

Если же все-таки хочется использовать функцию copy()?

Ну, тогда можно предложить, например, вот такой программный код:

$flag_copy = 0;
   for($i=0; $i < 100; $i++){ // Функция copy() не всегда может перезаписывать ИСХОДНЫЙ ФАЙЛ, так как он некоторое время может быть занят (хотя и никем не блокировался!). Если он недавно использовался для чтения, то некоторое время он может быть недоступным для перезаписи при помощи функции copy().

     
$flag_copy = copy($File1, $File2);
           if(!
$flag_copy) {
               usleep(10000);

               continue;
               }
   }
               if(!
$flag_copy){ // Если все-таки копирование не удалось даже за много попыток
                   
die(‘Скопировать файл не удалось!’);
               }

Примечание. Аргументы функции copy() представляют собой полные пути к файлам $File1 и $File2 соответственно.

Этот программный код предусматривает 100 итераций – попыток записи файла $File1 в файл $File2. После каждой попытки PHP «отдыхает» примерно 10000 микросекунд (т.е. 0,01 секунду). Затем, попытка копирования повторяется. Ну, а если уж и после 100-й итерации копирование не получилось (прошла уже целая секунда, это очень много) – стало быть, или с операционной системой сервера, или с PHP что-то явно не то. Это уже будет повод для разбирательства не с программным кодом РНР, содержащим функцию copy(), а непосредственно с самим PHP или с сервером.

В заключение

В лишний раз доказано: на мануалы надейся, а сам не плошай. И еще, в очередной раз: те «умники», которые при наличии вопросов посылают в Google (или т.п.) – просто-напросто не представляют, о чем их спрашивают. Поэтому что-то обсуждать с ними – бесполезно. И даже вредно, с точки зрения ненужных временных затрат.

Удачи Вам в работе с РНР!


Комментарии:
Всего комментариев:0
Пожалуйста, не забудьте ознакомиться с правилами оставления комментариев.



Подписаться на комментарии на этой странице

Мы можем выполнить

Другие услуги
Интересная и полезная
информация
НАПИШИТЕ НАМ
Яндекс.Метрика
Номер телефона
© Copyright Все права защищены 2013-2024 Научный консалтинг