PHP在保存文件时捕获错误

PHP在保存文件时捕获错误,php,file-io,exception-handling,error-handling,Php,File Io,Exception Handling,Error Handling,所以。。。。我正在写一个小函数,它返回文件是否被写入,以及为什么没有写入的原因。。。。比如: array ( 'success' => false, // easy, by checking function result 'reason' => 'Permission denied' // how am I supposed to find this??? ) 也许我遗漏了什么,但我似乎找不到一种方法来捕获保存文件失败时的任何错误消

所以。。。。我正在写一个小函数,它返回文件是否被写入,以及为什么没有写入的原因。。。。比如:

array (
    'success' => false,               // easy, by checking function result
    'reason' => 'Permission denied'   // how am I supposed to find this???
)
也许我遗漏了什么,但我似乎找不到一种方法来捕获保存文件失败时的任何错误消息

我的第一个想法是使用输出缓冲来捕获错误,但它荒谬地超出了范围,容易出错,而且是一个巨大的黑客攻击(即,不同类型的错误可能会干扰)


通常,这对于OOP风格的异常处理来说是一项很好的工作,除了
文件内容
f*
函数不会抛出任何异常

似乎是
SplFileObject
完成了这项工作……只有一个例外;这是一个基于行的类,并且

有什么建议吗


PS:称我懒惰,但我不认为我的代码应该检查所有异常情况(写入权限、无法访问的驱动器、错误的路径等)。

您的建议表面上看起来是正确的,它有一个总体API,可以执行将文件写入文件系统的基本操作。然而,我认为PHP开发人员让我们自己组装一个API来满足我们的应用程序需求,因为他们确实为我们自己提供了基本组件

下面是我用于文件写入操作的
File::write
方法的一个片段:

$fileInfo = new SplFileInfo($fileUri);

if (!is_dir($fileInfo->getPath())) {
    // I have some proprietary stuff here but you get the idea        
}

$file = new SplFileObject($fileUri, $mode);

if (!$file->flock(LOCK_EX | LOCK_NB)) {
    throw new Exception(sprintf('Unable to obtain lock on file: (%s)', $fileUri));
} 

elseif (!$file->fwrite($content)) {
    throw new Exception(sprintf('Unable to write content to file: (%s)... to (%s)', substr($content,0,25), $fileUri));
}

elseif (!$file->flock(LOCK_UN)) {
    throw new Exception(sprintf('Unable to remove lock on file: (%s)', $fileUri));
}

elseif (!@chmod($fileUri, $filePerms)) {
    throw new Exception(sprintf('Unable to chmod: (%s) to (%s)', $fileUri, $filePerms));
}
这些只是您可以测试的边缘情况的几个示例,如果您需要测试“驱动器是否已连接”,您可以调用。因此,您可以将其添加到检查列表中,并用对您的应用程序有意义的消息进行响应

然后,如果要记录所述错误,只需将调用代码包装在try/catch块中:

try {
    File::write($fileUri);
} catch (Exception $e) {
    error_log($e->getMessage);
}
更新 @哈克雷刚刚给了我一个好主意。默认的FS实现与我的系统不一致

解决此问题的一种方法是取消注册
file://
协议的默认流包装器,并注册我自己的,这实际上会引发异常。有了它,我就可以自由地使用
file\u put\u contents()
,同时捕获异常

哦,我还可以确保自定义流包装器也是原子的(通过强制锁定)


这是我到目前为止想到的。当然,它需要真正的FS检查(驱动器/路径存在、权限等)

这是相当冗长的,真的不像我想要的。通过FS检查,这可能会增加一点


如果一开始编码正确,那么所有这些代码都可以通过
file\u get\u contents()
实现。

file\u put\u contents
和其他人在失败时返回
false
,因此您可以检查它,然后抛出自己的异常。@tandu我需要原因,我为什么要向FS查询失败原因的信息?另外,由于它不是原子的,所以原因可能会在写入文件和检查错误的时间之间发生变化(虽然是毫秒,但不好的做法是不好的)。同样,这还不够。它失败的原因有一百万个(或者
可写
返回false),我需要这些原因。明白我的意思吗?@Christian:Define unrelated,你注册你的范围,然后注销。更新:也请看这里,这可能会起作用:@Christian:听起来像是与操作相关的错误。我使用的是SplFileObject(除了例外),它工作得很好,直到我意识到它只适用于文本文件。我已经在我的问题中谈到了这一点。我读了这个问题,有几个部分是我的答案的基础。也许发布您正在使用的代码可以减少任何重复。至于测试是否写入了二进制图像,为什么不在你的应用程序移动、调整大小、上传文件后检查
is_文件
?我不确定我是否理解你的后一个问题。由于
file\u put\u contents()
的结果(或
f*
FS函数的集体结果),我已经知道操作是否成功。你的帖子回答了我的问题。基本上我发布的内容,我看不出有任何问题。您可以通过添加和删除复选框来减少冗长。如果你选择自定义默认的FS功能,发布你的发现,我总是想学习一种新的剥猫方法。我不确定我对这两个答案是否满意,所以寻找更好答案的游戏仍然在进行(同时,我在值得的地方投了更高的票)。我喜欢这个问题,对结果感兴趣,如果有更好的解决方案出现,我可能不得不更新我的API。谢谢
    /**
     * Save file to source DSN.
     * @return boolean True on success, false on failure (see last_error for details). 
     */
    public function save(){
        $file = fopen($this->source, 'wb');
        if(!$file){
            $this->_last_error = 'Could not open file stream';
            return false;
        }
        if(!flock($file, LOCK_EX)){
            $this->_last_error = 'Could not lock file for writing';
            return false;
        }
        if(!ftruncate($file, 0)){
            $this->_last_error = 'Could not clear file';
            return false;
        }
        if(fwrite($file, $this->contents)===null){
            $this->_last_error = 'Could not write to file';
            return false;
        }
        if(!fflush($file)){
            $this->_last_error = 'Could not flush to file';
            return false;
        }
        if(!flock($file, LOCK_UN)){
            $this->_last_error = 'Could not unlock file';
            return false;
        }
        if(!fclose($file)){
            $this->_last_error = 'Could not close file';
            return false;
        }
        return true;
    }