In a PHP CLI script, what are the STDIN, STDOUT and STDERR constants, and what should each carry in a shell pipeline?
answer
- three pre-opened stream resources
- defined only by the CLI SAPI
- data out, diagnostics to the side
- fgets(STDIN) until false
- fwrite() bypasses the output buffer
basics
~20 sThey are already-open stream resources the CLI SAPI defines for standard input, output and error. Read data from STDIN, write results to STDOUT, and send progress, warnings and errors to STDERR, so the next program in the pipeline receives only data.
solid answer
~40 sThe CLI SAPI defines `STDIN`, `STDOUT` and `STDERR` as open stream resources, so `fgets(STDIN)`, `fwrite(STDOUT, ...)` and `fwrite(STDERR, ...)` work without `fopen()`. In a pipeline such as `cat users.csv | php import.php --dry-run | sort`, the script reads records from `STDIN` line by line until `fgets()` returns `false`, writes only result data to `STDOUT`, and writes progress, warnings and error messages to `STDERR`, which the shell shows on the terminal instead of passing to `sort`. `echo` also reaches standard output, but through PHP's output layer, so it can be held by `ob_start()` while `fwrite(STDOUT, ...)` goes straight out. The constants exist only under the CLI SAPI, not in web requests.
code
php · 17 lines<?php
declare(strict_types=1);
$ok = 0;
$bad = 0;
while (($line = fgets(STDIN)) !== false) {
$row = str_getcsv(rtrim($line, "\r\n"));
if (count($row) < 2) {
$bad++;
fwrite(STDERR, "skipped malformed line\n"); // diagnostics
continue;
}
fwrite(STDOUT, json_encode(['email' => $row[0], 'name' => $row[1]]) . "\n"); // data
$ok++;
}
fwrite(STDERR, "done: {$ok} ok, {$bad} skipped\n");
exit($bad > 0 ? 1 : 0);go deeper
Recall the three constants, that they are already open in the CLI, and that fgets(STDIN) returns false at end of input.
Explain the data-on-STDOUT, diagnostics-on-STDERR convention, how the shell redirects each, and why echo and fwrite(STDOUT) can reorder under output buffering.
Design commands that compose in pipelines: streaming line-by-line input, clean machine-readable output, diagnostics and PHP warnings on standard error, and prompts only when stream_isatty() says a person is there.
Set conventions for the team's commands — output formats, where logs go, what is machine-readable — so scripts can be chained and automated without special cases.
## Three standard streams Every process on Unix-like systems (and Windows) starts with three open channels: | Stream | Number | Typical use | PHP constant | |---|---|---|---| | standard input | 0 | data coming in (a file, a pipe, the keyboard) | `STDIN` | | standard output | 1 | the program's result data | `STDOUT` | | standard error | 2 | diagnostics for the human or the log | `STDERR` | PHP's command-line SAPI registers the three constants as ready-to-use **stream resources** when the script starts. Web SAPIs such as PHP-FPM do not define them, so code that uses them belongs in CLI entry points. ## Reading input For a data-import command that accepts records on standard input: ```php while (($line = fgets(STDIN)) !== false) { $row = str_getcsv(rtrim($line, "\r\n")); // ... } ``` - `fgets()` returns one line including its newline, or `false` at end of input — compare with `!== false`, because a final line consisting of just `0` would be falsy. - Reading line by line keeps memory flat on large inputs, unlike `stream_get_contents(STDIN)`, which loads everything at once. - `stream_isatty(STDIN)` tells you whether input is a terminal (a person typing) or a pipe/file, which decides whether to prompt. ## Writing output: data versus diagnostics The convention that makes a script composable is simple: 1. **`STDOUT` carries data only** — the rows imported, the report, the JSON. Another program may read it. 2. **`STDERR` carries everything else** — progress bars, "skipped row 17", warnings, the final error message. With that split: - `php import.php --dry-run < users.csv > plan.txt` puts the plan in the file while progress still appears on screen; - `php import.php ... 2> import.log` captures diagnostics separately; - a downstream `sort` or `jq` never chokes on a stray "Processing..." line. PHP's own warnings and notices follow `display_errors`; in the CLI you can point them to standard error by setting it to `stderr`, which keeps them out of the data stream too. ## `echo` versus `fwrite(STDOUT, ...)` Both end up on standard output, but by different routes: - `echo` and `print` go through PHP's **output layer**. If the script (or a library) called `ob_start()`, the text waits in the buffer until it is flushed. - `fwrite(STDOUT, ...)` writes to the stream directly and **bypasses output buffering**. Mixing the two while a buffer is active can reorder output: stream writes appear immediately, buffered `echo` output later. Pick one style for data output in a command, and use `fwrite(STDERR, ...)` for diagnostics. ## Related stream names The same channels can also be opened by name through `php://stdin`, `php://stdout` and `php://stderr`; opening one creates a new stream resource of your own. For CLI scripts the constants are simpler: already open, and closed automatically when the script ends. ## Common mistakes - Printing progress with `echo` in a command whose output is piped into another tool. - Writing the final error message to `STDOUT`, so a caller that captures output parses it as data. - Using `STDIN` inside code that also runs under FPM, where the constant is undefined and PHP throws an `Error`. - Forgetting that `fgets()` keeps the trailing newline, so values compare unequal until trimmed.
- Why can output written with fwrite(STDOUT, ...) appear before earlier echo output?`echo` goes through PHP's output layer, so if an output buffer is active (`ob_start()`), its text waits until the buffer is flushed. `fwrite(STDOUT, ...)` writes to the stream directly and bypasses buffering. Mixing the two while a buffer is open reorders the output.
- How does a PHP import script decide whether to show an interactive prompt?Check `stream_isatty(STDIN)`. It returns `true` when standard input is a terminal and `false` for a pipe or redirected file. Prompt only in the first case; with piped input, read the data and require explicit flags for any confirmation.
saying these in an interview costs you the question
- STDIN, STDOUT and STDERR are defined in every SAPI, including PHP-FPM
- Error messages belong on STDOUT so the caller can capture them
- echo and fwrite(STDOUT, ...) always produce output in the same order
- fgets(STDIN) returns an empty string at end of input
- You must fopen() STDIN before reading from it in a CLI script