check-file-io
File I/O Patterns Audit
Analyze PHP code for suboptimal or dangerous file I/O patterns.
Detection Patterns
1. Full File Read Into Memory
// CRITICAL: Entire file loaded into memory
$content = file_get_contents('/path/to/large-file.csv'); // 500MB file → OOM!
$lines = file('/path/to/large-file.csv'); // Loads all lines into array!
// CRITICAL: Reading large file to process line by line
$content = file_get_contents($path);
$lines = explode("\n", $content); // Double memory: content + lines array
foreach ($lines as $line) {
$this->process($line);
}
// CORRECT: Stream processing
$handle = fopen($path, 'rb');
while (($line = fgets($handle)) !== false) {
$this->process(trim($line));
}
fclose($handle);
// CORRECT: Generator for large files
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
// CRITICAL: Concurrent writes without locking
class FileLogger
{
public function log(string $message): void
{
$handle = fopen($this->path, 'ab');
fwrite($handle, $message . "\n"); // Race condition with concurrent writers!
fclose($handle);
}
}
// CRITICAL: Read-modify-write without lock
$counter = (int) file_get_contents('counter.txt');
$counter++;
file_put_contents('counter.txt', (string) $counter);
// Concurrent requests: both read 5, both write 6 instead of 7
// CORRECT: File locking
$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
// CRITICAL: Temp file created but never deleted
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);
// $tmpFile never deleted! Disk fills up over time
return $pdf;
}
}
// CORRECT: Always clean up in finally
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
// CRITICAL: File handle not closed on exception
class CsvProcessor
{
public function process(string $path): array
{
$handle = fopen($path, 'rb');
$results = [];
while (($row = fgetcsv($handle)) !== false) {
$results[] = $this->transform($row); // If this throws, handle leaks!
}
fclose($handle);
return $results;
}
}
// CORRECT: try/finally pattern
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
// ANTIPATTERN: Manual CSV parsing
$handle = fopen($path, 'rb');
$header = fgetcsv($handle);
while ($row = fgetcsv($handle)) {
$data = array_combine($header, $row);
}
fclose($handle);
// CORRECT: SplFileObject with generator
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
// CRITICAL: Path traversal + race condition
$path = '/uploads/' . $request->get('filename');
if (file_exists($path)) { // Check
$content = file_get_contents($path); // Use — TOCTOU!
}
// CORRECT: Validate and use realpath
$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
// CRITICAL: Building entire response in memory
class ExportController
{
public function export(): Response
{
$data = $this->repository->findAll(); // 100K rows
$csv = '';
foreach ($data as $row) {
$csv .= implode(',', $row) . "\n"; // String grows to 500MB
}
return new Response($csv);
}
}
// CORRECT: Streaming response
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
# Full file reads
Grep: "file_get_contents\(|file\(" --glob "**/*.php"
# Missing file locks
Grep: "fwrite\(|file_put_contents\(" --glob "**/*.php"
Grep: "flock\(" --glob "**/*.php"
# Temp files
Grep: "tempnam\(|sys_get_temp_dir\(|tmpfile\(" --glob "**/*.php"
Grep: "unlink\(.*tmp" --glob "**/*.php"
# File handles without finally
Grep: "fopen\(" --glob "**/*.php"
Grep: "finally.*fclose" --glob "**/*.php"
# CSV processing
Grep: "fgetcsv\(|SplFileObject" --glob "**/*.php"
# String concatenation for output
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:
// Stream-based / locked / cleaned-up pattern
Expected Improvement: Memory: 500MB → 4KB (streaming)
More from dykyi-roman/awesome-claude-code
psr-overview-knowledge
PHP Standards Recommendations (PSR) overview knowledge base. Provides comprehensive reference for all accepted PSRs including PSR-1,3,4,6,7,11,12,13,14,15,16,17,18,20. Use for PSR selection decisions and compliance audits.
22detect-code-smells
Detects code smells in PHP codebases. Identifies God Class, Feature Envy, Data Clumps, Long Parameter List, Long Method, Primitive Obsession, Message Chains, Inappropriate Intimacy. Generates actionable reports with refactoring recommendations.
15clean-arch-knowledge
Clean Architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Clean Architecture and Hexagonal Architecture audits.
15ddd-knowledge
DDD architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Domain-Driven Design audits.
14testing-knowledge
Testing knowledge base for PHP 8.4 projects. Provides testing pyramid, AAA pattern, naming conventions, isolation principles, DDD testing guidelines, and PHPUnit patterns.
12bug-root-cause-finder
Root cause analysis methods for PHP bugs. Provides 5 Whys technique, fault tree analysis, git bisect guidance, and stack trace parsing.
12