From acc
Audits PHP file I/O patterns detecting full-file reads into memory, missing locks, temp file cleanup issues, missing streams, and insecure operations.
How this skill is triggered — by the user, by Claude, or both
Slash command
/acc:check-file-ioThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Analyze PHP code for suboptimal or dangerous file I/O patterns.
Analyze PHP code for suboptimal or dangerous file I/O patterns.
// 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);
}
}
// 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);
// 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);
}
}
}
}
// 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);
}
}
}
// 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);
}
}
// 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);
// 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']);
}
}
# 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"
| 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 |
### 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)
npx claudepluginhub dykyi-roman/awesome-claude-code --plugin accDetects resource leaks in PHP code including unclosed file handles, unreleased database connections, unfreed streams, missing finally blocks, uncleaned temporary files, sockets, GD images, and Doctrine resources.
Review PHP code using PhpStorm inspections. Use when editing PHP files, reviewing code quality, fixing PHP issues, or when asked about PHP best practices.
Analyzes code quality in custom WordPress themes and plugins using two-pass approach: quick grep scans for deprecated functions and anti-patterns, then AI contextual review of flagged PHP/JS files.