PHPOffice/PHPWord · warning · PhpOffice\PhpWord\Exception\Exception
The file could not be deleted.
Error message
The file {tempFileName} could not be deleted. What it means
XMLWriter using file storage creates a temporary file; __destruct() unlinks it when the object is garbage collected. If unlink() fails on non-Windows systems, Exception('The file ... could not be deleted.') is thrown from the destructor — a notoriously awkward place for an exception, often surfacing as an uncatchable-looking error during shutdown.
Solutions
- Ensure the temp directory (from sys_get_temp_dir() or your configured path) is writable AND unlinkable by the PHP process user.
- Avoid sharing one XMLWriter/document across concurrent workers; generate unique temp files per process.
- Keep the XMLWriter object alive until output is fully written, and let it be collected cleanly; suppress competing cleanup scripts.
- Since it throws from __destruct, explicitly delete/check the temp file before the object goes out of scope, or wrap usage so exceptions during shutdown are surfaced while the request still runs.
Example fix
// before
$config->setTempDir('/var/cache/phpword'); // dir owned by root, php-fpm user cannot unlink
// after
$config->setTempDir(sys_get_temp_dir()); // writable and unlinkable by the PHP user
// or: chown/chmod the custom temp dir to the PHP-FPM user (e.g. www-data) with 0700 Defensive patterns
Strategy: try-catch
Validate before calling
$tmp = $config->getTempDir();
$probe = tempnam($tmp, 'pwtest');
if ($probe === false || !@unlink($probe)) {
throw new RuntimeException("Temp dir not writable/unlinkable: $tmp");
} Try / catch
try {
$document = $phpWord->save($target, 'Word2007');
} catch (\PhpOffice\PhpWord\Exception\Exception $e) {
if (str_contains($e->getMessage(), 'could not be deleted')) {
@unlink($e->getMessage() ? extractPath($e->getMessage()) : null);
$logger->warning('Stale PhpWord temp file could not be unlinked');
} else {
throw $e;
}
} Prevention
- Point the temp dir at a dedicated directory owned by the PHP user with 0700
- Avoid concurrent workers sharing one temp path; unique files per process
- Exclude temp dirs from external cleanup cron jobs while requests run
- Run a deploy-time smoke test that saves and deletes a sample document
When it happens
Trigger: The temp file was already removed by another process or a competing writer, permissions on the temp directory changed so the PHP user can no longer unlink it, or the file is held/locked (rare on Linux, e.g. certain mount types/immutable flags).
Common situations: Shared temp directories with restrictive permissions (sys_get_temp_dir() vs /tmp differences); parallel workers processing the same document; cleanup scripts deleting temp files mid-request; PHP-FPM user mismatch after permission changes.
Understand the failure class
Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.
Related errors
- Could not open ' . $sFileName . ' for reading! File does…
- Could not close zip file.
- Directory does not exist: $directory
- Invalid value, on of ' . implode(', ', $position) . '…
- Invalid value, on of ' . implode(', ', $restartNumbers) . '…
AI-assisted analysis of PHPOffice/PHPWord@aef95c0415 (2026-09-14).
Data as JSON: /api/errors/d1f803e74876ef8f.
Report an issue: GitHub.
Appendix: source
Thrown at src/PhpWord/Shared/XMLWriter.php:94
$this->setIndent(false);
$this->setIndentString('');
} else {
$this->setIndent(true);
$this->setIndentString(' ');
}
}
/**
* Destructor.
*/
public function __destruct()
{
// Unlink temporary files
if (empty($this->tempFileName)) {
return;
}
if (PHP_OS != 'WINNT' && @unlink($this->tempFileName) === false) {
throw new Exception('The file ' . $this->tempFileName . ' could not be deleted.');
}
}
/**
* Get written data.
*
* @return string
*/
public function getData()
{
if ($this->tempFileName == '') {
return $this->outputMemory(true);
}
$this->flush();
return file_get_contents($this->tempFileName);
}View on GitHub (pinned to aef95c0415)