关于使用 fuelphp 文件上传的注意事项

大家好,
我是Mandai,Wild团队负责开发工作的成员。
使用 FuelPHP 上传文件非常简单;通常只需从文档中复制粘贴代码即可。
但是,修改代码可能会有点棘手,所以我编写了这份指南。
准备文件上传(HTML + CSS + JS)
在实现文件上传功能之前,请准备好前端材料,例如 HTML。
我们先来看HTML代码。
这是一个位于form标签内的input标签。
<form id="form" name="form" method="post" enctype="multipart/form-data"><input type="file" name="f" class="hidden" id="f" multiple="multiple"><label for="f"><span class="btn">文件选择</span><span class="selected" id="f_selected">未选择</span></label></form>
接下来是CSS。
input.hidden { /* display: none;*/ } label > span.btn { display: inline-block; border: 1px solid #999; font: 13.333px arial; height: 21px; line-height: 21px; padding-left: 0.25em; padding-right: 0.25em; border-radius: 2px; background: -webkit-linear-gradient(#fcfcfc, #dcdcdc); background: linear-gradient(#fcfcfc, #dcdcdc); } label > span.btn:active { background: -webkit-linear-gradient(#dcdcdc, #fcfcfc); background: linear-gradient(#dcdcdc, #fcfcfc); } label > span.btn:hover { border: 1px solid #666; } label > span.selected { display: inline-block; font: 13.333px arial; height: 21px; line-height: 21px; } label { -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; user-select: none; }
最后,是 JavaScript。
(function(){ var f = document.getElementById('f'); var label_selected = document.getElementById(f.id + '_selected'); f.addEventListener('change', function(){ if (f.files.length == 0){ label_selected.textContent = '未选择'; } else if (f.files.length == 1){ label_selected.textContent = f.files[0].name; } else { label_selected.textContent = f.files.length + '文件'; } }); })();
如果你用浏览器查看,它会是这个样子。推荐使用 Chrome 浏览器。
原因你一看就明白了。
如您所见,这里有两个文件上传按钮。
按下任一按钮即可执行上传操作。
左边那张是原图,右边那张是用标签复制的。我
只是想这么做而已。
由于表单元素在不同浏览器中的显示方式不同,而且有人要求文件上传按钮本身也要符合设计规范,所以这些小技巧就派上了用场。我们
使用 CSS 和 JS 大致实现了行为和外观的匹配,在本例中,它被设计得类似于 Chrome 的表单元素。
使用 CSS 为 input 标签添加 `display: none` 属性,即可使其表现得像普通表单一样。
(此代码已被注释掉。)
重要的是不要忘记表单标签中的 enctype 属性。
正面已经准备就绪。
实施 fuelphp(准备工作)
在实施之前,我们将准备各种设置和模型。
FuelPHP 采用配置优先的设计理念,因此您可以通过配置文件显著改变其行为。
首先,在上传文件之前,请准备一个用于上传的配置文件。
原始配置文件位于“fuel/core/config/upload.php”,因此请将其复制到“fuel/app/config”并更改设置。
关键设置在于路径。
虽然将保存位置设置为“公开”可以简化下载过程,但将包含个人信息的 PDF 文件放在“公开”位置很危险,因为任何拥有 URL 的人都可以随意下载。
如果需要访问控制,可以在“public”目录上层创建一个名为“files”的目录,并在其中指定访问权限。
这样一来,外部就无法访问该目录,也无法下载文件,这虽然会增加系统开销,但可以通过创建一个单独的控制器来生成下载URL,从而解决这个问题。
这与文件上传略有不同,所以我希望以后能专门写一篇文章来介绍。
这次,我们将在与 public 目录同级(直接位于项目根目录下)创建一个 files 目录,并将文件保存在那里。
此外,通过在配置文件中将 randomize 设置为 true,将要保存的文件名设置为 32 个字符的 MD5 哈希值。
修改后的 config/upload.php 文件现在如下所示:
return array( 'path' => '/var/www/html/test/files', 'randomize' => true, );
接下来,我们创建一个模型。
它的主要作用是保存与文件名关联的哈希值,这样就无法记住原始文件名。
用于创建上传表以保存数据的 SQL 语句如下:
CREATE TABLE `uploads` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `origin_name` varchar(256) NOT NULL, `file_name` varchar(256) NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`), KEY `idx_file_name` (`file_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
执行完这条 SQL 语句后,最快的方法是使用以下 oil 命令创建模型:
php /path/to/oil r fromdb:model uploads
另外,由于我们将使用 ORM 包,因此需要修改 `config.php` 文件中的 `always_load` 部分。
取消注释 `packages` 部分将允许它们自动加载。
fuelphp 实现(控制器)
接下来我们将实现控制器在数据发布时的行为。
控制器中编写的源代码如下:
function post_index() { $data = []; $this->template->title = 'test'; /* * 随机化后,文件名将如下所示: * [0-9a-f]{32}.[ext] * 由于原始文件名将保存在单独的字段中,因此在保存之前,我们将引入一个处理,将文件名转换为哈希值 */ Upload::register('before', function(&$file) { $file['filename'] = substr($file['filename'], 0, 32); $file['path'] .= $file['filename'][0]. '/'. $file['filename'][1]. '/'; }); if (Upload::is_valid()) { Upload::save(); foreach (Upload::get_files() as $file) { Model_Upload::add($file); } } foreach (Upload::get_errors() as $file) { // 错误处理 } $this->template->content = View::forge('upload/index', $data); }
如果在配置文件 `upload.php` 中将 `auto_process` 设置为 true(默认值),则无需显式编写 `Upload::process()`。
文档,启用 `'auto_process' => true` 后,`Upload::process()` 将会执行两次。
由于配置可以通过 Upload::process() 覆盖,如果文件上传处理在多个地方进行描述,最好将 auto_process 设置为 false 并每次都运行 Upload::process()。
`Upload::register()` 方法用于在保存文件之前中断上传过程。
第一个参数可以从 'validate'、'before' 和 'after' 三个选项中选择。
在本例中,由于我们要在保存之前更改文件名和保存路径,因此我们选择了 'before'。
如果在 `config/upload.php` 文件中将 `randomize` 设置为 `true`,文件名将是一个 32 个字符的哈希值,但会保留原始文件扩展名。
为了确保文件名始终为 32 个字符,我们添加了一个将文件名转换为哈希值的流程。
此外,关于存储位置,系统会使用哈希值的前两个字符创建一个目录,然后将其重置为路径。
上传少量文件时,这并非特别必要;但上传大量文件(例如数万个文件)时,读写响应时间会显著降低。
为了解决这个问题,系统添加了处理机制,将单个目录中的文件数量分散开来。
Upload::register() 闭包中的 $file 参数指向 fuel/vendor/fuelphp/upload/src/File.php 中的一个类,似乎无法从外部访问该类。但是,
如果在保存之前编辑该类,则可以操作保存路径和文件。
如果你导出文件 $file,你会看到类似这样的内容:
Fuel\Upload\File::__set_state(array( 'container' => array ( 'element' => 'f', 'filename' => 'e9477391cbeefc40e69f7e1bc24868d2', 'name' => 'test.html', 'type' => 'text/html', 'tmp_name' => '/tmp/phpDsDmiL', 'error' => 0, 'size' => 335, 'extension' => 'html', 'basename' => 'test', 'mimetype' => 'text/html', 'path' => '/home/vagrant/workspace/test/files/e/9/', ), 'index' => 0, 'errors' => array ( ), 'config' => array ( 'langCallback' => '\\Upload::lang_callback', 'moveCallback' => NULL, 'max_size' => 0, 'max_length' => 0, 'ext_whitelist' => array ( ), 'ext_blacklist' => array ( ), 'type_whitelist' => array ( ), 'type_blacklist' => array ( ), 'mime_whitelist' => array ( ), 'mime_blacklist' => array ( ), 'prefix' => '', 'suffix' => '', 'extension' => '', 'randomize' => true, 'normalize' => false, 'normalize_separator' => '_', 'change_case' => false, 'path' => '/var/www/html/test/files', 'create_path' => true, 'path_chmod' => 511, 'file_chmod' => 438, 'auto_rename' => true, 'new_name' => false, 'overwrite' => false, ), 'isValidated' => true, 'isValid' => true, 'callbacks' => array ( 'before_validation' => array ( ), 'after_validation' => array ( ), 'before_save' => array ( 0 => Closure::__set_state(array( )), ), 'after_save' => array ( ), ), ))
这有点复杂,但通过 Upload::get_files() 获取的文件信息并非上面提到的类,而只是 Fuel\Upload\File 的容器部分。
此外,即使修改 Upload::get_files() 中的信息,也无法更改实际的文件信息,因此似乎修改已上传文件信息的唯一方法是通过 before 事件。
有文档的注册方法部分对此更详细的解释
就这样。
0
