Học PHP, Phần 2: Tải lên các tập tin và sử dụng XML để lưu trữ và hiển thị thông tin tập tin

Hướng dẫn này là Phần 2 của một loạt bài gồm ba phần "Học PHP", dạy cho bạn cách sử dụng PHP qua việc xây dựng một ứng dụng tiến trình công việc đơn giản. Hãy sử dụng hướng dẫn này nếu bạn có một sự hiểu biết cơ bản về PHP và muốn học về cách tải lên các tập tin từ trình duyệt, các phiên làm việc hoặc việc sử dụng PHP để xử lý XML.

Tyler Anderson, Kỹ sư, Backstop Media

Tyler's photoTyler Anderson đã tốt nghiệp chuyên ngành Khoa học Máy tính tại trường Đại học Brigham Young năm 2004 và hiện đang tham gia học kỳ cuối chương trình đào tạo Thạc sỹ khoa học chuyên ngành Công nghệ Máy tính. Trước đó, anh đã làm việc như là một lập trình viên cơ sở dữ liệu cho DPMG.COM, và hiện tại anh đang là kỹ sư cho tập đoàn Stexar, dựa trên nền tảng Beaverton, Oregon



Nicholas Chase, Tác giả tự do, Site Dynamics Interactive Communications

Nicholas Chase đã phát triển trang web cho các công ty lớn như Lucent Technologies, Sun Microsystems, Oracle, và Tampa Bay Buccaneers. Nick đã từng là một giáo viên vật lý ở trường phổ thông, một nhà quản lý thiết bị phóng xạ mức thấp, một nhà biên tập tạp chí khoa học viễn tưởng trực tuyến, một kỹ sư đa phương tiện, một hướng dẫn của Oracle, và một trưởng phòng công nghệ của một công ty tương tác truyền thông. Nick là tác giả của một số sách



30 09 2010

Trước khi bạn bắt đầu

Về hướng dẫn này

Hướng dẫn này dạy bạn cách sử dụng PHP bằng cách giải thích việc xây dựng một ứng dụng tiến trình công việc trên nền Web. "Học PHP, Phần 1" trình bày những kiến thức cơ bản, như cú pháp, các hàm, làm việc với các việc đệ trình các biểu mẫu HTML và các cơ sở dữ liệu và tạo một tiến trình để một người dùng mới có thể đăng ký một tài khoản theo nó.

Trong hướng dẫn này, bạn sẽ cho phép những người dùng tải lên các tập tin vào hệ thống bằng cách sử dụng các trình duyệt của họ và bạn sẽ sử dụng XML để lưu và hiển thị thông tin về từng tập tin.

Phần 3 sẽ xem xét việc sử dụng sự xác thực HTTP, cũng như việc bảo vệ các tập tin nhờ tạo luồng cho chúng từ thư mục truy cập được không phải Web. Bạn cũng sẽ xem xét việc tạo các đối tượng và sử dụng các ngoại lệ.

Theo tiến trình của hướng dẫn này, bạn sẽ xem xét:

  • Việc tạo và sử dụng các phiên làm việc và thông tin về phiên làm việc.
  • Tải lên các tập tin từ trình duyệt.
  • Tạo XML bằng cách sử dụng Mô hình đối tượng tài liệu (Document Object Model - DOM).
  • Thao tác dữ liệu XML bằng cách sử dụng DOM.
  • Tạo một API đơn giản cho trình xử lý nội dung XML (SAX).
  • Đọc dữ liệu XML nhờ SAX.

Ai nên sử dụng hướng dẫn này?

Hướng dẫn này là Phần 2 trong một loạt ba phần được thiết kế để dạy bạn những khía cạnh khác nhau về làm việc với PHP trong khi xây dựng một ứng dụng tiến trình công việc. Hãy sử dụng hướng dẫn này nếu bạn có một sự hiểu biết cơ bản về PHP và muốn học về cách tải lên các tập tin từ trình duyệt, các phiên làm việc hoặc việc sử dụng PHP để xử lý XML.

Hướng dẫn này giả định bạn đã có một sự hiểu biết cơ bản về PHP theo trình độ đã thảo luận trong Phần 1 của loạt bài này. Đó là sự hiểu biết cơ bản về các cấu trúc điều khiển, chẳng hạn như các vòng lặp và các lệnh if-then, cũng như các hàm và làm việc với các việc đệ trình các biểu mẫu HTML và các cơ sở dữ liệu. Hiểu biết rõ về XML rất có ích, nhưng không bắt buộc. (Bạn có thể tìm thêm thông tin về các chủ đề này trong phần Tài nguyên.)

Các điều kiện cần trước

Để làm theo hướng dẫn này, bạn cần có một máy chủ Web, PHP và cơ sở dữ liệu đã cài đặt và sẵn sàng sử dụng. Trừ khi bạn đang sử dụng một tài khoản máy chủ ngoài, hãy tải về và cài đặt các gói sau:

Máy chủ Web -- Cho dù bạn đang ở trong Windows® hay Linux® (hay Mac OS X), bạn có tùy chọn để sử dụng các máy chủ Web Apache. Hãy tùy ý chọn một trong hai phiên bản v1.3 hoặc 2.0, nhưng các hướng dẫn trong phần này tập trung vào V2.0. Hãy tải về Apache. Nếu bạn đang ở trong Windows, bạn cũng có thể sử dụng Các dịch vụ thông tin Internet (Internet Information Services), một phần của Windows.

PHP -- Tất nhiên, bạn sẽ cần một bản phân phối của PHP. Cả hai phiên bản V4 và V5 của PHP đang sử dụng tại thời điểm viết bài này, nhưng do những thay đổi trong phiên bản V5 của PHP, chúng tôi sẽ tập trung vào phiên bản đó. (Phiên bản này không phải rất quan trọng trong hướng dẫn này, nhưng nó tạo ra một sự khác biệt cho phần sau của loạt bài này). Hãy tải về PHP.

Cơ sở dữ liệu -- Một phần của dự án này liên quan đến việc lưu dữ liệu vào một cơ sở dữ liệu, vậy bạn sẽ cần một cơ sở dữ liệu trong những cơ sở dữ liệu đó. Trong hướng dẫn này, chúng tôi sẽ tập trung vào cơ sở dữ liệu MySQL vì nó vẫn thường được sử dụng với PHP. Hãy tải về MySQL.


Bắt đầu: tạo phiên làm việc

Quá trình đăng nhập, Phần 1

Phần 1 của loạt bài này đã xem xét hoạt động với các thông tin được đệ trình từ một biểu mẫu HTML và việc tương tác với một cơ sở dữ liệu qua việc tạo một hệ thống đăng ký cho những người dùng mới. Bạn có thể tìm các tập tin cuối cùng trong phần Tài nguyên.

Tuy nhiên, một điều bạn vẫn chưa làm là tạo một trang để người sử dụng có thể đăng nhập vào hệ thống. Bạn sẽ chú ý đến trang đó bây giờ, với một đánh giá lại ngắn gọn về những gì đã được trình bày rồi.

Đầu tiên, tạo một tập tin mới để trống và lưu nó với tên là login.php trong cùng thư mục với tập tin registration.php. Thêm vào mã sau đây:

<?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"); 
?>

