Файл перезаписывается в некоторых сценариях

Я работаю над системой, которая получает push-уведомления и обрабатывает их, анализируя их с помощью шаблона XSL и создавая (если он не существует) или добавляя проанализированные данные XML в файл. Иногда он получает несколько уведомлений одновременно, поэтому первое уведомление создает файл, а второе создает его снова, потому что в этот момент я думаю, что файл записывается, но еще не существует в файловой системе. Таким образом, в результате первые данные xml теряются из-за перезаписи вторым вызовом сценария.

Я думаю, что основная проблема заключается в попытке сканирования_каталога, чтобы проверить, существует ли уже файл. Первый вызов не находит файл (правильно), но второй вызов не может найти файл, потому что первый вызов записывает файл в этот момент. У меня нет возможности определить имя файла, поэтому я должен применить шаблон регулярного выражения, потому что не знаю, когда был создан файл, если он был создан. Первоначально я использовал glob с теми же результатами, поэтому изменил на scan_dir, потому что я думаю, что scan_dir быстрее.

/**
 * Main method
 * @return string XML resulting
 */
public function run()
{
    $xml = simplexml_load_string($this->xml);
    $xsl = new \DOMDocument();
    $xsl->load(self::XSLPATH);

    // Transformer config
    $proc = new \XSLTProcessor;
    $proc->registerPHPFunctions();
    $proc->importStyleSheet($xsl);

    $xmlResult = $proc->transformToXML($xml);

    //  Obtain path to destination folder
    $path = self::PATH."/{$this->folder}/";
    //  Get the file with pattern
    $file_list = glob($path."fileoutput*.xml");


    //  If there exists files matching the pattern, get the first one
    if (sizeof($file_list) > 0) {
        $this->append($xmlResult, $file_list[0]);
    } else {
        // ERROR! concurrent calls end here beceause file is not 
        // in filesystem so scan_dir can't detect it!
        $filename = "fileoutput_".date("d-m-Y_H-i-s").".xml";
        $this->writeFile(
            __DIR__."/../../../output/{$this->folder}/{$filename}",
            $xmlResult
        );
    }

    //  return the xml string
    return $xmlResult;
}

/**
 * This method uses flock to gain exclusive acces to resource.
 * 
 * @param string $filepath file path
 * @param string $data dat ato be written
 * @return void
 */
private function writeFile($filepath, $data)
{
    $fh = fopen($filepath, "w");
    $tries = 5;

    while ($tries > 0) {
        $locked = flock($fh, LOCK_EX);
        if (! $locked) {
            sleep(5);
            $tries--;
        } else {
            $tries = 0;
        }
    }

    if ($locked) {
        fwrite($fh, $data);
        flock($fh, LOCK_UN);
    }

    fclose($fh);
}

/**
 * Append xml data to existing xml
 * @param $xml string xml to append
 * @param $file string file where xml will be append
 */
private function append($xml, $filename)
{
    $xmlFromFile = simplexml_load_file($filename);
    $xmlToAppend = simplexml_load_string($xml);

    $nodeToAppend = $xmlToAppend->reserva;
    $this->sxml_append($xmlFromFile, $nodeToAppend);

    $this->writeFile($filename, $xmlFromFile->asXML());

}

/**
 * This method adds a childnode to xml with deep copy
 * @param $to SimpleXMLElement xml where childnode is copied
 * @param $from SimpleXMLElement xml childnode to copy to
 * @return void
 */
private function sxml_append(\SimpleXMLElement $to, \SimpleXMLElement $from)
{
    $toDom = dom_import_simplexml($to);
    $fromDom = dom_import_simplexml($from);

    $toDom->formatOutput = true;
    //$toDom->preserveWhiteSpace = false;

    $toDom->appendChild($toDom->ownerDocument->createTextNode("\n"));   
    $toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true));
}

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

flock()
RiggsFolly 14.02.2019 11:25

@RiggsFolly уже использует его, но проблема заключается в попытке проверить, есть ли предыдущий файл. Первый вызов не находит файл (это правильно), поэтому создает его с помощью flock. Второй вызов не может проверить, существует ли файл через scan_dir, потому что он записывается именно в этот момент, поэтому еще не существует в файловой системе. Мне нужно использовать scan_dir или glob, потому что нет способа узнать имя файла, это требование, шаблон имени файла — fileoutput_date_time.xml, поэтому невозможно узнать точное имя, я должен проверить, существует ли файл с этим шаблоном.

