Bug #81551 [Com]: SplFileObject::fgets( does not advance line pointer after SplFileObject::seek()
| From: | lucasfbustamante at gmail dot com | Date: | Sat, 23 Oct 2021 00:08:20 +0000 |
| Subject: | Bug #81551 [Com]: SplFileObject::fgets( does not advance line pointer after SplFileObject::seek() | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-237337@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=81551&edit=1
ID: 81551
Comment by: lucasfbustamante at gmail dot com
Reported by: lucasfbustamante at gmail dot com
Summary: SplFileObject::fgets( does not advance line pointer
after SplFileObject::seek()
Status: Feedback
Type: Bug
Package: SPL related
Operating System: Unix
PHP Version: 8.0.12
Assigned To: cmb
Block user comment: N
Private report: N
New Comment:
Indeed, the code snippets make much more sense in a foreach loop. I have re-written them in this
format to make it clearer.
The whole picture starts to make sense. I will recap some of the obvious parts for someone from the
outside that might be reading this.
If the iteration is starting from the beginning, all scenarios works consistently: https://3v4l.org/9BYb8
If the iteration is starting after a
seek, PHP 8.0.1 fixed an issue where fgets() would
start from the next line, but seems have introduced an inconsistent behavior if next() is called
before current(): https://3v4l.org/UpN1G
In my personal opinion, I think it wasn't a good idea to fix the fgets bug, as it breaks many
applications. I am here because I run a database importing script that uses seek() and fgets(), and
I was getting a duplicated primary key error because 8.0.1 fetches one row above what 8.0.0 would.
I personally think it would have been best to document the behavior of seek + fgets returning the
next line, and leave the behavior unchanged, considering it part of the behavior of SplFileObject, I
am not sure if we are past the point of rolling back this fix.
Either way, if the behavior of next() then current() was not expected, I believe we should aim to
make it consistent with the previous PHP versions, as to minimize impact on applications.
Previous Comments:
------------------------------------------------------------------------
[2021-10-22 14:47:34] requinix@php.net
I don't believe that is correct.
It's a bit tricky because key/current/next/etc. are almost always called through iteration with
foreach and never manually, and when calling them manually you're expected to follow a standard
pattern:
for ($object->rewind(); $object->valid(); $object->next()) {
$key = $object->key();
$value = $object->current();
// ...
}
or in other words,
1. rewind
2. valid
3. key/current
4. next
5. goto 2
However it should not matter if you call key/current before the first next because those are
supposed to be read-only operations, yet here it does matter:
https://3v4l.org/BVMsV vs https://3v4l.org/s1G2E
Note that it is incorrect to call methods like next in the same statement as key/current because PHP
does not guarantee that it will evaluate all operands in a statement in a particular order (though
it virtually always does go left to right). That means with a line like
array('triggerNext' => $file->next(), 'line' => $file->key(),
'contents' => trim($file->current())),
you can't actually be sure what methods will be called in what order, and having next before or
after key/current makes a difference. The same problem exists with expressions like ($i++) + (++$i).
https://en.wikipedia.org/wiki/Sequence_point
------------------------------------------------------------------------
[2021-10-22 12:33:42] lucasfbustamante at gmail dot com
Is this expected? https://3v4l.org/s4ZKA
<?php
$file = new SplTempFileObject();
for ($i = 0; $i < 100; $i++) {
$file->fwrite("Foo $i\n");
}
$file->seek(50);
echo json_encode(array(
array('triggerNext' => $file->next(), 'line' => $file->key(),
'contents' => trim($file->current())),
array('triggerNext' => $file->next(), 'line' => $file->key(),
'contents' => trim($file->current())),
array('triggerNext' => $file->next(), 'line' => $file->key(),
'contents' => trim($file->current())),
), JSON_PRETTY_PRINT);
?>
Results:
PHP 8.0.1+
[
{
"line": 51,
"contents": "Foo 50"
},
{
"line": 52,
"contents": "Foo 51"
},
{
"line": 53,
"contents": "Foo 52"
}
]
PHP 5.1 - 8.0.0:
[
{
"line": 51,
"contents": "Foo 51"
},
{
"line": 52,
"contents": "Foo 52"
},
{
"line": 53,
"contents": "Foo 53"
}
]
------------------------------------------------------------------------
[2021-10-21 22:21:08] lucasfbustamante at gmail dot com
I see the consistency this bugfix aims to provide between rewind (or starting from
zero) and seek: https://3v4l.org/HBTeF
It still escapes me why "Line" is 0 at "Foo 1", but either way, it's
consistent with the old implementation so it won't cause bugs in my application.
I can replace all my usage of fgets() with current() and
next() instead, so that key() will remain consistent after
rewind() or seek() with all PHP versions: https://3v4l.org/WYrig
------------------------------------------------------------------------
[2021-10-21 21:45:34] lucasfbustamante at gmail dot com
I understand what's happening now, and the reasoning behind the change.
However, this makes it very difficult for distributed libraries to have a comprehensive PHP version
constraint. If you use the SPLFileObject and depend on seek() and key(),
you either have to pick PHP 8 or PHP 5~7, because they have the same API but operates differently.
Taking the WordPress environment for instance (I am a WordPress plugin developer, my distributed
library is a WordPress plugin), PHP 8.0 is used by only 1.5% of the websites to this date: https://wordpress.org/about/stats/
I dislike the fact that PHP broke backwards compatibility for a function that exists and returns
this same result since PHP 5.3, especially considering that this was introduced in a minor patch
version bump, so PHP 8.0.0 will operate in a substantially different way than, say, PHP 8.0.1 or
8.1.
------------------------------------------------------------------------
[2021-10-21 20:37:41] requinix@php.net
Looks correct to me. Change the lines to get a better picture of what's happening.
https://3v4l.org/33lsI
key() returns the current line number, which should be the same at the "beginning" of the
line (eg, after a seek or rewind) as it is at the "end" of the line (after an fgets). The
line number increments according to iterator semantics, which say that the key/current doesn't
change until a call to next() - or, in this case, if you try to continue reading beyond the current
location using another call to fgets.
Before the bug fix:
- Rewinding returns key=0, fgets=line 0, key=0
- Seeking to 50 would return key=50, fgets=line 51, key=51
After the bug fix:
- Rewinding returns key=0, fgets=line 0, key=0
- Seeking to 50 returns key=50, fgets=line 50, key=50
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=81551
--
Edit this bug report at https://bugs.php.net/bug.php?id=81551&edit=1