Để đánh giá lại, khi người dùng đệ trình biểu mẫu, PHP sẽ tạo một mảng thông tin, $_POST, có hai mục nhập: username (tên người dùng) và password (mật khẩu). Bạn sẽ kiểm tra xem thông tin đó với cơ sở dữ liệu. Khi bạn nạp trang này trong trình duyệt, máy chủ tính đến cả các thành phần giao diện.

Hình 1. Trang login.php với các phần tử giao diện
Trang login.php với các phần tử giao diện

Quá trình đăng nhập, Phần 2

Tạo một trang mới khác và lưu nó như là tập tin có tên login_action.php. Thêm vào mã sau đây:

<?php 
  include("top.txt"); 
  require("scripts.txt"); 

  db_connect();

  $username = $_POST["username"]; 
  $password = $_POST["password"]; 
  $sql ="select * from users where username='".$username. 
                         "' and password='".$password."'"; 

  $result = mysql_query($sql); 
  $row = mysql_fetch_row($result); 
  if ($row) { 
     echo "You are logged in. Thank you!"; 
  } else { 
     echo "There is no user account with that username and password."; 
  } 
  mysql_close(); 
  include("bottom.txt"); 
?>

Sau khi bao gồm các thành phần giao diện cho phần đầu trang, hãy tạo kết nối đến cơ sở dữ liệu bằng hàm db_connect() được định nghĩa trong tập tin scripts.txt. Một khi bạn có một kết nối, hãy sử dụng thông tin đã đệ trình để tạo một câu lệnh SQL để tìm kiếm một tài khoản trùng khớp và thực hiện câu lệnh SQL trên cơ sở dữ liệu đó.

Bắt đầu một phiên làm việc

Bạn yêu cầu người sử dụng đăng nhập vào để cho trang web biết người dùng là ai. Bằng cách này, trang web biết các tập tin nào mà người dùng được phép xem. Tuy nhiên, bạn cần phải làm nhiều hơn là chỉ cần đăng nhập người sử dụng. Bây giờ mọi thứ đều đúng, khi bạn rời khỏi trang này, thông tin người dùng cũng sẽ biến mất.

Điều bạn muốn làm là tạo một phiên làm việc để máy chủ biết yêu cầu nào cần nhóm lại với nhau. Bạn cũng có thể kết hợp thông tin -- chẳng hạn tên người dùng -- với phiên làm việc.

Để bắt đầu, bạn sẽ phải tạo phiên làm việc. Hàm session_start() kiểm tra sự tồn tại của phiên làm việc, nếu không hàm này khởi động một phiên.

Việc sử dụng hàm session_start() hay, trên thực tế, bất cứ điều gì được thực hiện để thiết lập thông tin phiên làm việc có một hạn chế lớn. Nó phải xảy ra trước nội dung bất kỳ, bao gồm các thành phần giao diện, đi tới trình duyệt.

Vì vậy, trước hết, chúng ta hãy thêm phiên làm việc và cơ cấu lại tập tin login_action.php:

<?php 
  session_start(); 

  require("scripts.txt"); 

  db_connect();

  $username = $_POST["username"]; 
  $password = $_POST["password"]; 
  $sql= "select * from users where username='".$username. 
                        "' and password='".$password."'"; 

  $loginOk = false; 

  $result = mysql_query($sql); 
  $row = mysql_fetch_row($result); 
  if ($row) {
     $loginOk = true; 
  } 

  mysql_close(); 

  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"); 

?>

Những gì bạn đã làm ở đây là cấu trúc lại trang đó để bạn có thể thực hiện tất cả công việc của phiên làm việc của mình trước khi xuất ra bất cứ thứ gì tới trình duyệt.

Gắn vào phiên làm việc

Tất nhiên, không cần tạo một phiên làm việc trừ khi bạn có thông tin để chuyển từ yêu cầu này đến yêu cầu khác. Trong trường hợp này, bạn muốn chuyển tên người dùng và địa chỉ e-mail, do đó bạn thêm chúng vào phiên làm việc:

<?php 
  session_start(); 
  require("scripts.txt"); 

  db_connect();
  
  $username = $_POST["username"]; 
  $password = $_POST["password"]; 
  $sql= "select * from users where username='".$username. 
                           "' and password='".$password."'"; 

  $result = mysql_query($sql); 
  $row = mysql_fetch_row($result); 
  $loginOk = false; 
  if ($row) {
    $loginOk = true; 
    $_SESSION["username"] = $row["username"]; 
    $_SESSION["email"] = $row["email"]; 
  } 

  mysql_close(); 

  include("top.txt"); 

  if ($loginOk) { 
     echo "You are logged in. Thank you, ".$username."!"; 
  } else { 
     echo "There is no user account with that username and password."; 
  } 
  include("bottom.txt"); 

?>

Cũng giống như $_POST, $_SESSION là một mảng kết hợp. Nó cũng là một biến siêu toàn cầu, giữ giá trị của nó xuyên qua nhiều yêu cầu. Theo cách này, bạn có thể tham chiếu nó từ một trang khác.

Bây giờ, nếu bạn đang sử dụng một phiên bản PHP mới hơn V4.3.2, bạn có thể thấy một thông báo cho bạn biết rằng kịch bản lệnh của bạn có thể dựa vào một lỗi, cho phép bạn thiết lập biến $_SESSION trong phạm vi toàn cầu. Nếu vậy, bạn cần phải tìm tập tin php.ini của bạn và chắc chắn các biến sau được thiết lập:

 session.bug_compat_42 = 1 session.bug_compat_warn = 0

Sau khi thay đổi các giá trị này, bạn cần phải dừng lại và khởi động lại máy chủ Web của bạn để các thay đổi có hiệu lực.

Sử dụng một phiên làm việc hiện có

Hàm session_start() có đủ thông minh để biết liệu một phiên làm việc có tồn tại không, để bạn có thể tiếp tục và thêm nó vào phần bao gồm cho phần đầu của tập tin top.txt:

<? session_start(); ?> 
<html> 
   <head> 
      <title>Workflow System</title> 
   </head> 
   <body> 
      <table cellspacing="10">
         <tr>
            <td colspan="2">
                <h2>The Workflow System</h2>
            </td>
         </tr> 
...

Khi PHP gặp hàm session_start(), nó nối với phiên làm việc hiện có đang diễn ra hay bắt đầu một phiên mới nếu cần thiết. Theo cách này, các trang khác có quyền truy cập vào mảng $_SESSION. Ví dụ bạn có thể tìm giá trị $_SESSION["username"] trong phần chuyển hướng (navigation):

<tr> 
   <td width="30%" valign="top">
      <h3>Navigation</h3> 

<?php 
   if (isset($_SESSION["username"]) || isset($username)){  
?> 
  <p>You are logged in as <b><?=$_SESSION["username"].$username?></b>. 
   You can <a href="logout.php">logout</a> to login as a different user. </p> 
<?php 
  } else { 
?> 
   <p><a href="registration.php">Register</a> </p> 
   <p><a href="login.php">Login</a></p>
<?php 
  } 
?> 
  </td> 
  <td>
      <td>

Bây giờ, bạn có thể tự hỏi tại sao bạn cũng phải kiểm tra biến $username. Lý do là thế này: Khi bạn thiết lập biến $_SESSION, những thay đổi không trở nên sẵn sàng cho đến khi hàm session_start() được gọi trong thời gian tới. Vì vậy, trên trang login_action.php thực tế, biến đó vẫn chưa sẵn sàng, nhưng biến $username sẽ sẵn sàng, như trong Hình 2.