Hmorv 14.02.2019 11:33

Так что не блокируйте фактический файл. Создайте файл lock.dat. Обновляйте реальный файл только в том случае, если вы можете получить блокировку файла блокировки.

RiggsFolly 14.02.2019 11:36

@RiggsFolly Это помогло, спасибо!!

Hmorv 14.02.2019 12:35
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
4
34
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Прикрепляю измененный код. Пробовал с jMeter, успешно сделал 5 одновременных вызовов PHP-скрипта!

/**
 * Main method
 * @return string XML resulting
 */
public function run()
{
    $xml = simplexml_load_string($this->xml);
    $xsl = new \DOMDocument();
    $xsl->load(self::XSLPATH);

    // Transformer config
    $proc = new \XSLTProcessor;
    $proc->registerPHPFunctions();
    $proc->importStyleSheet($xsl);

    $xmlResult = $proc->transformToXML($xml);

    //  Obtain path to destination folder
    $path = self::PATH."/{$this->folder}/";
    //  Get the file with pattern
    $file_list = glob($path."fileoutput*.xml");

    $lockfile = fopen("lock.dat", "w");

    //  check if get lock for $lockfile
    if ($this->getLock($lockfile)) {
        $file_list = glob($path."fileoutput*.xml");

        //  If there exist files matching the pattern, get the first one
        if (sizeof($file_list) > 0) {
            $this->append($xmlResult, $file_list[0]);
        } else {
            //NICE! now it is sure we create file only when got lock in $lockfile,
            //so it won't be overwriten
            $filename = "fileoutput_".date("d-m-Y_H-i-s").".xml";
            $this->writeFile(__DIR__."/../../../output/{$this->folder}/{$filename}", $xmlResult);
        }

    }

    //  return the xml string
    return $xmlResult;
}

/**
* this method tries to get lock on a file
* @param resource $file The lock file
*
* @return boolean true if we get lock, false if not
*/
private function getLock($file)
    {
        $fh = $file;
        $tries = 5;

        while ($tries > 0) {
            $locked = flock($fh, LOCK_EX);

            if (! $locked) {
                sleep(5);
                $tries--;
            } else {
                $tries = 0;
            }
        }

        return $locked;
    }

/**
 * This method uses flock to gain exclusive acces to resource.
 * 
 * @param string $filepath file path
 * @param string $data dat ato be written
 * @return void
 */
private function writeFile($filepath, $data)
{
    $fh = fopen($filepath, "w");
    $tries = 5;

    while ($tries > 0) {
        $locked = flock($fh, LOCK_EX);
        if (! $locked) {
            sleep(5);
            $tries--;
        } else {
            $tries = 0;
        }
    }

    if ($locked) {
        fwrite($fh, $data);
        flock($fh, LOCK_UN);
    }

    fclose($fh);
}

/**
 * Append xml data to existing xml
 * @param $xml string xml to append
 * @param $file string file where xml will be append
 */
private function append($xml, $filename)
{
    $xmlFromFile = simplexml_load_file($filename);
    $xmlToAppend = simplexml_load_string($xml);

    $nodeToAppend = $xmlToAppend->reserva;
    $this->sxml_append($xmlFromFile, $nodeToAppend);

    $this->writeFile($filename, $xmlFromFile->asXML());

}

/**
 * This method adds a childnode to xml with deep copy
 * @param $to SimpleXMLElement xml where childnode is copied
 * @param $from SimpleXMLElement xml childnode to copy to
 * @return void
 */
private function sxml_append(\SimpleXMLElement $to, \SimpleXMLElement $from)
{
    $toDom = dom_import_simplexml($to);
    $fromDom = dom_import_simplexml($from);

    $toDom->formatOutput = true;
    //$toDom->preserveWhiteSpace = false;

    $toDom->appendChild($toDom->ownerDocument->createTextNode("\n"));   
    $toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true));
}

Другие вопросы по теме