35

I'm trying to create a function (for purposes of logging)

append($file, $data)

that

  1. creates $file if it doesn't exist and
  2. atomically appends $data to it.

It has to

  • support high concurrency,
  • support long strings and
  • be as performant as possible.

So far the best attempt is:

function append($file, $data)
{
    // Ensure $file exists. Just opening it with 'w' or 'a' might cause
    // 1 process to clobber another's.
    $fp = @fopen($file, 'x');
    if ($fp)
        fclose($fp);
    
    // Append
    $lock = strlen($data) > 4096; // assume PIPE_BUF is 4096 (Linux)

    $fp = fopen($file, 'a');
    if ($lock && !flock($fp, LOCK_EX))
        throw new Exception('Cannot lock file: '.$file);
    fwrite($fp, $data);
    if ($lock)
        flock($fp, LOCK_UN);
    fclose($fp);
}

It works OK, but it seems to be a quite complex. Is there a cleaner (built-in?) way to do it?

Taufik Nurrohman
  • 3,329
  • 24
  • 39
Jaka Jančar
  • 11,386
  • 9
  • 53
  • 74

2 Answers2

75

PHP already has a built-in function to do this, file_put_contents(). The syntax is:

file_put_contents($filename, $data, FILE_APPEND);

Note that file_put_contents() will create the file if it does not already exist (as long as you have file system permissions to do so).

Junior Mayhé
  • 16,144
  • 26
  • 115
  • 161
FtDRbwLXw6
  • 27,774
  • 13
  • 70
  • 107
  • 4
    I believe this will not use mode 'x' for opening a file (O_EXCL in C-land), so you can have a race condition if a file does not exist. See https://github.com/php/php-src/blob/master/ext/standard/file.c (it looks like it just uses 'c') – Jaka Jančar May 06 '13 at 21:37
  • 11
    30 ups and not a best answer. poor world! `:(` – Omar Tariq Dec 28 '13 at 15:02
  • This is wrong answer. This does not answer the question correctly: He asks for "atomically appends $data to it." and "support high concurrency". file_put_contents is not safe for conccurent writes. – John Boe Oct 14 '19 at 12:02
63

using PHP's internal function http://php.net/manual/en/function.file-put-contents.php

file_put_contents($file, $data, FILE_APPEND | LOCK_EX);

FILE_APPEND => flag to append the content to the end of the file

LOCK_EX => flag to prevent anyone else writing to the file at the same time (Available since PHP 5.1)

catalint
  • 1,895
  • 17
  • 17
  • This is a better solution than the most upvoted one. – Ilan Kleiman Sep 13 '19 at 05:21
  • 1
    file_put_contents is not safe for conccurent writes. – John Boe Oct 14 '19 at 12:04
  • @JohnBoe [According to this QA it has an easy solution](https://stackoverflow.com/questions/5479580/is-there-a-risk-in-running-file-put-contents-on-the-same-file-from-different-p) the `FILE_APPEND | LOCK_EX` flags are needed to make `file_put_contents` safe for concurrent use (as other callers will simply block). Do you have an authoritative document describing the concurrency issues you're warning us of? – Dai Apr 14 '23 at 13:43