1

I'm using angular http to post data from my Ionic app to a PHP script. In that script a json file gets read and decoded, so that the incoming data can be appended and then be written back to the file.

This basically works fine, but in some cases file_put_contents does not write the new encoded array and/or the submit order gets messed up.

Typescript:

this.names.forEach(name => {
    this.http.post("http://myServer.test/saveNames.php", {name: name["name"], mode: name["mode"]}).subscribe(
        err => {
            errors.push(name["name"]);
        }
    );
});

Typescript post data:

(3) [{…}, {…}, {…}]
0: {name: "User 1", mode: "namesM"}
1: {name: "User 2", mode: "namesW"}
2: {name: "User 3", mode: "namesJobs"}
length: 3

PHP script:

$postData = file_get_contents("php://input");
    if (isset($postData)) {
    $request = json_decode($postData, true);

    var_dump("Data after decode:");
    var_dump($request);

    $inp = file_get_contents('names.json');

    if (($inp) == "") return; // Return so file does not get messed up

    $allNames = json_decode($inp, true);

    var_dump("File, no decode:");
    var_dump($inp);
    var_dump("File, decoded:");
    var_dump($allNames);

    $allNames[] = $request;

    var_dump("File content + new data:");
    var_dump($allNames);
    var_dump("All data pretty printed:");
    var_dump(json_encode($allNames, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));

    file_put_contents('names.json', json_encode($allNames, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); // mit UTF8s
}

names.json before any writing (initial state):

[]

names.json after post is complete:

[
    {
        "name": "User 1",
        "mode": "namesM"
    },
    null
]

The file has 777 permissions and was not open during the process. As you can see, only one name was written, one null was inserted and then nothing happened.

When I checked the Chrome network tab, I noticed that after User 1 (whose pretty printed json looked good), the order got messed up. After User 1, User 3 was next, however file_get_contents(names.json) returned the empty array, so User 1 actually was not written. User 3 resulted in a correct json as well, but after that User 2 came next and file_get_contents('names.json') was empty (as in empty string), so it stopped here.

However I don't understand how after all that, there's still User 1 in the file and why the other two were not written in general.

Here are my var_dumps() from the Chrome network tab:

User 1 (first):

string(18) "Data after decode:"
array(2) {
  ["name"]=>
  string(6) "User 1"
  ["mode"]=>
  string(6) "namesM"
}
string(16) "File, no decode:"
string(2) "[]"
string(14) "File, decoded:"
array(0) {
}
string(24) "File content + new data:"
array(1) {
  [0]=>
  array(2) {
    ["name"]=>
    string(6) "User 1"
    ["mode"]=>
    string(6) "namesM"
  }
}
string(24) "All data pretty printed:"
string(66) "[
    {
        "name": "User 1",
        "mode": "namesM"
    }
]"

User 3 (second!?):

string(18) "Data after decode:"
array(2) {
  ["name"]=>
  string(6) "User 3"
  ["mode"]=>
  string(9) "namesJobs"
}
string(16) "File, no decode:"
string(2) "[]"
string(14) "File, decoded:"
array(0) {
}
string(24) "File content + new data:"
array(1) {
  [0]=>
  array(2) {
    ["name"]=>
    string(6) "User 3"
    ["mode"]=>
    string(9) "namesJobs"
  }
}
string(24) "All data pretty printed:"
string(69) "[
    {
        "name": "User 3",
        "mode": "namesJobs"
    }
]"

User 2 (third!?)

string(18) "Data after decode:"
array(2) {
  ["name"]=>
  string(6) "User 2"
  ["mode"]=>
  string(6) "namesW"
}

I'm really sorry for the long text but I just can't find a solution after trying for hours. I hope I documented it well enough and thanks for any help!

GoYoshi
  • 253
  • 1
  • 3
  • 17
  • 1
    I don't have an environment where I can test this but I am going to hazard a guess and say that your TypeScript is sending a post request to the PHP script for each item in `names` and that each request it receives, it is overwriting the previous data. Try adding `FILE_APPEND` to `file_put_contents` as a third argument and see if that resolves the issue. I could be way off though, let me know how it goes =) – PsychoMantis Feb 03 '19 at 17:42

1 Answers1

1

It seems to me that either of these problems might be occurring:

  1. The webserver you're running the PHP on may be running each POST request in parallel (multithreaded), which would cause the file to be partially open or busy being written by one request while the other is busy trying to read it.

  2. The filesystem of your webserver tells PHP that the file is written and everything is fine, when in fact, it's still busy caching the data internally before it's actually physically written to the disk. So when the next request to read the file comes, it hasnt been fully written yet.

So your possible solutions to try would be:

  1. Try using file_put_contents and file_get_contents with the LOCK_EX flag to exclusively read and write the file. See http://php.net/manual/en/function.file-put-contents.php

  2. Why are you sending multiple POST requests from the same client anyway? You should be doing something like this rather:

var allNames = [];
this.names.forEach(name => {
    allNames.push({name: name["name"], mode: name["mode"]});
    });


this.http.post("http://myServer.test/saveNames.php", allNames, ....);

So send the entire array of names all at once, then you will avoid running into this problem.

Disk operations are very slow, so you shouldnt count on them keeping up with requests coming from your clients. You should only do disk operations as little as possible and try to batch your data up as much as possible when you do.

Mikepote
  • 6,042
  • 3
  • 34
  • 38