Hình 2. Trang login_action.php với biến $username
Trang login_action.php với biến $username

Xóa dữ liệu

Khi đã hoàn thành phiên làm việc của mình, bạn có thể xóa dữ liệu của nó hoặc kết thúc hẳn phiên làm việc đó. Ví dụ, bạn có thể muốn đăng xuất từ một người dùng nhưng không kết thúc phiên. Hãy tạo một tập tin mới gọi là logout.php và thêm mã sau đây:

<? 
  session_start(); 
  unset($_SESSION["username"]);
  unset($_SESSION["email"]); 

  include("top.txt"); 
  echo "Thank you for using the Workflow System. 
     You may <a href=\"login.php\ ">log in again</a>.";
  include("bottom.txt"); 
?>

Thực hiện trang này bằng cách trỏ trình duyệt đến http://localhost/logout.php cho thấy rằng các giá trị đã bị xóa, như trong Hình 3.

Hình 3. Trang có các giá trị đã xóa
Các giá trị đã xóa

Một cách đơn giản để xóa dữ liệu là kết thúc phiên làm việc đó:

<? 
  session_start(); 
  session_destroy(); 

  include("top.txt"); 
  echo "Thank you for using the Workflow System. 
       You may <a href=\"login.php\">log in again</a>."; 
  include("bottom.txt"); 
?>

Lưu ý rằng bạn vẫn cần sử dụng hàm session_start() để kết nối phiên làm việc hiện tại trước khi bạn có thể loại bỏ nó. Bước này sẽ xóa tất cả các giá trị liên quan đến phiên làm việc đó.


Tải lên các tập tin

Cách việc tải lên hoạt động

Ngoài các thông tin văn bản, bạn có thể sử dụng các biểu mẫu HTML để gửi các tài liệu và đó là cách bạn cho phép người dùng thêm các tập tin vào hệ thống. Đây là cách tiến trình này hoạt động:

  1. Những người dùng tải một biểu mẫu để cho phép họ chọn một tập tin để tải lên.
  2. Những người dùng đệ trình biểu mẫu đó.
  3. Trình duyệt gửi tập tin đó và thông tin về nó đến máy chủ như là một phần của yêu cầu.
  4. Máy chủ lưu tập tin đó ở một vị trí lưu trữ tạm thời.
  5. Trang PHP xử lý việc đệ trình biểu mẫu đó chuyển tập tin từ chỗ lưu trữ tạm thời đến chỗ lưu trữ cố định.

Hãy bắt đầu tiến trình bằng cách tạo biểu mẫu thực tế.

Biểu mẫu tải lên

Biểu mẫu này đang được sử dụng để tải lên các tập tin là tương tự như các biểu mẫu được sử dụng cho các trang đăng ký và đăng nhập, với hai ngoại lệ quan trọng:

<?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");  
?>

Thuộc tính enctype báo cho trình duyệt biết rằng thông tin mà nó gửi phải theo một định dạng cụ thể cho phép nhiều phần thông tin hơn là một danh sách về các cặp tên-giá trị.

Đầu vào tập tin cung cấp một hộp thoại, cho phép người dùng nhấn vào Browse... và chọn tập tin, như trong Hình 4.

Hình 4. Chọn một tập tin để tải lên
Chọn một tập tin để tải lên

Lưu ý rằng liên kết đến tập tin này đã được thêm vào, uploadfile.php đến trang top.txt, do đó, nó xuất hiện trong trình duyệt.

Thông tin được tải lên

Khi bạn tải lên tập tin thông qua trình duyệt, PHP nhận được một mảng thông tin về nó. Bạn có thể tìm thấy thông tin này trong mảng $_FILE, dựa vào tên của trường nhập vào. Ví dụ biểu mẫu của bạn nhập vào tập tin tên là ufile, do đó tất cả các thông tin về tập tin đó được chứa trong mảng $_FILE['ufile'].

Mảng này cho phép người dùng tải lên nhiều tập tin. Miễn là mỗi tập tin có tên riêng của mình, nó sẽ có mảng riêng.

Bây giờ, lưu ý "$_FILE" sẽ được gọi là một mảng. Trong Phần 1 của loạt bài này, bạn gặp tình huống trong đó một giá trị mảng tự nó đã là một mảng khi bạn đã chuyển nhiều giá trị biểu mẫu có cùng tên với mật khẩu. Trong trường hợp này, mỗi giá trị của mảng $_FILE tự nó là một mảng kết hợp. Ví dụ, tập tin ufile của bạn có các thông tin sau:

  • $_FILE['ufile']['name'] -- Tên của tập tin (ví dụ, uploadme.txt).
  • $_FILE['ufile']['type']: -- Kiểu tập tin (ví dụ, image/jpg)
  • $_FILE['ufile']['size'] -- Kích thước tính theo byte, của tập tin đã được tải lên.
  • $_FILE['ufile']['tmp_name'] -- Tên và vị trí tạm thời của tập tin được tải lên trên máy chủ.
  • $_FILE['ufile']['error'] -- Mã lỗi, nếu có, do việc tải lên này gây ra.

Khi biết thông tin nào cần có, bạn nên kiểm tra để xem một tập tin thực sự đã được tải lên chưa trước khi bạn thực hiện bất kỳ việc xử lý nào.

Kiểm tra một tập tin

Trước khi bạn có bất kỳ hành động nào liên quan đến tập tin, bạn cần biết liệu một tập tin đã được tải lên ở vị trí đầu tiên chưa. Hãy tạo trang hành động cho biểu mẫu này, uploadfile_action.php và thêm mã sau đây:

<?php 
  include("top.txt"); 

  if (isset($_FILES['ufile']['name'])){
     echo "Uploading: ".$_FILES['ufile']['name']."<br />"; 
  } else { 
     echo "You need to select a file. Please try again."; 
  } 
  include("bottom.txt"); 
?>

Tiếp theo, bạn sẽ lưu tập tin đó.

Lưu tập tin

Trước khi bạn bắt đầu quá trình lưu tập tin được tải lên, bạn cần phải quyết định nơi đặt nó. Cho đến khi nội dung của tập tin được phê duyệt, bạn không muốn nó được truy cập từ trang Web, do đó hãy tạo một thư mục không nằm trong thư mục gốc của tài liệu chính.

Trong trường hợp này, bạn sẽ sử dụng thư mục /var/www/hidden/. Đây là nơi tất cả các tập tin sẽ được đưa tới, do đó đây có thể là một ý tốt cho việc định nghĩa một hằng số. Mở scripts.txt và thêm vào định nghĩa sau:

... 
  mysql_select_db($db); 
} 
   define(UPLOADEDFILES, "/var/www/hidden/"); 
?>

Bây giờ bạn có thể sử dụng định nghĩa này trong trang tải lên, miễn là bạn bao gồm scripts.txt trong trang đó:

<?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"); 
?>

Trước tiên, bạn đang nhận được vị trí hiện tại của tập tin và tên tạm thời của nó (tmp_name) và xác định nơi bạn muốn nó đi tới bằng cách sử dụng hằng số đã xác định.

