A common PHP error:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 8192 bytes)
PHP has a memory_limit setting (128 MB is a typical default for web requests). A script that tries to use more is stopped. The best fix is not to raise the limit, but to use less memory.
Where memory goes
PHP arrays are flexible but not small. An array of one million integers takes tens of megabytes, far more than the raw numbers. Loading a big file, a big query result or a big API response into one variable adds up fast.
<?php
$before = memory_get_usage();
$numbers = range(1, 200000);
$used = memory_get_usage() - $before;
echo $used > 1_000_000 ? "200,000 integers use more than 1 MB" : "less than 1 MB";
200,000 integers use more than 1 MB
1. Process in chunks, not all at once
<?php
file_put_contents("big.csv", implode("\n", range(1, 5000)));
// Loads the whole file into an array
$lines = file("big.csv");
// Reads one line at a time
$handle = fopen("big.csv", "r");
$sum = 0;
while (($line = fgets($handle)) !== false) {
$sum += (int) $line;
}
fclose($handle);
echo count($lines) . " lines, sum $sum";
5000 lines, sum 12502500
The second approach holds one line in memory at a time, whatever the file size.
2. Use generators
A generator produces one item at a time (see Generators).
<?php
function readLines(string $file): Generator
{
$h = fopen($file, "r");
try {
while (($line = fgets($h)) !== false) {
yield (int) $line;
}
} finally {
fclose($h);
}
}
file_put_contents("big.csv", implode("\n", range(1, 5000)));
$total = 0;
foreach (readLines("big.csv") as $n) {
$total += $n;
}
echo $total;
12502500
3. Query in batches
<?php
$pdo = require __DIR__ . "/db.php";
$batchSize = 2;
$lastId = 0;
$processed = [];
while (true) {
$stmt = $pdo->prepare("SELECT id, name FROM products WHERE id > :last ORDER BY id LIMIT :limit");
$stmt->bindValue(":last", $lastId, PDO::PARAM_INT);
$stmt->bindValue(":limit", $batchSize, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll();
if ($rows === []) {
break;
}
foreach ($rows as $row) {
$processed[] = $row["name"];
$lastId = $row["id"];
}
}
echo implode(", ", $processed);
Notebook, Pen, Bag, Marker, Diary
Only two rows are in memory at once. For very large tables, MySQL can also stream rows with an unbuffered query (PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false).
4. Select only what you need
Fewer columns and fewer rows mean fewer bytes. Do not load big text or binary columns unless you need them.
5. Free what you no longer need
<?php
$big = range(1, 100000);
$count = count($big);
unset($big); // the memory can be reused
echo $count;
100000
PHP frees memory when a variable goes out of scope (at the end of a function), so most code does not need unset(). In long loops that build large temporary values, it can help.
6. Avoid unneeded copies
PHP copies arrays only when you change a copy (copy-on-write). Passing a big array to a function does not copy it, unless the function modifies it. Do not worry about reading.
7. Raise the limit only as a last step
<?php
ini_set("memory_limit", "512M"); // for one heavy script, e.g. an import
Raising it hides a design problem and can take memory from other requests. For command-line jobs that process large data, use chunking, and set the limit explicitly.
8. Image processing needs care
Resizing a large image decodes it to raw pixels: a 6000 x 4000 photo needs about 96 MB in memory (width x height x 4 bytes). Check an image’s dimensions before you load it.
<?php
function imageMemoryBytes(int $width, int $height): int
{
return $width * $height * 4;
}
echo round(imageMemoryBytes(6000, 4000) / 1048576) . " MB";
92 MB
9. Find memory leaks in long scripts
Queue workers and import scripts run for a long time. Watch memory_get_usage() over time, clear caches, close handles and avoid storing everything in a growing array.