128. 文件上传与管理(下)

这是文件上传与管理的下集,应紧接上集阅读,本篇将介绍托管文件的实现原理及一些注意事项,在开始前我们先回顾一下php原生文件上传的基础知识。

PHP文件上传基础:

单个文件上传:

使用控件如:<input type="file" name="yunke">

这样的控件只能上传一个文件,通常浏览器也不允许选择多个文件

多个文件上传:

使用控件如:<input multiple="multiple" type="file" name="yunke[]" >

区别是name属性值末尾加了中括号,且明确声明了multiple属性,该属性告诉浏览器该控件可以上传多个文件,这样用户就可以按住“Ctrl”选择多文件一次性上传了,注意如果name属性不以中括号结尾,那么即便声明了multiple属性也只能上传一个文件,且无法预料是哪一个

 

和其他表单一样,name的值可以嵌套,如:

name="yunke[a][b]" 或:name="yunke[a][b][]"

是否嵌套并不影响文件上传的个数,另外中括号里面的变量名无需引号,表单需要POST方式上传(或PUT),且编码格式需要:

enctype="multipart/form-data"

在后端PHP通过超全局变量“$_FILES”来获取上传的文件信息,其是一个数组,第一级键名为name属性值,但如果是嵌套值,则为首级名字(也就是第一个中括号前面的名字,如前文的“yunke”),第二级键名有五个,如下:

name :客户端原带扩展名的完整文件名,不带路径,如: 3333.jpg

type :文件Mime类型,如:image/jpeg,完全由浏览器决定,且提供才有值,php不会检查(因此可能有欺骗)

tmp_name :上传后文件临时储存的全路径文件名,如:C:\Windows\phpA0DE.tmp

error :错误代码,为0说明上传成功,详见https://www.php.net/manual/zh/features.file-upload.errors.php

size :文件尺寸,以字节为单位

如果name的值是嵌套的,那么以上五个键的值也是嵌套的,且与name值对应,并相互间对应,如果是多文件上传(name值以“[]”结尾)那么最里层是一个索引数组

这里假设有一个name属性为“yunke[a][]”的表单进行了多值提交,获得的$_FILES如下:

Array
(
    [yunke] => Array
        (
            [name] => Array
                (
                    [a] => Array
                        (
                            [0] => TB1.jpg
                            [1] => TB2.jpg
                        )

                )

            [type] => Array
                (
                    [a] => Array
                        (
                            [0] => image/jpeg
                            [1] => image/jpeg
                        )

                )

            [tmp_name] => Array
                (
                    [a] => Array
                        (
                            [0] => C:\Windows\phpFA71.tmp
                            [1] => C:\Windows\phpFA82.tmp
                        )

                )

            [error] => Array
                (
                    [a] => Array
                        (
                            [0] => 0
                            [1] => 0
                        )

                )

            [size] => Array
                (
                    [a] => Array
                        (
                            [0] => 119372
                            [1] => 152939
                        )

                )

        )

)

和文件上传相关的php设置:

file_uploads:布尔值,是否允许文件上传

upload_tmp_dir:字符串值,上传后用于存放文件的临时目录,如果不可写php将回退使用系统默认临时目录,默认为NULL

upload_max_filesize:单个上传文件被允许的最大文件大小,字节为单位,默认2M

post_max_size:整个请求最大POST数据尺寸,应该大于单个文件尺寸,小于memory_limit尺寸,单位字节,如果超过,那么$_POST$_FILES将为空值

max_file_uploads:单次提交最多能上传的文件个数,表单中留空的文件控件虽会提交但不计数,默认20

max_input_time:整数,单位秒,脚本解析输入时间的最大值,有大文件上传时,需要适当加大

 

注意事项:

1、如果上传的文件没有被移动到其它地方,也没有被改名,则该文件将在表单请求结束时被删除。

2、有文件上传的表单务必提供重置按钮,否则用户不想上传某个文件控件时,部分浏览器可能无法清空控件,导致提交时该控件被迫上传

3、文件控件即便没有值也会被提交,除非设置为禁用状态

