Environment
PHP_VERSION=7.4
laravel/framework: ^7.0
Static Variables
- In many programming languages, static variables are defined as variables that:
- Have the same lifecycle as the program
- Are initialized only once
- However, in PHP’s common runtime environment (php-fpm mode), processes get recycled after each request, so static variables don’t persist in memory (only effective during the current request)
- Official PHP documentation states:
“Another important feature of variable scoping is the static variable. A static variable exists only in a local function scope, but it does not lose its value when program execution leaves this scope.” https://www.php.net/manual/zh/language.variables.scope.php
Preface
- The project contained the following pseudo-code logic: Since
json_datain the database is a JSON string, we used thestaticmodifier to avoid repeated parsing
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AttributeRequestLog extends Model
{
public function getJsonData($key)
{
static $jsonData;
if (is_null($jsonData)) {
$jsonData = json_decode($this->attributes['json_data'], true);
}
return $jsonData[$key] ?? null;
}
}
The code worked fine until we implemented queues for asynchronous tasks. After queue implementation, colleagues reported abnormal data. Log analysis revealed issues in queue task logging, which was ultimately traced back to this static variable usage.
- Laravel queues run in CLI mode as long-running processes
- Queue workers load code on startup and keep it in memory until process termination
Source Code Analysis
- Queue startup command:
php artisan queue:work - The entry file
src\Illuminate\Queue\Console\WorkCommand.phpextendsIlluminate\Console\Command, running thehandlemethod when executed
- The worker retrieves queue driver configuration and passes a
onceparameter to determine if it should process a single job - Examining the
daemonmethod insrc\Illuminate\Queue\Worker.php:
Laravel - The first three lines handle process signal listening
$lastRestartgets a timestamp from cache for graceful shutdown (reset byphp artisan queue:restart)- Uses an infinite loop to keep the process alive
- Checks for maintenance mode and other preconditions
getNextJobfetches the next task from queue driver (Redis, Database, etc.)registerTimeoutHandlerhandles task timeout using async signals if supported- Processes jobs or sleeps when no tasks available
Laravel
Laravel - The
firemethod uses reflection to invoke job handlers
Laravel stopIfNecessarychecks for termination conditions
- The static variable
$jsonDatapersists across multiple queue jobs - Since queue workers are long-running processes:
- Static variables retain values between different job executions
- Subsequent jobs may receive cached/stale data from previous executions
- This caused data contamination between different queue tasks
Solution
- Avoid static variables in queue-related classes
- Use class properties with proper reset mechanisms
- Implement
__destruct()to clear cached data - Alternative implementation without static caching:
public function getJsonData($key)
{
if (!isset($this->jsonDataCache)) {
$this->jsonDataCache = json_decode($this->attributes['json_data'], true);
}
return $this->jsonDataCache[$key] ?? null;
}
public function __destruct()
{
unset($this->jsonDataCache);
}
Key Takeaways
- Static variables behave differently in long-running processes vs. request-response cycles
- Queue workers maintain application state between jobs
- Always consider process lifecycle when using:
- Static variables
- Singleton patterns
- Class-level caching
- Implement proper cleanup mechanisms for resource-intensive operations