In PHP, when do you use file_get_contents() and file_put_contents() instead of fopen() with fgets() and fwrite(), and what does each approach cost?
answer
- whole file versus one piece at a time
- string|false on failure, plus E_WARNING
- memory_limit is 128M by default
- FILE_APPEND and LOCK_EX flags
- compare the result with === false
basics
~20 sfile_get_contents() and file_put_contents() read or write a whole file in one call, so the entire content sits in memory. fopen() returns a handle for fgets(), fread() and fwrite(), keeping memory flat for large or streamed files.
solid answer
~50 s`file_get_contents($path)` returns the whole file as one string, or `false` plus an `E_WARNING` when it cannot open it. `file_put_contents($path, $data)` opens, writes and closes in one call and returns the number of bytes written or `false`; by default it truncates, `FILE_APPEND` makes it append and `LOCK_EX` takes an exclusive lock for the write. They are the right tool for small files: a template, a JSON document, a cache entry. The cost is memory, because the whole content becomes one string, and a file bigger than what is left of `memory_limit` (128M by default) ends in a fatal "Allowed memory size exhausted" error. `fopen()` returns a stream handle that you read with `fgets()`/`fread()` and write with `fwrite()`, so only one line or chunk is in memory at a time; the price is more code and an `fclose()`.
code
php · 11 lines<?php
declare(strict_types=1);
$config = file_get_contents(__DIR__ . '/config.json');
if ($config === false) {
throw new RuntimeException('config.json is missing or unreadable');
}
$settings = json_decode($config, true, flags: JSON_THROW_ON_ERROR);
// Overwrite a small cache file; LOCK_EX serialises concurrent writers.
file_put_contents(__DIR__ . '/cache/settings.php', '<?php return ' . var_export($settings, true) . ';', LOCK_EX);go deeper
Recall that the shortcuts load or write the whole file in one call, return false on failure, and that FILE_APPEND switches file_put_contents() from truncate to append.
Explain the memory trade-off against memory_limit, why failure is tested with === false, and what the $offset, $length and LOCK_EX options add.
Show you pick the handle API for unbounded input, close handles in long-running scripts, and turn the false-plus-warning result into an exception at the boundary.
Frame it as a policy question: which code paths may assume bounded file sizes, and where a streaming abstraction is mandatory so an import never depends on memory_limit.
## Two styles of file I/O in PHP PHP gives you two ways to work with a file's contents: - **The one-call shortcuts** — `file_get_contents()`, `file_put_contents()` and `file()` — open the file, do the whole job and close it before they return. - **The handle API** — `fopen()` returns a **stream resource** (a handle), and you then call `fgets()`, `fread()`, `fwrite()`, `fseek()` and finally `fclose()` on it. Both end up in the same stream layer, so both accept local paths and the stream wrappers PHP supports. What differs is **how much of the file lives in PHP memory at once** and how much control you keep between operations. ## The shortcuts and what they return | Function | Does | Returns on success | Returns on failure | |---|---|---|---| | `file_get_contents($path)` | reads the whole file into one string | `string` | `false` + `E_WARNING` | | `file($path, $flags)` | reads the whole file into an array of lines | `array` | `false` + `E_WARNING` | | `file_put_contents($path, $data, $flags)` | writes `$data`, then closes | bytes written (`int`) | `false` | Details worth knowing: - `file_put_contents()` **truncates** an existing file unless you pass `FILE_APPEND`. Passing `LOCK_EX` takes an exclusive `flock()` on the file for the duration of the write; the flags combine with `|`. - `$data` may be a string, an array (its elements are written one after another with no separator) or a stream resource, whose remaining contents are copied. - `file_get_contents()` also takes an `$offset` and a `$length`, so `file_get_contents($path, false, null, 100, 50)` reads 50 bytes starting at byte 100. A negative offset counts from the end of the file. - `file()` keeps the line ending on every element unless you pass `FILE_IGNORE_NEW_LINES`. ## The handle API ```php <?php declare(strict_types=1); $in = fopen('/var/data/input.txt', 'r'); if ($in === false) { throw new RuntimeException('cannot open input'); } while (($line = fgets($in)) !== false) { // one line in memory at a time } fclose($in); ``` - `fopen($path, $mode)` returns a resource or `false`; the mode string (`'r'`, `'w'`, `'a'`, `'x'`, `'c'`, with an optional `'+'`) decides what happens to existing content. - `fgets($h)` returns the next line including its newline, or `false` when nothing is left. - `fread($h, $n)` returns up to `$n` bytes; `fwrite($h, $s)` returns the number of bytes written or `false`. - `fclose($h)` releases the handle. PHP also closes every handle at the end of the request, but a long script or worker that opens files in a loop without closing them can run into the operating system's open-file limit. ## Memory is the deciding cost A shortcut turns the whole file into a PHP string, and `file()` turns it into an array of strings, each with its own header and an array slot, so its footprint grows beyond the file size. Every script runs under `memory_limit` (128M in the built-in default and in both shipped php.ini files). A read that needs more stops the script with the fatal error *Allowed memory size of N bytes exhausted*. With a handle, memory stays near the size of one line or one chunk, no matter how large the file is. ## Checking for failure correctly Neither style throws on a missing or unreadable file: both return `false` and emit an `E_WARNING`. Always compare with `=== false`, because an empty file returns `""` and a file containing `0` returns `"0"`, and both are falsy: 1. Call the function and keep the result. 2. Test `$result === false`, never `!$result`. 3. Turn the failure into an exception or a clear error of your own, since the warning alone does not stop the script. ## Choosing - **Small, bounded files** (config, templates, a JSON payload, a cache file): the shortcuts — one line, closed for you. - **Large or unbounded files, logs, imports, exports**: `fopen()` plus `fgets()`/`fread()`/`fwrite()`, or `SplFileObject`, so memory stays flat. - **Several operations on the same open file** (lock, read, rewrite, unlock): a handle, because the shortcuts close the file before you can do the next step.
- What does file_put_contents() do when you pass it an array instead of a string?It writes each element's string value one after another with no separator, so `['a', 'b']` produces `ab`. If you want lines, add the newlines yourself, for example with `implode("\n", $lines) . "\n"`. It also accepts a stream resource as `$data` and copies whatever remains in that stream into the file.
- How do you read only part of a file with file_get_contents()?Pass the fourth and fifth arguments: `file_get_contents($path, false, null, $offset, $length)`. The offset may be negative to count from the end of the file, which is handy for the last few kilobytes of a log. A negative `$length` throws a `ValueError`. Seeking with an offset is not supported reliably on remote streams.
- Why might a script that reads files in a loop fail even though each file is small?If it uses `fopen()` and forgets `fclose()`, each iteration keeps another descriptor open until the request ends, and a long CLI job or worker can hit the operating system's per-process open-file limit, after which `fopen()` returns `false`. The shortcuts close the file themselves, and a handle is also released when its last reference goes away.
saying these in an interview costs you the question
- file_get_contents() streams the file, so it is safe for files of any size.
- Checking if (!$data) is enough to detect that the read failed.
- file_put_contents() appends to an existing file by default.
- fopen() throws an exception when the file does not exist.
- An unclosed fopen() handle stays open for later requests to reuse.