ellite/Wallos · error · Exception

$this->lang('file_open') . $path

Error message

$this->lang('file_open') . $path

What it means

encodeFile() throws lang('file_open') plus the path when fileIsAccessible() fails just before reading the attachment during message assembly (attachAll -> encodeFile). This is a re-check at send time — the file became inaccessible after addAttachment() accepted it. Severity STOP_CONTINUE means send() aborts the message but the error may be collected rather than thrown if exceptions are off.

Solutions

  1. Re-verify the file still exists and is readable immediately before send()
  2. Keep temporary attachment files alive until after send() returns (use copy() to a durable location)
  3. Ensure the sending process user retains read permission on the path
  4. Check $mail->ErrorInfo for the exact path that failed

Example fix

// before
$mail->addAttachment($tmpPath);
// later
$mail->send(); // tmp already deleted
// after
$mail->addAttachment($tmpPath);
if (!is_readable($tmpPath)) { copy($tmpPath, $perm = tempnam(sys_get_temp_dir(), 'att')); $mail->clearAttachments(); $mail->addAttachment($perm); }
$mail->send();
Defensive patterns

Strategy: validation

Validate before calling

foreach ($paths as $p) { if (!is_file($p) || !is_readable($p)) { throw new RuntimeException("Attachment disappeared: $p"); } }
$mail->send();

Type guard

function ensureReadableAtSend(string $p): string { if (!is_file($p) || !is_readable($p)) { throw new RuntimeException("File unreadable at send time: $p"); } return $p; }

Try / catch

try { $mail->send(); } catch (PHPMailer\PHPMailer\Exception $e) { if (str_contains($e->getMessage(), 'Could not access file') || str_contains($e->getMessage(), 'file_open')) { error_log('Attachment unreadable at send: ' . $e->getMessage()); } throw $e; }

Prevention

When it happens

Trigger: File exists when addAttachment() is called but is deleted, moved, or made unreadable before send(); also mismatched permissions after a job queue delay between add and send.

Common situations: Temp files (e.g. tmpfile uploads) cleaned up before send(), files removed by cron between validation and transmission, long-running workers holding stale paths.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of ellite/Wallos@52820e87ca (2026-09-13). Data as JSON: /api/errors/3b49c38860658935. Report an issue: GitHub.

Appendix: source

Thrown at libs/PHPMailer/PHPMailer.php:3402

        $mime[] = sprintf('--%s--%s', $boundary, static::$LE);

        return implode('', $mime);
    }

    /**
     * Encode a file attachment in requested format.
     * Returns an empty string on failure.
     *
     * @param string $path     The full path to the file
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
     *
     * @return string
     */
    protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
    {
        try {
            if (!static::fileIsAccessible($path)) {
                throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
            }
            $file_buffer = file_get_contents($path);
            if (false === $file_buffer) {
                throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
            }
            $file_buffer = $this->encodeString($file_buffer, $encoding);

            return $file_buffer;
        } catch (Exception $exc) {
            $this->setError($exc->getMessage());
            $this->edebug($exc->getMessage());
            if ($this->exceptions) {
                throw $exc;
            }

            return '';
        }
    }

View on GitHub (pinned to 52820e87ca)