skip to content

Host OS Interaction

PHP reaches the operating system by running shell commands, forking and signalling processes, reading environment variables, and running as a CLI script. Interviewers probe the safety of each.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

20

In a PHP CLI script, what do $argv and $argc contain, and why can a function not see $argv directly?

level: juniorimportance: must knowfreq 50%

answer

  1. index 0 is the script name
  2. $argc counts the script name too
  3. every value is a string
  4. globals, not superglobals
  5. $_SERVER['argv'] works anywhere

basics

~20 s

$argv is an array of the command-line arguments with the script name at index 0, and $argc is count($argv). Both are ordinary global variables, not superglobals, so inside a function use $_SERVER['argv'] or pass the array in.

solid answer

~40 s

When you run `php import.php users.csv 100`, `$argv` is `['import.php', 'users.csv', '100']` and `$argc` is `3` — the count includes the script name. Every element is a `string`, so `'100'` needs converting and validating before use as a number. `$argv` and `$argc` are **global variables**, not superglobals: inside a function or method they are undefined unless you write `global $argv;`, so real code reads `$_SERVER['argv']` or, better, passes the array into the entry point. Arguments that start with `-` can be taken by the PHP binary itself; put `--` before them so they reach the script. To test whether a script runs from the command line, check `PHP_SAPI === 'cli'` or `php_sapi_name()`, not whether `$argv` is set.

code

php · 19 lines
php
<?php
declare(strict_types=1);

/** @param list<string> $argv */
function main(array $argv): int
{
    if (count($argv) < 2) {
        fwrite(STDERR, "usage: {$argv[0]} <file.csv>\n");
        return 2;
    }
    $file = $argv[1];
    // ... import $file
    return 0;
}

if (PHP_SAPI !== 'cli') {
    exit(1);
}
exit(main($argv));

go deeper

for a junior

Recall that $argv[0] is the script name, that $argc counts it, and that every argument is a string.

for a middle

Explain why functions cannot see $argv, the options $_SERVER['argv'] and dependency passing, and how -- keeps dash-prefixed arguments away from the PHP binary.

for a senior

Structure commands so the entry point receives argv as a parameter, validates every value, and refuses to run outside the CLI SAPI.

for a principal

Decide when hand-rolled argv handling stops being acceptable and a shared console library should own argument parsing across the codebase.

## What the CLI gives a script When a PHP script runs from a terminal, cron or a CI job, the words after the script name are its **arguments**. PHP's command-line SAPI exposes them in two variables: | Variable | Content for `php import.php users.csv 100` | |---|---| | `$argv` | `[0 => 'import.php', 1 => 'users.csv', 2 => '100']` | | `$argc` | `3` | | `$_SERVER['argv']` | the same array as `$argv` | | `$_SERVER['argc']` | the same number as `$argc` | Key points: - `$argv[0]` is always the name used to run the script, exactly as typed (`import.php`, `./bin/import`, or a full path). - `$argc` is the number of elements in `$argv`, so it is **one more** than the number of real arguments. - Every element is a **string**. `'100'` must be converted and validated — for example with `filter_var($argv[2], FILTER_VALIDATE_INT)` — before being used as a limit. With inline code (`php -r '...'`) or code piped on standard input, `$argv[0]` is the string `"Standard input code"` rather than a file name. ## Why functions do not see `$argv` `$argv` and `$argc` are registered as ordinary **global variables** in the script's top-level scope. PHP functions have their own local scope and do not see globals unless told to. Only a fixed set of **superglobals** (`$_SERVER`, `$_GET`, `$GLOBALS` and the like) are visible everywhere. So: ```php <?php function main(): int { var_dump($argv); // Warning: Undefined variable $argv, then NULL return 0; } exit(main()); ``` Three fixes, from worst to best: 1. `global $argv;` inside the function — works, but hides the dependency. 2. `$_SERVER['argv']` — a superglobal, so visible anywhere. 3. **Pass it in**: `exit(main($argv));` with `function main(array $argv): int`. The function becomes testable with any array, which is how command classes in frameworks receive input. ## When `$argv` is missing The variables exist only when `register_argc_argv` is enabled, which the CLI SAPI always does. Under a web SAPI they are normally absent. That is why the manual recommends testing the SAPI rather than the variable: - `PHP_SAPI === 'cli'` or `php_sapi_name() === 'cli'` answers "am I on the command line?"; - `isset($argv)` can be fooled by configuration and says nothing reliable. A command meant only for the terminal often starts with `if (PHP_SAPI !== 'cli') { exit(1); }` so it can never be triggered through a web request. ## Arguments that start with a dash The `php` binary parses its own options (`-r`, `-d`, `-n` and so on) before starting the script. An argument placed after the code or file that looks like a PHP option can be taken by the binary instead of the script. The argument separator `--` ends PHP's own option parsing: - `php -r 'var_dump($argv);' -h` prints PHP's usage text; - `php -r 'var_dump($argv);' -- -h` passes `-h` to the code. With a script file, arguments after the file name normally go to the script, but `--` is a harmless habit in wrapper scripts. ## Beyond positional arguments Positional access (`$argv[1]`, `$argv[2]`) is fine for one or two required values. As soon as a command grows flags such as `--dry-run` or `--limit=100`, parse them with `getopt()` or a console library instead of scanning `$argv` by hand — manual parsing breaks on `--limit 100` versus `--limit=100`, repeated flags, and flags placed in unexpected positions.

  • What is $argc for php import.php with no arguments?
    `1`. `$argc` counts the elements of `$argv`, and `$argv[0]` is always the script name, so a run with no arguments still has one element. A check for "no arguments" is therefore `$argc < 2`, not `$argc === 0`.
  • Why does the manual recommend php_sapi_name() over isset($argv) to detect the CLI?
    `$argv` exists only when `register_argc_argv` is on, which is a configuration detail rather than a statement about the SAPI. `php_sapi_name()` (or the `PHP_SAPI` constant) returns `'cli'` exactly when the command-line SAPI is running, so it is the reliable test.

