PHP - SplFileObject - Wrong output for second line when using seek() method

1.4k Views Asked by At

Go to UPDATE to read what's the actual problem now. Old question was already resolved with the first answer submitted by Bert Peters.

OLD QUESTION: I have few files named as file.1.txt, file.2.txt, file.3.txt, ... I'm reading first file with SplFileObject and using foreach loop to iterate through its content:

$file = new SplFileObject("file.1.txt");
foreach ($file as $row) {
  // ...
}

Other files may be or may not be read, depending on the contents of the first file I'm reading. In all cases there should be only one file of others (file.2.txt or file.3.txt) which may be used in the next step. So somewhere inside foreach loop there is if statement which handles this.

All files have the same structure, so there comes the problem. I wouldn't like to create new foreach for reading next file - as I wrote it may not be needed at all, so I would like to use existing foreach instead of writing new one. Is there any possibility to overwrite $file variable with the contents of other file and iterate over it with using only one foreach or any other loop? For example:

foreach ($file as $row) {
  // ...
  if ($contentContainsSomething) {
    $file = new SplFileObject("file.2.txt");
    // somehow reset foreach to read file.2.txt from start
  }
}

I wouldn't like to use goto statement to solve this problem. The recursion seems to be appropriate solution, but if there's a way to change object in loop on the fly, I would prefer this solution.

UPDATE: As mentioned in "old question" all used files (file.1.txt, file.2.txt, ...) have the same structure, so that's why I wouldn't like to write more same loops and copy code. Instead I used code from @Danack (suggested by him on SO chat) which is already a part of solution. Here's the basic code for reading more files without any upgrade I need:

$path = "file.1.txt";
$whileCounter = 0;
while ($path != null) {
  $file = new SplFileObject($path);
  $file->setFlags(SplFileObject::READ_CSV);
  $file->setCsvControl("\t");
  $path = null;
  foreach ($file as $rowKey => $row) {
    // echo row  }
  $path = "file.2.txt";
  if ($whileCounter > 0) {
    break; // solution to stop loop, just for now
  }
  $whileCounter++;
}

So this code is working without any problem and outputs the file's lines as expected. The problem is when I would like to read next line of file with seek() method, because I would like to make decision on some information which is appended to each next line. So if I use seek($rowKey + 1) which helps me to get next line data (I use $file->current() when line is changed) and after that I call seek($rowKey) to get to previous line, then next file will output first line twice and second line will be missed. The third line and all after then are printed well. This is the problem achieved with the code below:

$path = "file.1.txt";
$whileCounter = 0;
while ($path != null) {
  $file = new SplFileObject($path);
  $file->setFlags(SplFileObject::READ_CSV);
  $file->setCsvControl("\t");
  $path = null;
  foreach ($file as $rowKey => $row) {
    if ($whileCounter > 0) {
      var_dump($row);
      echo "<br>";
    }
    $file->seek($rowKey + 1);
    if ($file->valid()) {
      $file->seek($rowKey);
    } else {
      var_dump($row);
      echo "<br>";
      $path = "file.2.txt";
    }
  }
  $whileCounter++;
}

If you apply custom .csv files (with at least five non-empty lines) instead of file.1.txt and file.2.txt, you will see that second and third output are the same (second and third output are first and "second" lines of file.2.txt). What could be wrong here?

1

There are 1 best solutions below

4
On

There is not. Foreach uses an iterator over your $file variable, and that iterator continues to be valid even though you changed the value of $file.

Or, to put this in another way, foreach will continue to look at the previous contents of $file, regardless of what you do with it afterwards. This is because $file is not actually the SplFileObject, but rather a reference to it, and the reference is used by foreach.