解决同时上传文件和json
在你开始前
在本教程中,您将学习如何在PHP中使用会话,如何通过DOM处理XML数据以及如何在PHP中创建,使用和读取JSON数据。
关于本教程
本教程通过演示基于Web的工作流应用程序的构建,教您如何使用PHP。 “ 学习PHP,第1部分 ”介绍了诸如语法,函数,使用HTML表单提交和数据库以及创建新用户可以用来注册帐户的过程之类的基础知识。
在本教程中,您将使用户能够使用他们的浏览器将文件上传到系统,并且将首先使用XML,然后使用JSON来存储和显示有关每个文件的信息。
第3部分介绍如何使用HTTP身份验证,以及通过从不可访问Web的目录流式传输文件来保护文件。 您还将研究如何创建对象和使用异常。
在本教程的过程中,您将检查:
- 创建和使用会话以及会话信息。
- 从浏览器上传文件。
- 使用文档对象模型(DOM)创建XML。
- 使用DOM处理XML数据。
- 创建JavaScript对象表示法(JSON)数据。
- 读取和使用JSON数据。
谁应该学习本教程?
本教程是一个分为三部分的系列文章的第2部分,旨在教您在构建工作流应用程序时使用PHP的各个方面。 如果您对PHP有基本的了解,并想了解有关从浏览器,会话上传文件或使用PHP处理XML或JSON的知识,请阅读本教程。
本教程假定您对PHP具有基本的了解,达到了本系列第1部分中讨论的水平。 这包括对控件结构(例如循环和if-then语句)以及函数以及与HTML表单提交和数据库一起使用的基本了解。 熟悉XML是有帮助的,但不是必需的。 (您可以在“ 相关主题”中找到有关这些主题的更多信息。)
先决条件
您需要安装并可用的Web服务器,PHP和数据库。 如果您拥有托管帐户,则只要服务器已安装PHP V5并有权访问MySQL数据库,就可以使用它。 否则,下载并安装以下软件包:
-
XAMPP
- 无论您使用的是Windows®,Linux®还是Mac,获取本教程所有必需软件的最简单方法是安装XAMPP,其中包括Web服务器,PHP和MySQL数据库引擎。 如果选择这种方式,请安装并运行控制面板以启动Apache和MySQL进程。 您还可以选择单独安装各个部件。 请记住,然后必须将它们配置为可以一起工作-XAMPP已经完成了这一步骤。
-
网络服务器
- 如果选择不使用XAMPP,则Web服务器有多个选项。 如果使用PHP 5.4(在撰写本文时,XAMPP仅使用PHP 5.3.8),则可以使用内置的Web服务器进行测试。 但是,对于生产而言,我假设您使用的是Apache Web服务器2.x版。 PHP 5.x
- 如果您不使用XAMPP,则需要单独下载PHP5.x。 标准发行版包括本教程所需的一切。 随意下载二进制文件; 您不需要本教程的源代码(或者永远都不需要,除非您想破解PHP本身)。 本教程是在PHP 5.3.8上编写和测试的。 的MySQL
- 该项目的一部分涉及将数据保存到数据库,因此您也需要一个数据库。 同样,如果安装XAMPP,则可以跳过此步骤,但是,如果选择这样做,则可以单独安装数据库。 在本教程中,我专注于MySQL,因为它通常与PHP一起使用。 如果选择这种方式,则可以下载并安装社区服务器。
入门:创建会话
让我们首先创建一个登录页面,以便您可以开始,填充和结束会话。
登录过程,第1部分
本系列的第1部分研究了如何处理从HTML表单提交的信息以及如何通过为新用户创建注册系统来与数据库进行交互。 您可以在最终文件中相关主题 。
但是,您没有做的一件事就是创建一个页面,用户可以通过该页面登录系统。 您现在就来解决这个问题,并简要回顾一下已经涵盖的内容。
首先,创建一个新的空白文件,并将其另存为login.php,与registration.php在同一目录中。 添加清单1中的代码。
清单1.创建登录表单
<?php
include("top.txt");
require("scripts.txt");
?>
<h1>Please log in</h1>
<form action="login_action.php" method="post">
Username: <input type="text" name="username" /><br />
Password: <input type="password" name="password" /><br />
<input type="submit" value="Log In" />
</form>
<?php
include("bottom.txt");
?>
要进行审查,当用户提交表单时,PHP将创建一个信息数组$_POST
,其中包含两个条目: username
和password
。 您将根据数据库检查该信息。 在浏览器中加载此页面时,服务器将包含界面元素,如top.txt和bottom.txt中所引用(参见图1 )。
图1.带有界面元素的login.php页面
登录过程,第2部分
创建另一个新页面,并将其另存为login_action.php。 添加清单2中的代码。
清单2.处理登录信息
<?php
include("top.txt");
require("scripts.txt");
$dbh = new PDO('mysql:host=localhost;dbname=workflow', 'wfuser', 'wfpass');
$stmt = $dbh->prepare("select * from users \
where username = :user and password = :pword");
$stmt->bindParam("user", $name);
$stmt->bindParam("pword", $pword);
$name = $_POST["username"];
$pword = $_POST["password"];
$stmt->execute();
if ($stmt->fetch()) {
echo "You are logged in. Thank you!";
} else {
echo "There is no user account with that username and password.";
}
$dbh = null;
include("bottom.txt");
?>
在包括页面顶部的界面元素之后,打开与数据库的连接并创建用于搜索数据库的PDO对象。 在此处准备一条语句,以使用用户提交的用户名和密码搜索用户帐户。
您可能会注意到此版本与您在第1部分中使用的版本略有不同。 之前,您是按位置指定变量的。 现在,您使用冒号(:)语法专门命名参数。 否则,它的工作方式完全相同。 执行该语句后,如果用户名和密码与现有帐户匹配,则PHP可以fetch()
行。 如果不是,则提取失败,并返回false
。
开始会议
您要求用户登录,以便站点知道单个用户可以看到哪些文件。 就目前情况而言,当用户离开此页面时,该信息就消失了。 您不仅要做登录,还需要做更多的事情。
您想要创建一个会话,以便服务器知道属于单个用户的哪些请求可以组合在一起。 您还可以将信息(例如用户名)与会话相关联,这很方便。
首先,使用session_start()
函数创建会话。 此函数首先检查会话是否存在,如果不存在,则开始会话。
请记住一个关键项目: session_start()
的使用,或者实际上是设置会话信息的所有操作都有一个主要限制。 它是HTTP标头的一部分,因此它必须在任何内容(包括接口元素)到达浏览器之前发生(请参见清单3 )。
首先,添加会话并重组login_action.php:
清单3.添加会话信息
<?php
session_start();
require("scripts.txt");
$dbh = new PDO('mysql:host=localhost;dbname=workflow', 'wfuser', 'wfpass');
$stmt = $dbh->prepare("select * from users
where username = :user and password = :pword");
$stmt->bindParam("user", $name);
$stmt->bindParam("pword", $pword);
$name = $_POST["username"];
$pword = $_POST["password"];
$stmt->execute();
$loginOK = false;
if ($stmt->fetch()) {
$loginOK = true;
}
$dbh = null;
include("top.txt");
if ($loginOK) {
echo "You are logged in. Thank you!";
} else {
echo "There is no user account with that username and password.";
}
include("bottom.txt");
?>
您在这里所做的是重新组织页面,以便可以在将所有内容输出到浏览器之前完成所有会话工作。
填充会话
除非您有信息可以在请求之间传递,否则创建会话是没有意义的。 在这种情况下,您希望传递用户名和电子邮件地址,因此将它们添加到会话中(请参见清单4 )。
清单4.保存会话信息
...
if ($row = $stmt->fetch()) {
$loginOK = true;
$_SESSION["username"] = $row["username"];
$_SESSION["email"] = $row["email"];
}
...
像$_POST
一样, $_SESSION
是一个关联数组。 它也是一个超级全局变量,通过多个请求保留其值。 这样,您可以从另一个页面引用它。
使用现有会话
当PHP遇到session_start()
函数时,它将加入正在进行的现有会话,或者在必要时启动一个新会话。 这样,其他页面可以访问$_SESSION
数组。 例如,您可以在导航部分中查找$_SESSION["username"]
值,如清单5所示 。
清单5.使用会话信息
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"<
<head<
<title<Workflow Manager</title<
<link rel="stylesheet" type="text/css" href="style.css" /<
</head<
<body<
<div id="wrapper"<<div id="bg"<<div id="header"<</div<
<div id="page"<<div id="container"<<div id="banner"<</div<
<div id="nav1"<
<ul style='float: left'<
<li<<a href="#" shape="rect"<Home</a<</li<
<li<<a href="#" shape="rect"<Upload</a<</li<
<li<<a href="#" shape="rect"<Files</a<</li<
<?php
if (isset($_SESSION["username"]) || isset($username)){
if (isset($_SESSION["username"])){
$usernameToDisplay = $_SESSION["username"];
} else {
$usernameToDisplay = $username;
}
?<
<li shape="rect"<</li<
<li<<p style='color:white;'<
Welcome, <?=$usernameToDisplay?<.
</p<</li<
<?php
} else {
?<
<li<<a href="registration.php"
shape="rect"<Register</a<</li<
<li<<a href="login.php"
shape="rect"<Login</a<</li<
<?php
}
?<
</ul<
</div<
<div id="content"<
<div id="center"<
您可能想知道为什么还要检查$username
变量。 原因是:设置$_SESSION
变量时,直到下次调用session_start()
时,更改才可用。 在实际的login_action.php页面上,它尚不可用,但是$username
将可用, 如图2所示。
图2.具有$username
变量的Login_action.php页面
清除资料
完成会话后,您可以清除其数据或完全结束会话。 例如,您可能想注销用户而不结束会话。 创建一个名为logout.php的新文件,并添加清单6中的代码。
清单6.清除会话信息
<?
session_start();
unset($_SESSION["username"]);
unset($_SESSION["email"]);
include("top.txt");
echo "Thank you for using the Workflow System.";
echo "You may <a href=\"login.php\">log in again</a>.";
include("bottom.txt");
?>
要查看此页面,请将浏览器指向http://localhost/logout.php
并显示已清除值, 如图3所示 。
图3.清除值的页面
清除数据的一种更简单的方法是结束会话(请参见清单7 )。
清单7.结束会话
<?
session_start();
session_destroy();
include("top.txt");
echo "Thank you for using the Workflow System.";
echo "You may <a href=\"login.php\">log in again</a>.";
include("bottom.txt");
?>
请注意,在销毁当前会话之前,仍必须使用session_start()
加入当前会话。 此步骤清除与会话关联的所有值。
上载档案
在本部分中,您将创建一个上传表单,供用户在示例工作流程应用程序中上传和保存文件。
上传的工作方式
除了文本信息之外,您还可以使用HTML表单发送文档,这就是使用户能够将文件添加到系统的方式。 该过程的工作原理如下:
- 用户加载一个使他们能够选择要上传的文件的表单。
- 用户提交表格。
- 浏览器将文件及其有关信息作为请求的一部分发送到服务器。
- 服务器将文件保存在临时存储位置。
- 处理表单提交PHP页面将文件从临时存储移动到永久存储。
让我们开始创建实际的表单。
上传表格
上传文件的表单类似于用于注册和登录页面的表单,但有两个重要的例外(请参见清单8 )。
清单8.创建上传表单
<?php
include("top.txt");
?>
<h3>Upload a file</h3>
<p>You can add files to the system for review by an administrator.
Click <b>Browse</b> to select the file you'd like to upload,
and then click <b>Upload</b>.</p>
<form action="uploadfile_action.php" method="POST"
enctype="multipart/form-data">
<input type="file" name="ufile" \>
<input type="submit" value="Upload" \>
</form>
<?php
include("bottom.txt");
?>
enctype
属性告诉浏览器,它发送的信息必须采用特定的格式,该格式必须允许信息的多个部分,而不仅仅是名称/值对的列表。
文件输入提供了一个框,使用户能够单击Browse ...并选择文件, 如图4所示。
图4.选择一个要上传的文件
在top.txt中添加指向该文件的链接(请参见清单9 )。
清单9.将上传表单添加到界面
...
<li><a href="#" shape="rect">Home</a></li>
<li><a href="uploadfile.php" shape="rect">Upload</a></li>
<li><a href="#" shape="rect">Files</a></li>
...
现在,您可以查看上传的信息。
上传的信息
通过浏览器上传文件时,PHP会收到有关该文件的一系列信息。 您可以根据输入字段的名称在$_FILE
数组中找到此信息。 例如,您的表单具有名为ufile的文件输入,因此有关该文件的所有信息都包含在数组$_FILE['ufile']
。
该数组允许用户上传多个文件。 只要每个文件都有自己的名称,它就会有自己的数组。
现在,注意“ $_FILE
”被称为数组。 在本系列的第1部分中,您遇到了以下情况:当您传递多个具有相同名称的密码的表单值时,数组值本身就是一个数组。 在这种情况下, $_FILE
数组的每个值本身都是一个关联数组。 例如,您的ufile文件具有以下信息:
-
$_FILE['ufile']['name']
-文件名(例如,uploadme.txt) -
$_FILE['ufile']['type']
-文件的类型(例如image/jpg
) -
$_FILE['ufile']['size']
-上载文件的大小(以字节为单位) -
$_FILE['ufile']['tmp_name']
-在服务器上上传的文件的临时名称和位置 -
$_FILE['ufile']['error']
-此次上传导致的错误代码(如果有)
由于您知道应该显示什么信息,因此在执行任何处理之前,请验证文件是否已实际上传。
检查文件
在对文件采取任何措施之前,您需要知道文件是否实际上已上传。 创建此表单的操作页面uploadfile_action.php,并添加清单10中的代码。
清单10.检查上传的文件
<?php
session_start();
include("top.txt");
if(isset($_FILES['ufile']['name'])){
echo "<p>Uploading: ".$_FILES['ufile']['name']."</p>";
} else {
echo "You need to select a file. Please try again.";
}
include("bottom.txt");
?>
如果用户未指定要上传的文件,则浏览器不会传递$_FILES['ufile']['name']
。 (请注意,如果变量的值为null
,则isset()
也会返回false。
接下来,您将保存文件。
保存文件
在开始保存上传的文件之前,请确定将文件放置在何处。 在文件获得批准之前,您不希望从网站上访问该文件,因此请创建一个不在主文档根目录下的目录。
在这种情况下,您将使用/ var / www / hidden /。 这就是所有文件的存放位置,因此定义一个常量可能是一个好主意。 常量就像变量一样,不同之处在于一旦设置了常量,就无法更改其值。 打开scripts.txt并添加以下定义(请参见清单11 )。
清单11.创建一个常量
...
}
define("UPLOADEDFILES", "/var/www/hidden/");
?>
现在,只要在该页面中也包含scripts.txt,就可以在上载页面中使用此定义(请参见清单12 )。
清单12.保存上载的文件
<?php
include("top.txt");
include("scripts.txt");
if(isset($_FILES['ufile']['name'])){
echo "Uploading: ".$_FILES['ufile']['name']."<br>";
$tmpName = $_FILES['ufile']['tmp_name'];
$newName = UPLOADEDFILES . $_FILES['ufile']['name'];
if(!is_uploaded_file($tmpName) ||
!move_uploaded_file($tmpName, $newName)){
echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] .
"<br>Temporary Name: $tmpName <br>";
} else {
echo "File uploaded. Thank you!";
}
} else {
echo "You need to select a file. Please try again.";
}
include("bottom.txt");
?>
首先,获取文件的当前位置及其临时名称( tmp_name
),然后使用定义的常量确定文件的位置。 (请注意,常量不是以$
开头。)
接下来,您在if-then语句中做两件事。 检查并确保您要移动的文件实际上是已上传到服务器的文件,而不是用户诱使您采取行动的文件/ etc / passwd。 如果is_uploaded_file()
函数返回false,则相反的!is_uploaded_file()
为true,PHP继续显示错误消息。
如果is_uploaded_file()
返回true
,则表示它是一个上载的文件,然后可以尝试将其从当前位置移动到新位置。 如果该操作不起作用,它将返回false,并且反之亦然,因此您将显示错误消息。
换句话说,如果它不是上载的文件,或者您无法移动它,则会显示一条错误消息。 否则,您将显示成功消息(请参见图5 )。
图5.成功消息
有了文件后,您需要记录其信息以供以后检索。
使用XML:DOM
在本部分中,您将创建一个XML文件,该文件记录有关用户上传的文档的信息。
什么是XML?
如今,如果不使用某种形式的XML,就无法进行大量编程。 幸运的是,XML很容易理解。 您可能已经处理过XML的相对形式:HTML。 考虑这个HTML V4.01页面(请参见清单13 )。
清单13. HTML的示例
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<HTML>
<HEAD>
<TITLE>Workflow System</TITLE>
</HEAD>
<BODY>
<H1 align="center">Welcome to the Workflow System!</H1>
We're glad you could make it.
<P>
Don't forget to log in!
</BODY>
</HTML>
该页面与XML有很多共同点。 首先,它由诸如HEAD
或BODY
类的元素组成 ,这些元素由标记(例如<BODY>
开始, </BODY>
结束)标记。 这些元素可以包含其他元素(例如TITLE
)或文本(例如Workflow System
)。 它们还可以具有属性,例如align="center"
。
但是XML有一些不适用于HTML的限制。 例如,XML必须很好形成的 ,这意味着对于每一个开始标记(如<H1>
则必须有结束标签(如</H1>
这意味着您的<P>
标签必须有一个</P>
结束标记(或者,您也可以将其写为空的 <P />
快捷方式元素。)此外,XML区分大小写,因此必须使用</P>
而不是</p>
关闭<P>
</p>
。
您将如何存储信息
在接下来的两部分中,您将处理一个XML文件,该文件列出了有关用户上传的文档的信息。 该文件将列出每个文档,其状态,上载者以及有关文档本身的信息。 该文件还将列出有关整个系统的统计信息。
该文件将类似于清单14 。
清单14. XML描述文件
<?xml version="1.0"?>
<workflow>
<statistics total="2" approved="1"/>
<fileInfo status="approved" submittedBy="roadnick2">
<approvedBy>tater</approvedBy>
<fileName>timeone.jpg</fileName>
<location>/var/www/hidden/</location>
<fileType>image/jpeg</fileType>
<size>2020</size>
</fileInfo>
<fileInfo status="pending" submittedBy="roadnick">
<approvedBy/>
<fileName>timeone.jpg</fileName>
<location>/var/www/hidden/</location>
<fileType>image/jpeg</fileType>
<size>2020</size>
</fileInfo>
</workflow>
我在此处添加了空格以使内容更清楚,但这是文件的整体结构。 每个文档都有其自己的fileInfo
元素,其中包含有关其状态和所有者的属性,并包含有关文件本身的信息。 统计文档还列出了有关文件本身的信息。
要处理此信息,您将使用文档对象模型(DOM)。
什么是DOM?
文档对象模型(DOM)是一种将XML数据表示为信息的分层树的方式。 以这个fileInfo
元素为例(参见清单15 )。
清单15.描述单个文件的XML
<fileInfo status="approved" submittedBy="roadnick2">
<approvedBy>tater</approvedBy>
<fileName>timeone.jpg</fileName>
<location>/var/www/hidden/</location>
<fileType>image/jpeg</fileType>
<size>2020</size>
</fileInfo>
XML文档中的每条信息都表示为一种节点。 例如,在这里您看到一个fileInfo
元素节点; status
属性节点; 以及多个文本节点,包括tater
, timeone.jpg
和2020
。 甚至元素之间的每个空白都被视为文本节点。
DOM以父子关系排列节点。 例如, approvedBy
元素是一个孩子fileInfo
元素,和tater
文本是一个孩子approvedBy
元素。 这种关系意味着fileInfo
元素具有11个子节点。 五个元素和六个空白文本节点。
存在各种API,使您能够使用DOM对象。 早期版本PHP实现了类似DOM的结构,但是该结构与实际DOM建议(由W3C维护)中的方法和定义并没有很好地结合。 因此,PHP V5具有一组与标准更加接近的新DOM操作。
您将使用此新API创建和修改文档信息文件。
关于对象的简短说明
本系列的第3部分将更详细地讨论PHP中的对象和面向对象的编程。 下一节中讨论的DOM API和SAX API都将使用它们。 在继续之前,您需要基本了解什么是对象以及它们如何工作。
将对象视为功能的集合; 例如,您可以创建一个类似于清单16的类或对象模板。
清单16.一个非常简单的类
class Greeting{
function getGreeting(){
return "Hello!";
};
function getPersonalGreeting($name){
return "Hello, ".$name."!";
};
}
创建对象时,可以引用其功能(请参见清单17 )。
清单17.创建和使用对象
$myObject = new Greeting();
$start = $myObject->getPersonalGreeting("Nick");
在这种情况下,您将创建myObject
对象,您可以使用变量来引用该对象,就像字符串或数字一样。 然后,您可以使用->
符号引用其功能。 除此之外,这就像调用常规函数一样。
现在,您只需要了解有关对象的信息即可。
准备保存信息
现在,您准备开始创建文档信息文件。 最终,您将创建一个名为save_document_info()
的函数。 首先在uploadfile_action.php中添加对此函数的调用(请参见清单18 )。
清单18.调用save_document_info()函数
if(!is_uploaded_file($tmpName) ||
!move_uploaded_file($tmpName, $newName)){
echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] .
"<br>Temporary Name: $tmpName <br>";
} else {
save_document_info($_FILES['ufile']);
}
...
您只希望传递有关已上传文件的信息,因此,通过仅传递ufile文件而不是整个$_FILES
数组的信息,可以使save_document_info()
的事情变得更容易。
现在是时候创建函数了。
功能
让我们继续在DOM的帮助下操作XML文件。
创建DOM文档
首先,创建一个可用于处理数据的DOM Document
对象。 打开scripts.txt文件,并添加清单19中的代码。
清单19.创建DOM文档
...
define("UPLOADEDFILES", "/var/www/hidden/");
function save_document_info($fileInfo){
$doc = new DOMDocument('1.0');
}
?>
创建Document
对象(在本例中为$doc
的实际过程很简单。 DOMDocument
类是PHP V5核心的一部分。 您可以使用此对象对数据执行大多数操作。
创建一个元素
现在您有了一个有效的Document
,您可以使用它来创建Document
的main或root元素(请参见清单20 )。
清单20.创建一个元素
...
function save_document_info($fileInfo){
$doc = new DOMDocument('1.0');
$root = $doc->createElement('workflow');
$doc->appendChild($root);
}
...
在这里,您告诉$doc
对象创建一个新的元素节点,并将其返回到$root
变量。 然后,您告诉Document
将该元素添加为其自身的子元素。
但是,如果将此文档另存为文件会怎样?
将文档保存到文件
使用PHP处理XML的一个优点是PHP提供了一种简单的方法来将Document
的内容保存到文件中。 (信不信由你,这并不总是那么容易。)要查看实际生成的内容,请将清单21中的代码添加到save_document_info()
。
清单21.保存一个DOM文档
...
function save_document_info($fileInfo){
$doc = new DOMDocument('1.0');
$root = $doc->createElement('workflow');
$doc->appendChild($root);
$doc->save(UPLOADEDFILES."docinfo.xml");
}
...
您之前定义了UPLOADEDFILES
常量,因此您可以继续进行引用,现在将其放置在同一目录中。 如果现在通过浏览器上传文件,则docinfo.xml文件应类似于清单22 。
清单22.基本文件
<?xml version="1.0"?>
<workflow/>
不用担心第一行; 它是XML声明,是标准的,但是可选的(在大多数情况下)。
请注意,您的元素已保存,但是由于您尚未真正添加任何子元素或内容,因此将其写为空元素。
现在让我们添加一个更复杂的元素。
创建属性
开始将实际信息添加到文件中。 通过测试上一步,您已经创建了docinfo.xml。 在完全保存第一个文件的信息之前,可以假定您仍在创建docinfo.xml文件。
首先创建statistics
元素(请参见清单23 )。
清单23.在元素上设置属性
...
function save_document_info($fileInfo){
$doc = new DOMDocument('1.0');
$root = $doc->createElement('workflow');
$doc->appendChild($root);
$statistics = $doc->createElement("statistics");
$statistics->setAttribute("total", "1");
$statistics->setAttribute("approved", "0");
$root->appendChild($statistics);
$doc->save(UPLOADEDFILES."docinfo.xml");
}
...
注意,您仍然使用Document
创建新元素,这次称为statistics
。 ( Document
充当大多数对象的“工厂”。)
拥有Element
对象$statistics
,就可以使用其内置函数设置两个属性: total
和approved
。 完成此操作后,将此元素添加为$root
的子元素,而不是$doc
的子元素。 如果保存文件,则可以看到区别(请参见清单24 )。
清单24.结果文件
<?xml version="1.0">
<workflow><statistics total="1" approved="0"/></workflow>
您会在这里注意到两件事。 首先,请注意, statistics
元素是workflow
元素的子元素。 还要注意,这里没有多余的空白。 statistics
元素是workflow
的第一个子级。 (尽管您经常看到XML被写为“漂亮的印刷品”,但每个文本块都是一个子节点。)
接下来,让我们看一下添加真实信息。
创建文件信息元素
现在,您可以创建实际的文件信息元素。 该过程使用您刚刚学到的技术(请参见清单25 )。
清单25.创建实际的文件信息
...
function save_document_info($fileInfo){
$doc = new DOMDocument('1.0');
$root = $doc->createElement('workflow');
$doc->appendChild($root);
$statistics = $doc->createElement("statistics");
$statistics->setAttribute("total", "1");
$statistics->setAttribute("approved", "0");
$root->appendChild($statistics);
filename = $fileInfo['name'];
$filetype = $fileInfo['type'];
$filesize = $fileInfo['size'];
$fileInfo = $doc->createElement("fileInfo");
$fileInfo->setAttribute("status", "pending");
$fileInfo->setAttribute("submittedBy", $_SESSION["username"]);
$approvedBy = $doc->createElement("approvedBy");
$fileName = $doc->createElement("fileName");
$fileNameText = $doc->createTextNode($filename);
$fileName->appendChild($fileNameText);
$location = $doc->createElement("location");
$locationText = $doc->createTextNode(UPLOADEDFILES);
$location->appendChild($locationText);
$type = $doc->createElement("fileType");
$typeText = $doc->createTextNode($filetype);
$type->appendChild($typeText);
$size = $doc->createElement("size");
$sizeText = $doc->createTextNode($filesize);
$size->appendChild($sizeText);
$fileInfo->appendChild($approvedBy);
$fileInfo->appendChild($fileName);
$fileInfo->appendChild($location);
$fileInfo->appendChild($type);
$fileInfo->appendChild($size);
$root->appendChild($fileInfo);
$doc->save(UPLOADEDFILES."docinfo.xml");
}
...
即使这里有很多代码,但很少有新代码。 首先,您从传递给函数的信息中提取有关文件的实际信息。 然后,创建fileInfo
元素,其中将包含您要添加的所有信息。 您可以在此元素上设置status
和submittedBy
属性,然后查看创建其子级。
approvedBy
元素很容易。 它尚未被批准,因此将保持空白。 另一方面, fileName
元素则有点困难,因为您需要向其添加文本子代。 幸运的是,这也很简单。 创建元素,然后使用Document
创建一个新的文本节点,其内容为文件名。 然后,您可以将该文本节点添加为fileName
元素的子级。
您将以这种方式前进,创建最终将成为fileInfo
子元素的所有元素。 完成后,将它们全部附加为fileInfo
元素的子代。 最后,将fileInfo
元素本身添加到根元素workflow
。
结果(为清楚起见添加了空格)类似于清单26 。
清单26.结果文件
<?xml version="1.0"?>
<workflow>
<statistics total="1" approved="0"/>
<fileInfo status="pending" submittedBy="roadnick">
<approvedBy/>
<fileName>signed.pem</fileName>
<location>/var/www/hidden/</location>
<fileType>application/octet-stream</fileType>
<size>2754</size>
</fileInfo>
</workflow>
当然,每次有人上载文档时,您都无法覆盖信息文件,因此接下来,您将研究使用现有结构。
加载现有文档
现在,您知道了如何向文件中添加信息,并准备在以后的上传中着手处理文件。 首先检查文件是否已经存在,然后采取相应措施(请参见清单27 )。
清单27.检查文件是否已经存在
...
function save_document_info($fileInfo){
$doc = new DOMDocument('1.0');
$xmlfile = UPLOADEDFILES."docinfo.xml";
if(is_file($xmlfile)){
$doc->load($xmlfile);
$workflowElements = $doc->getElementsByTagName("workflow");
$root = $workflowElements->item(0);
} else{
$root = $doc->createElement('workflow');
$doc->appendChild($root);
$statistics = $doc->createElement("statistics");
$statistics->setAttribute("total", "1");
$statistics->setAttribute("approved", "0");
$root->appendChild($statistics);
}
$filename = $fileInfo['name'];
$filetype = $fileInfo['type'];
$filesize = $fileInfo['size'];
$fileInfo = $doc->createElement("fileInfo");
...
$fileInfo->appendChild($size);
$root->appendChild($fileInfo);
$doc->save($xmlfile);
}
在清单27中 ,您将创建一个变量来表示文件的位置,因为现在将在多个位置引用它。 接下来,验证该文件是否已经存在。 如果是这样,请调用load()
函数,而不要创建一个新对象。
这个静态函数,我将在本系列的第3部分中进一步讨论。 现在,了解它们是可以从类而不是从对象调用的函数,返回一个已经用Document
表示的所有元素,文本等填充的Document
对象。
拥有Document
对象之后,就需要workflow
元素,因为最终将需要向其中添加新的fileInfo
元素。 通过首先检索文档中所有元素的列表(称为workflow
,然后选择列表中的第一个元素,即可获得workflow
元素。
从那里,只需添加新的fileInfo
元素,它将出现在原始元素之后。 请参见清单28 ,为清楚起见添加了空格。
清单28.带有附加信息的文件
<?xml version="1.0"?>
<workflow>
<statistics total="1" approved="0"/>
<fileInfo status="pending" submittedBy="roadnick">
...
</fileInfo>
<fileInfo status="pending" submittedBy="roadnick">
<approvedBy/>
<fileName>timeone.jpg</fileName>
<location>/var/www/hidden/</location>
<fileType>image/jpeg</fileType>
<size>2020</size>
</fileInfo>
</workflow>
但是statistics
呢? 显然,它们不再正确。 那将必须解决。
处理现有数据
除了将信息添加到文档之外,您还可以更改已经存在的信息。 例如,您可以更新statistics
元素上的total
属性(请参见清单29 )。
清单29.更新统计信息属性
...
if(is_file($xmlfile)){
$doc = DOMDocument::load($xmlfile);
$workflowElements = $doc->getElementsByTagName("workflow");
$root = $workflowElements->item(0);
$statistics = $root->getElementsByTagName("statistics")->item(0);
$total = $statistics->getAttribute("total");
$statistics->setAttribute("total", $total + 1);
} else{
...
首先,您将获得对现有statistics
元素的引用,就像您获得对现有workflow
元素的引用一样。 在这种情况下,您将两个步骤合并为一个。 一旦有了对元素的引用,就可以使用getAttribute()
函数获得total
属性的当前值。 然后,您可以使用setAttribute()
使用该值为total
属性提供更新的值。
结果是您所期望的,这次没有添加空间,如清单30所示。
清单30.结果文件
<?xml version="1.0"?>
<workflow><statistics total="2" approved="0"/><fileInfo status="pending"
submittedBy="roadnick">...
尽管从技术上讲是正确的,但在添加新元素之后通过计数来设置项目数更为正确。 然后,您只需设置一次(参见清单31 )。
清单31.计数元素
...
if(is_file($xmlfile)){
$doc->load($xmlfile);
$workflowElements = $doc->getElementsByTagName("workflow");
$root = $workflowElements->item(0);
} else{
...
$root->appendChild($fileInfo);
$statistics = $root->getElementsByTagName("statistics")->item(0);
$total = $root->getElementsByTagName("fileInfo")->length;
$statistics->setAttribute("total",$total);
$doc->save($xmlfile);
}
在这种情况下,在添加了fileInfo
元素之后,将统计信息处理移到了底部。 然后,您可以获得所有fileInfo
元素的列表,并通过引用length
属性来知道有多少个元素。 您将在本系列的第3部分中了解有关属性的更多信息。 现在,您可以将属性视为分配给对象的变量。 您可以说它是一个属性而不是一个函数,因为它后面没有括号。
既然您知道如何使用XML创建文件,那么请看一下存储分层数据的另一种方法:JSON。
使用JSON
让我们看一下存储数据的另一种方法,这次使用JSON而不是XML。
什么是JSON?
JavaScript Object Notation或JSON是一种指定信息的方法,该信息使您可以以与存储XML相同的方式来存储数据,但是使用的方式更为冗长。 例如,数据存储的JSON表示类似于清单32 。
清单32.一个示例JSON文件
{
"statistics" : {
"total" : 2, "approved": 0
},
"fileInfo" : [
{
"status" : "pending",
"submittedBy" : "nickChase",
"approvedBy" : "",
"fileName" : "NoTooMiRoadmap.jpg",
"location" : "\/var\/www\/hidden\/",
"fileType" : "image\/jpeg",
"size" : 12813
},
{
"status" : "pending",
"submittedBy" : "stanley",
"approvedBy" : "",
"fileName" : "NoTooMiBeta.pem",
"location" : "\/var\/www\/hidden\/",
"fileType" : "application\/octet-stream",
"size" : 1692
}
]
}
看几个不同的符号。 数据不是单个workflow
根元素,而是单个对象的一部分,并且对象之间用花括号( {}
)分隔。 因此,整个对象显示在外部括号内。
一个对象具有一个或多个属性。 在这种情况下,属性为statistics
和fileInfo
。
所述的值statistics
特性中示出后的冒号( :
)]是一个对象,它可以告诉因为它包裹在花括号{}
该对象本身具有两个属性,即total
和approved
,这两个属性均具有整数值。 属性以逗号分隔。 还要注意,所有字符串(包括属性名称)都用引号引起来。
fileInfo
属性的值略有不同。 它不是一个对象,而是一个对象数组,您可以说出来,因为它被包裹在直括号( []
)中。 在这种情况下,它是一个对象数组,每个对象都用大括号括起来,这使您直接回到起点。
总结一下:
- 对象包装在花括号中。
- 对象是属性的集合,名称/值对之间用冒号分隔,并用逗号分隔。
- 数组是对象或属性的集合,用逗号分隔并括在方括号中。
JSON对象还可以具有作为函数的属性,但这超出了本教程的范围。 (请参阅相关主题以获取更多信息。)
现在,您可以查看实际使用这些数据了。
如何在PHP中使用JSON数据
在PHP中使用JSON数据的过程通常分为三个步骤:
- 创建一个PHP对象,该对象将数据保存在与要创建的对象匹配的结构中。 在某些情况下,您将从头开始创建该结构。 在其他情况下,您将从现有的JSON数据存储中获取它。
- 操作PHP对象。 这可能包含更改值,也可能包含添加或删除数据。
- 将PHP对象转换为JSON并保存。
您将使用工作流数据执行以下三个步骤。
数组与对象
上面看到的是对象的基于文本的序列化。 要在PHP中使用它,您需要将其转换为实际PHP对象,可以通过两种方式来实现。
- 首先是将其转换为实际的对象,该对象使用PHP的对象表示法,例如:
$fileInfo->size
- 第二种方法是使用数组符号,它将数据视为关联数组,您在前面已经看到过,如:
$fileInfo["size"]
使用哪一个取决于在PHP中如何创建变量。
由于两个原因,您将在此示例中使用数组符号。 首先,也是最明显的是,您对数组很熟悉,但是还没有涉及到对象。 第二个问题是使用对象表示法很难或不可能执行某些操作(例如添加到JSON数组)。
准备JSON对象
第一步是创建基本PHP数组来保存数据。 为简单fileInfo
,创建一个等效的fileInfo
元素。 在scripts.txt文件中,创建清单33中的函数。
清单33.准备基本的JSON对象
function save_document_info_json($file){
$filename = $file["name"];
$filetype = $file["type"];
$filesize = $file["size"];
$fileInfo["status"] = "pending";
$fileInfo["submittedBy"] = $_SESSION["username"];
$fileInfo["approvedBy"] = "";
$fileInfo["fileName"] = $filename;
$fileInfo["location"] = UPLOADEDFILES;
$fileInfo["fileType"] = $filetype;
$fileInfo["size"] = $filesize;
}
尽管已进行了一些重新安排,但这里没有什么新鲜的。 首先,您从$file
数组中提取值。 然后,将它们分配给新创建的$fileInfo
数组。
$fileInfo
数组代表一个对象; 如果将其另存为JSON(稍后将做),则它类似于清单34 。
清单34. $ fileInfo对象的外观
{
"status" : "pending",
"submittedBy" : "stanley",
"approvedBy" : "",
"fileName" : "NoTooMiBeta.pem",
"location" : "\/var\/www\/hidden\/",
"fileType" : "application\/octet-stream",
"size" : 1692
}
接下来,您将看到如何处理嵌套属性。
创建和保存JSON
到目前为止,您已经处理了具有简单值的简单对象。 现在,您会发现深入研究时会发生什么。
在前面的示例中,您构建了一个fileInfo
对象。 但是,最终,您将构建整体workflow
对象,其中包括statistics
属性。 但是statistics
属性是一个本身具有属性的对象(请参见清单35 )。
清单35.属性的属性
function save_document_info_json($file){
$workflow["statistics"]["total"] = 1;
$workflow["statistics"]["approved"] = 0;
$filename = $file['name'];
...
如您所见,先创建$workflow
变量,然后创建statistics
属性。 有了这些属性之后,就可以指定total
和approved
属性。
您可以沿这条路径前进到任何级别(在合理的范围内)。
将项目添加到数组
现在有了$workflow
变量,您需要将$fileinfo
对象添加到其fileInfo
属性中。 请记住, fileInfo
是一个数组,因此您可以使用array_push()
函数来执行此操作(请参见清单36 )。
清单36.向数组添加一个项
function save_document_info_json($file){
$workflow["fileInfo"] = array();
$workflow["statistics"]["total"] = 1;
......
$fileInfo["fileType"] = $filetype;
$fileInfo["size"] = $filesize;
array_push($workflow["fileInfo"], $fileInfo);
}
array_push()
函数将一个或多个项目添加到目标数组。 在这种情况下,该数组是$workflow["fileInfo"]
,您在脚本顶部将其创建为空数组。 这是一个典型的(非关联)数组,因此,第一次添加项目时,可以返回并引用为:
$workflow["fileInfo"][0]
现在,您准备好查看保存数据了。
序列化并保存JSON
至此,您已经创建了一个变量,该变量代表您想要保存为JSON的数据结构,因此该看看序列化和保存该数据了。
通过提供json_encode()
PHP实际上使将$workflow
类的变量转换为JSON文本变得异常简单(参见清单37 )。
清单37.向数组添加一个项
function save_document_info_json($file){
$jsonFile = UPLOADEDFILES."docinfo.json";
$workflow["fileInfo"] = array();
$workflow["statistics"]["total"] = 1;
...
$fileInfo["fileType"] = $filetype;
$fileInfo["size"] = $filesize;
array_push($workflow["fileInfo"], $fileInfo);
$jsonText = json_encode($workflow);
file_put_contents($jsonFile, $jsonText);
}
json_encode()
函数将$workflow
对象转换为JSON文本,然后file_put_contents()
将该文本保存到docinfo.json
文件。
您只需告诉upload_action.php页面调用此新函数,而不是基于XML的函数(请参见清单38 )。
清单38.调用新函数
...
if(!is_uploaded_file($tmpName) ||
!move_uploaded_file($tmpName, $newName)){
echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] .
"<br>Temporary Name: $tmpName <br>";
} else {
save_document_info_json($_FILES['ufile']);
}
...
现在,您可以上传一个新文档并检查docinfo.json
文件的内容。 它看起来应该类似于清单39 (为清楚起见添加了空格)。
清单39.生成的JSON文件
{
"statistics":{"total":1,"approved":0},
"fileInfo":[
{"status":"pending",
"submittedBy":"stanley",
"approvedBy":"",
"fileName":"NoTooMiBeta.pem",
"location":"c:\/sw\/temp\/",
"fileType":"application\/octet-stream",
"size":1692}
]
}
因此,您无需做任何工作即可创建JSON表示法; json_encode()
函数为您做到了。 取回变量同样容易,接下来您将看到如何执行该操作。
从文件读取JSON
现在您可以编写JSON了,如何阅读呢? 就像处理XML一样,您需要将新的fileInfo
对象添加到现有的docinfo.json
文件中。 为此,您可以使用已经看过的函数的亲戚(请参见清单40 )。
清单40.生成的JSON文件
function save_document_info_json($file){
$jsonFile = UPLOADEDFILES."docinfo.json";
if (is_file($jsonFile)){
$jsonText = file_get_contents($jsonfile);
$workflow = json_decode($jsonText, true);
} else{
$jsonText = '{"statistics": {"total": 0, "approved": 0}, "fileInfo":[]}';
$workflow = json_decode($jsonText, true);
}
$filename = $file['name'];
...
array_push($workflow["fileInfo"], $fileInfo);
$total = count($workflow["fileInfo"]);
$workflow["statistics"]["total"] = $total;
$jsonText = json_encode($workflow);
file_put_contents($jsonFile, $jsonText);
}
和以前一样,您首先要检查文件是否存在。 如果是这样,您首先要使用file_get_contents()
函数提取现有的JSON文本,然后使用json_decode()
函数将其转换为PHP变量,而不是从头开始创建$workflow
变量。
请注意json_decode()
函数中的第二个参数,该参数告诉PHP是否返回数组。 默认值(换句话说,如果您不提供值,函数将使用的默认值)为false
,这意味着PHP将不返回数组,而是返回一个对象。 在这种情况下,您需要一个数组,因此提供的值为true
。
仅出于示例的目的,如果文件不存在,并且您是从头开始创建内容,此示例说明您可以仅提供纯JSON文本-毕竟,这就是从docinfo.json
还是要归档!
最后,在保存文件之前,可以通过对该数组执行count()
来找出fileInfo
对象的count()
。 然后,您可以将该值设置为total
。
在继续实际使用所有这些数据之前,您需要了解一件非常重要的事情。
有关安全性的重要说明
json_decode()
函数不执行任何代码; 它只是加载数据。 这并不意味着它就不会受到安全问题的影响,因为像其他容易受到缓冲区溢出和其他技术影响的字符串处理函数一样,它可以用于邪恶。 那是非常罕见的情况。
更大的问题是当您将JSON数据提供回JavaScript实现时。 您可能会想使用JavaScript的eval()
函数将其转换为JavaScript对象。 除非您确切地知道该数据中的内容并且可以信任它, 否则请不要 。 请改用为此目的设计的JSON库。
整体展示
现在,您可以将到目前为止学到的所有内容放在一起,以构建一个脚本,该脚本读取JSON并在upload_action.php页面上显示文件列表。 首先在scripts.txt中创建函数(请参见清单41 )。
清单41.读取数据
function display_files(){
echo "<table width='100%'>";
echo "<tr><th>File Name</th>";
echo "<th>Submitted By</th><th>Size</th>";
echo "<th>Status</th></tr>";
$workflow = json_decode(file_get_contents(UPLOADEDFILES."docinfo.json"), true);
$files = $workflow["fileInfo"];
for ($i = 0; $i < count($workflow["fileInfo"]); $i++){
echo "<tr>";
echo "<td>".$thisFile["fileName"]."</td>";
echo "<td>".$thisFile["submittedBy"]."</td>";
echo "<td>".$thisFile["size"]."</td>";
echo "<td>".$thisFile["status"]."<td>";
echo "</tr>";
}
}
首先使用json_decode()
获得$workflow
对象,以分析docinfo.json
文件的内容。
一旦有了,就可以通过提取fileInfo
属性来获得表示所有文件的数组,然后遍历每个数组,显示其信息。
现在,您需要将该函数添加到upload_action.php文件中(参见清单42 )。
清单42.调用显示函数
...
if(!is_uploaded_file($tmpName) ||
!move_uploaded_file($tmpName, $newName)){
echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] .
"<br>Temporary Name: $tmpName <br>";
} else {
save_document_info_json($_FILES['ufile']);
echo "<h3>Available Files</h3>";
display_files();
}
} else {
echo "You need to select a file. Please try again.";
}
include("bottom.txt");
?>
最终结果是一个HTML表,其外观应类似于图6 。
图6.可读信息表
在第3部分中 ,您将研究如何使用链接以及其他信息和功能来增强此表。
摘要
在本教程中,您开始创建工作流应用程序的核心:用户添加文件的能力。 您使用户能够登录系统并创建一个识别他们的会话并上传文件。 您将文件保存在服务器上,并且首先使用XML,然后使用JSON保存有关该文件的信息。 在此过程中,涵盖了以下主题:
- 建立会议
- 使用现有会话
- 上载档案
- 使用DOM创建XML文件
- 使用DOM加载XML数据
- 使用DOM处理XML数据
- 创建JSON数据
- 使用JSON处理数据
- 保存和加载文本文件
在第3部分中 ,您将完成应用程序。
翻译自: https://www.ibm.com/developerworks/opensource/tutorials/os-phptut2/os-phptut2.html
解决同时上传文件和json