Tiếp theo, đúng trong câu lệnh if-then, bạn sẽ làm hai việc. Bạn sẽ kiểm tra để đảm bảo rằng tập tin mà bạn đang cố gắng di chuyển thực sự là một tập tin được tải lên máy chủ, chứ không phải là một tập tin như là /etc/passwd, tập tin của người dùng đã lừa để chúng ta hành động theo. Nếu hàm is_uploaded_file() trả về sai, thì giá trị ngược lại của nó !is_uploaded_file(), là đúng và PHP tiếp tục hiển thị thông báo lỗi.

Mặt khác, nếu việc kiểm tra đó trả về đúng, nghĩa là nó là một tập tin được tải lên, thì bạn có thể có ý chuyển nó từ vị trí hiện tại của nó đến vị trí mới. Nếu việc chuyển đó không thực hiện được, nó sẽ trả về sai và giá trị ngược của nó là đúng, do đó bạn sẽ hiển thị thông báo lỗi.

Nói cách khác, nếu nó không phải là một tập tin được tải lên hay bạn không thể chuyển nó, bạn hiển thị một thông báo lỗi. Nếu không, bạn sẽ hiển thị thông báo thành công (xem Hình 5).

Hình 5. Thông báo thành công
Thông báo thành công

Bây giờ bạn có tập tin này, bạn cần ghi lại thông tin của mình để phục hồi sau.


Sử dụng XML: DOM

XML là gì?

Hiện nay, thật khó thực hiện nhiều việc lập trình mà không cần chạy trong XML với một số biểu mẫu. May mắn thay, đó không phải là một chủ đề khó hiểu. Trong thực tế, các cơ hội là, bạn đã đề cập đến một trình bà con của XML rồi: HTML. Hãy xem trang phiên bản V4.01 của HTML này:

<!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>

Trang này đã có rất nhiều điểm chung với XML. Trước hết, nó do các phần tử tạo thành, như là HEAD hay BODY, được phân biệt bằng các thẻ, chẳng hạn như <BODY> để bắt đầu và </BODY> để kết thúc. Các phần tử này có thể chứa các phần tử khác (như TITLE) hoặc văn bản (như là Workflow System). Chúng cũng có thể có các thuộc tính, như là align="center".

Nhưng XML có một vài hạn chế không áp dụng cho HTML. Ví dụ, XML cần được định dạng đúng, có nghĩa là với mỗi thẻ bắt đầu (chẳng hạn như <H1>), bạn phải có một thẻ kết thúc (như là </H1>. Điều đó có nghĩa là thẻ <P> của bạn phải có một thẻ kết thúc: </P>. (Hay là, bạn có thể viết nó như là phần tử rỗng: <P />.) Ngoài ra, XML có phân biệt chữ hoa, chữ thường, do đó bạn phải đóng <P> bằng </P>, không phải là </p>.

Trong phần này, bạn sẽ tạo một tập tin XML để ghi thông tin về các tài liệu được người sử dụng tải lên.

Cách bạn sẽ lưu thông tin

Trong tiến trình của hai phần kế tiếp, bạn sẽ xử lý tập tin XML liệt kê thông tin về các tài liệu mà người sử dụng đã tải lên. Tập tin này liệt kê từng tài liệu, trạng thái của nó, ai đã tải nó lên và thông tin về chính các tài liệu đó. Tập tin đó cũng sẽ liệt kê thông tin thống kê về toàn hệ thống.

Tập tin đó sẽ giống như sau:

<?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>

Chúng ta đã thêm vào khoảng trống ở đây để làm cho mọi thứ rõ ràng hơn, nhưng đây là toàn bộ cấu trúc của tập tin. Mỗi tài liệu sẽ có phần tử fileInfo riêng, bao gồm các thuộc tính về trạng thái và chủ sở hữu của nó và chứa thông tin về chính tập tin đó. Tài liệu thống kê cũng liệt kê thông tin về chính tập tin đó.

Để thao tác thông tin này, bạn sẽ sử dụng DOM (Mô hình đối tượng tài liệu).

DOM là gì?

DOM là một cách thể hiện dữ liệu XML như một cây phân cấp các thông tin. Ví dụ, lấy phần tử fileInfo này:

<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>

Mỗi đoạn thông tin trong một tài liệu XML được thể hiện như là một kiểu nút. Ví dụ ở đây bạn sẽ thấy một nút phần tử fileInfo; một nút thuộc tính status; và nhiều nút văn bản, bao gồm tater, timeone.jpg2020. Thậm chí mỗi đoạn để trống giữa các phần tử cũng được coi là một nút văn bản.

DOM sắp xếp các nút theo mối quan hệ cha-con. Ví dụ, phần tử approvedBy là phần tử con của phần tử fileInfo và văn bản tater là phần tử con của phần tử approvedBy. Mối quan hệ này có nghĩa là phần tử fileInfo11 nút con; năm phần tử, và sáu nút văn bản trống.

Các API khác nhau tồn tại để cho phép bạn làm việc với các đối tượng DOM. Những phiên bản sớm của PHP thực hiện một cấu trúc giống như DOM, nhưng cấu trúc này thực sự đã không khớp tốt với các phương pháp và định nghĩa trong các các khuyến cáo DOM thực sự (do W3C quản lí). Vậy, PHP V5 có một bộ mới về các hoạt động DOM gần gũi hơn với chuẩn này.

Bạn sẽ sử dụng API mới để tạo và sửa đổi tập tin thông tin tài liệu.

Một nhận xét ngắn gọn về các đối tượng

Phần 3 của loạt bài này sẽ nói về các đối tượng và lập trình hướng đối tượng trong PHP cụ thể hơn, nhưng cả hai DOM API và SAX API được thảo luận trong các phần tiếp theo có sử dụng chúng, vậy trước khi đi tiếp, ít nhất bạn sẽ cần hiểu cơ bản về các đối tượng và cách chúng hoạt động.

Hãy suy nghĩ về một đối tượng như là một bộ các hàm, ví dụ bạn có thể tạo một lớp hoặc khuôn mẫu đối tượng, giống như:

class Greeting{ 
   function getGreeting(){ 
      return "Hello!"; 
   };
   function getPersonalGreeting($name){ 
      return "Hello, ".$name."!"; 
   }; 
}

Khi bạn tạo một đối tượng, thì bạn có thể tham khảo các hàm của nó. Ví dụ:

$myObject = new Greeting(); 
$start = $myObject->getPersonalGreeting("Nick");

Trong trường hợp này, bạn tạo đối tượng myObject, bạn có thể tham chiếu nó với biến, giống như một chuỗi hoặc một số. Rồi bạn có thể tham chiếu các hàm của nó bằng cách sử dụng ký hiệu ->. Ngoài điều đó, nó giống hệt như gọi một hàm thông thường.

Đó là tất cả những thứ mà bạn cần biết về các đối tượng cho đến nay.

Chuẩn bị lưu thông tin

Bây giờ bạn đã sẵn sàng bắt đầu tạo tập tin thông tin tài liệu. Về sau, bạn sẽ tạo một hàm được gọi là save_document_info(), do đó hãy bắt đầu bằng cách thêm một cuộc gọi đến hàm này trong uploadfile_action.php:

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']); 
} 
...

Bạn muốn chuyển thông tin chỉ vào tập tin được tải lên, vì thế bạn sẽ làm cho việc này dễ dàng hơn với hàm save_document_info() bằng cách chỉ chuyển thông tin vào tập tin ufile, thay vì toàn bộ mảng $_FILES.

Bây giờ là lúc để tạo hàm đó.


Các hàm

Tạo tài liệu DOMphuong12lien

Trước tiên, bạn tạo một đối tượng Document DOM mà bạn có thể sử dụng để thao tác dữ liệu. Hãy mở tập tin scripts.txt và thêm vào mã sau đây:

... 
  define(UPLOADEDFILES, "/var/www/hidden/"); 
  function save_document_info($fileInfo){ 
    $doc = new DOMDocument('1.0'); 
  } 
?>

Quá trình thực sự tạo một đối tượng Document, trong trường hợp này gọi là $doc, khá đơn giản. Lớp DOMDocument là một phần của lõi PHP V5. Bạn sử dụng đối tượng này để thực hiện hầu hết các hành động của mình trên dữ liệu.

Tạo một phần tử

Bây giờ đã có đối tượng Document làm việc, bạn có thể sử dụng nó để tạo phần tử chính, hoặc phần tử root, của tài liệu:

... 
function save_document_info($fileInfo){ 
  $doc = new DOMDocument('1.0'); 
  $root = $doc->createElement('workflow');
  $doc->appendChild($root); 
} 
...

Tại đây bạn lệnh cho đối tượng $doc tạo một nút phần tử mới và trả nó cho biến $root. Sau đó bạn lệnh cho đối tượng Document thêm vào phần tử đó như là một phần tử con của chính nó.

Nhưng điều gì sẽ xảy ra nếu bạn lưu tài liệu này như là một tập tin?

Lưu tài liệu vào một tập tin

Một lợi thế về việc sử dụng PHP để xử lý XML của bạn là PHP cung cấp cách dễ dàng để lưu nội dung của Document vào một tập tin. (Tin PHP hay không là tùy, nó không phải luôn là điều dễ dàng). Để xem những gì thực sự được tạo, hãy thêm đoạn mã sau vào hàm save_document_info():

... 
function save_document_info($fileInfo){ 

  $doc = new DOMDocument('1.0'); 
  $root = $doc->createElement('workflow');
  $doc->appendChild($root); 

  $doc->save(UPLOADEDFILES."docinfo.xml"); 
} 
...

Bạn đã định nghĩa hằng số UPLOADEDFILES ở trên, vì vậy bạn chỉ có thể đi tiếp và tham chiếu nó ngay, khi đặt các tập tin mới trong cùng thư mục. Nếu bây giờ bạn tải lên một tập tin thông qua trình duyệt, tập tin docinfo.xml cần trông giống như thế này:

<?xml version="1.0"?> 
<workflow/>

Đừng lo lắng về dòng đầu tiên, đó là khai báo XML và đó là chuẩn, nhưng là tùy chọn (trong đa số trường hợp).

Lưu ý rằng phần tử của bạn được lưu, nhưng vì bạn thực sự vẫn chưa thêm vào bất kỳ phần tử con, hoặc nội dung nào, tới nó, phần tử đó được viết như là một phần tử rỗng.

Bây giờ chúng ta hãy thêm một phần tử phức tạp hơn.

Tạo các thuộc tính

Bắt đầu thêm thông tin thực sự vào tập tin. Bạn đã tạo một tập tin nhờ việc thử nghiệm bước trước, nhưng cho đến khi bạn nhận được thông tin của tập tin đầu tiên được lưu xong, bạn có thể giả đinh bạn vẫn đang tạo tập tin docinfo.xml.

Bắt đầu bằng cách tạo phần tử statistics:

... 
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");
} 
...

Lưu ý rằng bạn vẫn sử dụng đối tượng Document để tạo phần tử mới, lúc này gọi là statistics. Đối tượng Document này hoạt động như một "nhà máy" cho hầu hết đối tượng của bạn.

Một khi bạn đã có đối tượng Element, $statistics, bạn có thể sử dụng các hàm được dựng sẵn của nó để thiết lập hai thuộc tính: totalapproved. Một khi đã làm điều đó, bạn cần thêm phần tử này như con của $root, chứ không như con của $doc. Nếu bạn lưu tập tin này, bạn có thể thấy sự khác biệt:

<?xml version="1.0"> 
<workflow><statistics total="1" approved="0"/></workflow>

Bạn sẽ nhận thấy hai điều ở đây. Trước tiên, lưu ý rằng phần tử statistics là một phần tử con của phần tử workflow. Cũng lưu ý rằng không có khoảng trống thừa ở đây. Phần tử statistics là phần tử con đầu tiên của phần tử workflow.

Bây giờ hãy xem việc bổ sung thông tin thực.

Tạo phần tử thông tin tập tin

Bây giờ bạn có thể đi tiếp và tạo các thông tin tài liệu thực sự. Quá trình này sử dụng các kỹ thuật mà bạn vừa học:

... 
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");
} 
...

Mặc dù có rất nhiều mã ở đây, nhưng có rất ít mã mới. Trước tiên bạn trích xuất thông tin thực sự về tập tin từ thông tin được chuyển tới hàm đó. Sau đó, bạn tạo phần tử fileInfo sẽ chứa tất cả thông tin mà bạn đang thêm vào. Bạn thiết lập các thuộc tính statussubmittedBy trên phần tử này và rồi xem xét việc tạo phần tử con của nó.

Phần tử approvedBy là dễ. Nó chưa được phê duyệt, do đó nó vẫn rỗng. Thế nhưng phần tử fileName hơi khó khăn một chút bởi vì bạn cần phải thêm một phần tử con văn bản cho nó. May mắn thay, điều đó cũng khá đơn giản. Bạn tạo phần tử này, sau đó sử dụng đối tượng Document để tạo một nút văn bản mới có tên tập tin như là nội dung của nó. Sau đó bạn có thể thêm nút văn bản như một phần tử con của phần tử fileName.

Bạn xử lý theo cách này, khi tạo tất cả các phần tử mà thực ra là con của phần tử fileInfo. Sau khi hoàn thành, bạn nối tất cả chúng như là con của phần tử fileInfo. Cuối cùng, bạn thêm chính phần tử fileInfo vào phần tử gốc, workflow.

Các kết quả, với khoảng trống được thêm vào cho rõ ràng, trông như sau:

<?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>

Tất nhiên, bạn không thể để ghi đè lên tập tin thông tin mỗi khi ai đó tải lên một tài liệu, nên tiếp theo bạn sẽ xem xét công việc với cấu trúc hiện có.

Nạp một tài liệu hiện có

Bây giờ bạn biết cách thêm thông tin vào tập tin, bạn cần xem xét hoạt động với tập tin trong các lần tải lên kế tiếp. Trước tiên, bạn cần phải xem tập tin đã tồn tại chưa, sau đó hành động cho phù hợp:

