Không có đầu ra trước khi gửi tiêu đề!
Các chức năng gửi / sửa đổi các tiêu đề HTTP phải được gọi trước khi bất kỳ đầu ra nào được thực hiện .
summary ⇊
Nếu không, cuộc gọi thất bại:
Cảnh báo: Không thể sửa đổi thông tin tiêu đề - các tiêu đề đã được gửi (đầu ra bắt đầu tại script: line )
Một số chức năng sửa đổi tiêu đề HTTP là:
Đầu ra có thể là:
Cố ý:
print
, echo
Và các chức năng khác sản xuất ra
- Phần thô mã
<html>
trước <?php
.
Tại sao nó xảy ra?
Để hiểu lý do tại sao các tiêu đề phải được gửi trước khi xuất, cần phải xem
phản hồi HTTP thông thường . Các tập lệnh PHP chủ yếu tạo nội dung HTML, nhưng cũng chuyển một bộ tiêu đề HTTP / CGI cho máy chủ web:
HTTP/1.1 200 OK
Powered-By: PHP/5.3.7
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8
<html><head><title>PHP page output page</title></head>
<body><h1>Content</h1> <p>Some more output follows...</p>
and <a href="/"> <img src=internal-icon-delayed> </a>
Trang / đầu ra luôn theo sau các tiêu đề. PHP phải chuyển các tiêu đề đến máy chủ web trước. Nó chỉ có thể làm điều đó một lần. Sau khi phá vỡ dòng đôi, nó không bao giờ có thể sửa đổi chúng.
Khi PHP nhận ra đầu tiên ( print
, echo
, <html>
) nó sẽ
tuôn tất cả các tiêu đề thu thập được. Sau đó, nó có thể gửi tất cả đầu ra mà nó muốn. Nhưng việc gửi thêm các tiêu đề HTTP là không thể.
Làm thế nào bạn có thể tìm ra nơi đầu ra sớm xảy ra?
Các header()
cảnh báo có chứa tất cả các thông tin liên quan để xác định vấn đề nguyên nhân:
Cảnh báo: Không thể sửa đổi thông tin tiêu đề - các tiêu đề đã được gửi bởi
(đầu ra bắt đầu tại / www / usr2345 / htdocs / auth.php: 52 ) trong /www/usr2345/htdocs/index.php trên dòng 100
Ở đây "dòng 100" đề cập đến kịch bản mà lệnh header()
gọi không thành công.
Ghi chú " đầu ra bắt đầu tại " trong ngoặc đơn có ý nghĩa hơn. Nó biểu thị nguồn của đầu ra trước đó. Trong ví dụ này, nó auth.php
và dòng52
. Đó là nơi bạn phải tìm kiếm đầu ra sớm.
Nguyên nhân điển hình:
In, tiếng vang
Đầu ra có chủ ý từ print
và các echo
câu lệnh sẽ chấm dứt cơ hội gửi các tiêu đề HTTP. Luồng ứng dụng phải được cơ cấu lại để tránh điều đó. Sử dụng các chức năng
và sơ đồ khuôn mẫu. Đảm bảo header()
cuộc gọi xảy ra trước khi tin nhắn được viết ra.
Các hàm tạo ra đầu ra bao gồm
print
, echo
, printf
,vprintf
trigger_error
, ob_flush
, ob_end_flush
, var_dump
,print_r
readfile
, passthru
, flush
, imagepng
,imagejpeg
trong số những người khác và các chức năng do người dùng định nghĩa.
Khu vực HTML thô
Các phần HTML chưa được chỉnh sửa trong một .php
tệp cũng là đầu ra trực tiếp. Các điều kiện tập lệnh sẽ kích hoạt một header()
cuộc gọi phải được lưu ý trước bất kỳ<html>
khối thô nào .
<!DOCTYPE html>
<?php
// Too late for headers already.
Sử dụng sơ đồ tạo khuôn mẫu để tách xử lý khỏi logic đầu ra.
- Đặt mã xử lý biểu mẫu trên kịch bản.
- Sử dụng các biến chuỗi tạm thời để trì hoãn tin nhắn.
- Logic đầu ra thực tế và đầu ra HTML xen kẽ nên theo sau.
Khoảng trắng trước <?php
cho cảnh báo "script.php dòng 1 "
Nếu cảnh báo đề cập đến đầu ra theo dòng 1
, thì đó chủ yếu là khoảng trắng , văn bản hoặc HTML trước <?php
mã thông báo mở .
<?php
# There's a SINGLE space/newline before <? - Which already seals it.
Tương tự, nó có thể xảy ra đối với các đoạn script được nối thêm hoặc các đoạn script:
?>
<?php
PHP thực sự ăn lên một đơn linebreak sau thẻ đóng. Nhưng nó sẽ không bù nhiều dòng mới hoặc tab hoặc khoảng trắng được chuyển vào những khoảng trống như vậy.
UTF-8 BOM
Linebreaks và không gian một mình có thể là một vấn đề. Nhưng cũng có những chuỗi nhân vật "vô hình" có thể gây ra điều này. Nổi tiếng nhất là
BOM UTF-8 (Byte-Order-Mark)
không được hiển thị bởi hầu hết các trình soạn thảo văn bản. Đó là chuỗi byte EF BB BF
, là tùy chọn và dự phòng cho các tài liệu được mã hóa UTF-8. PHP tuy nhiên phải coi nó là đầu ra thô. Nó có thể hiển thị dưới dạng các ký tự 
trong đầu ra (nếu máy khách diễn giải tài liệu là Latin-1) hoặc "rác" tương tự.
Trong các trình soạn thảo đồ họa cụ thể và các IDE dựa trên Java không biết đến sự hiện diện của nó. Họ không hình dung nó (bắt buộc theo tiêu chuẩn Unicode). Tuy nhiên, hầu hết các biên tập viên lập trình và giao diện điều khiển đều làm:
Có dễ dàng nhận ra vấn đề sớm. Các biên tập viên khác có thể xác định sự hiện diện của nó trong menu tệp / cài đặt (Notepad ++ trên Windows có thể xác định và
khắc phục sự cố ), Một tùy chọn khác để kiểm tra sự hiện diện của BOM là dùng đến một hexeditor . Trên các hệ thống * nix hexdump
thường có sẵn, nếu không phải là một biến thể đồ họa giúp đơn giản hóa việc kiểm tra những vấn đề này và các vấn đề khác:
Một cách khắc phục dễ dàng là đặt trình soạn thảo văn bản để lưu tệp dưới dạng "UTF-8 (không có BOM)" hoặc danh pháp tương tự như vậy. Thường thì những người mới sử dụng để tạo các tệp mới và chỉ cần sao chép và dán mã trước đó vào lại.
Tiện ích sửa chữa
Ngoài ra còn có các công cụ tự động để kiểm tra và viết lại các tệp văn bản ( sed
/awk
hoặc recode
). Đối với PHP cụ thể có phptags
thẻ tidier . Nó viết lại các thẻ đóng và mở thành các dạng dài và ngắn, nhưng cũng dễ dàng sửa các khoảng trắng hàng đầu và dấu, các vấn đề BOM Unicode và UTF-x:
phptags --whitespace *.php
Thật tuyệt vời khi sử dụng trên toàn bộ thư mục bao gồm hoặc dự án.
Khoảng trắng sau ?>
Nếu nguồn lỗi được đề cập là đằng sau việc
đóng?>
thì đây là nơi một số khoảng trắng hoặc văn bản thô được viết ra. Điểm đánh dấu kết thúc PHP không chấm dứt thực thi tập lệnh tại thời điểm này. Bất kỳ ký tự văn bản / dấu cách nào sau nó sẽ được viết dưới dạng nội dung trang.
Đặc biệt, thông thường, đối với những người mới sử dụng, ?>
nên bỏ qua các thẻ đóng PHP kéo dài . Điều này tránh một phần nhỏ của những trường hợp này. (Khá thường include()d
các kịch bản là thủ phạm.)
Nguồn lỗi được đề cập là "Không xác định trên dòng 0"
Đó thường là phần mở rộng PHP hoặc cài đặt php.ini nếu không có nguồn lỗi nào được cụ thể hóa.
- Thỉnh thoảng,
gzip
cài đặt mã hóa luồng
hoặcob_gzhandler
.
- Nhưng nó cũng có thể là bất kỳ
extension=
mô-đun được tải gấp đôi nào tạo ra một thông báo khởi động / cảnh báo PHP ẩn.
Thông báo lỗi trước
Nếu một câu lệnh hoặc biểu thức PHP khác gây ra một thông báo cảnh báo hoặc thông báo được in ra, thì đó cũng được tính là đầu ra sớm.
Trong trường hợp này, bạn cần tránh lỗi, trì hoãn thực thi câu lệnh hoặc chặn thông báo bằng vd
isset()
hoặc @()
- khi không cản trở việc gỡ lỗi sau này.
Không có thông báo lỗi
Nếu bạn có error_reporting
hoặc display_errors
bị vô hiệu hóa php.ini
, thì sẽ không có cảnh báo nào xuất hiện. Nhưng bỏ qua lỗi sẽ không làm cho vấn đề biến mất. Tiêu đề vẫn không thể được gửi sau khi đầu ra sớm.
Vậy khi nào header("Location: ...")
chuyển hướng âm thầm thất bại, rất nên thăm dò các cảnh báo. Reenable chúng với hai lệnh đơn giản trên đỉnh kịch bản lệnh:
error_reporting(E_ALL);
ini_set("display_errors", 1);
Hoặc set_error_handler("var_dump");
nếu tất cả những thứ khác đều thất bại.
Nói về tiêu đề chuyển hướng, bạn thường nên sử dụng một thành ngữ như thế này cho các đường dẫn mã cuối cùng:
exit(header("Location: /finished.html"));
Tốt nhất là ngay cả một chức năng tiện ích, trong đó in một tin nhắn người dùng trong trường hợp header()
thất bại.
Bộ đệm đầu ra như cách giải quyết
Bộ đệm đầu ra của PHP
là một cách giải quyết để giảm bớt vấn đề này. Nó thường hoạt động đáng tin cậy, nhưng không nên thay thế cho cấu trúc ứng dụng phù hợp và tách đầu ra khỏi logic điều khiển. Mục đích thực tế của nó là giảm thiểu chuyển giao chunked đến máy chủ web.
Các output_buffering=
thiết lập tuy nhiên có thể giúp đỡ. Định cấu hình nó trong php.ini
hoặc thông qua .htaccess
hoặc thậm chí .user.ini trên các thiết lập FPM / FastCGI hiện đại.
Kích hoạt nó sẽ cho phép PHP đệm đầu ra thay vì chuyển nó đến máy chủ web ngay lập tức. Do đó, PHP có thể tổng hợp các tiêu đề HTTP.
Nó cũng có thể được tham gia với một cuộc gọi để ob_start();
ở trên kịch bản lệnh gọi. Tuy nhiên, điều này ít đáng tin cậy hơn vì nhiều lý do:
Ngay cả khi <?php ob_start(); ?>
bắt đầu tập lệnh đầu tiên, khoảng trắng hoặc BOM có thể bị xáo trộn trước đó, khiến nó không hiệu quả .
Nó có thể che giấu khoảng trắng cho đầu ra HTML. Nhưng ngay khi logic ứng dụng cố gắng gửi nội dung nhị phân (ví dụ hình ảnh được tạo), đầu ra bên ngoài được đệm trở thành một vấn đề. (Cần thiết ob_clean()
như cách giải quyết xa hơn.)
Bộ đệm bị giới hạn về kích thước và có thể dễ dàng tràn ngập khi để mặc định. Và đó cũng không phải là chuyện hiếm gặp, khó theo dõi
khi nó xảy ra.
Do đó, cả hai phương pháp đều có thể trở nên không đáng tin cậy - đặc biệt khi chuyển đổi giữa các thiết lập phát triển và / hoặc máy chủ sản xuất. Đó là lý do tại sao bộ đệm đầu ra được coi rộng rãi chỉ là một cái nạng / đúng là một cách giải quyết.
Xem thêm ví dụ sử dụng cơ bản
trong hướng dẫn và để biết thêm ưu và nhược điểm:
Nhưng nó hoạt động trên máy chủ khác!?
Nếu bạn không nhận được cảnh báo tiêu đề trước đó, thì cài đặt php.ini đệm đầu ra
đã thay đổi. Có thể nó chưa được định cấu hình trên máy chủ hiện tại / mới.
Kiểm tra với headers_sent()
Bạn luôn có thể sử dụng headers_sent()
để thăm dò nếu vẫn có thể ... gửi tiêu đề. Điều này hữu ích để in một cách có điều kiện một thông tin hoặc áp dụng logic dự phòng khác.
if (headers_sent()) {
die("Redirect failed. Please click on this link: <a href=...>");
}
else{
exit(header("Location: /user.php"));
}
Giải pháp dự phòng hữu ích là:
<meta>
Thẻ HTML
Nếu ứng dụng của bạn có cấu trúc khó sửa, thì một cách dễ dàng (nhưng hơi không chuyên nghiệp) để cho phép chuyển hướng là tiêm <meta>
thẻ HTML
. Một chuyển hướng có thể đạt được với:
<meta http-equiv="Location" content="http://example.com/">
Hoặc với một độ trễ ngắn:
<meta http-equiv="Refresh" content="2; url=../target.html">
Điều này dẫn đến HTML không hợp lệ khi được sử dụng qua <head>
phần. Hầu hết các trình duyệt vẫn chấp nhận nó.
Chuyển hướng JavaScript
Thay thế, một chuyển hướng JavaScript
có thể được sử dụng cho các chuyển hướng trang:
<script> location.replace("target.html"); </script>
Mặc dù điều này thường tuân thủ HTML nhiều hơn so với <meta>
cách giải quyết, nhưng nó phát sinh sự phụ thuộc vào các máy khách có khả năng JavaScript.
Tuy nhiên, cả hai cách tiếp cận đều tạo ra các dự phòng chấp nhận được khi các cuộc gọi tiêu đề HTTP () chính hãng không thành công. Lý tưởng nhất là bạn luôn kết hợp điều này với một tin nhắn thân thiện với người dùng và liên kết có thể nhấp như là phương sách cuối cùng. (Ví dụ,
phần mở rộng PECL của http_redirect () làm gì.)
Tại sao setcookie()
và session_start()
cũng bị ảnh hưởng
Cả hai setcookie()
và session_start()
cần phải gửi một Set-Cookie:
tiêu đề HTTP. Do đó, các điều kiện tương tự được áp dụng và các thông báo lỗi tương tự sẽ được tạo cho các tình huống đầu ra sớm.
(Tất nhiên chúng bị ảnh hưởng nhiều hơn bởi các cookie bị vô hiệu hóa trong trình duyệt hoặc thậm chí các sự cố proxy. Chức năng phiên rõ ràng cũng phụ thuộc vào dung lượng đĩa trống và các cài đặt php.ini khác, v.v.)
Liên kết khác