4、文件上传时间不包括在脚本最大执行时间内(max_execution_time),系统调用、数据库查询、sleep等都不包括在内

5、不应将普通的输入字段和文件上传字段混用同一个表单变量(如foo[]

 

drupal文件上传基础:

drupal中,默认所有的文件上传name值均是以“files”开始的嵌套值,如:name="files[a][]",但仅嵌套一层,默认不支持进一步嵌套(自定义name除外),这样便于程序处理(这和php文件超全局数组以及Symfony文件包实现有关,后者不支持多级嵌套),换句话说不可能出现“files[a][c][]”,它会被强制转化为“files[a][]”,这里的二级键名采用表单元素的#parents中第一个元素(注意:有时这个二级键名会被直接称为文件字段名)。

在下文的“托管文件”上传中,name值最终是:

$name = 'files[' . implode('_', $element['#parents'] . ']';

这也是文件上传自定义name值的通常做法

 

托管文件上传前端实现:

这由文件库(file/drupal.file)实现:

文件:/core/modules/file/ file.js

es6文件:/core/modules/file/file.es6.js

依赖:

    - core/jquery

    - core/jquery.once

    - core/drupal

    - core/drupalSettings

要理解该库需要先明白“托管文件”上传的html默认结构:

示例:这里假设表单渲染数组如下:

        $form['file']['yunke'] = array(
            '#type'        => 'managed_file',
            '#multiple' => true,
            '#title'       => '文件提交',
            '#description' => "托管文件上传",
        );

她产生的初始html表单类似如下:

<div id="edit-yunke" class="js-form-managed-file form-managed-file">
    <input data-drupal-selector="edit-yunke-upload" multiple="multiple" type="file" id="edit-yunke-upload"
           name="files[yunke][]" size="22" class="js-form-file form-file">
    <input class="js-hide button js-form-submit form-submit" data-drupal-selector="edit-yunke-upload-button"
           formnovalidate="formnovalidate" type="submit" id="edit-yunke-upload-button" name="yunke_upload_button"
           value="上传">
    <input data-drupal-selector="edit-yunke-fids" type="hidden" name="yunke[fids]">
</div>

上传几个文件后会通过ajax变为如下结构:

<div id="edit-yunke--t4B22tqvqsk" class="js-form-managed-file form-managed-file">
    <input data-drupal-selector="edit-yunke-upload-uhh-zqd-djk" multiple="multiple" type="file"
           id="edit-yunke-upload--uhH-ZqD-dJk" name="files[yunke][]" size="22" class="js-form-file form-file">
    <input class="js-hide button js-form-submit form-submit" data-drupal-selector="edit-yunke-upload-button"
           formnovalidate="formnovalidate" type="submit" id="edit-yunke-upload-button--JhH_0eYGr0I"
           name="yunke_upload_button" value="上传">
    <input data-drupal-selector="edit-yunke-fids" type="hidden" name="yunke[fids]" value="87 88">

    <div class="js-form-item form-item js-form-type-checkbox form-type-checkbox js-form-item-yunke-file-87-selected form-item-yunke-file-87-selected">
        <input data-drupal-selector="edit-yunke-file-87-selected" type="checkbox"
               id="edit-yunke-file-87-selected--r6DIsyZBRLA" name="yunke[file_87][selected]" value="1"
               class="form-checkbox">
        <label for="edit-yunke-file-87-selected--r6DIsyZBRLA" class="option">
            <span class="file file--mime-image-jpeg file--image"> <a
                    href="http://www.dp.com/zh-hans/system/temporary?file=drupal8.jpg" type="image/jpeg; length=70211">drupal8.jpg</a></span>
        </label>
    </div>
    <div class="js-form-item form-item js-form-type-checkbox form-type-checkbox js-form-item-yunke-file-88-selected form-item-yunke-file-88-selected">
        <input data-drupal-selector="edit-yunke-file-88-selected" type="checkbox"
               id="edit-yunke-file-88-selected--H1xK0R2tqTw" name="yunke[file_88][selected]" value="1"
               class="form-checkbox">
        <label for="edit-yunke-file-88-selected--H1xK0R2tqTw" class="option">
            <span class="file file--mime-image-jpeg file--image"> <a
                    href="http://www.dp.com/zh-hans/system/temporary?file=drupal%E4%BA%91%E5%AE%A2.jpg"
                    type="image/jpeg; length=72728">drupal云客.jpg</a></span>
        </label>
    </div>
    <input data-drupal-selector="edit-yunke-remove-button" formnovalidate="formnovalidate" type="submit"
           id="edit-yunke-remove-button--i8CbN_Odc_s" name="yunke_remove_button" value="Remove selected"
           class="button js-form-submit form-submit">
</div>

 

说明:

整个结构被具备类属性“js-form-managed-file”和“form-managed-file”的div元素包裹,内部有:

一个文件上传控件,需要具备类属性“js-form-file”和“form-file

一个隐藏的提交按钮,用于触发AJAX提交,需要具备类属性“js-form-submit”和“form-submit

一个隐藏的输入字段,用于记录已上传并保存在文件实体中的文件实体id

另外已上传文件会用一个a标签做预览链接,位于具备“file”类的span中,点击后可以打开或下载

预览链接选择器:“div.js-form-managed-file .file a

 

实际的文件上传动作由AJAX库完成,文件库仅做以下事情:

1、在客户端执行允许上传的文件扩展名验证

需要在客户端执行扩展名验证的文件控件需要预先在全局设置对象中配置好,配置项为:

drupalSettings.file.elements

其值是一个对象,属性名为文件上传表单控件的id值(有#前缀),属性值为允许上传的文件扩展名(不带点前缀,逗号或空格分隔),这在以下方法中初始化:

Drupal.behaviors.fileValidateAutoAttach

将为这些控件绑定改变事件(change.fileValidate),验证逻辑在以下方法中执行:

Drupal.file.validateExtension(event)

该方法不支持允许多个文件上传的文件控件,进一步讲,在多文件单控件上传时仅能验证其中一个文件,就其原因是内部验证的是控件对象的“value”值(用户可以覆写该方法,采用控件的“files”属性进行验证),如验证不通过将停止事件传播(不触发后续上传动作),清空表单,并将错误消息显示在父级具备“js-form-managed-file”类属性的div元素中

 

2、为托管文件控件附加自动上传动作

在以下方法中初始化:

Drupal.behaviors.fileAutoUpload

该方法将为页面所有文件类型控件侦听改变事件(change.autoFileUpload),注意是所有,但在处理函数中仅处理托管文件,处理函数为:

Drupal.file.triggerUploadButton(event)

该方法将在触发元素上向父级查找具备类属性“js-form-managed-file”的元素,然后在其中查找具备类属性“js-form-submit”的元素,然后在该元素上触发“mousedown”事件,换句话说,仅“托管文件”才会自动上传,AJAX库会响应“mousedown”事件进行文件上传的实际操作

BUG提示:Drupal.file.triggerUploadButton(event)方法中,为所有处于当前托管文件元素内的提交按钮触发了鼠标按下事件,这也导致删除按钮被触发,因此当已经有托管文件列表时(不管是默认值还是已上传的),同一个文件会被上传两次,在数据库中产生两个文件实体;修复:应仅在上传按钮上触发。(该bug已在D8.7.8中修复)

 

3、文件自动上传前禁用无关控件

处理方法:Drupal.file.disableFields(event),在js表单提交按钮上侦听了鼠标按下事件(mousedown),这样的按钮具备类属性“js-form-submit”,当事件发生时,将其他“托管文件”上传控件暂时禁用,一秒后重新开启(为什么是一秒?在当前事件循环中不会被开启),禁用操作能防止无关控件中的文件被上传。

BUG提示:由于禁用和上传操作都是侦听“mousedown”事件,该方法没有保证禁用操作在上传操作之前执行,这导致禁用的目的不能达到,其他文件控件中只要有值就会被一并上传,这是不必要的,需要改进,方案是禁用操作侦听改变事件,然后再派发鼠标按下事件

 

4、处理文件上传进度条

处理方法:Drupal.file.progressBar(event),如果设置了特定的进度条元素将启动该进度条,换句话说如果存在类属性为“ajax-progress-bar”的DIV元素,将显示出来,并触发“fileUpload”事件,开发者可以通过该事件调整进度条。该方法其实用处不大,AJAX API已经处理了进度条问题

 

5、处理已经上传文件的预览链接

处理方法:Drupal.file.openInNewWindow

点击已上传文件的预览链接后,在一个新窗口打开预览

 

托管文件上传后端原理:

要理解实现原理需要清楚理解表单处理流程,因此让我们先回顾一下表单流程知识:

在表单流程中,对于一个特定的表单元素来说,相关回调执行顺序如下:

1、运行值回调(#value_callback),返回值优先级高于默认值,默认回调在元素类型插件管理器中统一设置

2、运行处理回调(#process),在值处理后,遍历子元素前运行

3、在遍历处理完子元素后,运行构建后回调(#after_build

4、如在提交中,运行元素验证回调(#element_validate),提交元素运行验证处理器(#validate

5、如在提交中,运行提交处理器(#submit),该步仅适用于提交元素,如无设置将运行表单默认提交处理器

6、运行渲染前回调(#pre_render

 

方法说明:

public static function valueCallback(&$element, $input, FormStateInterface $form_state)

值处理,该方法将保存被上传的文件,并新建对应的文件实体并保存,详见下文的文件上传保存。该方法在两种情况下调用:有用户提交或需要设置默认值,理解该方法是理解文件上传的核心,在表单提交阶段$input是用户输入值,在这里是一个嵌套数组,$input['fids']保存着之前已经上传的文件实体id,是一个由空格分隔的整数字符串,详见#process回调,如值不合法将强制采用默认值,如果$input全等于false说明是在处理默认值,可视情况返回值(此时如不返回将直接采用默认值)。

 

public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form)

该方法为“托管文件”表单渲染数组设置子元素,并设置AJAX提交行为,是构建前端html结构的核心方法,在表单流程中在值回调之后、遍历子元素之前运行,子元素有:

1、视觉隐藏的提交按钮:

她没有验证处理器,提交处理器为函数file_managed_file_submitAJAX回调为本类的uploadAjaxCallback方法,传递了#array_parents参数,用以取回“托管文件”的表单渲染数组

2、移除按钮:

和提交按钮一样,她没有验证处理器,提交处理器为函数file_managed_file_submit,该函数处理最终得到的文件实体id情况,会在预渲染回调中控制其显示状态,

3、视觉隐藏的已上传文件实体id表单:

4、文件上传表单

5、可选的文件上传进度条,详见下文

 

public static function uploadAjaxCallback(&$form, FormStateInterface &$form_state, Request $request)

这是上传和移除按钮的AJAX回调,在提交处理器之后运行,比较简单,详见本系列AJAX主题

 

public static function preRenderManagedFile($element)

渲染前回调,控制上传按钮和移除按钮的可见性

 

public static function validateManagedFile(&$element, FormStateInterface $form_state, &$complete_form)

执行上传验证,较简单不多讲。注意该方法依据“#extended”项的设置改变了提交值在表单状态对象中的路径,在取回提交值时,如果为真需要继续从“fids”子键中取回,示例如下:

if ($form['file']['yunke']['#extended']) {
            $fids = $form_state->getValue('yunke')['fids'];
        } else {
            $fids = $form_state->getValue('yunke');
        }

 

文件上传保存:

在全局请求对象建立时,系统用文件包对象代理$_FILES超全局变量,这是更高层级的封装,其类如下:

\Symfony\Component\HttpFoundation\FileBag

该文件包对象被赋值给请求对象的files属性,其中每一个被上传文件的信息使用以下类对象表示:

\Symfony\Component\HttpFoundation\File\UploadedFile

在系统中与上传文件相关的底层交互直接使用\Drupal::request()->files即可,无需再关注文件超全局变量,获取某个上传表单上传的文件如下:

        $all_files = \Drupal::request()->files->get('files', []);//取得全部上传文件
        if (empty($all_files[$form_field_name])) { //这里$form_field_name为表单name属性的二级键名
            return NULL;
        }
        $file_upload = $all_files[$form_field_name];

这里如果是单值上传,那么$file_upload是前文所述的UploadedFile对象(后简称为被上传文件对象),如果是多值上传(name属性带“[]”后缀),那么$file_upload是一个由UploadedFile对象构成的数组,键名为上传自动分配的序号(数组下标,从零开始,下文称为上传序号)。文件上传在以下函数中处理,说明如下:

file_managed_file_save_upload($element, FormStateInterface $form_state)

用于保存通过“托管文件”元素类型上传的任意文件,参数$element是“托管文件”表单渲染数组,注意是非引用接收,换句话说就是该方法不会修改表单渲染数组,仅用来保存文件,渲染数组能携带文件保存必须的信息,如多单值、保存位置、验证回调等,参数$form_state为表单状态对象,用于在保存发生错误时传递错误信息等,在文件目标保存目录创建失败或没有文件上传时,该函数返回false,否则返回一个数组,键名为文件实体ID,键值为文件实体对象,对于新上传的文件,文件已被移动到目标位置,该实体对象已经被保存,但处于临时状态(如果后续没有将其转变为永久状态,那么文件及实体对象会被自动任务删除),上传的部分或全部文件可能发生保存错误,这些文件不被返回,仅标记表单错误,因此返回数组仅包含保存成功的文件,所以也可能返回空数组。

 

_file_save_upload_from_form(array $element, FormStateInterface $form_state, $delta = NULL, $replace = FILE_EXISTS_RENAME)

该方法仅辅助处理错误,并不执行真正的文件保存,她通过消息服务来处理错误,这样实现了从表单渲染数组保存文件过渡到通用上传文件保存,存在的目的是架构上更优雅

 

file_save_upload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME)

从文件包保存单个表单上传的文件,表单可能是单值或多值的,该方法通过静态缓存避免了同一个上传文件被多次处理;参数如下:

$form_field_name:文件表单name属性的第二级键名,如“name="files[yunke][]" ”中的“yunke

$validators:数组值,上传验证回调,一个元素一个回调,见上文

$destination:文件上传目标保存位置,必须为流包装器格式,默认为false(此时将采用temporary://

$delta:整数或NULL,文件上传序号,指示多值上传保存后返回哪一个文件的实体对象,如为NULL将返回全部,单值上传该参数应为null0,其他整数将返回null值,不管该值是什么,该表单上传的全部文件都会执行保存操作,并非指没有指定就不保存

$replace:如果已有相同文件名存在时的处理方法,默认为换个名字后再保存(增加序号后缀),此外有替换、抛出错误两种

有四种可能的返回值,如下:

文件实体:如果指定了有效的$delta整数参数,保存成功时将仅返回对应的文件实体对象

FALSE:在指定了$delta参数时,文件保存错误将返回false

NULL:没有文件上传时返回NULL

数组:键名为文件上传序号,从0开始(非实体id),键值为文件实体对象(保存失败时为false),如表单为单值上传则该数组仅有一个元素,其键名为0

 

_file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $validators = [], $destination = FALSE, $replace = FileSystemInterface::EXISTS_REPLACE)

文件保存的核心方法,执行真正的保存逻辑,保存单个已上传文件,参数$file_info为被上传文件对象:

\Symfony\Component\HttpFoundation\File\UploadedFile

其他参数含义同上,该方法将新建文件实体、执行文件验证、改正可能有风险的文件名;如果验证出现错误那么文件和新建的文件实体不被保存;如果操作是替换操作,那么文件实体采用原ID,仅更新信息;如果文件是匿名用户上传,且目标地址不为公共文件目录,那么通过会话系统让用户可以查看已上传的文件,文件实体id保存在会话的“anonymous_allowed_file_ids”项中:

\Drupal::request()->getSession()->get('anonymous_allowed_file_ids', []);

值为一个数组,键名键值均为文件实体的ID

 

中文文件名乱码解决方案:

导致文件储存时中文文件名乱码的原因是php和操作系统使用的字符编码不兼容造成的,好在PHP7.1已经解决该问题,目前(201910D8要求的最低php版本已从该版本开始,以后不必再担心该问题,但低版本phpdrupal仍然需要自行解决。有多种解决方案,各有优缺点,如下

方案一:

由于文件上传操作的是文件包对象,因此我们可以在整个系统流程中派发请求事件阶段注册一个服务,用该服务将文件包中的中文文件名改为哈希值、uuid、时间戳等,典型的推荐使用音译服务将其更改为拼音,由于被上传文件对象没有set方法:

\Symfony\Component\HttpFoundation\File\UploadedFile

因此可以给文件包对象:

 \Symfony\Component\HttpFoundation\FileBag

注入新实例化经过修改的被上传文件对象来实现,建议将$_FILES超全局变量一起修改(但不是必须的)

该方案适用于系统中任意的文件上传,但缺点是丢失了原来的中文文件名,以后查看会不直观

 

方案二:

由于托管文件是通过文件实体记录的,模块可以通过保存前钩子去修改物理文件名和文件实体的uri字段,这样可以完整保留原中文文件名,但导致文件实体对象的文件名和实际文件名不一致,在系统中有些对此有一致性要求的地方可能会出问题,此外该钩子运行过程中产生的错误没有一个简单的方法传递给表单状态对象

 

方案三:

利用文件验证钩子“file_validate”来修改中文文件名,钩子运行时文件还没有保存到目标文件位置,钩子修改文件实体的“destination”属性即可,修改后的属性需要再次调用以下方法:

$destination=\Drupal::service('file_system')->getDestinationFilename($destination, $replace);

期间产生的异常需要作为验证错误返回,系统后续流程会按照修改后的目标地址去保存文件

该方案同方案一相比同样不能保留原中文文件名,但和方案二比可以很方便的传递表单错误,该方案仅能用于托管文件的上传

 

文件上传安全处理:

文件上传必须对扩展名进行安全处理,有些服务器(比如Apache)在处理文件链接时,遇到不能识别的扩展名,会回退使用前面的扩展名,比如请求文件yunke.php.pps时,如不能识别pps,那么将回退使用php做扩展名,因此文件会被当做php文件执行,这导致了安全漏洞,因此文件上传后需要对文件名进行特殊检查处理,drupal使用以下函数来干这件事:

  file_munge_filename($filename, $extensions, $alerts = TRUE)

该函数遇到多级扩展名的文件时,如果中间的扩展名不在白名单中,那么将追加下划线,如“yunke.php.pps”将变为“yunke.php_.pps”如果不想改变,那么中间的扩展名也必须被设置到白名单中

除外该函数受到以下配置项的影响:

\Drupal::config('system.file')->get('allow_insecure_uploads')

该配置项默认为false,在进行网站安全配置时需要注意该选项

 

文件上传进度显示:

PHP5.4开始,最理想的文件上传进度信息是从会话中获取,详见:

https://www.php.net/manual/zh/session.upload-progress.php

为什么说是最理想呢?因其是php原生官方默认实现不必额外安装扩展,支持多机共享,但是目前Drupal实现尚未采用该方式,而是依赖“uploadprogress”或“apc”扩展,这不具备上述优势,因此这里仅简单讲述:

要显示上传进度,托管文件渲染数组须设置上传进度条类型(#progress_indicator属性)为“bar”,且php安装了上述扩展之一,进度获取路由名为:file.ajax_progress,控制器为:

\Drupal\file\Controller\FileWidgetAjaxController::progress

云客这里提供一个自定义的采用会话获取进度信息的思路:

自定义一个模块,提供一个从会话获取进度信息的路由,在构造“托管文件”渲染数组时,提供一个自定义的构建后“#after_build”回调,以更改进度条部分的实现方式。

 

文件字段:

文件字段插件idfile

类:Drupal\file\Plugin\Field\FieldType\FileItem

默认控件:file_generic

默认格式化器:file_default

文件字段控件是基于托管文件API的,关于字段定义、格式化器、控件实现请参看本系列相关专门主题,这里仅列出关于文件字段的一些关注点

1、文件使用追踪在以下类中维护:

\Drupal\file\Plugin\Field\FieldType\FileFieldItemList

2、文件保存路径设置涉及到了占位符,请参考本系列token主题

3、文件字段的默认控件进行了深度定制,在允许多值时,单个控件可一次性上传多个文件,但“multiple_values”的值依然为false,这并不冲突,因为上传控件并非值控件

 

托管文件使用追踪注意事项:

你可能已经发现了可以通过富文本编辑器的文件控件上传文件,其实这是不推荐的做法(是媒体类型之前的产物)通过这样的方式上传的文件,其使用跟踪会被记录,保存信息后形成永久文件,但信息删除后(不论是删除整个信息还是仅在富文本编辑器中删除),追踪的使用次数却不会减少,且文件依然是永久状态,不会自动删除,因此出现了问题,这是不规范的做法,且文件地址默认不能使用token(这需要修复),推荐采用媒体实体代替

 

文件系统函数:

这里仅做一个简介,便于理解系统提供的功能:

function file_copy(FileInterface $source, $destination = NULL, $replace = FILE_EXISTS_RENAME)

拷贝一个文件实体及对应的文件,相当于高级版本的copy函数

function file_move(FileInterface $source, $destination = NULL, $replace = FILE_EXISTS_RENAME)

移动文件并更新文件实体

function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME)

相当于高级版本的file_put_contents($filename, $data);,成功时返回文件实体对象

function file_get_content_headers(FileInterface $file)

为文件下载提供http头信息

function file_cron()

实现计划任务钩子,删除临时文件

function file_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata)

提供文件类型token

function file_icon_class($mime_type)

通过MIME类型返回文件的前端类属性标识

function file_icon_map($mime_type)

根据MIME类型决定文件的图标

file_get_file_references(FileInterface $file, FieldDefinitionInterface $field = NULL, $age = EntityStorageInterface::FIELD_LOAD_REVISION, $field_type = 'file')

得到引用传入文件的实体

 

除以上函数外,在以下文件中还有许多有用的函数:

/core/includes/file.inc

 

文件链接及负载均衡:

创建文件的URLweb访问地址)应该通过以下函数创建:

file_create_url($uri)

特别注意在系统任何地方或模块任何地方都应该使用该函数获取文件链接(模板中使用file_url($uri),在底层依然调用了该函数),原因在于集中处理负载均衡,见下文。

参数$uri是一个字符串,既可以是本地路径或外部路径,也可以是流包装器路径,有几种情况:

1、如果以“/”开始,将原样返回

2、如果有协议,且是httphttpsdata之一,将原样返回

3、一个合法的流包装器,由包装器返回URL

4、既无协议,也不以“/”开始,将返回本服务器绝对地址,带域名和协议

5、以上情况都不是,将返回false,通常是出现了无效的流包装器路径

在该方法执行初期即派发了“file_url”修改钩子(换句话说以上逻辑是修改钩子之后执行):

hook_file_url_alter(&$uri)

这是实现负载均衡的关键,当有大规模访问时,一台服务器可能应付不过来,文件需要存放在CDN或文件专用服务器群集中,此时可以实现该钩子,在钩子内修改访问连接并可选的依据负载均衡机制复制文件到其他服务器。

通常我们在处理文件链接时,还会经常使用以下函数:

function file_url_transform_relative($file_url)

她接收由上面函数返回的url,将绝对路径转化为相对根目录的相对路径,仅对本地文件有效,外部文件将不转化

 

补充:

1bug提示:当普通文件控件和“托管文件”控件共存于一个表单时,后者AJAX上传时会连同前者一并上传,这是不必要的,后台也不会处理,有待修正

2、得到文件上传的系统临时目录可用如下代码:

\Drupal\Component\FileSystem\FileSystem::getOsTemporaryDirectory()

3、本篇介绍的是通用的文件上传处理,关于图片、视频、音频等推荐用媒体实体

4、托管文件多值上传时,部分文件失败将仅提示错误,其他上传成功的文件依然会保存文件实体和文件

5、临时文件会在file_cron()钩子函数中被删除,详见本系列计划任务主题,此外可用本系列配套模块“yunke_help”手动删除未被使用的文件,该模块须V2.1及以上版本

本书共136小节:

评论 (写第一个评论)