Fix PHP 'Allowed Memory Size Exhausted'

September 25, 2026 · 2 views
Fix PHP 'Allowed Memory Size Exhausted'

You deploy a Laravel import script or a WooCommerce product sync, watch it run fine on your laptop, and then it dies in production with Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes). This is the classic PHP allowed memory size exhausted error, and it almost never means your server actually needs more RAM — it usually means a specific line of code is holding onto data it should have released minutes earlier.

This guide covers exactly what triggers the error, the fast temporary fix, and the actual code-level fix that stops it from coming back the next time your dataset grows.

What "Allowed Memory Size Exhausted" Actually Means

PHP enforces a per-script memory ceiling defined by the memory_limit directive in php.ini (commonly 128M by default, sometimes lower on shared hosting). When a single request or CLI process tries to allocate more memory than that ceiling allows, PHP kills the process immediately and throws this fatal error rather than letting the server run out of RAM entirely.

The number in the error message tells you two things:

  • The first number (134217728 bytes = 128MB) is your current memory_limit.
  • The second number (tried to allocate 20480 bytes) is the allocation that finally pushed the script over the edge — not necessarily the biggest offender, just the one that happened to hit the wall.

That second detail trips people up constantly: the line PHP reports is rarely the actual cause. By the time that small 20KB allocation fails, your script has usually already loaded megabytes of data into memory that never got released.

The Quick Fix (and Why It's Not a Real Fix)

If you need the script to survive right now, you can raise the limit:

// At the top of a specific script
ini_set('memory_limit', '512M');

// Or via CLI for a one-off Artisan command
// php -d memory_limit=512M artisan import:products

Or in php.ini / .htaccess for a permanent (but blunt) change:

memory_limit = 512M

This works, but it's treating the symptom. If your product import currently needs 512MB for 10,000 rows, it will need 2GB for 40,000 rows, and you'll be back here again the moment your catalog grows. Raising the limit buys time — it doesn't fix the underlying pattern that's eating memory in the first place.

Finding the Real Cause

The usual culprit is loading an entire dataset into a PHP array or Eloquent collection at once, instead of processing it in smaller pieces. This shows up constantly in three places in Aliyan's typical stack: Laravel data imports, WooCommerce/Shopify bulk product syncs, and any script that reads a large CSV or API response into memory before processing it.

Confirm it with memory_get_usage() before you guess:

echo memory_get_usage(true) / 1024 / 1024 . " MB\n";

$products = Product::all(); // loads every row into one collection
echo memory_get_usage(true) / 1024 / 1024 . " MB\n"; // jumps dramatically here

If the second number spikes hard after one line, you've found it. Product::all() on a 50,000-row table doesn't just load 50,000 rows — it hydrates 50,000 full Eloquent model objects, each carrying its own memory overhead, attributes, and relations, all held in memory simultaneously.

The Actual Fix: Process in Chunks, Not All at Once

Replace bulk loading with Laravel's chunk() or cursor(), which pull and release rows in small batches instead of holding the entire result set in memory:

// Before: loads the entire table into memory
$products = Product::all();
foreach ($products as $product) {
    $product->syncToWooCommerce();
}

// After: processes 500 rows at a time, releases memory between batches
Product::chunk(500, function ($products) {
    foreach ($products as $product) {
        $product->syncToWooCommerce();
    }
});

// Even lighter: cursor() uses a generator and holds only ONE row in memory at a time
foreach (Product::cursor() as $product) {
    $product->syncToWooCommerce();
}

chunk() runs a new query for each batch and only keeps that batch's models in memory. cursor() goes further, using PHP generators to hydrate one model at a time — the right choice when even a 500-row batch is too heavy, such as when each row triggers an external API call like a WooCommerce or Shopify sync.

For raw CSV imports, avoid file() (which reads the whole file into an array) in favor of fgetcsv() in a loop, which reads one line at a time:

$handle = fopen(storage_path('imports/products.csv'), 'r');
while (($row = fgetcsv($handle)) !== false) {
    Product::updateOrCreate(['sku' => $row[0]], [
        'name' => $row[1],
        'price' => $row[2],
    ]);
}
fclose($handle);

Common Mistakes That Cause This Error

  • Calling Model::all() or ->get() on large tables instead of chunk(), cursor(), or pagination
  • Eager-loading relationships on a huge collection (->with('images', 'variants')) without also chunking the base query
  • Reading entire files with file_get_contents() or file() instead of streaming them line by line
  • Appending to an array inside a loop indefinitely without ever unsetting or processing it in batches
  • Processing large image uploads or PDF generation in the same request instead of offloading to a queued job with its own memory limit
  • Running long import jobs via a queue worker that inherited a low memory_limit from a shared hosting default, rather than a job-specific override

Best Practices to Prevent It Long-Term

  • Default to chunk() or cursor() for any Eloquent query that could realistically return more than a few hundred rows
  • Move heavy imports, exports, and syncs into queued jobs, where you can set a dedicated, generous memory limit without affecting your main web requests
  • Call unset($variable) on large temporary arrays once you're done with them inside long-running loops, especially in CLI scripts that process for minutes at a time
  • Monitor real memory usage in staging with memory_get_peak_usage(true) before a feature ships, not after it fails in production
  • For WooCommerce/Shopify bulk syncs specifically, batch API calls too — don't hold thousands of product responses in memory waiting to loop through them

Frequently Asked Questions

What memory_limit should I set for a Laravel queue worker? There's no universal number — start by measuring actual peak usage with memory_get_peak_usage(true) on a realistic dataset, then set the limit 30-50% above that measured peak, not an arbitrary round number like 1G "just in case."

Does raising memory_limit to -1 (unlimited) fix this safely? No. It removes PHP's own safety net and lets a runaway script consume all available server RAM, which can crash other processes on the same machine, including your web server. Set an explicit, generous number instead.

Why does this happen in production but not locally? Production datasets are almost always larger than your local seed data, and shared hosting or containerized environments often ship with a lower default memory_limit than your local PHP install.

Does chunk() guarantee I'll never hit this error again? It prevents the "load everything at once" pattern, but a chunk size that's still too large, or code inside the chunk callback that itself accumulates data across iterations, can still exhaust memory — the fix is the pattern, not just the function name.

Key Takeaways

The "Allowed memory size exhausted" error is almost always a signal to change how your code loads data, not a signal to raise a number in php.ini. Start by measuring where the spike actually happens with memory_get_usage(), then replace bulk all()/get() calls with chunk(), cursor(), or line-by-line file reads wherever a dataset can realistically grow past a few hundred rows. Raise memory_limit as a short-term stopgap if you need the script running today, but treat it as borrowed time, not a fix.

#laravel #php #debugging #woocommerce #memory-management #eloquent
Share this article:

0 Comments

No comments yet — be the first to share your thoughts.

Leave a comment

Never published.