Chiùyitg 10: x ử ỉý cá c lỗi Cbươĩtg II: Bào vệ an toàn PHP Chươĩtg 12: M ở rộng PHP
156
C h ư ơ n g 10: x ử iý các lỗỉ
Xứ lụ các iôi
Nhữtig kỹ năng và k h á i niệm chinh u
T ìm h i ể i i c á c c ắ p l ỗ i c ủ a P H P
ẵ ! D i ề ĩ i k ũ iỂ n c á c t ô i n a o đ iíỢ c h i ể n i h ị t r u n g s c n p t P H Ỉ ’
*
B ỏ q u a p h iíơ n g th ứ c x ử lý lỗ i m ặ c đ ịn h c ủ a P H P v à c h u y ể n h ư ở n g c á c lỗ i s a n g m ộ t h à m tù y ỷ
M T ìn t h i ổ ỉ t c á c h t ạ o v à X ĩ} l ý c á c n ^ o ạ i l ệ K
T ự đ ộ n g g h i n b ậ l k ý ( lo g ) c á c lỗ i s a n g m ộ l f i l e h o ặ c đ ỉ a c h ỉ e - n i a i l
^
T ạ o m ộ t b a c k ir a c e đ ể g ỡ rố i c á c lồ i s c rip t
M
ột k h ái niệm sai lầm thường th ấ y đặc b iệ t tro n g số những ngưdi đã p h á t triể n ít có k in h nghiệm hơn là m ột chương trìn h "tốt” là m ột chương trìn h làm việc k h ô n g có lỗi. Thực tế, điều n ày không h o àn toàn đúng: Một đ ịn h nghĩa rõ hơn có th ể là chương trìn h dự đoán trước tâ't cả điều kiện lôi có th ể có và g iải quyết chúng m ột cách n h ấ t quán và chính xác. Viết các chương trìn h "thông minh" tu â n theo đ ịnh n g h ĩa n ày là m ột đòi hỏi nghệ th u ậ t cũng như kỹ năng. K inh nghiệm và tr í tưởng tượng đóng m ột vai trò quan trọng cho việc đ án h giá những nguyên n h â n gây lỗi tiềm
C h ư d n g 10: x ử lý các lỗi____________________________________________________ 15 7
án và dịnh n g h ĩa h à n h động hiệu chỉnh nhưng không k ém p h ần quan trọng hơn là bản th â n ngôn ngữ lập trìn h dịnh nghĩa những công cụ v à chức nàng có sẵ n để bẫy và giải quyết các lỗi. T h ậ t m ay th ay , P H P giỏi về lĩnh vực này; ngôn ngữ n ày có m ột framew ork tín h vi để giúp các n h à p h á t triển đón b ắt các lỗi và có h à n h động sửa chữa. Chương n ày sẽ giới thiệu bạn về framework này, trìn h bày cho b ạn về mô h ìn h ngoại lệ P H P 5.3 và hướng dẫn bạn cách tạo các thường trìn h xử
lý lỗi được tùy b iến theo những nhu cầu của ứng dụng PHP.
Xử lý các lỗi S cript N hư bạn đã làm việc qua những đự án trong sách này, chắc chắn b ạn sẽ gặp m ột vài sự cố; M ột dấu ngoặc được đ ặt sai chỗ ở đây, thiếu m ột dấu chấm phẩy ở đó; có lẽ m ột h àm được gọi ra nhầm ở nơi k hác nào đó. Và bạn sẽ. chú ý ră n g P H P thực sự r ấ t giỏi về việc chỉ ra rõ nhữ ng lỗi này. Trong trường hợp P H P tạo m ột th ô n g báo lỗi nhưng tiếp tục thực th i script; trong nhữ ng trường hợp khác nghiêm trọng hơn, nó tạm dừng việc thực th i script bằn g m ột th ô n g báo chỉ định số dòng đã gây ra lỗi. Loại lỗi vừa được mô tả là các lỗi cấp "script"; chúng xảy ra k h i bộ máy PH P gặp p h ải nhữ ng khiếm khuyết trong cú pháp hoặc cấu trúc của một PH P. Chúng chỉ thường n h ậ n th ấy được một khi P H P thực sự b á t đầu p h ân tích và thực th i m ộ t script. Để m inh họa, hăy thử tạo v à chạy scrip t sau dây:
/ / try divỉding by 2ero echo 45/0; // try calling an undetined tunction echo someFunc();
?> Đầu ra của sc rip t này trông giông như h ình 10.1. N hư h ìn h 10.1 m in h họa, script này đã tạo h ai loại lỗi: m ột "cảnh báo" (w arning) về nỗ lực chia cho zero, và "lỗi nghiêm trọng" (fata error) về nỗ iực gọi r a m ột h àm k hông xác định. Thực tế các lỗi P H P có th ể được p h ân loại rộng th à n h b a h ạ n g mục chính như được liệt kê tro n g b ản g 10.1. Có m ột sự p h â n cấp rõ ràn g cho các thông báo lỗi của PH P: các thông báo (notice) ít ng h iêm trọ n g hơn các cảnh báo (vvarning) m à lầ n lượt ít
nghiêm trọ n g hơn các lỗi nghiêm trọng (fatal error). Theo m ặc định PHP
158
C h ư ơ n g 10: x ử lý các lỗi
chỉ hiển th ị các cảnh báo và lỗi nghiêm trọ n g tro n g đầu r a scrip t (mặc dù như bạn sẽ th ấ y ngay sau đó, bạn có th ể th ay đổi h à n h vi m ặc định n ày sao cho th ậ m chí các th ô n g báo chỉ n h ìn th ấy được trong đầu r a script). Các Ỉ5i có th ể xảy ra tạ i các giai đoạn khác nhau tro n g vòng đời của m ột sc rip t - lúc khởi động, vào thời gian p h ân tích, vào thời gian biên dịch hoặc vào thời gian chạy - và do dó P H P cũng đưa ra những k hác b iệt nội tạ i về những giai đoạn khác n h au này. Nói chung, tổng cộng là 12 cấp lỗi kháo n h au (cộng với h ai cấp "đặc biệt") được tượng trưng b ằn g các h ằn g có tên . M ột d an h sách hoàn chỉnh các cáp lỗi n ày có th ể tìm th ấ y tạ i w w w .php.neưm anuai/eĩi/' ref.errorfunc,php#errorfunc.constants. B ảng 10.2 liệ t kê các cấp lỗi m à bạn sô thường sử dụng n h ất. H ầu h ế t nhừng câp lỗi n ày dễ hiểu. Các cấp lỗi m à có lẽ b a n gặp trụ c trặc là các cấp lỗi E_ƯSER được đ ặ t sang m ột bên d àn h cho các lỗi cấp ứng dụng t'jy ỹ Đừng bận tâ m về những cá'p lỗi này vì p h ần iđn chúnịỊ có dược thay th ế bdi mô hình ngoại lộ mới được giới thiệu tro n g P H P 5
Waỉtúng Divuionh y u r c inC:^ogram ỉĩl«sUnr0n 8t
ĩooấi\Apầche^Ju4o«^h^boak'«cblO*iútíAsi\(««-empr».phponlne 3 m »r 'tỉ .0 uiỉ'JeL/iedfinc&cnSởn<ỉ?u.icOmc Tod<\ApaeK6lhi4ors\pỉip6-bMk\rhiO\lìitiii^<\iftè*errorf.php hne6
Cõ
Hình 10.1 Một ví dụ về một trang lỗi PHP Bảng 10.1 Các hạng mục lỗ i PHP
L o ạ i lỗ i
M ô tả
Ví dụ
Các thông báo (notice)
Các lỗi không quan tr ọ n g k h ô n g n g á n P H P th ự c th i m ộ t script
Truy cập một biến dã không được khởi tạo
Các cảnh báo (warning)
Các lỗi nghiêm trọng hơn đòi h ỏ i chú ý nhimg vẫn không dừng việc thực th i sc rip t (mặc dù một số phần của script có thể hoạt động không chính xác)
Đọc m ộ t file k hông
hiện hữu trong đường dẫn được nêu rõ.
159
C h ư ơ n g 10: x ủ lý c á c lỗi
Các lỗi nghiêm trọng (fatal error)
Các lỗi cú pháp hoặc các lỗi quan trọ n g buộc PHP dừng thực thi một script
Thể hiện cụ th ể một đô’i tư ợ n g cùa m ột class không xác định
C ấ p lỗ i
M ô tả
E_PA R SE
Các lỗi phân tích (parse) nghiêm trọng
E .N O T I C E
Các lỗi thời gian chạy (thông báo) không nghiêm trọng
E _ W A R N IN G
Các lỗi thời gian chạy (cảnh báo) không quan trọng
E .E R R O R
Các lỗi thời gian chạy nghiêm trọng buộc kết thúc script
E _ U S E R _ N O T IC E
Các lỗi ứng dụng (thông báo) không nghiêm trọng do người dùng ấn định
E_ƯSER_WARNING
Các lỗi ứng dụng (cảnh báo) không nghiêm trọng do người dùng ấn định
E_U SER _ER R O R
Các lỗi ứng dụng nghiêm trọng do người dùng ấn dịnh
E _ S T R IC T
Các lỗi thời gian chạy không nghiêm trọng nảy sinh từ cú pháp PHP thừa kêVkhông được tán thành
E_A LL
Tất cả lỗi Bảng 10.2 Các cấp lỗi PHP
Điều khiển việ c báo cáo lỗi B ạn có th ể điều k h iển các lỗi nào dược hiển thị tro n g đầu ra scrip t bằng m ột hàm P H P cài sẵ n dược gọi là errQr^reportingO, H àm này chấp n h ận m ột hoặc nhiều h ằ n g được đ ạ t tên từ báng 10.2 và yêu cầu scrip t báo cáo các iỗi khớp với các lỗi đó. Tuy nhiên, có m ột ngoại lệ: việc chuyển các lỗi CE_PARSE) nảy sin h từ những khiếm khuyết cú pháp tro n g m ột script PH P không th ể được ẩ n sử dụng hàm error_reporting(), Để th ấ y trực tiế p điều này, xem xét b ản viết lại sau đây của m ột trong các script trước đó để "làm ẩn" các lỗi không nghiêm trọ n g // only đisplay fatal errors error_reporting {E_ERROR);
1 6 0 _______________________________________________________ C h ư ớ n g 10: x ử lý cá c lỗi echo 45/Q;
?> T rong trường hợp này, khi script thực th i, cảnh báo sẽ không dược tạo ra mặc dù những sc rip t thực h iện phép chia cho zero. Hỏi chuyên gia
H ỏ i: C ấ p lỗi £ _ S T R IC T làm đ iẻ u gi?
Đáp: Tại cấp lỗi E _ S T R I C T , PHP kiểm tra mã vào thời gian chạy và tự động lạo các đề xuất vể cách nó có tnể đưọc cải tiến nhu thế nào. sử d ụ n g E . S T R I C T I h ư ờ n a c ó th ể tnộ t b á o đ ộ i ì g v ổ rihữniạ h à in m à s ẽ bi h ỏ n g trcng tnôt phièn bản tương lai c ủ a
PHP
v à v iệ c sử d ụ n g PÓ dưỢc
đ ề n g h ị đ ể c ả i t h iệ n k h ả n ă n g b ả o trl m ã t ro n g th ờ i h ạ n d ài.
Ban cũng có th ể sử dụn^ v»rror reportiiigO để tắ t việx? háo nghiêm trọ n g vào th ờ i gian chạy n h ư trong ví dụ sau đâ>:
ỉỗi
// only dispỉay vvarnings error_reportíng (E_WARNING); echo someFunc():
?> Điều quan trọ n g là p h ải n h ậ n ra rằ n g error_reporting() không tự động làm cho scrip t k hông có lỗi; tấ t cả những ^ m à nó làm là che giấu các lỗi có m ột loại n h ấ t đ ịnh. M ặc dù script trước sẽ khòng h iển th ị m ột th ô n g báo lỗi n h ìn th ấ y được, n h ư n g nó sẽ vẫn tạo ra m ột lỗi nghiêm trọ n g và việc thực th i scrip t sẽ v ẫn dừng tạ i điểm lỗi. B ạn cũng cố th ể chuyển đến error_reporting() m ột tổ hợp các cấp lỗi để
tùy biến hệ th ô n g báo cáo lỗi của P H P th ậ m chí xa hơn. Ví dụ, xem xét script sau đây, sc rip t n ày chỉ báo cáo các th ô n g báo và lỗi nghiêm trọng nhưng không p h ả i là các cảnh báo: // only dỉspỉay notices and fatal errors error_reportỉng{E_NOTICE I E_ERROR); echo $var; // notice echo 45/0; / / vvarning
C hư dn g 10; x ử
lý các lỗi_________________________ ___________________ 161
echo someFunc(); / / íatal
?> Cũng có th ể t ắ t chức n ăn g báo cáo lỗi một cách có chọn lọc, trê n cơ sở mỗi hàm bằng việc b ắ t đầu lệ n h gọi hàm bằng to án tử Ví dụ, đoạn m ã sau đây thường tạo ra m ột lỗi nghiêm trọng bởi vì som eFunction() không h iện hữu; // call a non-existent tunction someFunction();
?> Tuy nhiên, lỗi này có th ể được bỏ qua bằng việc đ ặ t m ột ký hiệu @ trước lện h gọi hàm n h ư sau:
/ / call a non-existant íunction @someFunctỉon();
?> Sử dụng m ột phương thức xử lý lỗi tùy ỷ Theo mặc định, k h i m ột lỗi scrip t được kích khởi, phương thức xử lý lỗi (error handler) cài sẵn của P H P n h ận dạng loại lỗi, h iể n th ị m ột th ô n g báo lỗi th ích hợp (dựa vào xác lập error_repotingO) và tùy ý tạ m dừng việc thực th i scrip t (nếu lỗi nghiêm trọng). Thông báo được tạo r a bdi phương thức xử lý lỗi sử dụng m ột khuôn mẫu (tem plate) chuẩn: nó chỉ d ịn h loại lỗi, lý do cho loại lỗi, và tê n file và số dòng nơi lồi đã được tạo (xem h ìn h 10.1 để th â y m ột ví dụ). Tuy nhiên, khi các ứng dụng P H P ngày càng trở n ê n phức tạ p hơn, cơ câu xử lý lỗi mặc đ ịnh n ày rố t cuộc thường khong th ỏ a đáng. Ví dụ có th ể b ạn muôn tùy biến tem p late được sử dụng bởi phương thức xử lý lôi dể hiển th ị n h iều hoặc ít th ông tin hcfn hoặc có th ể b ạn muốn ghi n h ậ t ký lỗi sang m ột íĩle hoặc cơ sở dữ liệu th a y vì hiển th ị nó cho người dùng. Đối với tấ t cả tình huông này, P H P đưa r a h àm set_error_handler(), h à m n ày cho phép b ạn th a y th ế phương thức xử lý lỗi cài sẵn của P H P b ằn g phương thức xử lý lỗi riê n g của bạn.
1 6 2 ________________________________________ ____________ C h ư ớ n g 10: x ử lý các lỗi
H àm set_error_handler() chấp n h ậ n m ột đối số: tê n của h àm do người dùng định n g h ĩa m à sẽ được gọi ra khi m ộ t lỗi xảy ra. H àm tùy ý n ày phải có k h ả nỗLng chấp n h ậ n tối th iểu h ai đối sô' b ấ t buộc (loại lỗi và th ô n g báo mô tả tương ứng) và lên đến th ê m ba dối sô' (tên file, sô' dòng nơi lỗi đã xảy ra, và m ột sự k ế t xuất không gian biến vào th ờ i gian lỗi). --------------------------------------------------------------------------------------
M ộ t p h ư ơ n g thứ c x ử lý lỗ i tù y ý k h ô n g th ế c h ặ n c ấ c ỉỗ i n g h iê m trọ n g (E _ E R R O R ), c á c lỗ i p h â n tíc h (E _ P A R S E ), h o ặ c c á c th ô n g b á o m ã th ừ a k ế (E _ S T R IC T ).
Đê’ m in h h ọ a diều này, hãy xem x é t ví dụ sau đây: ở dây m ột hàm xử lý lôi do người dùng định n g h ĩa th a y th ế phương thức xử 1>' (handler) cài sẵii uủa PK P, tạo động m ột tra n g lỗi tùy ý. <!D0CTVPE html PUBLIC "-.//W3C//DTD XHTiVIL 1,0 Transitional//EN'"D TD /xhtm l1-transitional.đtđ"> <htm l xm lns="http://w w w .w 3.org/1999/xhtm !” xm l:lang=” en’' lang=” en”> <head> < title x /tiíle > <slyle type="text/css"> .notice { font-weight: bolder; color: purple;
) ,warning { font*weight; bolđer; font-size: iarger; color: red; I
</style> </head>
<body> // đivert errors to custom error handler
C h U ớ n g 1 0 : x ử lý c á c lỗi_____________________ __________________________________ 1 6 3 set_error_handler{‘m yHandler’); // report ail errors error_reporting{E_A LL); // generate some errors echo $var;
// notice
echo 23/0;
// warning
// custom error handler íunction m yHandler($type, $m sg, $file, $íine, Scontext) I $text = “An error occurred on line $line while Processing your request,
Please visií our <a href=>home page</a> and try again,"; sw itch{$type) I case E.NOTICE: echo “<div class=\” notice\” >$text</điv>
"; break; case E_WARNING; echo “ <div class=V'wamingV'>$text</div>
” : break;
)
1
?> </body> < /íitm l>
ở đây h à m set_error_handler() tự động chuyển hướng tấ t cả lỗi sang h à m do người dùng định n g h ĩa myHandlerO- Khi m ột lôi xảy ra, hàm này được chuyển loại lỏi, th ô n g báo lỗi, file và số dòng nơi lỗi đâ xảy ra cùng với ngữ cảnh k h ả biến. Sau đó nó hiển th ị m ột tran g lỗi tùy ý chứa số dòng của lỗi b ằn g m àu tía (các th ô n g báo) hoặc m àu đỏ (các cản h báo), và tạm đừng b ằn g ta y việc thực th i script. H ình 10.2 m inh họa k ế t quả.
16 4
C h ư ơ n g 10: x ử lý các lồi
An emr oceurred on Iỉne27«4ùỉe proce^síngyỡor request H e ầ ỉ e Y Ìs it o u r b o m ê p ạ g ẹ and X ry h g 9 Ìn . A n m o r o c m iT c d P ì e ,i S € v ĩ ỉ U
0U
lin í 2 S
p r o c ^ s ỉn g y o ìư re q u rs t.
>IU h o n i c p ạ g ẹ a i ì J U y
rm Kình 10.2 Một trang lỗi được tạo ra bồi mộỉ phưđng thức xử lý lỗi tùy ý
Hỏi chuyên gia H ỏ i: Làrn tn ế n â o tò i p h ụ c hồi c ơ cãu x ử lý lối m ặ c đ ịn h c ủ a P H P rr.ộ t khi tõi đ à gọi s e t_ e rro r_ h a n d !e r() ? Đ á p : C ó hai c á ch đ ể làm đ ié u n à y. P h ư ơng p h á p đơn g iả n n h á t là sử d ụ n g hàm re s to re _ e rro r_ h a n d ie r() c u a PH P. H àm n à y p h ụ c h ồ i p h ư ơ n g th ứ c x ử lý lỏi sau c ù n g đ a n g s ử d ụ n g trư ớc khi s e t_ e rro r_ h a n d le r() đ ư ợ c gọi, T ro n g hẩu h ế t c á c trư ờn g h ợ p , đ â y s ẽ là p h ư ơ n g th ứ c x ử lý lỗi m ặ c định của PHP.
Một tùy chọn k h ác là yêu cầu phương thức xử lý lỗi tùy ý tr ả về false; điều này sè buộc lỗi được chuyển trở lại phương thức xử lý m ặc đ ịn h của PH P để xử lý m ột cách bình thường. Kỹ th u ậ t n ày th ì tiệ n lợi nếu bạii cần chuyển các lôi qua m ộ t h àm tùy ý trước tiê n để xử lý trước hoặc ghi n h ậ t k'; và sẽ th a y th ế m ột ví dụ trong p h ần "ghi n h ậ t ký" m ột lỗi, Thực hành 10.1: Tạo m ột tran g lỗi sạch Phương thức xử lý lỗi mặc định của P H P chỉ h iển th ị th ô n g tin về m ột lỗi. Thông báo lỗi n ày thường xuất h iện tro n g khi m ột tr a n g đầu ra đang được tạo, p h á vỡ việc sắp đ ặ t (layout) tra n g và gây r a sự n h ầm lẫ n và sự căng th ẳ n g người dùng không cần th iế t (xem h ìn h 10.3 để th ấ y m ột ví dụ về m ột tra n g như vậy). Tuy n h iên , bằng việc th a y th ế phương thức xử lý lỗi m ặc định b ằn g m ột h à m tùy ý, b ạ n có th ể dễ d àn g g iải quyết v ấn đề này bằng việc tạo m ột tra n g lỗi "sạch" khác trong k h i đồng th ờ i ghi n h ậ t ký các lồi scrip t sang m ột cơ sở dữ liệu để xem lại sau đó. Ví dụ tiế p theo theo sau đây sè hướng d ẫn b ạn cách làm điều này.
165
C h ư ơ n g 10: x ử lý các lỗi
Đ ể b ắ t đầu, tạo m ột cơ sở dữ liệu SQLite mới, và m ột b ản g để chứa thông tin lỗi như được m in h họa ở đây: shell> s q lite app.db sqíite> CREATE TABLE errors ( „ > id INTEGER PRIMARY KEY, ..> date TEXT NOT NULL, „ > e rro r TEXT NOT NULL, ..> s c rip t TEXT NOT NULL, ..> lin e TEXT NOT NULL ...>
):
T iếp theo đ ịn h nghĩa m ột phương thức xử lý lỗi tùy ý chặn tấ t cả lỗi scrip t và ghi chúng sang bảng này sử đụng PDO n h ư tro n g scrip t sau dây (app.php): // report ali errors error_reporting(E_ALL); O Proỉe(t 10- ỉ: Generãting a CustomCrrorl^dge - MoỉiHd FlrefoK ẹte
w
;H8tọfY
Ioofe
:
Hello and welcoiiie to thỉs page. W an ù n g includiectỡcyư) C :\Prosraỉn FiJe$\ỉntemet Tooỉs\A pdche\htdoc$^hp 6 -bcak\chl 0\proiect01\àpp.php O ĩiiĩii 60 op^ĩmig ’ni' 5 suìg 61e p h p ' fo r ứỉcluMộn (mciudtf_pattỉ=*.;c.\ProgramFile\IntemetToộlí\PHP^eâr’) ìn Cĩ^l^-ogram F iies\lnl^m et Tooli\Apache\htđĐC 5^hpỐ-boQk\cỉilO\prạịectŨlVapp.php on hnt 60 Ooođbyc. W a n d n g ư ith iđ p ũ ÍB-mcticm m d u d e l Pôilcd
ŨOO0 Kình 10.3 Dầu ra lỗi xen lẫn với nội dung trang // dse custom handler set_error_handỉer(‘myHandler'); // create an output butter
166
C h ư ơ n g 1 0 : X ử lý c á c lỗi
ob_start(); // detine a cusíom handler // vvhich logs errors to the database / / then generates an error page íunction m yHanđler($type, $msg, $file, $line. $context) {
// log error to database $db ^ 'app.db'; $ p d o = ,new PDO(‘‘sqlite:$db”); Smsg
// reset and close the output buffer n generate a new error page ob_end_clean{); $errorPage = ‘ “ DTD/xhtm l1-transitionai,dtci"> <htm l xmlns="lìtlp://www,w3.org/19S9/xh1.m í'’ !(ml:ỉan9 = 'e n ’’ ìang=^” eiV’> <head> <titỉe>Error Page</titỉe> </head> <body> / / outpưt some page text
echo “ Goũdbye."; // dump output bưffer o b _en d Jlush ();
?> </body> < /htm l>
N goài nhữ ng th à n h p h ần m à b ạn dã quen thuộc (PDO và các phương thức xử lý lỗi tùy ý), sc rip t này cũng giới thiệu m ột sin h v ậ t mới; các hàm điều k h iể n đầu r a của PH P. Như vứi tê n gọi, những h à m n à y cung cấp m ột cách cho các n h à p h á t triể n tác động m ột mức độ điều k h iể n cao lên trê n đầu ra được tạo r a bởi m ột script PHP.
168
C h ư ơ n g 10: x ử lý các lỗi
Các h à m điều k h iể n đầu ra của P H P làm việc bằng cách chuyển hướng tấ t cả đầu ra dược tạo bdi m ột scrip t sang m ột bộ đệm đầu ra (output buíTer) đ ặc b iệ t tr o n g bộ n h ớ th a y vì gởi n ó trực tiế p s a n g t r ì n h d u y ệ t Client. Nội dung của bộ đệm này vẫn n ằm trong bộ n h ớ cho dến khi chúng được làm cho n h ìn th ấ y được đối với người dùng một cách tường m inh. H oặc chúng có th ể bị xóa đơn giản b ằn g việc xác lập lại bộ đệm. Có ba h à m chính để học về API điều khiểr, đầi: ra của PH P: K Hàm ob_start() khởi lạo một bộ đệm đầu ra va chuẩn t)ị nó đ ê chặn idệc X ìiấ t một script. Không cản phải nói bàm này nên đĩcạc gọi intôc hhi hất kỳ iíii/ ra được tạo bồi scripi. m Hàm ob_cndJĩushO kết thúc việc đệm đầu ra (onípul biiffcring) và gởi
nội đung hiện hành cứa bộ đệìu đầu ra sang thiết bị đẩu ra Ohường là trình duyệt của ngKòi dùng). •
ỉỉà m o b _ c n d .íic a u C kổĩ ihúc viộc đ ệm đâu ra , ’à làm scỊCi' n ộ i dìDĩiĩ
hiộn hãnh của bộ đệm đầíi ra. Sau k h i ghi nhớ lý th u y ết này, hăy quay trở lại scrip t trước để th ấ y các bộ đệm đầu ra có th ể giúp tạo các tran g lỗi sạch hơn như th ế nào. N hìn th o án g qua scrip t n ày và bạn sẽ th ấy rằn g nó b ắ t đầu b àn g việc khởi tạo m ột bộ đệm đầu ra mới b ằn g ob_start() và m ột phương thức xử iý lỗi tùy ý với set_error_handler(). B ất kỳ đầu ra được tạo ra bởi scrip t bây giờ sẽ được lưu trừ tro n g bộ đệm đầu ra cho đến khi nó được giải phóng san g Client b ằn g cách gọi đ ến ob_end_flush(). B ây giờ h ãy xem x ét những gì xảy ra k h i m ột lỗi x u ất h iệ n tro n g script. Đ ầu tiê n phương thức xử lý lỗi tùy ý sẽ chặn lỗi nàv, m ở m ột h an d le PDO d ẫn đến íile cơ sở dữ liệu SQLite được tạo ở bước trước và chuyển đổi lỗi (thông báo hoặc cảnh báo) th à n h m ột query SQL INSERT. Sau đó query này sẽ được sử dụng để lưu lỗi sang cơ sở dữ liệu b ằn g phương thức execO của PDO cùng với ngày th á n g và thời gian chính xác, Sau đó việc gởi đến ob_end_clean{) sẽ xóa sạch bộ đệm dầu ra, gởi m ột tra n g lỗi tùy ý đ ến trìn h duyệt và k ế t thúc việc thực th i script. K ết quả của những h à n h động này là th ậ m chí nếu dã có m ột tra n g Web đang được tạo n h a n h k h i lỗi xảy ra, nó sẽ k hông bao giờ th ấ y á n h sán g ban ngày vì nó sẽ bị loại bỏ và được th a y th ế bằng tra n g lỗi tùy ý. M ặt k hác nếu scrip t thực th i không có b ấ t kỳ lỗi nào, việc gọi sau cùng đến ob_end_flush() sẽ giải quyết việc gởi tra n g đầy đủ và sau cùng đến trìn h duyệt.
C h ư ơ n g 10: X ử iý các lỗi
169
^ ^E rto rP d Q e • NoỉH là FÌreÍ6M
St ij htlp://Vxd(hc»ỉ/^Tỗ*boc^ch)0/pr9)ect01/4pp.php
Oops! T Id ỉ $crip: e n c o u n te rc 4 àn inU m ã! tTTữĩ and w a s u nâble t o ex e c u ỉe T he ecTor ha$ b t m lo g g c d a n d wỉU b e re c b £ c d a s ỉỗ o n â s
possible
U aỉiỉ tbcDỉ p ỉe a s e r e tu m to th e h o m e p ỉg e ao d se ỉe c t anoCher acbvicy.
i»«*.
- í - '
'■
Hình 10.4 Một trang lỗi sạch
Và bây giờ, k h i b ạ n chạy script, thay vì đầu ra bị hỏng được m in h họa trước đó tro n g h ìn h 10.3, b ạn th ấ y m ột tra n g lỗi sạch hơn nhiều, H ìn h 10.4 m inh họa nhữ ng gì có lẽ b ạn sẽ thấy. T rong k h i b ạ n xem nó, cũng h ãy n h ìn vào báng SQLite. B ạn có th ể th ấy
các th ô n g báo được đưa ra bởi scrip t như được m inh họa ở đây: 1l13-Feb-2008 06:25;30IUndefíned variable: m yVar!/ch10/project01/ app.phpl28 2l13-Feb-2008 06:25:301include(missingfíle.php): íaiied to open stream; No suctí file or đirectoryl /ch10/projecf01/app.phpl31 3l13-Feb-2008 06:25:30iinclude(): Failed opening 'm issingíile.php' for inclusion (include_path='. ')l/ch10/project01/app.phpl31
M ột th iế t lặp n h ư vậy làưi cho m ột Iihà quản trị n h à p h á t triể n dỗ duy trì m ột record thư ờng trực của các lỗi script và quay trở về record n ày b ất cứ lúc nào để xem hoặc kiểm tr a các lỗi trong khi cũng bảo đảm rằ n g sự trả i nghiệm người dùng là n h ấ t quán và không phức tạp.
C h ư ó n g 10: x ử lý các lỗi
170
Hỏi chuyên gia
Hỏi: Điều gi xảy ra nếu bản thân hàm xử lý lỗi tùy ý chứa một lỗi? Đ áp: Nếu bản th â n p h ư ơ ng thúc xử lý lỗi tùy ý chứa một lỗi, lỗi sẽ được chuyển hướng lên cơ cấu xử iý lỗi mặc định của PHP. Do đó. ví dụ nếu m ã b ê n t r o n g m ộ t p h ư ơ n g t h ứ c lỏi t ù y ý tạ o r a m ộ t c ả n h b á o , c ả n h b á o
này sẽ được báo cáo theo cãp báo cáo lỗi hiện hành của PHP và được x ỉ í lý b ở i p h u ơ n g th ứ c x ử lý lỗi m ặ c đ ịn h c ủ a P H P .
Sử dụng các ngoại iệ N goài các ỉõi, P H P 5 cũng giới thiệu m ột mò h ìn h ngoại lộ mới [ương tự
như mô h ìn h được sử dụng bởi những ngôn ngữ lậo trìn h k h ác n h ư Ja v a và P ython, T rong phương p h áp dựa vào ngoại lệ này, m ã chương trìn h được hao tvong rAỘt. lchối và cốc ngoí^i lệ >1ươc tạo ''a bỏ'i '10 được "đón bổt" và được giổi quyết ‘udi n iộ t hoặc n h iềa khô'i catch. Bởi vì nhiều khô'i cAtch cõ th ể thực h iệ n được, các Iihà p h á t triề n có th ế bẩy (trap ) các loại ngoại lô khác n hau và xử lý mỗi loại ngoại lệ m ột cách khác nhau. Để m in h h ọ a điều này, hãy xem xét listin g sau đây, listin g n ày ccT tạo m ột p h ầ n tử m ản g không tồn tạ i sử đụng m ột A rray lterato r: <’ php / / detine array $cities » ãrray( "United Kingdom ” => “ London", "United States" => “W ashington” , “ Prance" => '‘Paris", "In d ia ” => “ Delhi”
): // try accessing a non-existent array element // generates an OutOíBoundsException //
oLrtpưt; 'Exception: Seek posỉtion 10 is out of range’
Xử lý các lòi_____________________________________________________ 1 7 1
C h ự ớ n g 10:
echo ‘ ERROR: An exception occurred in your script.';
) ?> K hi P H P gập p h ải lỗi được bao bọc bên trong m ột khôi try-catch, đầu tiên nó cô’ thực th i m ã bên trong khối try. Nếu m ã này được xử lý m à không có bâ’t kỳ ngoại lệ được tạo ra, quyền diều khiển được chuyển san g các dòng sau khcTi try-catch. Tuy nhiên, nếư m ột ngoại lệ được tạo ra tro n g k h i chạy m ã bên tro n g khô'i try (như xuất h iện trong listing trước), P H P dừng thực th i khối tạ i điểm đó và b ắ t đầu kiểm tr a mỗi khôi catch để xem có m ột phương thức xử lý trong ngoại lệ hay không. Nếu m ột phương thức xử lý được tìm th ấy , m ã bên trong khối catch thích hợp được thực th i và sau đó các đòng sau khối try được thực thi. Nếu không, m ột lồi ngh iêm trọ n g sẽ được tạo ra và việc thực th i script dừng tại điểm lỗi. Mọi đối tượng Exception đều chứa thêm một sô’ th ô n g tin m à có th ể được sử dụng cho việc gỡ rối nguồn của ỉỗi. Thông tin này có th ể được tru y cập thông qua các phương thức cài sẵn của đối tượng E xception và chứa m ột thông báo lỗi mô tả, m ột m ã lỗi, tê n file và số dòng gây r a lỗi và m ột backtrace của các lần gọi ra hàm d ẫn đến lỗi. B ảng 10.3 liệ t kê những phương thức này. Và bản ch ĩn h sửa sau đây của listing trước m inh họa nhữ ng phương thức này đ ang sử dụng: / / detine array $cỉties = array{
?> Bảng 10.3 Các phương thức cùa dãi tượng ngoại lệ PKP
T ên p h ư ơ n g th ứ c
C hứ c n ă n g
gt'tHessag'ỉ(;
Trả về laột ihón^ báo mô tả trực trặo xảy ra
getCodeO
Trâ về một mă lỗi dạng sô’
getFile()
Trả về đường dẫn đĩa và tê n củascript tạo ra ngoại lệ
getLineO
Trả về số dòng đã tạo ra ngoại lệ
getTraceO
Trả về một backtrace của các lệnh gọi dã dẫn đến lổi dưới dạng một mảng
getTraceAsStringO
Trả về một backtrace của các lệnh gọi đã
dẫn đến lỗi dưới dạng một chuỗi
gì dá
đà
• • • • • • •
Thủ thuật c ỏ t h ể x ứ lý c á c loại ngoại lệ k h á c nhau m ộ t cá c h k h á c n h a u b ằ n g v iệ c tạo nhiều khốỉ catrh và gán các hành độníỊ khác nhau vào mỗi khối này. Bạn sẽ thấy một ví dụ về điều nàỵ thêm nữa trong chương. Bây giờ nếu b ạn nghĩ về nó, có th ể b ạn muôn k ế t luận rà n g các ngoại lệ chỉ là rượu cũ tro n g m ột b ìn h mới. Rốt cuộc những k h ả n ăn g đă được mô tả dường như hoàn toàn dễ dàng sao chép sử dụng m ột phương thức xử lý lỗi tùy ý như được mô tả tro n g p h ần trước. Dù vậy, thực t ế phương pháp dựa vào ngoại lệ n ày tin h vi hơn nhiều so với vẻ bề ngoài bởi vì nó m ang lại thêm những lợi ích sau đây: # Trong mô hình trỉiyền thống, cần kiểm tra giá trị trả vẻ của mọi hám được gọi đ ể nhận dạng xem một lỗi đã xảy ra bav kbông và có hành động Sĩỉa chũa. Điều n à v có th ể tạo ra mã phức tạp không cẩỉì thiết và các khối mã điíợc xếp lồng sản. Trong mô hình dựa vào ngoại lệ, một
C h ư ớ n g 10: x ù lý các lỗi___________________________________ 173
khối catch có thể điíợc sử dụng đ ể đầy bất kì> lỗi xả v ra trong khối mã irìíớc. D iều n à v loại bỏ nhu cầti về n hiều cuộc test lỗi p h â n tầng và nó
tạo ra mã đơn giản bơn lủ dẻ đọc hơn.
» về bản chất mô hình tniyền thống có tinh m ệnh lệnh: nó đòi hỏi nhà p h á t triển suy n g h i qua tổi cả lỗi m à có th ể x ả v ra và viết m à đểXÌC lý từ ng k h ả n ă n g này. Trái lại, phương p h á p d ự a i>ào m ô h ìn h lin h hoạt b ơ n . M ộ t p h ỉ i ơ n g t h ứ c x ừ lý n g o ạ i lệ g e n e r i c l à m v i ệ c n h ĩC m ộ t l ư ớ i a n
loàn, đ ó n bắt và x i'( ỉỷ thâm chí các lỗi m à m ã X íỉ lý lỗi cụ th ể đ ã không đĩCỢC niết. Đ iều n à y c h ỉ giúp là m cho m ã ĩỉng d ụ n g m ạ n h hơn và kiên ciíờng irìcởc n h ũ n g tình hiiống không lítòng tntớ c đĩCỢC.
K Bởi vì mô hình ngoại lệ đã sứ dụng mộ/ phương pháp dựa vào đối tượng, các nhà phát triển đã có thểsữdiỊng nhũng khái niệm OOP về sự tbừa k ế (inheritance) và khả nãng mở rộng (extensibiỉity) đ ể tạo subclass (class con) cho đối tỉiỢng Exception cơ sở và tạo nhĩìng đối tiíợng Exception khác nhau cho những loại ngoại lẽ khác nhan. Diềỉi nàv làm cho có thể phân biệt giũa các loại lỗi khác nhau và x ứ lý mọi loại lỗi mộl cách kbác nhau. ^ Phtíơng pháp dựa vào ngoại lệ bỉiộc các nhà phái triển đìCa ra những quyết định khó khăn í,’ể cách x ứ lý các loại lỗi khác nhau. Không giống n h ư trong m ô h ìn h truyền thống nơi các nhà p h á t tn ể n có th ể d ễ dàng
bỏ qita (i>ô ỷ hoặc cố ý) các cuộc lest đ ể kiểm tra giá trị trà về của các hàm , cá c ngoại lộ không d ễ bỏ qua n h ií vậv. B ằng việc đ ò i hỏi cá c nhà p h á t triển tạo I'à tập hợp lại các khối catch, m ô b ìn h ngoại lệ buộc họ su y n g h ĩ ĩ>è nhữ n g ngnvôn n h â n và hậít quả cửa cá c lỗi nà cuối củng d ã n đ ế n sự th iết k ế tốt hơn và Sỉ[ thực thi m ạ n h bơn.
K huyết diểm duy nh ất? Các ngoại lệ dã được giới th iệ u chỉ tro n g PH P 5, và k ế t quả chúng chỉ được tạo riêng bởi extension ngôn ngữ mới hơn chẳng h ạn n h ư SimpIeXML, P H P D ata Object (PDO), Service D ata Objects (SDO), và S ta n d a rd P H P L ibrary (SPL). Đô'i với tâ't cả hàm P H P khác, cần phải kiểm tr a giá tr ị tr ả về của hàm dể tìm các lỗi và chuyển đổi nó th à n h m ột
ngoại lệ (exception) bằng th ủ công. Tạo hoặc đưa ra các ngoại lệ bằng thủ công theo cách n ày là m ột tác vụ cho câu lệ n h throw của PH P. Câu lệnh này cần được chuyển m ột th ô n g báo lỗi mô tả và m ột m ã lỗi tùy ý. Sau đó nó tạo ra m ột đối tượng Exception sử dụng n h ữ n g th a m sô n ày và làm cho th ô n g báo và m ă có sẵ n cho phương thức xử lý ngoại lệ. T iến trìn h này dược m inh họa tro n g listin g sau đây:
174
C h ư ở n g 1 0 : x ử lý c á c lỗi
<’í’ php // set file name / / attem pt to copy and then đelete file $file = 'đum m y.txt'; try { if {!file..exists($fiỉe)) ỉ t h r c w r e w E x c e p tỉo n (“ rỉle '$ file ’ w a s not íGund,");
I 'f (fi:e_extst3í"$f:le.new"}) ! throw new Exception(“ Destination file '$file.new’ already exists.” )'
1 if (!copv($file, "$íile.n9w” }) { throw new Exception(“ filfi '$file’ couid not be copieđ."),
) íf (!unlínk($file)) {
throw new Exception(“ File ’$file’ Cũuld not be rem oved,” };
) í catch {Exception $e) { echo ‘Oops! Something bad happened on ỉ i n e ' , $e->getLíne() .
,
$e->getMessage(); exit(); I echo 'SUCCESS; Filc operation successtul.';
?> ở đây phụ thuộc vào k ế t quả của các h o ạ t động file k h ác nhau, m ột ngoại lệ mới được đưa ra bởi script bằng th ủ công. Sau đó ngoại lệ n ày được đón b ắ t bởi phương thức xử lý ngoại lệ generic và thông tin d à n h riên g cho lỗi được trích xuất và được h iển th ị cho người dùng. Sử dụng các ngoại lệ tù y ý M ột phương p h áp tin h vi hơn là tạo subclass cho đối tượng Exception generic và tạo các đôi tượng Exception riên g b iệ t cho mỗi lỗi. Phưcmg pháp này hữu dụng k h i b ạn cần xử lý các loại ngoại lệ khác n h au m ột cách khác
x ù lý các íỗi____________________________
C h ư d n g 10;
___________________1 7 5
nhau vì nó cho phép b ạn sử dụng một khối catch riên g b iệt (và m ã xử lý riêng biệt) cho mỗi loại ngoại lệ. Sau đây là m ột b ản chỉnh sửa của ví dụ trước m inh h ọ a phương pháp này: / / subclass Exception class MỉssingPileException extends Exception I Ị class DuplicateRleException extends Exception { ) ciass PilelOException extends Exception {1 / / set file name // attem pt to Cũpy and then delete file $file = 'dum m y.txt’ ; trỵ { if (!file_exists($file)) { throvv new MissingFileException($file);
1 if (file_exlsts("$file,new ")) í throvv new DuplicateFileException('‘$file,new’’);
} if (!copy($fiie, “ $file.new” )) { throw new FilelQException(“ $fỉle.new” ); I if (!unlink($file)) { throw new FilelOException($fiie);
1 } catch (MissingPileException $e) { echo 'ERROR: Could not find file \” , $e->getMessage() , exit();
} catch (DuplicatePileException $e) ( echo 'ERROR: Destinaíion file V’ . $e->getMessage() . 'V already exỉsts’; exit(); I catch (PỉlelOException $e) { echo 'ERROR: Could not pertorm tile inpuưoutput operation on file \" .
1 7 6 _______________________________________________________ C h ư ớ n g 1 0 : x ử lý c á c lỗi $e->getMessage() . exit{); } catch (Exception $e) I echo ‘Oops! Something bad happened on line ‘ , $e->getLine() .
‘ . $e-
>gelMessage(); exiự);
} echo '3UCCESS: Rle operatìon successíul.’;
?> Script này mở rộng class Exception cơ sở để tạo ba loại E xception mới, mối loại tượng trư n g cho m ột lỗi catch có th ể có. M ột khô’i catch riên g biệt cho niỗi Exception bày giờ làin cho có th ể vùy biến cách xử lý mui m ọt trong ba Exception Iià.v, lỉliối catch sau cùng là m ột phương thúe xử lý "bao tĩùm " generic: Các Iigoại lệ khỏiig được xử lý bởi các khô'i cụ th ế nơn ở tré n nó sẽ th ấ t bại và sẽ được giải quyết bằng phương thức xử lý nàvHỏi chuyên gia H ỏ i: T ô i đ ă th ấ y rằ n g c á c n g o ạ i lệ k h ô n g đư ợ c đ ó n b ắ t tạ o ra m ộ i lỗi p g h iê m trọ n g kh iế n c h o s c n p t k ế t th ú c đ ộ t ngột. T ô i cc th ể th a y d ổ i (iiể u
n à y đ ư ợc h a y kh ò ng ?
Đáp: Có và kh ô n g . PHP đưa ra hàm set_error_handler(), cho phép bạn thay thế phương thức xử lý ngoại lệ mặc định của PHP bằng mâ tùy ý riêng của bạn giống y như cách với set_error_handler(). Tuy nhiên, có một điều kiện quan trọng cần lưu ý ở dây. Như bạn đã thấy phương thức xử lý ngcại lệ mặc định của PHP hiển thị một thông báo và sau đó kết thúc ngoại !ệ script. s ử đụng một phương Ihúc xử lý ngoại lệ tùy ý cho p h é p giới n ạn Kiểm so á t h à n n vi này; Trong khi b ạ n c ó thể th ay th ế kiểu
cách và diện mạo của sự hiển thị thông báo, bạn không thể làm cho s c rip t tiế p tụ c th ự c thi bên n g o à i đ iể m n ơ i n g o ạ i lệ đ ả đ ư ợ c tạ o .
Bài học của v ấn đề n ày là m ột ngoại lệ không được đón b ắ t sẽ luôn dẫn đến việc k ế t thúc script. Đây ià lý do tạ i sao luôn n ê n bao h àm m ột khối catch generic tro n g m ă xử lý ngoại lệ để đón b ắ t tấ t cả ngoại lệ được đưa ra bởi script, b ấ t kể loại.
C h ư d n g 10; x ử íý
các lỗi__________________________________
177
Thực hành 10.2: Hiệu lực hóa đầu vào của Form Bây giờ k h i b ạn đã hiểu cơ bản cách làm việc của các ngoại lệ cũng như cách đưa ra v à đón b ắ t các ngoại lệ tùy ý, hãy áp dụng k iến thức này vào m ột dự á n nhỏ m in h h ọ a thực t ế những công cụ này. Ví dụ tiế p theo này cho phép người dùng đ ặ t h à n g m ột số tác phẩm mỹ th u ậ t b ằn g cách chọn m ột
nghệ sĩ, phương tiệ n và phạm vi giá cả. Sau đó đơn đ ặ t h àn g được hiệu lực hóa, được đ ịn h d ạn g b ằn g m ột th ô n g báo email, và được chuyển san g mail se rv e r để gởi. Các lỗi tro n g tiế n trìn h này được xử lý sử dụng các phương thức xử lý ngoại lệ tùy ý, tạo ra m ã dọc dễ hơn và dễ duy tr ì hơn. Sau đây là m ã (art.php): “ D T D /xh tm ll-tran sitio na l.d td ’’> <htm! xm ins= "http ;//w w w .w 3.org/1999/xhtm r' xm l:lang=” en” lang="en” > <heađ> <title>Project 10-2: Vaỉidating Form lnpưtc/tit!e>