... 
function save_document_info($fileInfo){  
 
  $xmlfile = UPLOADEDFILES."docinfo.xml"; 

  if(is_file($xmlfile)){ 
    $doc = DOMDocument::load($xmlfile); 
    $workflowElements = $doc->getElementsByTagName("workflow"); 
    $root = $workflowElements->item(0);
  } else { 
      $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->appendChild($size); 
  $root->appendChild($fileInfo);
  $doc->save($xmlfile); 
}

Bắt đầu từ đầu, bạn tạo một biến biểu thị vị trí của tập tin, vì bây giờ bạn sẽ tham chiếu nó từ nhiều hơn một vị trí. Tiếp theo, bạn kiểm tra xem tập tin đó đã có chưa. Nếu có, bạn gọi hàm load(), thay vì tạo đối tượng mới.

Hàm tĩnh này -- chúng ta sẽ nói về nó nhiều hơn trong Phần 3 của loạt bài này; ở đây hãy hiểu chúng là các hàm mà bạn có thể gọi từ lớp, chứ không phải là từ một đối tượng -- trả về một đối tượng Document đã được đặt cho tất cả các phần tử, văn bản và như vậy nó được biểu diễn trong tập tin.

Một khi bạn có đối tượng Document, bạn cần phần tử workflow vì sau này bạn sẽ cần thêm phần tử fileInfo mới cho nó. Bạn nhận được phần tử workflow bằng cách trước tiên lấy danh sách tất cả các phần tử trong tài liệu có tên là workflow, rồi chọn một phần tử đầu tiên trong danh sách.

Từ đó, bạn có thể chỉ cần thêm phần tử fileInfo mới và nó sẽ xuất hiện sau phần tử fileInfo ban đầu. Một lần nữa, với khoảng trống được thêm vào cho rõ ràng:

<?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>

Nhưng phần tử statistics là về cái gì? Rõ ràng, chúng không còn đúng nữa. Điều đó sẽ phải được sửa lại.

Thao tác dữ liệu hiện có

Ngoài việc thêm các thông tin vào tài liệu, bạn có thể thay đổi thông tin đã có trong tài liệu. Ví dụ, bạn có thể cập nhật thuộc tính total trên phần tử statistics:

... 
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  { 
...

Trước hết, bạn tham chiếu đến phần tử statistics hiện có theo cùng cách mà bạn đã tham chiếu phần tử workflow hiện có, chỉ cần kết hợp cả hai bước thành một. Một khi tham chiếu đến phần tử này, bạn có thể nhận được giá trị hiện tại của thuộc tính total bằng cách sử dụng hàm getAttribute(). Sau đó, bạn có thể sử dụng giá trị đó để cung cấp một giá trị cập nhật cho thuộc tính total nhờ sử dụng hàm setAttribute().

Các kết quả như bạn mong đợi, lần này không có khoảng trống nào được thêm vào:

<?xml version="1.0"?> 
<workflow>
  <statistics total="2" approved="0"/> <fileInfo status="pending" 
submittedBy="roadnick"> ...

Bây giờ đây là lúc bạn biết cách sử dụng DOM để tạo tập tin, hãy xem cách đọc lại nó nhờ sử dụng một XML API khác, đó là SAX.


Sử dụng XML: SAX

SAX là gì?

Trong phần trước, bạn đã học về DOM, xử lý dữ liệu XML như một loạt các đối tượng được bố trí hệ thống phân cấp. Như vậy, DOM được biết đến như là một API dựa trên đối tượng. Trong phần này, bạn sẽ tìm hiểu về API đơn giản cho XML hoặc SAX, một API dựa trên sự kiện. Bạn sẽ sử dụng API này để hiển thị thông tin về các tập tin có sẵn cho người dùng cụ thể xem.

SAX hoạt động nhờ trình xử lý nội dung để theo dõi luồng các sự kiện. Ví dụ hãy xem tài liệu XML:

<workflow> 
    <statistics total="3" approved="2"/> 
    <fileInfo submittedBy="roadnick" status="approved">
       <fileName>timeone.jpg</fileName> 
    </fileInfo> 
</workflow>

Trong việc phân tích cú pháp tài liệu này, một trình xử lý nội dung SAX sẽ thấy các sự kiện sau đây:

Start document 
Start element (workflow) 
Characters (whitespace)
Start element (statistics) 
End element (statistics) 
Characters (whitespace) 
Start element (fileInfo) 
Characters (whitespace) 
Start element (fileName) 
Characters (timeone.jpg) 
End element (fileName) 
Characters (whitespace) 
End element (fileInfo)
Characters (whitespace) 
End element (workflow) 
End document

Vì trình xử lý nội dung nhìn thấy những sự kiện này, nên bạn có thể gán cho nó một hàm để thực hiện từng sự kiện đó.

Tạo trình xử lý nội dung

Bước đầu tiên là tự tạo trình xử lý nội dung. Trong trường hợp này, bạn đang xử lý một lớp có tên là Content_Handler, nhưng trong PHP, tên là hoàn toàn tùy ý. Mở scripts.txt và thêm vào như sau:

class Content_Handler{ 
  function start_element($parser, $name, $attrs){ 
    echo "Start element: ".$name."<br />"; 
  } 

  function end_element($parser, $name){ 
    echo "End element: ".$name."<br />"; 
  } 

  function chars($parser, $chars){
    echo "Text: ".$chars."<br />"; 
  } 
}

Vào lúc này, bạn muốn các hàm xuất vừa đủ để bạn biết chúng đã được gọi. Bây giờ bạn cần phải báo cho trình phân tích cú pháp biết tìm trình xử lý nội dung này ở đâu.

Chỉ định các trình xử lý

Để PHP phân tích cú pháp tập tin, PHP cần biết các hàm nào thực thi với các sự kiện nào. Thêm hàm display_files() xem thấy dưới đây, vào scripts.txt:

... 
function display_files(){ 
  $handler = new Content_Handler();
  $doc_parser = xml_parser_create(); 
  xml_set_object($doc_parser, $handler);
  xml_set_element_handler($doc_parser, 
                          "start_element", 
                          "end_element");
  xml_set_character_data_handler($doc_parser, "chars");
  xml_parser_set_option($doc_parser, 
                        XML_OPTION_CASE_FOLDING, 0); 
} 
...

Trước tiên, bạn tạo một đối tượng Content_Handler mới là $handler. Tiếp theo, bạn nhận được một tham chiếu đến trình phân tích cú pháp XML đã được dựng sẵn trong PHP và sử dụng hàm xml_set_object() để báo cho nó biết nơi tìm thấy các hàm mà bạn sắp tham chiếu.

Tiếp theo, báo cho trình phân tích cú pháp biết các hàm nào bạn muốn sử dụng để xử lý các sự kiện khác nhau. Trong một số các API, các tên hàm này được đặt cố định, nhưng trong PHP, chúng là tùy ý. Vì vậy, bạn cần nói rõ cho PHP biết, bằng cách sử dụng hàm xml_set_element_handler(), rằng PHP cần xử lý sự kiện "phần tử bắt đầu" bằng hàm start_element().

Lưu ý rằng bạn có thể đã bỏ qua hàm xml_set_object() và chỉ cần sử dụng:

xml_set_element_handler($doc_parser, 
                        "$handler->start_element",
                        "$handler->end_element");

Cuối cùng, bạn tắt tùy chọn chữ in để trình phân tích cú pháp không chuyển đổi tất cả các tên phần tử sang tất cả các chữ hoa. Bây giờ bạn đã sẵn sàng để phân tích cú pháp tập tin hiện có.

Mở và phân tích cú pháp tập tin

Tại thời điểm này, bạn đã sẵn sàng để phân tích cú pháp tập tin, nhưng không có gì sẽ xảy ra cho đến khi bạn làm hai việc. Đầu tiên là để thực sự thêm việc phân tích cú pháp của tập tin vào hàm display_files():

... 
  xml_parser_set_option($doc_parser, 
                     XML_OPTION_CASE_FOLDING, 0);
 
  $xmlfile = UPLOADEDFILES."docinfo.xml"; 

  $file_to_parse = fopen($xmlfile, "r");
  if (!$file_to_parse) die("Can't open XML file."); 

  while($data = fread($file_to_parse, 4096)){ 
     xml_parse($doc_parser, $data,
     feof($file_to_parse)); 
  } 
} ...

Để quá trình này có hiệu quả (và có thể duy trì tiếp), bạn cũng sử dụng hằng số UPLOADEDFILES ở đây để xác định vị trí của tập tin thông tin tài liệu. Từ đó, bạn sử dụng các khả năng quản lý tập tin dựng sẵn của PHP để tạo một tập tin xử lý cho tập tin đó, $file_to_parse, và nếu tập tin đó không hoạt động, bạn hiển thị một thông báo về hiệu lực của tập tin đó.

Một khi bạn đã tham chiếu đến tập tin, bạn chạy qua nó theo các đoạn lên đến 4.096 byte. Với mỗi đoạn, bạn gửi dữ liệu tới trình phân tích cú pháp. Vòng lặp tiếp tục cho tới khi đến cuối tập tin và dừng lại.

Việc thứ hai bạn cần làm với bất cứ điều gì xảy ra là gọi hàm display_files() từ uploadfile_action.php:

... 
    echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] . 
        "<br> Temporary Name: $tmpName <br>"; 
  } else { 
      save_document_info($_FILES['ufile']); display_files(); 
  } 
