← All writing

Close a file even when CSV processing throws an exception

PHP 8.3+Sources checked 2026-09-13

A CSV import opens a file and processes rows until a validation error throws. If cleanup exists only after the loop, it is skipped on that error path. Resource cleanup belongs in a finally block.

Separate processing from cleanup

<?php
$stream = fopen('php://temp', 'w+');
if ($stream === false) throw new RuntimeException('Cannot open stream');
try {
    fwrite($stream, "sku,quantity\nAX-1,2\n");
    rewind($stream);
    $header = fgetcsv($stream, escape: '');
    assert($header === ['sku', 'quantity']);
} finally {
    fclose($stream);
}
assert(!is_resource($stream));

The cleanup executes whether the try block completes or throws. Keep it small. Throwing a new exception or returning a value from finally can obscure the original outcome, making the real import failure harder to diagnose.

Cleanup is not rollback

Closing a stream does not undo database changes already made for earlier rows. Decide whether the import is atomic, chunked, or intentionally partial. Record enough progress for a retry to know which work already succeeded.

For a remote download, avoid holding a database transaction open while waiting for the network. Obtain and validate the input first, then perform appropriately bounded writes. A file lifecycle and a transaction lifecycle often have different boundaries.

Test an exception midway through processing and verify that resources are closed and the recorded import state is correct. A happy-path row count does not cover cleanup. Temporary files that survive a failure should have an explicit retention and removal policy, particularly when they contain customer data.

Reference

Official documentation. The examples here illustrate the stated boundary; adapt them to your application and test its failure paths.