Description
From manual page: https://php.net/function.headers-sent
It says:
If the optional filename and line parameters are set, headers_sent() will put the PHP source file name and line number where output started in the filename and line variables.
Therefore, if I call headers_sent($filename, $linenum)
and pass it both arguments, and it returns true, I expect it to set both filename
and linenum
to the file and line number where the output started.
And yet, I (pretty often) observe cases where it returns true and yet doesn't set filename and line number. I don't know how to reproduce, because, because of the very fact that it doesn't tell me where the output started, I have been unable to debug it. Otherwise I would be happy to provide example code that reproduces.
If there are legitimate cases for headers_sent()
being unable to tell the file name and line number where the output started, then these cases should be documented in excruciating detail.
But I can't think of any such legitimate cases. My script is initiated by a php file, so wherever the output started, that "somewhere" MUST be at least backtraceable to some filename and line number (if it was started by eval()'d code, then it's the filename and line number that called eval(), that is given that you decided that only a filename and line number are to be returned and not a backtrace of any sort).
Activity
php4fan commentedon Jun 5, 2023
Oh cr** I followed the "report a bug" link in the documentation, but I intended to report this against PHP as an actual bug.
damianwadley commentedon Jun 5, 2023
I could imagine some built-in function emitting a warning message being one possible cause for headers being outputted without a concrete file and line number. Not saying that it should do that, but it's a potential explanation.
Do you get any "cannot send headers, output started at..." error messages? Either they'll have a file and line number themselves or, perhaps more likely, they'll cite Unknown:0.
KapitanOczywisty commentedon Jun 5, 2023
@php4fan You can try to use
ob_start
to find where output was started. This will obviously add significant overhead to any output, so before sending intended data useob_end_clean
.Example code: https://3v4l.org/kMt6f
Edit: You can also use header_register_callback https://3v4l.org/vm8KO
php4fan commentedon Jun 5, 2023
I found out what actually starts the output and triggers the issue in my case.
It's the warning about the number of uploaded files exceeding
max_file_uploads
. That is emitted before script execution has even started, and therefore the concepts of file name and line number don't apply at all.The very idea of
headers_sent()
"returning" a file name and line number is flawed, because the concepts of file name and line number don't always apply, and don't always satisfactorily answer the question "When/where did output start?".Note, by the way, that even when the concepts of filename and line number do apply, they can sometimes be of very little help in debugging: if the file and line belong to a function that is called from many different places in the code, what one really would want to see is a backtrace (and in the case of situations like mine, where output is started outside of/before actual php code, extra information).
The only decent solution would be to provide a better API that gives all the information. For exmple, instead of just "returning" a filename and line number, "return" an array:
Obviously there's BC to take into account. So either this:
or
but this last one would rely on
$filename_or_array
having to be already set to an array, and only in this case it would be filled as such; I don't know which is worse: this one or having 3 arguments. Anyway, all of this would be just for BC.The point is, "file name and line number" is not enough to identify the place where output started, so it's an ill-designed interface.
Shorter-term solution
In the meantime, while sticking to the "filename + line number" interface (given that I guess that will take years to overcome, if it ever is), I think it would be very important to return special values in $filename, rather than the empty string, for distinguishable cases. At the very, very least something signifying "Before script execution" or "Startup" or something, because right now it requires quite a leap of faith to assume that that is the case (hopefully it's the only case in which filename can currently be empty, but even you didn't immediately assume that it was. If that is really true, then it should be made 100% sure and then stated in the docs). But ideally something more than that.
(note: even this requires some thought regarding BC, as there's bound to be code out there checking whether $filename is empty, and if it is not, assuming it is a file name).
KapitanOczywisty commentedon Jun 5, 2023
In general php errors should not be sent to browser, but If sent e.g. on debug enviroment, you should see that upload warning before headers sent one. Even if you have logger set up (and for some reason same errors are also shown in browser), you likely will see upload warning and then warning about headers sent right after. So real solution is to improve logging situation.
php4fan commentedon Jun 5, 2023
(emphasis added) Except I don't see it because I am not the user.
I am very aware that my "logging situation" is not ideal; I may have my reasons for that or I could just be being sloppy. Either way, PHP's job is to... do its job.
headers_sent()
is supposed to tell me where the output has started, whatever the conditions are. Given thatdisplay_errors
exists and can be on (regardless of whether it's good practice or not to have it turned on), the output starting because of a warning is something that can happen and therefore should be taken into account.KapitanOczywisty commentedon Jun 6, 2023
Showing errors to users is a security issue, and fixing that should be the first priority.
Going back to proposition: What exactly is improved by having
headers_sent
return some"startup"
condition instead of empty string? I don't think empty string can indicate any other condition than startup, so rather than returning unexpectednot-a-filename
in$filename
variable or changing function signature, I'd improve documentation by mentioning that$filename
and$line
will be empty when output was started before executing PHP file.header_sent
returning backtrace could be useful in very rare occasions, but I'm not sure this justifies generating backtrace every single time output is started, which would be needed. And I don't think people haveheader_sent
check before normal output, so "headers already sent" warning would need to be also changed?Nevertheless, if you think this should be implemented anyway, you can go through RFC process.
DeveloperRob commentedon Jun 6, 2023
@KapitanOczywisty: If the headers are sent by something other than PHP, will
headers_sent()
returntrue
? I am not sure what kind of setup would lead to this, but if this is possible; it could be a legitimate reason to set$filename
tophp_startup
if PHP generated the output prior to script execution (and potentiallypre_php
if the output occurred beforehand).php4fan commentedon Jun 6, 2023
By all means, if you are 100% sure that that is the only case, then do that. Otherwise, document all the other cases too.
KapitanOczywisty commentedon Jun 6, 2023
I'm not 100% sure, but also until you or anybody else finds some other case, we won't be able to return that in
headers_sent
anyway, so for now this is true-enough statement.Output has to go through PHP output handler to be detected and headers to be sent, If something (not sure what this could be) sends data before that, you will likely get 500 error from apache/nginx. We're talking about something in PHP causing output before main PHP script is executed - e.g. handing upload, parsing input (_POST, _GET) etc.
php4fan commentedon Jun 6, 2023
Ok then I guess that statement should go into the documentation. Then if anyone ever finds a counter-example, they/we can report it as a documentation issue.