Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safe\fgetcsv throws Exception when csv file ends with a new line #361

Open
Gemineye opened this issue Jun 7, 2022 · 15 comments · Fixed by #388
Open

Safe\fgetcsv throws Exception when csv file ends with a new line #361

Gemineye opened this issue Jun 7, 2022 · 15 comments · Fixed by #388
Assignees

Comments

@Gemineye
Copy link

Gemineye commented Jun 7, 2022

When iterating over a file we get an Exception if the last line is empty. Is this intended?

@Kharhamel
Copy link
Collaborator

this is definitely not intended, the exception should only trigger if the base function would return false.
What kind of exception do you get? A FilesystemException? Do you correctly get null if you use the base function?

@Gemineye
Copy link
Author

Gemineye commented Jun 9, 2022

Yes i get a FilesystemException (An error occured). The base returns with false in the 3. row (PHP 8.1)
test.csv

@Kharhamel
Copy link
Collaborator

If the base function returns false then it is normal than the safe function return an exception. I don't know why you have an error but it doesn't come from safe.

@Gemineye
Copy link
Author

Gemineye commented Jun 9, 2022

Ok Thank you!

@Gemineye Gemineye closed this as completed Jun 9, 2022
@pyrou
Copy link

pyrou commented Aug 11, 2022

@Kharhamel How are we supposed to read a csv using Safe ?
Because original fgetcsv return false on purpose at the end of the file, according to documentation example :

while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
    // ...
}

I tried using feof,

while (!feof($handle)) {
   $data = \Safe\fgetcsv($handle, 1000, ",");
   // ...
}

But with a final "\n" in csv file, which is something definitely pretty common, feof still isn't false after last line fgetcsv, and so next fgetcsv throw a FilesystemException. I checked implementation, and at this point error_get_last() is null.

How can we safely detect we are at the end of the file to get out of the while loop before exception ?

@pyrou
Copy link

pyrou commented Aug 11, 2022

Also resolution proposed in #325 is absolutely nonsensical to me. What would be the point of using a Safe function if the ONLY way to use it, is about muting and canceling the benefit it provide ?

@pointybeard
Copy link

I hit this problem also and having to use the workaround is ridiculous. The expected behaviour of fgetcsv() is to return false when there is no data. Safe\fgetcsv() should respect this and only throw an exception when there is actually an error of some kind.

@Kharhamel Kharhamel self-assigned this Aug 23, 2022
@Kharhamel
Copy link
Collaborator

Kharhamel commented Aug 23, 2022

But how do we know if there is an error without checking if the return is false?

@Kharhamel
Copy link
Collaborator

Kharhamel commented Aug 23, 2022

According to the doc (https://www.php.net/manual/en/function.fgetcsv.php):

A blank line in a CSV file will be returned as an array comprising a single null field, and will not be treated as an error.

So i guess this should be the proper way to do it.

@pyrou
Copy link

pyrou commented Aug 23, 2022

A blank line in a CSV file will be returned as an array comprising a single null field, and will not be treated as an error.

This only concerns blank line (like in the middle on the file) not trailing newline.

But how do we know if there is an error without checking if the return is false?

Checking feof() value is a good choice IMHO. A false value on \fgetcsv while \feof returns true is not an error. This is how \fgetcsv() currently behaves.

Here is a possible implementation that would satisfy either the original behavior of \fgetcsv() and the purpose of Safe :

function fgetcsv($stream, int $length = null, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): ?array|false
{
    error_clear_last();
    $safeResult = \fgetcsv(...func_get_args());

    if ($safeResult === false && feof($stream) === false) {
        throw FilesystemException::createFromPhpError();
    }

    return $safeResult;
}

Alternatively a specific exception can also be thrown

function fgetcsv($stream, int $length = null, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): ?array
{
    error_clear_last();
    $safeResult = \fgetcsv(...func_get_args());

    if ($safeResult === false) {
        if (feof($stream)) {
            throw FilesystemEOFException();
        }
        throw FilesystemException::createFromPhpError();
    }

    return $safeResult;
}

This way would allow us to catch and ignore FilesystemEOFException while still letting FilesystemException bubbling up if any. To reduce breaking change, FilesystemEOFException can extends FilesystemException. So you'll remain 100% backward compatible.

*EOFException is a naming coming from java land.

@wederfabricio
Copy link

I got the same problem here, I guess we should use the @pyrou solution to resolve this.

@Kharhamel Kharhamel reopened this Sep 8, 2022
@Kharhamel
Copy link
Collaborator

@pyrou I think I prefer the first solution since it doesn't require users to learn a new behavior. Working on it.

@Kharhamel
Copy link
Collaborator

Do you think there are other functions that might have the same issue?

@pyrou
Copy link

pyrou commented Sep 9, 2022

Do you think there are other functions that might have the same issue?

fgets is a similar case. Btw php.net example for fgets confirms reading foef after getting false was the right implementation :)

@Kharhamel
Copy link
Collaborator

The fix was deployed in version 2.3, I will do a MR later for fgets

@Kharhamel Kharhamel reopened this Sep 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants