| name | check-file-io |
| description | Audits file I/O patterns in PHP code. Detects full-file reads into memory, missing file locks, temp file cleanup issues, missing stream usage, and insecure file operations. |
File I/O Patterns Audit
Analyze PHP code for suboptimal or dangerous file I/O patterns.
Detection Patterns
1. Full File Read Into Memory
$content = file_get_contents('/path/to/large-file.csv');
$lines = file('/path/to/large-file.csv');
$content = file_get_contents($path);
$lines = explode("\n", $content);
foreach ($lines as $line) {
$this->process($line);
}
$handle = fopen($path, 'rb');
while (($line = fgets($handle)) !== false) {
$this->process(trim($line));
}
fclose($handle);
function readLines(string $path): \Generator
{
$handle = fopen($path, 'rb');
try {
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
} finally {
fclose($handle);
}
}
2. Missing File Locks
class FileLogger
{
public function log(string $message): void
{
$handle = fopen($this->path, 'ab');
fwrite($handle, $message . "\n");
fclose($handle);
}
}
$counter = (int) file_get_contents('counter.txt');
$counter++;
file_put_contents('counter.txt', (string) $counter);
$handle = fopen($this->path, 'cb+');
if (flock($handle, LOCK_EX)) {
$counter = (int) stream_get_contents($handle);
$counter++;
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, (string) $counter);
flock($handle, LOCK_UN);
}
fclose($handle);
3. Temp File Not Cleaned Up
class PdfGenerator
{
public function generate(array $data): string
{
$tmpFile = tempnam(sys_get_temp_dir(), 'pdf_');
file_put_contents($tmpFile, $this->render($data));
$pdf = $this->converter->convert($tmpFile);
return $pdf;
}
}
class PdfGenerator
{
public function generate(array $data): string
{
$tmpFile = tempnam(sys_get_temp_dir(), 'pdf_');
try {
file_put_contents($tmpFile, $this->render($data));
return $this->converter->convert($tmpFile);
} finally {
if (file_exists($tmpFile)) {
unlink($tmpFile);
}
}
}
}
4. Missing File Handle Cleanup
class CsvProcessor
{
public function process(string $path): array
{
$handle = fopen($path, 'rb');
$results = [];
while (($row = fgetcsv($handle)) !== false) {
$results[] = $this->transform($row);
}
fclose($handle);
return $results;
}
}
class CsvProcessor
{
public function process(string $path): array
{
$handle = fopen($path, 'rb');
try {
$results = [];
while (($row = fgetcsv($handle)) !== false) {
$results[] = $this->transform($row);
}
return $results;
} finally {
fclose($handle);
}
}
}
5. SplFileObject Not Used for CSV
$handle = fopen($path, 'rb');
$header = fgetcsv($handle);
while ($row = fgetcsv($handle)) {
$data = array_combine($header, $row);
}
fclose($handle);
function readCsv(string $path): \Generator
{
$file = new \SplFileObject($path, 'rb');
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
$header = $file->fgetcsv();
foreach ($file as $row) {
if ($row === [null]) continue;
yield array_combine($header, $row);
}
}
6. Unsafe File Path Construction
$path = '/uploads/' . $request->get('filename');
if (file_exists($path)) {
$content = file_get_contents($path);
}
$basePath = realpath('/uploads');
$fullPath = realpath('/uploads/' . basename($request->get('filename')));
if ($fullPath === false || !str_starts_with($fullPath, $basePath)) {
throw new SecurityException('Invalid file path');
}
$content = file_get_contents($fullPath);
7. Writing Large Output Without Streaming
class ExportController
{
public function export(): Response
{
$data = $this->repository->findAll();
$csv = '';
foreach ($data as $row) {
$csv .= implode(',', $row) . "\n";
}
return new Response($csv);
}
}
class ExportController
{
public function export(): StreamedResponse
{
return new StreamedResponse(function () {
$handle = fopen('php://output', 'wb');
foreach ($this->repository->findAllIterable() as $row) {
fputcsv($handle, $row);
flush();
}
fclose($handle);
}, 200, ['Content-Type' => 'text/csv']);
}
}
Grep Patterns
Grep: "file_get_contents\(|file\(" --glob "**/*.php"
Grep: "fwrite\(|file_put_contents\(" --glob "**/*.php"
Grep: "flock\(" --glob "**/*.php"
Grep: "tempnam\(|sys_get_temp_dir\(|tmpfile\(" --glob "**/*.php"
Grep: "unlink\(.*tmp" --glob "**/*.php"
Grep: "fopen\(" --glob "**/*.php"
Grep: "finally.*fclose" --glob "**/*.php"
Grep: "fgetcsv\(|SplFileObject" --glob "**/*.php"
Grep: "\\\$.*\.=.*\\\\n|\\\$.*\.= implode" --glob "**/*.php"
Severity Classification
| Pattern | Severity |
|---|
| Full file read of unbounded size | 🔴 Critical |
| File handle leak on exception | 🔴 Critical |
| Write without lock (shared file) | 🟠 Major |
| Temp file not cleaned up | 🟠 Major |
| Building large string in memory | 🟠 Major |
| Missing SplFileObject for CSV | 🟡 Minor |
Output Format
### File I/O: [Description]
**Severity:** 🔴/🟠/🟡
**Location:** `file.php:line`
**Impact:** [Memory/Performance/Data integrity]
**Issue:**
[Description of the file I/O problem]
**Code:**
```php
// Problematic pattern
Fix:
Expected Improvement:
Memory: 500MB → 4KB (streaming)