PHP V5.2 中的新增功能,第 5 部分: 跟踪文件上传进度

可接收文件的帐户

要接收文件,必须先设置接收文件的表单。很方便的是,HTML 附带了文件的标准字段类型。同所有 HTML 表单字段一样,它在逻辑上被命名为类型 file。默认情况下,附带了显示在块右侧的便捷 Browse 按钮。


清单 1. upload.php 的 HTML 表单

                
<?php
   $id = $_GET['id'];
?>

<form enctype="multipart/form-data" id="upload_form" 
      action="target.php" method="POST">

<input type="hidden" name="APC_UPLOAD_PROGRESS" 
       id="progress_key"  value="<?php echo $id?>"/>

<input type="file" id="test_file" name="test_file"/><br/>

<input onclick="window.parent.startProgress(); return true;"
 type="submit" value="Upload!"/>

</form>

 

需要为此表单创建一个 PHP 页面,因为需要使用惟一密钥来跟踪上传。最后,它将是用于调用此页面作为 GET 值的 URL 的一部分。此数字将是稍后将检索的 APC 缓存条目密钥的值。要传递该值,表单字段需要有一个拥有特殊名称的隐藏字段,使 APC 知道它需要保存文件上传状态。此字段被称为 APC_UPLOAD_PROGRESS。这是前述的启动缓存过程的 hook。为确保 PHP 可以访问缓存中的正确条目,我们使用检索到的惟一 ID 作为隐藏字段的值,从而创建该值的密钥。用户提交表单后 —— 我们将简短地处理提交按钮 —— 浏览器将把文件和密钥作为发送给服务器的 POST 数据的一部分进行发送。

装入到浏览器中后,此页面应当提供一个非常简单的表单,如图 1 所示:


图 1. 上传表单
上传表单 

要在不重新装入整个页面的情况下使用户可以提交文件,需要把此表单嵌入到另一个文件的 iframe 中。如果尝试仅使用表单操作页面 (target.php) 来检索数据,则无法看到任何缓存信息,因为在上传完成之前页面不会返回任何信息。鉴于这个原因,使用此新 hook 的最常见示例都是用 Ajax 编写的。通过该方法,您可以提交表单并且还可以在同一个窗口中继续检查上传的状态而无需刷新。

要使脚本运行,需要继续转到一个包含页面,该页面将设置 iframe 并接收已上传文件的信息。还需要使用一组 JavaScript 函数来为进度指示器获得数据以及显示进度指示器。

 

捕获已接收的文件

提交表单中包括文件时,该文件将发送到服务器的临时位置中,直至它被保存到永久位置。当它在临时存储设备中时,可以通过$_FILES 关联数组获得它。使用 PHP 附带的标准版本文件上传函数,可以选择路径并将这些函数保存到服务器上,或者按自己的需要处理这些函数。


清单 2. target.php 文件

                
<?php  

if($_SERVER['REQUEST_METHOD']=='POST') {
  move_uploaded_file($_FILES["test_file"]["tmp_name"], 
  "c:\\sw\\wamp\\www\\" . $_FILES["test_file"]["name"]);
  echo "<p>File uploaded.  Thank you!</p>";
}

?>

 

首先,查看来自表单的 POST 变量是否已被设定并表示我们已经收到了表单数据。如果收到表单数据,并且但愿包括文件,则还应当有一个全局数组 $_FILES。把已上传的文件移到安全位置,这取决于需要对其采取的操作。在本例中,只需把文件移到 \sw\wamp\www(当然,这是完全任意的位置。请随意选择一个所需位置)。完成该操作后,我们将感谢用户。

在这里包括实际文件处理主要是为了实现完整性。由于本文讲述的是进度条,因此收到实际文件后如何处理它无关紧要。

 

制作进度条

还将需要一个返回实际上传进度的脚本。清单 3 显示了一个非常简单的版本。


清单 3. getprogress.php 文件

                
<?php
if(isset($_GET['progress_key'])) {

  $status = apc_fetch('upload_'.$_GET['progress_key']);
  echo $status['current']/$status['total']*100;

}
?>

 

此脚本首先将查找 progress_key,它是先前讨论的 $id 值(不必担心,您马上就将看到它的来源)。这将导致调用从 APC 缓存返回数据的 apc_fetch()。我们需要正确的文件信息,因此需要惟一 ID,在本文中表示为 $_GET['progress_key']。调用带有upload_xxxxxx 参数的 apc_fetch(),其中 xxxxxx 是惟一 ID;PHP 将自动预先追加 upload_ part。

获得数据后,可以使用 JSON 扩展给信息设定一种更便于在 JavaScript 中使用的格式并返回整个对象(如果需要)。$status 对象是拥有以下字段的数组:

