处理上传文件
CodeIgniter 使得处理通过表单上传的文件比直接使用 PHP 的 $_FILES
数组更简单、更安全。这扩展了 文件类,因此获得了该类的所有功能。
注意
这与 CodeIgniter v3.x 中的文件上传类不同。这提供了对上传文件的原始接口,并具有一些小功能。
流程
上传文件涉及以下一般流程
显示一个上传表单,允许用户选择文件并上传。
提交表单后,文件将上传到您指定的目的地。
在此过程中,将验证文件以确保它根据您设置的首选项允许上传。
上传完成后,将向用户显示成功消息。
为了演示此流程,这里有一个简短的教程。之后您会找到参考信息。
创建上传表单
使用文本编辑器,创建一个名为upload_form.php的表单。在其中,放置此代码并将其保存到您的app/Views/目录
<!DOCTYPE html>
<html lang="en">
<head>
<title>Upload Form</title>
</head>
<body>
<?php foreach ($errors as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach ?>
<?= form_open_multipart('upload/upload') ?>
<input type="file" name="userfile" size="20">
<br><br>
<input type="submit" value="upload">
</form>
</body>
</html>
您会注意到我们正在使用表单助手来创建打开的表单标签。文件上传需要多部分表单,因此助手会为您创建正确的语法。您还会注意到我们有一个$errors
变量。这样我们就可以在用户做错事时显示错误消息。
成功页面
使用文本编辑器,创建一个名为upload_success.php的表单。在其中,放置此代码并将其保存到您的app/Views/目录
<!DOCTYPE html>
<html lang="en">
<head>
<title>Upload Form</title>
</head>
<body>
<h3>Your file was successfully uploaded!</h3>
<ul>
<li>name: <?= esc($uploaded_fileinfo->getBasename()) ?></li>
<li>size: <?= esc($uploaded_fileinfo->getSizeByUnit('kb')) ?> KB</li>
<li>extension: <?= esc($uploaded_fileinfo->guessExtension()) ?></li>
</ul>
<p><?= anchor('upload', 'Upload Another File!') ?></p>
</body>
</html>
控制器
使用文本编辑器,创建一个名为Upload.php的控制器。在其中,放置此代码并将其保存到您的app/Controllers/目录
<?php
namespace App\Controllers;
use CodeIgniter\Files\File;
class Upload extends BaseController
{
protected $helpers = ['form'];
public function index()
{
return view('upload_form', ['errors' => []]);
}
public function upload()
{
$validationRule = [
'userfile' => [
'label' => 'Image File',
'rules' => [
'uploaded[userfile]',
'is_image[userfile]',
'mime_in[userfile,image/jpg,image/jpeg,image/gif,image/png,image/webp]',
'max_size[userfile,100]',
'max_dims[userfile,1024,768]',
],
],
];
if (! $this->validate($validationRule)) {
$data = ['errors' => $this->validator->getErrors()];
return view('upload_form', $data);
}
$img = $this->request->getFile('userfile');
if (! $img->hasMoved()) {
$filepath = WRITEPATH . 'uploads/' . $img->store();
$data = ['uploaded_fileinfo' => new File($filepath)];
return view('upload_success', $data);
}
$data = ['errors' => 'The file has already been moved.'];
return view('upload_form', $data);
}
}
路由
使用文本编辑器打开 **app/Config/Routes.php**。在其中添加以下两个路由
<?php
// ...
/*
* --------------------------------------------------------------------
* Route Definitions
* --------------------------------------------------------------------
*/
// We get a performance increase by specifying the default
// route since we don't have to scan directories.
$routes->get('/', 'Home::index');
$routes->get('upload', 'Upload::index'); // Add this line.
$routes->post('upload/upload', 'Upload::upload'); // Add this line.
// ...
上传目录
上传的文件存储在 **writable/uploads/** 目录中。
试试看!
要尝试您的表单,请使用类似于此的 URL 访问您的网站
example.com/index.php/upload/
您应该会看到一个上传表单。尝试上传一个图像文件(**jpg**、**gif**、**png** 或 **webp**)。如果控制器中的路径正确,它应该可以工作。
访问文件
所有文件
当您上传文件时,可以通过 PHP 中的 $_FILES
超级全局变量以原生方式访问它们。当处理一次上传的多个文件时,此数组有一些重大缺点,并且存在许多开发人员没有意识到的潜在安全漏洞。CodeIgniter 通过在通用接口后面标准化您的文件使用来帮助解决这两个问题。
文件通过当前的 IncomingRequest
实例访问。要检索使用此请求上传的所有文件,请使用 getFiles()
。这将返回一个由 CodeIgniter\HTTP\Files\UploadedFile
实例表示的文件数组
<?php
$files = $this->request->getFiles();
当然,有多种方法可以命名文件输入,除了最简单的之外,任何方法都可能产生奇怪的结果。数组以您期望的方式返回。对于最简单的用法,单个文件可能会像这样提交
<input type="file" name="avatar">
这将返回一个简单的数组,例如
[
'avatar' => // UploadedFile instance,
];
注意
UploadedFile 实例对应于 $_FILES
。即使用户只单击提交按钮而不上传任何文件,该实例仍然存在。您可以通过 UploadedFile 中的 isValid()
方法检查文件是否已实际上传。请参阅 验证文件。
如果您对名称使用了数组表示法,则输入将类似于
<input type="file" name="my-form[details][avatar]">
getFiles()
返回的数组将更像这样
[
'my-form' => [
'details' => [
'avatar' => // UploadedFile instance
],
],
]
在某些情况下,您可能需要指定要上传的文件数组
Upload an avatar: <input type="file" name="my-form[details][avatars][]">
Upload an avatar: <input type="file" name="my-form[details][avatars][]">
在这种情况下,返回的文件数组将更像这样
[
'my-form' => [
'details' => [
'avatar' => [
0 => // UploadedFile instance,
1 => // UploadedFile instance,
],
],
],
]
单个文件
如果您只需要访问单个文件,可以使用 getFile()
直接检索文件实例。这将返回一个 CodeIgniter\HTTP\Files\UploadedFile
实例。
最简单的用法
在最简单的用法中,单个文件可能像这样提交
<input type="file" name="userfile">
这将返回一个简单的文件实例,例如
<?php
$file = $this->request->getFile('userfile');
数组表示法
如果您对名称使用了数组表示法,则输入将类似于
<input type="file" name="my-form[details][avatar]">
要获取文件实例
<?php
$file = $this->request->getFile('my-form.details.avatar');
多个文件
<input type="file" name="images[]" multiple>
在控制器中
<?php
if ($imagefile = $this->request->getFiles()) {
foreach ($imagefile['images'] as $img) {
if ($img->isValid() && ! $img->hasMoved()) {
$newName = $img->getRandomName();
$img->move(WRITEPATH . 'uploads', $newName);
}
}
}
其中 images
是表单字段名称的循环。
如果有多个具有相同名称的文件,您可以使用 getFile()
来单独检索每个文件。
在控制器中
<?php
$file1 = $this->request->getFile('images.0');
$file2 = $this->request->getFile('images.1');
您可能会发现使用 getFileMultiple()
更容易,以获取具有相同名称的上传文件的数组
<?php
$files = $this->request->getFileMultiple('images');
另一个例子
Upload an avatar: <input type="file" name="my-form[details][avatars][]">
Upload an avatar: <input type="file" name="my-form[details][avatars][]">
在控制器中
<?php
$file1 = $this->request->getFile('my-form.details.avatars.0');
$file2 = $this->request->getFile('my-form.details.avatars.1');
注意
使用 getFiles()
更合适。
使用文件
检索到 UploadedFile 实例后,您可以以安全的方式检索有关文件的信息,以及将文件移动到新位置。
验证文件
您可以通过调用 isValid()
方法来检查文件是否确实通过 HTTP 上传,并且没有错误。
<?php
if (! $file->isValid()) {
throw new \RuntimeException($file->getErrorString() . '(' . $file->getError() . ')');
}
如本例所示,如果文件存在上传错误,您可以使用 getError()
和 getErrorString()
方法检索错误代码(整数)和错误消息。通过此方法可以发现以下错误
文件超过了您的
upload_max_filesize
ini 指令。文件超过了表单中定义的上传限制。
文件仅部分上传。
没有上传文件。
文件无法写入磁盘。
无法上传文件:缺少临时目录。
文件上传被 PHP 扩展阻止。
文件名
getName()
您可以使用 getName()
方法检索客户端提供的原始文件名。这通常是客户端发送的文件名,不应该被信任。如果文件已被移动,这将返回移动后的最终文件名。
<?php
$name = $file->getName();
getClientName()
始终返回客户端发送的上传文件的原始名称,即使文件已被移动。
<?php
$originalName = $file->getClientName();
getTempName()
要获取上传过程中创建的临时文件的完整路径,可以使用 getTempName()
方法。
<?php
$tempfile = $file->getTempName();
其他文件信息
getClientExtension()
返回原始文件扩展名,基于上传的文件名。
<?php
$ext = $file->getClientExtension();
警告
这不是一个可信来源。对于可信版本,请改用 guessExtension()
。
getClientMimeType()
返回客户端提供的文件的 MIME 类型(MIME 类型)。这不是一个可信的值。对于可信版本,请改用 getMimeType()
。
<?php
$type = $file->getClientMimeType();
echo $type; // image/png
getClientPath()
版本 4.4.0 中新增。
当客户端通过目录上传上传文件时,返回上传文件的 webkit 相对路径。在低于 8.1 的 PHP 版本中,这将返回 null
。
<?php
$clientPath = $file->getClientPath();
echo $clientPath; // dir/file.txt, or dir/sub_dir/file.txt
移动文件
使用原始文件名
每个文件都可以使用名为 move()
的方法移动到其新位置。这将以目录作为第一个参数来移动文件。
<?php
$file->move(WRITEPATH . 'uploads');
默认情况下,使用原始文件名。
使用新文件名
您可以通过将其作为第二个参数传递来指定一个新文件名。
<?php
$newName = $file->getRandomName();
$file->move(WRITEPATH . 'uploads', $newName);
覆盖现有文件
默认情况下,如果目标文件已存在,将使用新的文件名。例如,如果目录中已存在 **image_name.jpg**,则文件名将自动变为 **image_name_1.jpg**。
您可以通过将 true
作为第三个参数传递来覆盖现有文件。
<?php
$file->move(WRITEPATH . 'uploads', null, true);
检查文件是否已移动
删除临时文件后,您可以使用 hasMoved()
方法检查文件是否已移动,该方法返回一个布尔值。
<?php
if ($file->isValid() && ! $file->hasMoved()) {
$file->move($path);
}
移动失败时
在某些情况下,移动上传的文件可能会失败,并出现 HTTPException。
文件已移动
文件未成功上传
文件移动操作失败(例如,权限不足)
存储文件
每个文件都可以使用名为 store()
的方法移动到其新位置。
在最简单的用法中,单个文件可能像这样提交
<input type="file" name="userfile">
默认情况下,上传文件保存在 **writable/uploads** 目录中。将创建 **YYYYMMDD** 文件夹和随机文件名。返回文件路径。
<?php
$path = $this->request->getFile('userfile')->store();
您可以将要移动文件的目录指定为第一个参数。通过将其作为第二个参数传递来指定新的文件名。
<?php
$path = $this->request->getFile('userfile')->store('head_img/', 'user_name.jpg');
移动上传的文件可能会失败,并出现 HTTPException
,在某些情况下。
文件已移动
文件未成功上传
文件移动操作失败(例如,权限不足)