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_data
in the database is a JSON string, we used thestatic
modifier 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.php
extendsIlluminate\Console\Command
, running thehandle
method when executed
- The worker retrieves queue driver configuration and passes a
once
parameter to determine if it should process a single job - Examining the
daemon
method insrc\Illuminate\Queue\Worker.php
:
Laravel - The first three lines handle process signal listening
$lastRestart
gets 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
getNextJob
fetches the next task from queue driver (Redis, Database, etc.)registerTimeoutHandler
handles task timeout using async signals if supported- Processes jobs or sleeps when no tasks available
Laravel
Laravel - The
fire
method uses reflection to invoke job handlers
Laravel stopIfNecessary
checks for termination conditions
- The static variable
$jsonData
persists 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