Laravel导入带图片Excel报file_put_contents目录不存在错误如何修复
报错核心原因
你遇到的failed to open stream: No such file or directory报错,本质是两个核心问题:
- 写入文件时使用了相对路径
images/detail_pengajuan/,PHP执行时的工作目录和你预期的项目根目录不一致,解析出的路径根本不存在 - 没有提前创建目标存储目录,也没有做目录存在性校验,程序不会自动生成缺失的目录层级
另外你贴的代码里还有3个影响功能的逻辑bug,就算手动建好目录,也会出现图片和行不匹配、文件名重复覆盖、大文件导入超时/内存溢出的问题。
修复步骤
- 修正路径写法:所有文件读写都用绝对路径。Laravel框架中公共可访问的文件统一放在
public目录下,写入时用public_path()辅助函数拼接完整路径,避免工作目录变化导致的路径解析错误。 - 增加目录自动创建逻辑:不要依赖手动建目录,可以用Laravel自带的
File::ensureDirectoryExists()方法,在写入文件前自动检测目录,不存在就按指定权限递归创建。 - 修正原有逻辑缺陷:
- 把加载Excel、提取图片的逻辑从
onRow方法移到构造函数中,不要每处理一行就重新加载一次整个文件,否则大文件会直接内存溢出、请求超时。 - 提取图片时记录图片所在的行坐标,处理每行数据时只取当前行对应的图片,避免所有行都匹配到最后一张图片的问题。
- 文件名生成不要只用
time() + 自增计数器,同一秒内处理多行很容易出现重名覆盖,改用uniqid()生成唯一文件名。 - 计数器
$i不要放在行处理逻辑内,否则每处理一行都会重置,完全起不到去重作用。
- 把加载Excel、提取图片的逻辑从
修正后的代码参考
首先在类头部引入文件操作类:
use Illuminate\Support\Facades\File;
完整实现代码:
namespace App\Imports; use App\SubmissionDetail; use Maatwebsite\Excel\Row; use Maatwebsite\Excel\Concerns\OnEachRow; use Maatwebsite\Excel\Concerns\WithHeadingRow; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; use Illuminate\Support\Facades\File; class SubmissionDetailImport implements OnEachRow, WithHeadingRow { protected $id; // 提前存储所有行对应的图片数据 protected $rowImages = []; function __construct($id) { $this->id = $id; // 初始化目标存储目录,不存在自动递归创建,权限设为755 $targetDir = public_path('images/detail_pengajuan'); File::ensureDirectoryExists($targetDir, 0755, true); // 只加载一次Excel文件,提前提取所有图片和对应行号 $spreadsheet = IOFactory::load(request()->file('file')); $worksheet = $spreadsheet->getActiveSheet(); foreach ($worksheet->getDrawingCollection() as $drawing) { // 解析图片所在的行坐标 preg_match('/[A-Z]+([0-9]+)/', $drawing->getCoordinates(), $match); $currentRow = (int)$match[1]; // 提取图片内容和扩展名 if ($drawing instanceof MemoryDrawing) { ob_start(); call_user_func( $drawing->getRenderingFunction(), $drawing->getImageResource() ); $imageContents = ob_get_contents(); ob_end_clean(); switch ($drawing->getMimeType()) { case MemoryDrawing::MIMETYPE_PNG : $extension = 'png'; break; case MemoryDrawing::MIMETYPE_GIF: $extension = 'gif'; break; case MemoryDrawing::MIMETYPE_JPEG : $extension = 'jpg'; break; } } else { $zipReader = fopen($drawing->getPath(), 'r'); $imageContents = ''; while (!feof($zipReader)) { $imageContents .= fread($zipReader, 1024); } fclose($zipReader); $extension = $drawing->getExtension(); } $this->rowImages[$currentRow] = [ 'content' => $imageContents, 'ext' => $extension ]; } } public function onRow(Row $row) { $rowData = $row->toArray(); $currentRowIndex = $row->getIndex(); $myFileName = null; // 只处理当前行绑定的图片 if (isset($this->rowImages[$currentRowIndex])) { $img = $this->rowImages[$currentRowIndex]; // 生成唯一文件名,避免重名覆盖 $myFileName = uniqid() . '.' . $img['ext']; // 用绝对路径写入文件 file_put_contents( public_path('images/detail_pengajuan/' . $myFileName), $img['content'] ); } $submissionDetails = SubmissionDetail::firstOrCreate( ['submission_id' => $this->id, 'nama_barang' => $rowData['nama_barang']], [ 'submission_id' => $this->id, 'nama_barang' => $rowData['nama_barang'], 'image_path' => $myFileName, 'jumlah' => $rowData['jumlah'], 'harga_satuan' => $rowData['harga_satuan'], 'harga_total' => $rowData['harga_total'], 'keterangan' => $rowData['keterangan'], ] ); if (! $submissionDetails->wasRecentlyCreated) { $submissionDetails->update([ 'image_path' => $myFileName, 'jumlah' => $rowData['jumlah'], 'harga_satuan' => $rowData['harga_satuan'], 'harga_total' => $rowData['harga_total'], 'keterangan' => $rowData['keterangan'], ]); } } }
额外注意事项
- 如果是Linux服务器部署,要确保
public/images目录的所有者和web运行用户(一般是www-data、nginx用户)一致,避免权限不足无法写入。 - 更规范的做法是把用户上传的文件存在
storage/app/public目录下,执行php artisan storage:link创建软链对外访问,框架会自动处理路径和权限问题,比手动操作public目录更安全。
内容的提问来源于stack exchange,提问作者Ipenk
相关产品推荐
相关产品推荐