saying these in an interview costs you the question

  • $argv[0] holds the first argument after the script name
  • $argc is the number of arguments excluding the script name
  • $argv is a superglobal, visible inside every function
  • Numeric arguments arrive in $argv as int
  • isset($argv) is the reliable way to detect the CLI
open as a page

In PHP, how do getenv() and putenv() work, and what does getenv() return for a variable that is not set?

level: juniorimportance: must knowfreq 50%

basics

~20 s

getenv('NAME') returns the variable's value as a string, or false when it is not set; getenv() with no argument returns all of them as an array. putenv('NAME=value') sets a variable for the current process and its children until the request ends.

open as a page

In PHP, how do exec(), shell_exec(), system() and passthru() differ in what they return, what they print and how they report the exit code?

level: juniorimportance: must knowfreq 52%

basics

~20 s

exec() returns the last output line, appends every line to $output and sets $result_code. shell_exec() returns the whole output but no exit code. system() prints output and returns the last line; passthru() streams raw bytes.

open as a page

In PHP, how do escapeshellarg() and escapeshellcmd() differ, and which one should wrap a user-supplied filename passed to exec()?

level: middleimportance: must knowfreq 48%

basics

~20 s

escapeshellarg() wraps one value in single quotes so the shell sees exactly one literal argument; use it on every dynamic argument. escapeshellcmd() backslash-escapes metacharacters in a whole command but leaves spaces and paired quotes, so injected extra arguments survive.

open as a page

What do the PHP CLI options -r, -l and -a do, and how does a shebang line make a PHP script runnable as ./import?

level: juniorimportance: should knowfreq 35%

basics

~10 s

php -r runs inline code without <?php tags; php -l only checks syntax; php -a opens an interactive shell that needs readline. A first line #!/usr/bin/env php plus chmod +x makes ./import runnable.

open as a page

How would you parse --dry-run and --limit=100 in a PHP CLI import script with getopt(), and what does getopt() return for each?

level: middleimportance: should knowfreq 38%

basics

~10 s

Call getopt('', ['dry-run', 'limit:'], $rest). A given --dry-run appears as key 'dry-run' with value false, --limit=100 as 'limit' => '100', absent options are missing keys, and $rest is the index where positional arguments begin.

open as a page

In a PHP CLI script, what are the STDIN, STDOUT and STDERR constants, and what should each carry in a shell pipeline?

level: middleimportance: should knowfreq 35%

basics

~20 s

They 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.

open as a page

In a PHP project, what does a .env loader do, and should production read its database credentials from a .env file?

level: middleimportance: should knowfreq 40%

basics

~20 s

PHP never reads .env files; a loader library parses one at boot and copies the values into $_ENV/$_SERVER, optionally putenv(). It suits local development; production sets real environment variables, and a .env with secrets is never committed.

open as a page

In PHP, why is $_ENV often empty even though getenv() returns the variable, and what controls whether $_ENV is filled?