...

Bây giờ là lúc để xem điều gì sẽ xảy ra.

Các kết quả cơ bản nhất

Tại thời điểm này, nếu bạn tải lên một tập tin, PHP sẽ lưu tập tin đó, thêm thông tin về tập tin đó vào tập tin docinfo.xml và gọi trình phân tích cú pháp (và với trình này là đối tượng Content_Handler) để phân tích cú pháp dữ liệu. Khi quá trình đó xảy ra, mỗi hàm bắt đầu được gọi cho sự kiện cụ thể riêng của nó và bạn nhận được các kết quả như sau:

Start element: workflow 
Start element: statistics 
End element: statistics 
Start element: fileInfo 
Start element: approvedBy 
End element: approvedBy
Start element: fileName 
Text: timeone.jpg 
End element: fileName 
Start element: location 
Text: /var/www/hidden/ 
End element: location 
Start element: fileType 
Text: image/jpeg 
End element: fileType 
Start element: size 
Text: 2020 
End element: size 
End element: fileInfo 
Start element: fileInfo 
Start element: approvedBy 
End element: approvedBy 
Start element: fileName 
Text: timeone.jpg 
End element: fileName 
Start element: location 
Text: /var/www/hidden/ 
End element: location 
Start element: fileType 
Text: image/jpeg 
End element: fileType 
Start element: size 
Text: 2020 
End element: size 
End element: fileInfo 
End element: workflow

Tất nhiên các kết quả này là thông tin, nhưng không chính xác hoàn toàn. Bây giờ hãy xem việc tạo một trang thực sự hiển thị thông tin một cách có ích.

Hiển thị tổng thể

Tại thời điểm này, bạn có đầu ra và bạn có thể thấy cách trình xử lý đang được gọi, nhưng bạn cần phải nhào nặn thông tin này thành một số loại đầu ra hợp lý. Cuối cùng, bạn cần có một bảng thông tin trông giống như Hình 6.

Hình 6. Bảng thông tin có thể đọc được
Bảng thông tin có thể đọc được

Bảng này chuyển thành một trang HTML về:

... 
Uploading: bespin-lunch-1.jpg<br /> 
<!-- At the start of the document --> 
<h3>Available files</h3> 
<table width='100%'> 
<!-- At the start of the document --> 
<!-- For each fileInfo element --> 
  <tr>
      <th>File Name</th>
      <th>Submitted By</th>
      <th>Size</th>
      <th>Status</th>
  </tr> 
<!-- For each fileInfo element -->
  <tr>
     <td>timeone.jpg</td>
     <td>roadnick</td>
     <td>2020</td>
     <td>pending</td>
  </tr> 
  <tr>
     <td>timetwo.jpg</td>
     <td>roadnick</td>
     <td>1416</td>
     <td>pending</td>
  </tr>
  <tr>
     <td>signupActivate.html</td>
     <td>tater</td>
     <td>37594</td>
     <td>approved</td>
  </tr>
  <tr>
     <td>signup.html</td>
     <td>tater</td>
     <td>32194</td> 
     <td>approved</td>
  </tr>
  <tr>
     <td>bespin-lunch-1.jpg</td>
     <td>roadnick</td>
     <td>29304</td>
     <td>pending</td>
  </tr> 
<!-- At the end of the document --> 
</table> 
<!-- At the end of the document --> 
</td>

Khi xem các bình luận, bạn có thể thấy bạn cần ngắt trang này để xem các sự kiện khác nhau như thế nào. Hãy tiếp tục và làm điều đó.

Điểm bắt đầu và điểm kết thúc của tài liệu

Trong một số ngôn ngữ, bạn có thể thiết lập một hàm cụ thể để xử lý điểm bắt đầu và điểm kết thúc của một tài liệu, nhưng PHP không làm cho nó hoàn toàn đơn giản. Bạn có thể xử lý các sự kiện này một cách riêng biệt, nhưng trong trường hợp này, bạn còn có tùy chọn để chỉ xử lý điểm bắt đầu và điểm kết thúc của phần tử workflow như là điểm bắt đầu và kết thúc của tài liệu đó. Ví dụ, thêm phần sau vào định nghĩa lớp Content_Handler:

... 
class Content_Handler{ 
  function start_element($parser, $name, $attrs){ 
    if ($name == "workflow"){ 
        echo "<h3>Available files</h3>";
        echo "<table width='100%'><tr>". 
              "<th>File Name</th><th>Submitted By</th>".
              "<th>Size</th><th>Status</th></tr>"; 
    } 
  }
  function end_element($parser, $name){ 
    if ($name == "workflow"){ 
        echo "</table>"; 
     } 
  } 
  function chars($parser, $chars){ 
    //echo "Text: ".$chars."<br />"; 
  }
} 
...

Khi trình phân tích cú pháp gửi sự kiện start element, nó gọi hàm start_element() và chuyển qua trình phân tích cú pháp, tên của phần tử này và bất kỳ các thuộc tính nào tới hàm đó. Bạn có thể sử dụng đầu ra đó để quyết định xem có hiển thị thông tin không.

Trong trường hợp này, bạn nhận được chỉ là một hàng đơn cho phần đầu bảng và thẻ </table> cho cuối bảng, như bạn có thể thấy trong Hình 7.

Hình 7. Hiển thị thông tin từ các phần tử bắt đầu
Dòng đơn và thẻ bảng

Tuy nhiên, việc hiển thị các hàng riêng biệt không đơn giản như thế.

Phương thức start_element()