total
文件的总大小
current
到目前为止收到的文件数
rate
上传速度(以字节每秒为单位)
filename
文件名
name
变量名
temp_filename
PHP 保存文件的临时副本的位置
cancel_upload
上传是已取消 (1),还是未取消 (0)
done
上传是已完成 (1),还是尚未完成 (0)

在本例中,只需要完成百分比。您可以在自己的应用程序中选择使用更多信息。

 

显示进度条的 JavaScript

现在已经准备好开始构建实际的进度条。为了简单起见,脚本将使用 CSS 创建一个用于模拟进度条并可以使用 JavaScript 进行控制的 div,如清单 4 所示:


清单 4. 主文件 progress.php

                
<html>
<head><title>Upload Example</title></head>
<body>

<script type="text/javascript">

var counter = 0;

function startProgress(){
    document.getElementById("progressouter").style.display="block";
    fire();
}

function fire(){
   if (counter < 101){
     document.getElementById("progressinner").style.width =
                                                     counter+"%";
     counter++;
     setTimeout("fire()",100);
   }
}

</script>

<div id="progressouter" style=
    "width: 500px; height: 20px; border: 6px solid red; display:none;">
   <div id="progressinner" style=
       "position: relative; height: 20px; background-color: purple; width: 0%; ">
   </div>
</div>

<span onclick="startProgress()">Start me up!</span>

</body>
</html>

 

此页面包含了两个嵌套的 div 元素,外面的那个用作边框。脚本将调整内部 div 相对于边框的大小以显示进度。当用户单击 Start me up! 文本时,startProgress() 脚本将调用 fire() 函数。该函数将检查计数器的值,并且如果该值尚未超过 100,就把内部 div 设为外部 div 宽度的该百分比值。然后它将增加计数器的值并告诉浏览器每十分之一秒就执行一次全部上述过程。

结果将与图 2 非常相似:


图 2. 进度条脚本
进度条脚本 

现在只需要有一种获得脚本以更新宽度的方法,此宽度不是任意的数字而是完成百分比。

 

整合

现在剩下的只是要把所有内容 hook 到一起。您可以通过 progress.php 页面来完成此操作。


清单 5. 最终的 progress.php 页面

                
<?php
   $id = uniqid("");
?>
<html>
<head><title>Upload Example</title></head>
<body>

<script src="http://maps.google.com/maps?file=api&v=2&key=<yourkeyhere>"
            type="text/javascript"></script>

<script type="text/javascript">

function getProgress(){
  GDownloadUrl("getprogress.php?progress_key=<?php echo($id)?>", 
               function(percent, responseCode) {
                   document.getElementById("progressinner").style.width = percent+"%";
                   if (percent < 100){
                        setTimeout("getProgress()", 100);
                   }
               });

}

function startProgress(){
    document.getElementById("progressouter").style.display="block";
    setTimeout("getProgress()", 1000);
}

</script>

<iframe id="theframe" name="theframe" 
        src="upload.php?id=<?php echo($id) ?>" 
         > 
</iframe>
<br/><br/>

<div id="progressouter" style=
   "width: 500px; height: 20px; border: 6px solid red; display:none;">
   <div id="progressinner" style=
       "position: relative; height: 20px; background-color: purple; width: 0%; ">
   </div>
</div>

</body>
</html>

 

从底层开始向上层工作,我们已经添加了嵌入清单 1 中的 upload.php 脚本的 iframe,给它提供了在页面顶部生成的惟一 ID。

现在,是否还记得该表单中的 Submit 按钮?

<input onclick="window.parent.startProgress(); return true;"
 type="submit" value="Upload!"/>

 

该按钮将完成两项工作。提交表单,像普通的 Submit 按钮一样;但在执行该操作之前,它将在主窗口中调用 startProgress() 脚本。startProgress() 脚本将告诉进度条显示自身 —— 开始时无显示属性,然后告诉浏览器等待一秒,然后再执行 getProgress() 脚本。

现在,getProgress() 脚本将使事情变得有趣。记不记得在前面我说过将需要使用 Ajax 或某种类似的方法来检查文件的进度?对,在本例中,表单将采用捷径,调用来自 Google Maps API 库的 GdownloadUrl() 函数(注意,表单将导入位于页面顶部的库。您将需要获得自己的访问此库的密钥,但是它是从 Google 免费获取的)。

此函数将下载 URL 的内容 —— 本例中为 getprogress.php 脚本 —— 并执行在其中定义的匿名函数。函数所接受的第一个参数是从 URL 返回的数据,本例中为百分比,以便使用它更新进度条。最后,如果文件尚未完成下载,则告诉浏览器每十分之一秒重试一次(在实际情况中,可能无法那么快地执行这些调用,但是浏览器将尽其所能进行操作)。

最终结果是页面使用户可以查看文件正被上传的进度。


图 3. Progress.php 的输出
Progress.php 的输出 

posted @ 2011-12-03 16:49  Childhood Memory  阅读(194)  评论(0编辑  收藏  举报