level: middleimportance: should knowfreq 45%

basics

~10 s

$_ENV is filled only when the variables_order directive contains E. Both php.ini-production and php.ini-development ship variables_order = "GPCS", so $_ENV stays empty, while getenv() reads the environment directly and still works.

open as a page

In PHP, how does parse_ini_file() handle sections and INI_SCANNER_TYPED when loading staging and production database settings from one file?

level: middleimportance: should knowfreq 28%

basics

~20 s

Pass true as the second argument to get one sub-array per [section]; otherwise sections are flattened and later keys overwrite earlier ones. INI_SCANNER_TYPED returns real bool, null, int and float values instead of strings like "1" and "".

open as a page

In a PHP CLI parent that forks workers with pcntl_fork(), how do you reap finished children and read how each one ended?

level: middleimportance: should knowfreq 20%

basics

~10 s

Call pcntl_wait() or pcntl_waitpid() for every child: they return the PID of a child that ended and fill a status integer. Decode it with pcntl_wifexited()/pcntl_wexitstatus() or pcntl_wifsignaled()/pcntl_wtermsig(); WNOHANG makes the call non-blocking.

open as a page

In a PHP CLI script, why might a pcntl_signal() handler never run, and how do pcntl_async_signals() and pcntl_signal_dispatch() fix that?

level: middleimportance: should knowfreq 30%

basics

~10 s

pcntl_signal() only makes PHP queue the signal; the PHP callback runs when the queue is dispatched. Asynchronous dispatch is off by default, so either call pcntl_async_signals(true) or call pcntl_signal_dispatch() regularly in the loop.

open as a page

A PHP import script run by cron ends with exit('Import failed') on errors, yet the scheduler records success; why, and how should it report failure?

level: seniorimportance: should knowfreq 32%

basics

~20 s

exit() with a string prints it and ends with status 0, meaning success. Write the message to STDERR and exit() with an integer: 0 for success, 1-254 for failures; PHP itself uses 255 for fatal errors and uncaught exceptions.

open as a page

Where should a PHP application keep its staging and production database passwords, and who can read the value in each place?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Never in the repository: commit a PHP config file that returns an array without the secret, and inject each environment's password through an environment variable or a secret file outside the document root; phpinfo() and child processes can expose environment values.

open as a page

How would you make a PHP CLI queue-consumer daemon finish its current job and exit cleanly when its process manager sends SIGTERM?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Enable pcntl_async_signals(true), register SIGTERM (and SIGINT) handlers that only set a stop flag, check the flag between jobs, keep blocking waits short, and exit(0) after the current job is acknowledged — within the manager's grace period before SIGKILL.

open as a page

Why prefer proc_open() with an array command in PHP, and how do you read stdout and stderr and get the exit code without deadlocking?

level: seniorimportance: should knowfreq 30%

basics

~20 s

With an array command, proc_open() runs the program without a shell, so each element is one argument and nothing needs escaping. Drain stdout and stderr together with stream_select() or redirect one, close the pipes, then proc_close() returns the exit code.

open as a page

In PHP's pcntl extension, what does pcntl_fork() return, and how do the parent and the child tell which one they are?

level: juniorimportance: nice to knowfreq 18%

basics

~20 s

pcntl_fork() returns the new child's PID in the parent, 0 in the child, and -1 in the parent if the fork failed. Both processes continue from the line after the call, each with its own copy of every variable.

open as a page

How do you prompt for confirmation in a PHP CLI script with readline(), and what must change when input is piped instead of typed?

level: middleimportance: nice to knowfreq 15%

basics

~20 s

readline('Proceed? [y/N] ') shows a prompt and returns the typed line without its newline, or false when input ends. With piped input nobody can answer, so check stream_isatty(STDIN) first and require an explicit flag instead of prompting.

open as a page

A PHP CLI script opens a PDO connection and then calls pcntl_fork() to start four workers that each run queries; what breaks, and how do you fix it?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Every child inherits the same database socket, so their queries interleave on one connection and corrupt it, and a child's exit can close the session the parent still uses. Open connections after pcntl_fork(), separately in each process.

open as a page

How do you stop an external command that PHP started with proc_open() when it runs too long, and what does proc_terminate() actually do?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

Start the tool with proc_open(), poll its pipes with stream_select() and proc_get_status() against a deadline, then call proc_terminate(). It sends SIGTERM by default and returns immediately with a bool; wait briefly, send SIGKILL (9) if still running, then proc_close().

open as a page