处理上传文件
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_filesizeini 指令。
- 文件超过了表单中定义的上传限制。 
- 文件仅部分上传。 
- 没有上传文件。 
- 文件无法写入磁盘。 
- 无法上传文件:缺少临时目录。 
- 文件上传被 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,在某些情况下。
- 文件已移动 
- 文件未成功上传 
- 文件移动操作失败(例如,权限不足)