Output buffering (OB - đệm đầu ra, ĐĐR) là một tính năng tinh xảo của PHP 4. Mặc dù nguyên tắc hoạt động khá đơn giản, ĐĐR là công cụ khá mạnh để viết các chương trình tiên tiến và có hiệu quả.
Trong bài này, tôi sẽ giải thích HTTP header, cách ĐĐR giúp bạn thao tác với nó, và một vài ứng dụng của ĐĐR.
Đối với mỗi yêu cầu dùng giao thức HTTP , có 2 phần chính được web server trả về: header (đầu) và body (thân). Thí dụ: bạn có một tập tin văn bản tên example.txt chứa dòng 'Hello, world!' ở web server, thì hồi đáp của web server cho nó như sau:
HTTP/1.1 200 OK
Date: Sat, 02 Sep 2000 21:40:08 GMT
Server: Apache/1.3.11 (Unix) mod_macro/1.1.1 PHP/4.0.2-dev
Last-Modified: Sat, 02 Sep 2000 21:39:49 GMT
ETag: "12600b-e-39b173a5"
Accept-Ranges: bytes
Content-Length: 14
Connection: close
Content-Type: text/plain
Hello, world!
Phần đầu là header, sau đó (bắt buộc) là một dòng trống, kế đến là phần thân. Phần đầu được trình duyệt nhận và xử lí, nó không hiện ra cho bạn. Cấu trúc phần này đơn giản, có dạng:
Bạn có thể thêm các thông tin khác cho header từ script PHP của bạn. Bạn có thể làm việc này một cách tường minh bằng hàm header() hoặc một cách ẩn bằng hàm SetCookie(). Thí dụ:
Từ phiên bản PHP/FI 2.0, sự cần thiết của ĐĐR đã được thể hiện rõ. Trong phiên bản đỏ (và cả các bản PHP 3, 4 sau này), khi bạn đặt cookie hoặc header sẽ thường gặp câu thông báo "Cannot add header information - headers already sent". Ban đầu ĐĐR dùng để giải quyết vấn để này, nhưng sau đó nó lại được áp dụng cho nhiều tình huống khác.
Tại sao có thông báo lỗi đó? Khi một script thi hành, nó xuất ra cả header và body. Nhưng header không được gửi ngay, nó được giữ lại (để bạn tiện xử lí) cho đến khi nhận được phần body. Sau đó, bạn không thể nào sửa hoặc thêm vào phần header được (do đặc điểm của giao thức HTTP).
Mặc dù việc giải quyết vấn đề trên thường không khó, nhưng đôi khi nó làm rối loạn cấu trúc của chương trình. ĐĐR sẽ giúp bạn giải quyết tốt hơn.
Khi bật ĐĐR, phần header không được gửi ngay cả đến khi script xuất phần body. Tất cả giá trị này được lưu trong một bộ đệm động. Bạn có thể thay đổi mọi thứ, cả header và body. Khi script kết thúc, mọi giá trị sẽ được gửi đến trình duyệt. Thật đơn giản.
Có 4 hàm giúp bạn điều khiển ĐĐR:
Ngoài ra, bạn còn có thể sửa php.ini (chỉ thị output_buffering). Khi đó, mọi script đều đối xử như là có một lệnh ob_start() khởi đầu.
Thí dụ 1
Ở đây, bạn có thể đặt cookie sau khi xuất body. Chú ý là dùng ĐĐR cho mục đích này làm tốc độ hơi chậm đi, nên mặc định ĐĐR là tắt. Tuy nhiên, với các script phức tạp, ĐĐR sẽ giúp bạn không phải lo đến chuyện là 'tôi nhất định phải xuất header trước body'.
Thí dụ 2
Thí dụ trên minh hoạ một cách (không hiệu quả) để xác định độ dài một chuỗi. Thay vì gửi vào strlen, nó bật ĐĐR, xuất ra, sau đó tính độ dài bộ đệm. Khi hoàn tất, bộ đệm được xoá (mà không gửi) và tắt ĐĐR.
Mặc dù ban đầu ĐĐR được dùng để giải quyết sự phiền toái của HTTP header, nhưng sau đó nó lại chứng minh được khả năng của mình không chỉ có thế. Thí dụ 2 ở trên đã cho bạn thấy điều đó. Thay vì xuất thẳng thông tin, bây giờ bạn có thể giữ lại, xử lí, và thậm chí xoá nó nếu cần. Điều đáng nói là bạn không cần thay đổi chương trình, chỉ phải bật ĐĐR lên mà thôi.
Chú ý trong các ứng dụng nâng cao là ĐĐR được gọi nhiều lần, bạn có thể bật ĐĐR trong một ĐĐR khác. Nhờ đó bạn có thể viết các hàm ứng dụng ĐĐR mà bạn có thể gọi nó ở bất cứ đâu (không quan tâm trước và sau nó có lệnh gì).
Khi đề cập đến ứng dụng web, ĐĐR có thể rất hưu ích. Tôi sẽ đưa ra 2 thí dụ: một phiên bản khác của eval, và một cách để giảm lưu thông HTTP bằng cách nén.
eval() là một hàm ở mức ngôn ngữ, cho phép bạn thực thi câu lệnh lưu trong một chuỗi. Nhiều người cảm thấy kì quặc vì eval không trả về nội dung xuất ra, mà lại in thẳng ra màn hình. Tất nhiên, việc làm của eval() là đúng, vì nhiệm vụ của nó là thi hành lệnh, tất phải xuất ra màn hình. Tuy nhiên, đôi khi bạn cũng muốn nó làm khác đi. Với ĐĐR, việc đó rất dễ:
Hàm này sẽ thực thi lệnh $code, giá trị thay vì xuất ra màn hình sẽ được hàm trả về. Nhờ khả năng gọi nhiều lần của ĐĐR, bạn có thể gọi hàm này khi đang bật ĐĐR (câu ob_start() sẽ thực hiện nhiều lần ở các mức khác nhau).
Nhiều trình duyệt cho phép nén (tiềm ẩn) bằng gzip. Nếu máy chủ nhận diện được trình duyệt hỗ trợ mã hoá gzip, nó có thể nén và gửi thông tin theo mã hoá gzip (dùng headerContent-Encoding: gzip). Khi nhận thông tin, trình duyệt tự động giải mã, và người dùng xem nó như chưa từng được nén bao giờ. Nhờ vậy lưu thông HTTP giảm đáng kể (cỡ 70-80%), khi đường truyền chậm nó lại còn làm tăng tốc độ duyệt web lên rất nhiều.
Thao tác này hơi phức tạp hơn thí dụ trên. Và bạn phải thêm mã vào mỗi script bạn muốn nén. Vì lí do đó, bạn nên đưa các lệnh này vào các script include.
Với các ghi chú trên, đoạn mã này không có gì khó hiểu. Chú ý là ta gọi ob_start() với tham số là hàm compress_output(). Khi ĐĐR kết thúc, PHP không gửi bộ đệm đi, mà nó gửi cho compress_output xử lí, rồi đem giá trị hàm này trả về để gửi đi.
Bạn cũng có thể chỉ định hàm xử lí ĐĐR bằng cách dùng chỉ thị output_handler trong php.ini nếu bạn muốn mọi script PHP đều được ĐĐR bằng một hàm nào đó.
Trong đoạn mã trên đây, bạn có thể đăng kí gzencode() trực tiếp thay vì compress_output(), nhưng tôi viết vậy do sáng sủa.
Thí dụ trên yêu cầu PHP 4.0.4 (không sao, giờ đã có PHP 4.3) và zlib bật.
Trong PHP 4.0.4, một hàm mới được bổ sung: ob_gzhandler(). Hàm này dùng để kiểm traContent-Encoding header để nén, sau đó tự động nén thông tin bằng chuẩn nén được trình duyệt hỗ trợ. Sử dụng hàm này thật dễ dàng:
hoặc là đặt trong php.ini:
Mặc dù bạn có thể cảm thấy ĐĐR không làm thay đổi cuộc đời bạn (thường là cuộc đời bạn quá tốt rồi), tôi cũng mong rằng bạn thấy nó hữu ích. Tôi tin chắc rằng chúng ta sẽ thấy ngày càng nhiều ứng dụng của tính năng này.
Có nhiều tình huống mà ĐĐR giúp bạn đáng kể. Một thí dụ tôi mong đợi nó sẽ phổ biến: xử lí các trang XML thông qua mẫu XSL. Instead of manually rendering XML through XSL on every page, you could generate simple XML pages, and run them through an XSL stylesheet using the output processing function of ob_start()-- all that without 'polluting' the XML page with unrelated rendering code. (sorry, I don't understand )
Một thí dụ khác: ở các script lớn (như PHP Nuke) cho phép bạn thêm module. Trong module đó bạn tha hồ xử lí như là nó chạy độc lâp vậy, nhưng kết quả vẫn để vào đúng chỗ cần thiết. Đó là nhờ ĐĐR.
Và, tất nhiên, những điều thú vị nhất là những điều chưa được khám phá.