Một khía cạnh quan trọng của một API dựa trên sự kiện là thực tế tại thời điểm đã cho bất kỳ , trình phân tích cú pháp có thông tin chỉ về sự kiện cụ thể. Điều đó có nghĩa là trong khi bạn có thể dịch chuyển lên và xuống cây khi bạn đang xử lý DOM, thì khi bạn sử dụng SAX, bạn không có cách nào để biết những gì đến trước, trừ khi bạn đã lưu nó một cách rõ ràng. Nói cách khác, khi bạn có phần tử fileName, bạn không biết liệu phần tử fileInfo có sẵn để hiển thị hay không, trừ khi bạn đã ghi nhận nó rõ ràng:

... 
class Content_Handler{ 

  private $available = false; 
  private $submittedBy = ""; 
  private $status = ""; 

  function start_element($parser, $name, $attrs){ 
     if ($name == "workflow"){ 
         echo "<h3>Available files</h3>"; 
         echo "<table width='100%'>
           <tr>". "
              <th> File Name</th>
              <th>Submitted By</th>"."
              <th>Size</th>
              <th>Status</th>
           </tr>"; 
      } 
      if ($name == "fileInfo"){ 
          if ($attrs['status'] == "approved" || 
              $attrs['submittedBy'] == $_SESSION["username"]){ 
             $this->available = true; 
          } 
          if ($this->available){
             $this->submittedBy = $attrs['submittedBy']; 
             $this->status = $attrs['status']; 
          } 
       } 
  } 

  function end_element($parser, $name){ 
...

Khi đến đầu phần tử fileInfo, bạn muốn kiểm tra thuộc tính status và phần tử submittedBy để xem cả hai trong chúng có là một lý do để hiển thị tài liệu không. Bạn có thể làm điều đó bằng cách sử dụng mảng $attrs.

Nếu status được chấp thuận hoặc tập tin đã được người dùng hiện tại đệ trình lên, bạn cần lưu ý rằng bất kỳ dữ liệu nào sắp xảy ra trong phần này là có sẵn. Để làm điều đó, bạn đã tạo một biến gọi là available trong chính lớp đó. (Đừng lo lắng về cái private; chúng ta sẽ thảo luận về nó trong Phần 3). Vì biến là một phần của đối tượng này, bạn có thể tham chiếu nó bằng cách sử dụng $this->available, và giá trị này sẽ vẫn được thiết lập ngay cả khi bạn chuyển đến sự kiện tiếp theo.

Nếu thông tin sẵn, bạn cần lưu nó sao cho nó có thể được hiển thị khi thời gian đến. Để kết thúc, bạn lưu nó trong các biến submittedBystatus.

Bạn cần làm điều tương tự khi xử lí văn bản nào đó.

Phương thức characters()

Một số thông tin bạn muốn hiển thị được lưu như các thuộc tính của phần tử fileInfo, nhưng một số thông tin được lưu như các nút văn bản của các phần tử khác. Tuy nhiên, để lưu thông tin này bạn cần biết bạn đang xử lý phần tử nào:

class Content_Handler{ 
  private $available = false; 
  private $submittedBy = ""; 
  private $status = ""; 
  private $currentElement = ""; 
  private $fileName = ""; 
  private $fileSize = ""; 

  function start_element($parser, $name, $attrs){ ... 
      $this->currentElement = $name; 
  } 
  function end_element($parser, $name){ 
      if ($name == "workflow"){ 
          echo "</table>"; 
      }
      $this->currentElement = ""; 
  } 
  function chars($parser, $chars){ 
      if ($this->available){ 
           if ($this->currentElement == "fileName"){
                $this->fileName = $this->fileName . $chars; 
           } 
           if ($this->currentElement == "size"){ 
               $this->fileSize = $this->fileSize . $chars; 
           } 
       } 
  } 
}

Hãy bắt đầu từ trên cùng. Trước tiên, bạn tạo một biến để lưu trữ tên của phần tử hiện tại và lưu lại biến đó mỗi khi bạn thực hiện hàm start_element(). Bạn xóa biến đó mỗi khi bạn gặp hàm end_element(), nhưng điều quan trọng cần lưu ý là việc chỉ xóa biến không làm cho nó trở lại giá trị trước đó của nó. Không giống như đang đi trên cây XML.

Phần quan trọng là bạn biết được bạn đang xử lý phần tử nào khi bạn bắt đầu hàm chars(). Hàm này được thực hiện một hoặc nhiều lần cho mỗi nút văn bản. Mỗi khi thực hiện hàm này, nếu tập tin có sẵn để hiển thị, bạn sẽ kiểm tra phần tử hiện tại và thêm các ký tự hiện tại cho các biến thích hợp nếu cần thiết.

Phương thức end_element()

Bây giờ bạn đã lưu tất cả thông tin này, bạn cần phải hiển thị nó cho mỗi phần tử fileInfo. Cách dễ nhất để hiển thị là xuất ra khi bạn bắt đầu kết thúc mỗi phần tử fileInfo:

... 
function end_element($parser, $name){ 
  if ($name == "workflow"){ 
    echo "</table>"; 
  } 
  if ($name == "fileInfo"){ 
    echo "<tr>
             <td>".$this->fileName."</td>"."
             <td>".$this->submittedBy."</td>"."
             <td>".$this->fileSize."</td>"."
             <td>".$this->status."</td>
          </tr>"; 

     $this->fileName = "";
     $this->submittedBy = ""; 
     $this->fileSize = ""; 
     $this->status = "";

     $this->available = false; 
  } 
  $this->currentElement = ""; 
} 
...

Khi hàm end_element() thực hiện, trước tiên bạn hiển thị một dòng bảng với thông tin phù hợp trong nó. Khi đã hiển thi, bạn cần phải xóa sạch tất cả các biến, bao gồm cả biến $available , vì thế bạn đã sẵn sàng để xử lý phần tử fileInfo tiếp theo.

Kết quả là một bảng thông tin như trong Hình 8.

Hình 8. Bảng thông tin kết quả
Bảng thông tin kết quả

Trong Phần 3, bạn sẽ xem bảng này với các liên kết và thông tin và chức năng khác.


Tóm tắt

Trong hướng dẫn này, bạn đã bắt đầu tạo phần chính của ứng dụng tiến trình công việc: khả năng của người dùng thêm các tập tin. Bạn đã cho phép những người dùng đăng nhập vào hệ thống và tạo một phiên làm việc để ghi nhận họ và tải lên một tập tin. Bạn đã lưu tập tin đó trên máy chủ và đã sử dụng XML để lưu thông tin về nó. Theo cách đó, các chủ đề sau đã được đề cập:

  • Tạo một phiên làm việc.
  • Sử dụng một phiên làm việc hiện có.
  • Tải lên một tập tin.
  • Tạo một tập tin XML bằng cách sử dụng DOM.
  • Nạp dữ liệu XML bằng cách sử dụng DOM.
  • Thao tác dữ liệu XML bằng cách sử dụng DOM.
  • Tạo một trình xử lý nội dung SAX.
  • Phân tích cú pháp một tập tin XML bằng cách sử dụng SAX.
  • Theo vết dữ liệu XML nhờ SAX.

Trong Phần 3, bạn sẽ hoàn thành ứng dụng này.

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở
ArticleID=548698
ArticleTitle=Học PHP, Phần 2: Tải lên các tập tin và sử dụng XML để lưu trữ và hiển thị thông tin tập tin
publish-date=09302010