pokročilé programování v php5

50
George Schlossnagle Techniky objektově orientovaného programování, používání vzorů a šablon. Tvorba aplikačního programového rozhraní. Obsluha chyb a výjimek. Ladění výkonu a testování. Distribuované aplikace a interakce s databázemi. Správa relací (session), autentizace a bezpečnost. PHP a Zend Engine. P H P pokročilé programování v E N C Y K L O P E D I E W E B D E S I G N E R A © Foto: Jiří Heller www.zonerpress.cz DEVELOPER’S LIBRARY

Upload: zoner-software-as

Post on 09-Mar-2016

251 views

Category:

Documents


10 download

DESCRIPTION

Pokročilé programování v PHP5

TRANSCRIPT

Page 1: Pokročilé programování v PHP5

CYAN MAGENTA YELOW BLACK CYAN MAGENTA YELOW BLACK

CYAN MAGENTA YELOW BLACKCYAN MAGENTA YELOW BLACK

GeorgeSchlossnagle

ENCYKLOPEDIE WEBDESIGNERA

Pokr

očilé

prog

ram

ován

í v

George Schlossnagle

• Techniky objektově

orientovaného programování, používání vzorů a šablon.

• Tvorba aplikačního programového rozhraní.

• Obsluha chyb a výjimek.

• Ladění výkonu a testování.

• Distribuované aplikace a interakce s databázemi.

• Správa relací (session), autentizace a bezpečnost.

• PHP a Zend Engine.

PHP pokročilé programování v

E N C Y K L O P E D I E W E B D E S I G N E R A

© Foto: Jiří Heller

www.zonerpress.cz

DEVELOPER’SLIBRARY

Pokročilé programování v

PHP je velmi oblíbený a rozšířený programovací jazyk, speciálně vyvinutý pro vytváření funkčně bohatých webových stránek, které jsou rychlé a spolehlivé. Časem však PHP přerostl své původní ur-čení a nyní se používá v různých prostředích pro nejrůznější účely. Programátoři PHP začali využívat tento jazyk úplně všude – počí-naje internetem a konče příkazovou řádkou, a to takovými způso-by, jaké by si ještě před nedávnem málokdo dokázal představit.

Kniha Pokročilé programování v PHP 5 poskytuje zkušeným vývojá-řům techniky pro používání PHP ve velkém měřítku v podnikovém prostředí. Kniha se zaměřuje na PHP 5 a detailně se věnuje tech-nikám objektově orientovaného programování, testování, bezpeč-nosti, technikám a technologiím cachování, vyvíjení škálovatel-ných distribuovaných aplikací a ladění výkonu. Rovněž obsahuje komplexní pojednání o různých rozšířeních PHP. Kniha prakticky a jasně vysvětluje i nejsložitější koncepty, a pro ilustraci poskytuje vždy úplné a reálné příklady.

Georgie Schlossnagle je přispěvovatel projektu PHP a autor modu-lu Apache. Má dlouholeté praktické zkušenosti s vytvářením vel-kých PHP webů a aplikací. Dva roky pracoval jako senior architekt v CommunityConnect, Inc., kde pomáhal při řešení systému se zá-těží až 130 miliónů dynamických PHP požadavků denně. Je rovněž autorem dvou rozšíření Zend Engine pro PHP – obě byla vyvinu-ta v rámci zvýšení výkonu na transakčních webových systémech. Přednáší na konferencích a je ředitelem OmniTI Consulting, kde buduje vysoce výkonná webová a emailová řešení.

George Schlossnagle

PHP 5

Zoner Press tel.: 532 190 883 fax: 543 257 245e-mail: [email protected]://www.zonerpress.cz

ZONER software, s.r.o., Koželužská 7, 602 00 Brno

E N C Y K L O P E D I E W E B D E S I G N E R A

Pod tímto logem vycházejí publikace určené pro každého, kdo se zajímá o tvorbu webových stránek. Od ryze praktických příruček a průvodců až po komplexní publikace o všem, co potřebuje web-designér při každodenní práci. Na slevy, které můžete získat, a vy-davatelský plán, v němž vedle knih domácích odborníků najdete celou řadu titulů světově uznávaných autorů, se informujte na ad-rese vydavatelství. Věrným čtenářům je určen výhodný PRÉMIOVÝ PRÉMIOVÝ PLUSPLUS PROGRAM PROGRAM.

© F

oto:

Jiří

Hel

ler,

ww

w.h

elle

r.cz

Foto

grafi

e z

nabí

dky

foto

bank

y H

ELLE

R.CZ

9 7 8 8 0 8 6 8 1 5 1 4 5

ISBN 80-86815-14-5KATALOGOVÉ ČÍSLO: ZR412

ZONER software, s.r.o. významný producent software v oblasti digitální fotografie,

počítačové grafiky a multimédií, poskytovatel internetových

služeb, souvisejících s prezentací na internetu a e-komercí,

a nakladatelství odborné literatury.

www.zoner.cz

obal_PHP_opraveny.indd 1obal_PHP_opraveny.indd 1 17.12.2004 17:28:0317.12.2004 17:28:03

Page 2: Pokročilé programování v PHP5

George Schlossnagle

PHPPokročilé programování v

5

DEVELOPER’SLIBRARY

php5 - pokrocile programovani.indb 1 21.9.2004 10:32:50

Page 3: Pokročilé programování v PHP5

Authorized translation from the English language edition, entitled ADVANCED PHP PROGRAMMING, 1st Edition, 0672325616 by SCHLOSSNAGLE, GEORGE, published by Pearson Education, Inc, publishing as Que/Sams, Copyright © 2004 by Sams Publishing.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. CZECH language edition published by ZONER software s.r.o., Copyright © 2004

Autorizovaný překlad anglického vydání nazvaného ADVANCED PHP PROGRAMMING, první vydání, 0672325616 autor SCHLOSSNAGLE, GEORGE, vydal Pearson Education, Inc, ve vydavatelství Que/Sams; Copyright © 2003 Sams Publishing.

Všechna práva vyhrazena. Žádná část této publikace nesmí být reprodukována nebo rozšiřována žádnou formou nebo způsobem, elektronicky ani mechanicky, včetně fotokopií a natáčení, ani žádnými jinými systémy pro ukládání dat bez výslovného svolení Pearson Education, Inc. České vydání vydal ZONER software s.r.o., Copyright © 2004.

Pokročilé programování v PHP 5Autor: George Schlossnagle

Copyright © ZONER software s.r.o. Vydání první v září 2004. Všechna práva vyhrazena.

KATALOGOVÉ ČÍSLO: ZR412

Zoner PressZONER software s.r.o.Koželužská 7, 602 00 Brno

Překlad: Jan Gregor, Jan Kuklínek, Václav Šimek, Martin Wokoun

Odpovědný redaktor: Miroslav Kučera

DTP: Miroslav Kučera

© Cover foto: Jiří Heller, HELER.CZ s.r.o., www.heller.cz

© Cover: Ing. Pavel Kristián

Informace, které jsou v této knize zveřejněny, mohou byt chráněny jako patent. Jména produktů byla uvedena bez záruky jejich volného použití. Při tvorbě textů a vyobrazení bylo sice postupováno s maximální péčí, ale přesto nelze zcela vyloučit možnost výskytu chyb.

Vydavatelé a autoři nepřebírají právní odpovědnost ani žádnou jinou záruku za použití chybných údajů a z toho vyplývajících důsledků.

Všechna práva vyhrazena. Žádná část této publikace nesmí být reprodukována ani distribuována žádným způsobem ani prostředkem, ani reprodukována v databázi či na jiném záznamovém prostředku či v jiném systému bez výslovného svolení vydavatele s výjimkou zveřejnění krátkých částí textu pro potřeby recenzí.

Veškeré dotazy týkající se distribuce směřujte na:

Zoner Press ZONER software s.r.o. Koželužská 7, 602 00 Brno

tel.: 532 190 883, fax: 543 257 245 e-mail: [email protected] http://www.zonerpress.cz

ISBN 80-86815-14-5

php5 - pokrocile programovani.indb 2 21.9.2004 10:33:42

Page 4: Pokročilé programování v PHP5

Stručný obsah

Část I:Implementační a vývojářská metodikaKapitola 1 – Styl zápisu kóduKapitola 2 – OOP prostřednictvím návrhových

vzorůKapitola 3 – Zpracování chybKapitola 4 – Implementujeme s PHP:

Šablony a webKapitola 5 – Implementujeme s PHP:

Samostatné skriptyKapitola 6 – Testování jednotekKapitola 7 – Správa vývojového prostředíKapitola 8 – Návrh dobrého API

Část II:CachováníKapitola 9 – Vnější ladění výkonu Kapitola 10 – Cachování dat Kapitola 11 – Znovupoužití výpočtů

Část III:Distribuované aplikaceKapitola 12 – Interakce s databázemiKapitola 13 – Autentizace uživatele a bezpeč-nost relaceKapitola 14 – Zpracování relaceKapitola 15 – Tvorba distribuovaného prostředíKapitola 16 – RPC: Interakce se vzdálenými

službami

Část IV:VýkonKapitola 17 – Srovnávací testy: testování celé

aplikace Kapitola 18 – ProfilováníKapitola 19 – Syntetické benchmarky:

zaostřeno na bloky kódu a funkce

Část V:RozšiřitelnostKapitola 20 – Pohled do útrob PHP a Zend

EnginuKapitola 21 – Rozšiřujeme PHP: část IKapitola 22 – Rozšiřujeme PHP: část IIKapitola 23 – Vytváříme rozhraní SAPI

a rozšiřujeme Zend Engine

php5 - pokrocile programovani.indb 3 21.9.2004 10:33:42

Page 5: Pokročilé programování v PHP5

Stručný obsah – 3

O autorovi – 10

Poděkování – 10

Sdělte nám svůj názor – 11

Služby čtenářům – 11

Předmluva – 12

Úvodní povídání – 13PHP v komerční sféře – 13

Struktura a uspořádání knihy – 15Část I – Implementační a vývojářská metodika – 15Část II – Cachování – 16Část III – Distribuované aplikace – 16Část IV – Výkon – 17Část V – Rozšiřitelnost – 18

Platformy a verze – 18

I – Implementační a vývojář-ská metodika – 19

1. Styl zápisu kódu – 21Volba správného stylu – 22

Formátování a úprava kódu – 22Odsazení – 22Délka řádku – 25Formátovací znaky a mezery – 26Direktiva SQL – 26Řízení toku programu – 27

Názvosloví – 32Konstanty a globální proměnné – 33Dlouhodobé proměnné – 35Dočasné proměnné – 35Víceslovné názvy – 36Názvy funkcí – 37Názvy tříd – 37Názvy metod – 37Názvy proměnných přizpůsobte názvům schémat – 38

Vyhněte se matoucímu kódu – 39Vyhněte se používání zkrácených značek – 39

Vyhněte se používání příkazu echo pro vytváření HTML – 40Závorky používejte rozumně – 41

Dokumentace – 42Vnořené komentáře – 42Dokumentace API – 43

Další informace – 48

2. OOP prostřednictvím návrhových vzorů – 49

Objektově orientované programování – 50Dědičnost – 52Zapouzdření – 53Statické (třídní) atributy a metody – 53Speciální metody – 54

Stručný úvod do návrhových vzorů – 56Adaptér vzoru – 56Šablona vzoru – 61Polymorfismus – 62Rozhraní a kontrola typu – 64Vzor Factory – 67Vzor Singleton – 68

Přetěžování – 70SPL a iterátory – 76_ _call() – 81

Další informace – 85

3. Zpracování chyb – 87Zpracování chyb – 90

Zobrazování chyb – 90Logování chyb – 91Ignorování chyb – 92Reagování na chyby – 92

Zpracování vnějších chyb – 94

Výjimky – 97Používání hierarchií výjimek – 100Příklad s typovými výjimkami – 102Řazení výjimek do kaskády – 108Zpracování chyby konstruktoru – 112Instalování funkce pro zpracovánívýjimek nejvyšší úrovně – 113Validace údajů – 115

Podrobný obsah

php5 - pokrocile programovani.indb 5 21.9.2004 10:33:42

Page 6: Pokročilé programování v PHP5

Kdy používat výjimek – 119Další informace – 120

4. Implementujeme s PHP: Šablonya web – 121

Smarty – 122Instalace systému Smarty – 122Vaše první Smarty šablona: Ahoj světe! – 124Zkompilované šablony pod pokličkou – 125Řídící struktury systému Smarty – 125Funkce systému Smarty a více – 128Cachování se systémem Smarty – 131Pokročilé vlastnosti Smarty – 132

Vytvoření vlastního řešení pomocí šablon – 134Další informace – 136

5. Implementujeme s PHP: Samostatné skripty – 137

Úvod do rozhraní příkazové řádky PHP (CLI – Command-Line Interface) – 139Zpracování vstupu/výstupu (I/O) – 139Analýza argumentů příkazové řádky – 142Vytváření a správa procesů potomků – 144

Zavírání sdílených zdrojů – 145Sdílení proměnných – 146Úklid procesu potomka – 146Signály – 148

Vytváření démonů – 152Nastavení pracovního adresáře – 153Zrušení oprávnění – 154Zaručení exkluzivity – 155

Kombinace toho, co jste se naučili: monitorování servisů – 155Další informace – 164

6. Testování jednotek – 167Úvod k testování jednotek – 168

Tvorba testů pro automatizované testování jednotek – 169Tvorba vašeho prvního testu jednotky – 169Přidávání hromadných testů – 170

Tvorba vložených a externích testů jednotek – 171

Přibalení mezi řádky – 172Zabalení samostatných testů – 174Současné spouštění hromadných testů – 175

Další vlastnosti PHPUnit – 176Tvorba chybových zpráv s vyšší informační hodnotou – 177Přidání více testových podmínek – 178Použití metod setUp() a tearDown() – 179Přidání naslouchačů – 180Používání grafického rozhraní – 182

Testy řízený návrh – 182Fleschův výpočet hodnocení – 183Testování třídy Word – 183Chybová zpráva 1 – 192

Testování jednotek ve webovém prostředí – 194

Další informace – 196

7. Správa vývojového prostředí – 197Kontrola změn – 198

Základy CVS – 199Úpravy souborů – 202Sledování rozdílů mezi soubory – 203Snazší spolupráce více vývojářů na jednom projektu – 206Symbolické značky – 207Větve – 208Údržba vývojových a provozních prostředí – 209

Správa balíčkování – 214Balíčkování a přesun kódu – 215Balíčkování binárek – 217Balíčkování Apache – 218Balíčkování PHP – 219

Další informace – 220

8. Návrh dobrého API – 221Návrh dovolující přepracování a rozšiřování – 222

Doplnění funkcí významem – 223Udržování jednoduchých tříd a funkcí – 224Jmenné prostory – 224Redukce vazeb – 226

Opatrné programování – 227Zavádění standardních zásad – 228Používání sterilizačních technik – 228

php5 - pokrocile programovani.indb 6 21.9.2004 10:33:43

Page 7: Pokročilé programování v PHP5

Další informace – 230

II – Cachování – 2319. Vnější ladění výkonu – 233Ladění jazyka – 233

Cache kompilátoru – 233Optimalizátory – 236HTTP akcelerátory – 237Reverzní proxy – 238Vylaďování operačního systému na vysoký výkon – 242Proxy cache – 243

PHP aplikace vhodné pro cachování – 244

Komprese obsahu – 248

Další informace – 249Dokumenty RFC – 249Cache kompilátoru – 249Proxy cache – 249Komprese obsahu – 250

10. Cachování dat – 251Problematika cachování – 251

Rozpoznání cachovatelných dat – 253

Volba vhodné strategie: vlastní nebo hotové třídy – 253

Výstupní mezipamět – 254

Cachování v paměti – 256Cache v obyčejných souborech – 256Udržování velikosti cache – 256Souběžnost a souvislost cache – 257

Cachování založené na DBM – 263Souběžnost a souvislost cache – 264Zneplatnění cache a její správa – 264

Cachování ve sdílené paměti – 268

Cachování pomocí cookies – 269Správa velikosti cache – 274Souběžnost a souvislost cache – 275

Integrace cachování do kódu aplikace – 275Cachování domovských stránek – 278Chytřejší cachování s mod_rewrite pro Apache – 285Cachování části stránky – 288Implementace cache dotazů – 291

Další informace – 293

11. Znovupoužití výpočtů – 295Ilustrační příklad: Fibonacciho posloupnost – 295

Cachování znovupoužitých dat uvnitř požadavku – 302

Cachování znovu používaných dat mezi požadavky – 304

Znovupoužití výpočtů uvnitř PHP – 308PCRE – 308Délka polí – 308

Další informace – 309

III – Distribuované aplikace – 311

12. Interakce s databázemi – 313Jak fungují databáze a dotazy – 314

Zkoumání dotazu pomocí EXPLAIN – 317Hledání dotazů k profilování – 319

Vzory pro přístup do databáze – 321Dotazy Ad Hoc – 321Vzor Active Record – 322Vzor Mapper – 324Vzor Integrated Mapper – 330

Ladění přístupu k databázi – 332Omezení množiny výsledků – 332Líná inicializace – 334

Další informace – 336

13. Autentizace uživatele a bezpečnost relace – 339

Jednoduché autentizační schéma – 340Základní autentizace HTTP – 341Mungování řetězce dotazu – 341Cookie – 342

Registrace uživatelů – 343Ochrana hesel – 343Ochrana hesel proti sociálnímu inženýrství – 346

Udržování autentizace: zajišťování, že stále komunikujete se stejnou osobou – 347

php5 - pokrocile programovani.indb 7 21.9.2004 10:33:43

Page 8: Pokročilé programování v PHP5

Kontrola neměněného stavu proměnné $_SER-VER['REMOTE_IP'] – 347Kontrola neměněného stavu proměnné $_SERVER['USER_AGENT'] – 348Použití nezašifrovaných cookie – 348Co byste měli udělat – 348Příklad implementace autentizace – 350

Jednorázové přihlášení – 355Implementace jednorázového přihlášení – 358

Další informace – 363

14. Zpracování relace – 365Klientské relace – 366

Implementace relací přes cookie – 367Menší vylepšení aplikace – 369

Serverové relace – 370Sledování identifikátoru relace – 371Stručný úvod do relací PHP – 373Metody uživatelského ovladače relace – 375Garbage Collection aneb "sběr odpadu" – 380Rozhodování mezi klientskými a serverovými relace-mi – 382

15. Tvorba distribuovaného prostředí – 383

Co je to cluster? – 383

Základy tvorby clusterů – 386Plánování chyb – 386Dobrá spolupráce s ostatními – 387Distribuce dat do clusterů – 389Horizontální škálování – 389Specializované clustery – 390

Cachování v distribuovaném prostředí – 391Centralizované cachování – 393Plně decentralizovaná cache pomocí nástroje Spread – 395

Škálování databází – 398Tvorba aplikací pro architekturu master/slave – 402Alternativy k replikaci – 404Alternativy k systémům RDBMS – 405

Další informace – 406

16. RPC: Interakce se vzdálenými službami – 407

XML-RPC – 408Tvorba serveru: implementace MetaWeblog API – 410Informativní služby XML-RPC – 414

SOAP – 417WSDL – 419Přepis system.load na službu SOAP – 422Webové služby Amazonu a komplexní typy – 424Generování kódu proxy – 426

Srovnání SOAP s XML-RPC – 427

Další informace – 428SOAP – 428XML-RPC – 428Veřejně dostupné webové služby – 429

IV – Výkon – 431

17. Srovnávací testy : testování celé aplikace – 433

Pasivní identifikace slabých míst – 434

Generátory zátěže – 436Ab – 436httperf – 438Daiquiri – 440

Další informace – 441

18. Profilování – 443Požadavky na PHP profiler – 444

Nabídka profilovacích programů – 444

Instalace a použití APD – 445

Příklad trasování – 447

Profilování větší aplikace – 448

Zjišťování obecných nedostatků – 454

Odstranění zbytečných funkcionalit – 456

Další informace – 460

19. Syntetické benchmarky: zaostřeno na bloky kódu a funkce – 463

Základy benchmarků – 464

Vytváříme sadu benchmarků – 465Benchmarky a kolekce PEAR – 465Praktická stránka tvorby vlastních testů – 468

php5 - pokrocile programovani.indb 8 21.9.2004 10:33:43

Page 9: Pokročilé programování v PHP5

Pokaždé náhodná data – 469Odstraňujeme režii benchmarků – 471Přidáváme doplňující časové údaje – 472Píšeme inline benchmarky – 476

Benchmarky v příkladech – 477Porovnání znaků na začátku řetězce – 477Rozvoje makra – 478Interpolace versus konkatenace – 485

V – Rozšiřitelnost – 487

20. Pohled do útrob PHP a Zend Enginu – 489

Jak pracuje Zend Engine: operační kód a operační pole – 490

Proměnné – 496

Funkce – 499

Třídy – 501Manipulátory objektů – 503Vytvoření objektu – 503Další významné struktury – 504

Životní cyklus požadavku v prostředí PHP – 506

Vrstva SAPI – 507Jádro PHP – 510API pro rozšíření PHP – 510API pro rozšíření Zend Enginu – 511Jak jednotlivé části zapadají dohromady – 513

Další informace – 514

21. Rozšiřujeme PHP: část I – 515Základy rozšiřování – 516

Vytváříme základ rozšíření – 516Sestavujeme a používáme rozšíření – 519Používáme funkce – 520Typy a správa paměti – 523Parsování řetězců – 526Manipulace s typy – 527Typové konverze s testováním a řízení přístupu – 532Používáme zdroje – 536Generování chybových hlášení – 541Používáme zpětná volání u modulů – 541

Příklad: klient Spread a jeho obálka – 549

MINIT – 551

MSHUTDOWN – 552

Funkce modulu – 552

Používáme modul Spread – 560

Další informace – 561

22. Rozšiřujeme PHP: část II – 563Implementujeme třídy – 563

Vytváříme novou třídu – 564

Rozšíření vlastností třídy – 565

Třída a dědičnost – 568

Rozšíření třídy o metody – 568

Rozšíření třídy o konstruktor – 571

Generování výjimek – 572

Použití vlastních objektů a soukromých proměnných – 573

Používáme metody šablon – 576

Vytváříme a implementujeme rozhraní – 577

Vytváříme vlastní manipulátory spojení – 578

API pro práci s proudy – 583

Další informace – 594

23. Vytváříme rozhraní SAPI a rozšiřujeme Zend Engine – 595

Rozhraní SAPI – 595CGI SAPI – 596

Vestavěné SAPI – 606

SAPI a vstupní filtry – 607

input_filter – 608

Pohled do útrob Zend Enginua jeho modifikace – 613

Varovná hlášení a výjimky – 613

Jak na výpis opkódů – 616

APD – 619

APC – 621

Rozšíření Zend a zpětná volání – 621

Za domácí úkol – 624

Rejstřík – 625

php5 - pokrocile programovani.indb 9 21.9.2004 10:33:44

Page 10: Pokročilé programování v PHP5

O autoroviGeorge Schlossnagle je ředitelem průmyslové společnosti OmniTI Computer Consulting, která síd-lí v Marylandu a zaměřuje se na vysokokapacitní webové a emailové systémy. Předtím vedl technické operace na několika silně navštěvovaných komunitních webech, přičemž se zdokonalil ve správě PHP ve velmi velkých podnikových prostředí. Patří mezi časté přispěvovatele komunity PHP a jeho práci lze najít v PHP rozšířeních PEAR a PECL.

Před vstupem na pole informačních technologií studoval George matematiku a dva roky působil jako učitel v Peace Corps (tedy v mírových sborech). Jeho zkušenosti jej naučily si vážit disciplinovaného řešení problému, které analyzuje jeho pravou příčinu před prostým adresováním symptomů.

PoděkováníPsaní této knihy bylo pro mne neocenitelnou zkušeností a rád bych poděkoval všem lidem, kteří mi to umožnili. Děkuji všem vývojářům PHP za náročnou práci při vytváření tak skvělého produktu. Bez jejich ustavičného úsilí by tato kniha neměla žádný námět.

Děkuji Shelley Johnston, Damon Jordan, Sheila Schroeder, Kitty Jarrett a ostatním zaměstnancům vy-davatelství Sams za to, že věřili nejenom mě, ale i této knize. Bez nich by všechno bylo pouze nezreali-zovaným cílem, který se mi honí v hlavě.

Děkuji technických editorům Briane France, Zak Greant a Sterling Hughes za čas a úsilí, které věnovali čtení a komentování jednotlivých kapitol. Bez jejich snažení bych si nemohl být jist, že tato kniha je kompletní a neobsahuje hromadu chyb.

Děkuji svému bratru Theovi za jeho technické konzultace a že mi byl zdrojem inspirace. V neposlední řadě také za to, že mi poskytl v práci časovou volnost, když jsem pracoval na dokončení této knihy.

Děkují rodičům, že ze mě vychovali takového člověka jakým jsem dnes a především mé matce Sherry za to, že se vlídně dívala na každou kapitolu této knihy. Doufám, že jste oba na mě pyšní.

A největší dík patří mé ženě, Pei, za neoblomnou podporu a za nesobecké obětování mnoha nocí a ví-kendů tomuto projektu. Za její lásku, trpělivost a podporu má mou nehynoucí vděčnost.

php5 - pokrocile programovani.indb 10 21.9.2004 10:33:44

Page 11: Pokročilé programování v PHP5

Sdělte nám svůj názorJako čtenáři této knihy se stáváte těmi nejdůležitějšími kritiky a komentátory. Vážíme si vašeho názoru a chtěli bychom vědět, co děláme správně, co bychom mohli dělat lépe, ve kterých oblastech bychom měli publikovat a také vaše další podnětné myšlenky, o které jste ochotni se s námi podělit.

Jako odborný redaktor Zoner Press vítám vaše názory. Můžete mi psát – poslat e-mail nebo dopis – a sdělit mi, co se vám v této knize lí bilo nebo nelíbilo, stejně tak, co bychom měli udělat, aby naše další knihy byly lepší. Pokud mi napíšete, nezapomeňte prosím připojit název knihy, ISBN, jméno autora, vaše jméno, telefon, fax nebo e-mail. Pozorně zhodnotím vaše názory a poskytnu je autorovi a redakto-rům, kteří pracovali na této knize.

Prosím, vězte, že nemohu pomoci s technickými problémy, které se týkají obsahu knihy, a že díky velké-mu množství e-mailů, které dostávám, nemohu zaručit odpověď na každou zprávu.

E-mail: [email protected]

Adresa: Zoner Press

ZONER software, s.r.o

Miroslav Kučera

Koželužská 7

602 00 Brno

Služby čtenářůmDalší informace o této a dalších knihách vydaných v Sams Publishing najdete na adrese www.samspub-lishing.com. Do vyhledávacího pole zadejte kód ISBN (bez pomlček) nebo titul knihy pro nalezení požadované knihy.

Zdrojové soubory k této knize je možné stáhnout z adresy http://www.zonerpress.cz.

php5 - pokrocile programovani.indb 11 21.9.2004 10:33:44

Page 12: Pokročilé programování v PHP5

PředmluvaV poslední době jsem určitým způsobem pracoval na různých knihách Williama Gibsona a v knize All Tomorrow’s Parties jsem narazil na tohle:

To, co je pře-designované, příliš hodně specifické, předvídá výsledek; předvídání výsledku zaručuje, pokud ovšem neselže, nedostatek půvabu.

Gibson tak elegantně shrnul nedostatky spousty projektů všech velikostí. Když něco vytváříte, řešte pro-blém, který máte nyní. U velmi složité architektury se nesnažte předvídat, jak by mohl problém vypadat za rok a pokud vytváříte univerzální nástroj, nechtějte, aby byli lidé nucení používat váš nástroj pouze jedním způsobem.

Samotné PHP je na hraně mezi specifickým řešením problému webu a vyhýbání se snažení vnucovat lidem specifickou formu řešení problému. Někdo by označil PHP za elegantní. Jako skriptovací jazyk utržil za několik let služby v první linii webu spoustu šrámů po bitvách. Elegantní však je jednoduchost přístupu PHP.

Každý vývojář prochází různými fázemi, jak přistupuje k řešení problémů. Zpočátku převažují jednodu-chá řešení, protože není dostatečně pokročilý na to, aby pochopil složitější principy, které jsou potřeba pro všechno ostatní. Čím více se toho dozvíte, tím komplexnější řešení budete navrhovat a bude se zvět-šovat okruh problémů, které můžete řešit. V tomto bodě je snadné upadnou do rutiny komplexnosti.

Je-li dostatek času a prostředků, lze každý problém vyřešit téměř s jakýmkoliv nástrojem. Úkolem ná-stroje je, aby vám nenutilo žádný konkrétní způsob řešení a o to se snaží i PHP. Nezavádí žádný konkrét-ní programovací vzor, ponechává na vás, abyste se sami rozhodli a tvrdě se snaží minimalizovat počet vrstev mezi vámi a řešeným problémem. To znamená, že všechno je připraveno tak, abyste s pomocí PHP našli jednoduché a elegantní řešení problému.

Ovšem to, že máte k dispozici připraveny všechny nástroje, které nevytváří zrůdnosti, nezaručí, že se tak nestane. A právě zde se dostává ke slovu George a tato jeho kniha. George vás provede nejenom po cestě za poznáním PHP, která se velmi podobá jeho vlastní cestě s PHP, ale provede vás také s vývojem a řešením problémů obecně. Po pár dnech čtení se naučíte to, co se on sám učil po mnoho let za svého působení v této branži. A proto nejlépe hned přestaňte číst tuhle neužitečnou předmluvu a nalistujete si kapitolu 1 a vykročte na svou vlastní cestu.

Rasmus Lerdorf

php5 - pokrocile programovani.indb 12 21.9.2004 10:33:45

Page 13: Pokročilé programování v PHP5

Cílem této knihy je udělat z vás odborníky na programování v PHP. Být odborníkem na programování neznamená mít úplnou znalost syntaxe a vlastností jazyka (i když to určitě pomůže), ale znamená to, že můžete jazyk efektivně používat k řešení problémů. Po přečtení této knihy byste měli dobře znát silné a slabé stránky jazyka PHP a také byste měli vědět, jak jej nejlépe používat k odstranění problémů jak uvnitř, tak i mimo webovou doménu.

Tato kniha usiluje o to, aby byla soustředěnou myšlenkou popisující obecné problémy a používající kon-krétní příklady pro ilustraci – jako protiklad ke kuchařce, kde jsou jak problémy, tak i řešení obvykle velmi specifické. Jak říká jedno stařé přísloví: „Dejte člověku rybu a on ji za den sní. Naučte ho, jak ji chytnout a bude ji jíst po celý život“. Cílem je poskytnout vám nástroje pro řešení jakéhokoliv problému a naučit vás rozpoznat správný nástroj pro daný úkol.

Podle mého názoru se nejlépe učí na příkladech a tato kniha je plná praktických příkladů, které imple-mentují všechny myšlenky, o nichž se hovoří. Příklady nejsou bez kontextu příliš užitečné, proto veškerý kód v této knize je skutečný kód, které provádí skutečné úlohy. V této knize nenajdete příklady s názvy tříd jako např. Foo a Bar; kde to bylo možné, použili jsme příklady z aktuálních open-source projektů, abyste mohli vidět probírané nápady ve skutečných implementací.

PHP v komerční sféřeKdyž jsem v roce 1999 začal profesionálně programovat v PHP, tento skriptovací jazyk právě přestávat být pouze jazykem pro nadšence. Byl to čas PHP 4 a první Zend Engine měla zrychlit a zlepšit stabilitu PHP. Exponenciálně rovněž rostl počet implementací PHP, nicméně, stále bylo obtížné prosadit PHP pro použití na velkých komerčních webových stránkách.

Úvodní povídání

php5 - pokrocile programovani.indb 13 21.9.2004 10:33:45

Page 14: Pokročilé programování v PHP5

Pokročilé programování v PHP 514

Tato obtížnost pramenila hlavně ze dvou zdrojů:

• Vývojáři Perl/ColdFusion a dalších skriptovacích jazyků, kteří odmítali aktualizovat své porozu-mění schopnostem PHP od doby, kdy to byl ještě vznikající jazyk.

• Javoví vývojáři, kteří chtěli mít velký a kompletní systém, robustní objektově orientovanou pod-poru, statický zápis a další “podnikové“ vlastnosti.

Žádný z těchto argumentů však dlouho neobstál a PHP tak již není jazykem používaným bezvýznamný-mi nadšenci – stal se výkonným skriptovacím jazykem, jehož design z něj dělá ideální volbu pro řešení problémů na webové doméně.

Aby byl programovací jazyk použitelný v aplikacích, musí splňovat těchto šest zásadních kritérií:

• Rychlé prototypování a implementace.

• Podpora moderních programovacích forem.

• Škálovatelnost.

• Výkon.

• Interoperabilita.

• Rozšiřitelnost.

První kritérium – rychlé prototypování – bylo od počátku silnou stránkou PHP. Zásadní rozdíl mezi vývojem webu a vývojem krabicového softwaru spočívá v tom, že na webu nejsou téměř žádné náklady na dodávání produktu. V krabicovém software však i miniaturní chyba znamená, že jste vypálili tisíce CD s chybným kódem. Oprava chyb zahrnuje komunikaci se všemi uživateli, kteří mají software s chy-bou a následném vyžádání, aby si stáhli a aplikovali opravu. Když opravíte chybu na webu, uživatel získá korektní verzi ihned, jakmile znovu načte stránku. Díky tomu mohou být webové aplikace vyvíjeny s pomocí velmi agilní metodologie s vysokou frekvencí vydávání nových verzí.

Skriptovací jazyky jsou obecně lepší pro agilní produkty, protože vám umožní rychle vyvinout a otesto-vat nové nápady bez toho, aniž byste museli procházet celý kompilační, testovací a ladící cyklus. PHP je k tomu dobré zejména proto, že má tak malou křivku osvojování znalostí, díky čemuž s ním mohou začít pracovat noví vývojáři s minimálními předchozími zkušenostmi.

PHP rovněž plně splňuje všechny ostatní kritéria. Jak uvidíte v této knize, nový objektový model PHP poskytuje stabilní a standardní objektově orientovanou podporu. PHP je rychlé a škálovatelné, napří-klad, různé programovací strategie, které můžete v PHP použít, vám umožní snadnou opětovnou im-plementaci zásadní části obchodní logiky v nízko úrovňových jazycích. PHP poskytuje obrovské množ-ství rozšíření pro spolupráci s dalšími službami – od databázových serverů po SOAP. Nakonec má PHP nejzásadnější charakteristickou vlastnost jazyka: je snadno rozšiřitelný. Pokud jazyk neposkytuje vlast-nost nebo dovednost jakou právě potřebujete, můžete přidat její podporu.

php5 - pokrocile programovani.indb 14 21.9.2004 10:33:45

Page 15: Pokročilé programování v PHP5

Úvodní povídání 15

Struktura a uspořádání knihyKniha je rozdělena na pět částí, které jsou víceméně na sobě nezávislé. Přestože je kniha vytvořena tak, aby zainteresovaný čtenář mohl jednoduše přeskočit vpřed na konkrétní kapitolu, doporučujeme číst knihu od začátku do konce, protože celá řada příkladů je postupně vytvářena v průběhu celé knihy.

Uspořádání této knihy vychází z přirozeného vývoje – nejprve budeme hovořit o tom, jak napsat dobrý kód PHP, poté se zaměříme na specifické techniky a ladění výkonu a nakonec na rozšíření jazyka. Ten-to formát je založen na mém přesvědčení, že největší zodpovědností profesionálního programátora je psát spravovatelný kód a že je snazší zrychlit provádění dobře napsaného kódu, než zlepšovat mizerně napsaný kód, který již běží rychle.

Část I – Implementační a vývojářská metodika

Kapitola 1 – Styl zápisu kódu

V této kapitole vás uvedeme do konvencí používaných v této knize vyvíjením stylu programování. Bu-deme hovořit o důležitosti vytváření konzistentního, dobře zdokumentovaného programového kódu.

Kapitola 2 – OOP prostřednictvím návrhových vzorů

Kapitola 2 podrobně pojednává o objektově orientovaném programování (OOP) v PHP. Schopnosti jsou předvedeny v kontextu prozkoumávání celé řady běžných designérských vzorů. Díky kompletnímu přehledu jak nových vlastností OOP v PHP5, tak i nápadů se vzorem OOP, je tato kapitola určena nejen pro nováčky v oblasti objektového programování, ale i pro zkušené programátory.

Kapitola 3 – Zpracování chyb

Chyby jsou nedílnou součástí života, proto s nimi musíme počítat. V kapitole 3 se budeme zabývat jak procedurálními, tak i OOP metodami pro ošetření chyb. Zejména se zaměříme na nové schopnosti PHP 5 pro ošetření chyb na základě výjimek.

Kapitola 4 – Implementujeme s PHP: Šablony a web

V kapitole 4 se podívám na šablonové systémy – sady nástrojů, které zjednodušují rozdvojení zobrazení a aplikace. Srovnáme si výhody a nevýhody hotových šablonových systémů (jako příklad je použit sys-tém Smarty) a ad hoc šablonových systémů.

Kapitola 5 – Implementujeme s PHP: Samostatné skripty

Velmi málo dnešních webových aplikací nemá žádnou back-endovou komponentu. Schopnost znovu použít stávající kód PHP pro psaní dávkových úloh, shell skriptů a rutin pro jiné než webové zpraco-vání je zásadní pro užitečnost jazyka v podnikovém prostředí. V kapitole 6 si probereme základy psaní samostatných skriptů a démonů v PHP.

php5 - pokrocile programovani.indb 15 21.9.2004 10:33:45

Page 16: Pokročilé programování v PHP5

Pokročilé programování v PHP 516

Kapitola 6 – Testování jednotek

Testování jednotek je způsob validace, že váš kód dělá přesně to, co má. V kapitole 6 se podíváme na strategie jednotkového testování a předvedeme si, jak implementovat flexibilní sady jednotkového tes-tování s PHPUnit.

Kapitola 7 – Správa vývojového prostředí

Pro většinu vývojářů nepředstavuje řídící kód příliš vzrušující úkol, přesto je zásadní. V kapitole 7 se podíváme na řídící kód ve velkých projektech a podrobně vás uvedeme do používání CVS (Concurrent Versioning System) pro správu PHP projektů.

Kapitola 8 – Návrh dobrého API

V kapitole 8 najdete návody na vytváření kódové základny, která je spravovatelná, flexibilní a jednoduše slučitelná s dalšími projekty.

Část II – Cachování

Kapitola 9 – Vnější ladění výkonu

Použití cachovacích strategií je jednoduše nejefektivnější způsob, jak zvýšit výkon a škálovatelnost apli-kací. V kapitole 9 se blíže podíváme na externí cachovací strategie PHP a budeme se zabývat kompilá-tory a proxy cache.

Kapitola 10 – Cachování dat

V kapitole 10 budeme o hovořit o tom, jak lze začlenit cachovací strategie do samotného kódu PHP. Řekneme si, jak a kdy integrovat cachování do aplikace a vyvineme plně funkční cachovací systém s ně-kolika back-endy pro ukládání.

Kapitola 11 – Znovupoužití výpočtů

V kapitole 11 se budeme zabývat vytvářením jednotlivých algoritmů a efektivnějším zpracováním po-mocí ukládání intermediálních dat, tzn. dat, která nejsou konečným výsledkem funkce. V této kapitole vyvineme obecnou teorii za využíváním výsledků výpočtů (computation reuse) a aplikujeme ji na prak-tické příklady.

Část III – Distribuované aplikace

Kapitola 12 – Interakce s databázemi

Databáze jsou hlavní komponentou téměř všech dynamických webových stránek. V kapitole 12 se za-měříme na efektivní strategie pro přemostění PHP a databázových systémů.

php5 - pokrocile programovani.indb 16 21.9.2004 10:33:46

Page 17: Pokročilé programování v PHP5

Úvodní povídání 17

Kapitola 13 – Autentizace uživatele a bezpečnost relace

V kapitole 13 prozkoumáme metody pro řízení autentizace uživatele a zabezpečování komunikace kli-ent-server. Tato kapitola se rovněž zaměřuje na ukládání zašifrovaných informací o relaci v cookie a pl-nou implementaci systému jednorázového přihlášení.

Kapitola 14 – Zpracování relace

V kapitole 14 budeme pokračovat v problematice uživatelských relací a probereme si rozšíření relace PHP a vytváření uživatelských ovladačů relace.

Kapitola 15 – Tvorba distribuovaného prostředí

V kapitole 15 budeme hovořit o tom, jak vytvářet škálovatelné aplikace. Dále si detailně prozkoumáme vytváření a řízení počítačových clusterů pro efektivní řízení cachování a databázových systémů.

Kapitola 16 – RPC: Interakce se vzdálenými službami

Webové služby představují termín pro služby, které poskytují jednoduchou komunikaci mezi počítači přes web. V této kapitole se podíváme na dva nejběžnější protokoly: XML-RPC a SOAP.

Část IV – Výkon

Kapitola 17 – Srovnávací testy: testování celé aplikace

Srovnávací testy aplikace jsou nepostradatelné pro zjištění, zdali je aplikace schopna zvládnout provoz, k jehož zpracování byla vytvořena a pro identifikaci komponent, které představují potencionální slabé místa systému. V kapitole 17 se podíváme na různé sady srovnávacích testů aplikací, které vám umožní měřit výkon a stabilitu aplikace.

Kapitola 18 – Profilování

Po použití technik srovnávacích testů pro identifikaci rozsáhlých potenciálních slabin aplikace, můžete aplikovat nástroje profilování k izolaci specifických problémových oblastí v kódu. V kapitole 18 budeme hovořit o profilování a poskytneme vám podrobný návod na použití profileru APD (Advanced PHP Debugger) k prozkoumání kódu.

Kapitola19 – Syntetické benchmarky: zaostřeno na bloky kódu a funkce

Není možné porovnat dvě části kódu, pokud nemůžete kvantitativně změřit jejich rozdíly. V kapitole 19 se podíváme na metodologie srovnávacích testů a projdeme si implementaci a hodnocení sad uživatel-ských srovnávacích testů.

php5 - pokrocile programovani.indb 17 21.9.2004 10:33:46

Page 18: Pokročilé programování v PHP5

Pokročilé programování v PHP 518

Část V – Rozšiřitelnost

Kapitola 20 – Pohled do útrob PHP a Zend Enginu

Informace o tom, jak PHP funguje „zevnitř“ vám pomohou provádět racionální rozhodnutí, které vy-užívají silné stránky PHP a vyhýbají se slabinám. V kapitole 20 se podíváme z technického hlediska na nejenom na vnitřní funkci PHP, ale také na to, jak aplikace jako např. webové servery komunikují s PHP, jak se skripty syntakticky analyzují do intermediálního kódu a jak probíhá provádění skriptu v Zend Engine.

Kapitola 21 – Rozšiřujeme PHP: část I

V kapitola 21 vás důkladně uvedeme do problematiky tvorby rozšíření PHP v jazyce C. Budeme se za-bývat přenášením stávajícího kódu PHP do C a vytvářením rozšíření, které poskytují PHP přístup ke knihovnám třetích stran v jazyce C.

Kapitola 22 – Rozšiřujeme PHP: část II

V kapitole 22 navážeme na téma z kapitoly 21 a podíváme se na pokročilé náměty jako např. vytváření tříd v kódu rozšíření a používání proudů a zařízení relací.

Kapitola 23 – Vytváříme rozhraní SAPI a rozšiřujeme Zend Engine

V kapitole 23 se podíváme na to, jak se vnořuje kód PHP do aplikací a na rozšíření Zend Engine pro modifikaci základního chování jazyka.

Platformy a verzeTato kniha se zaměřuje na PHP 5, ale s výjimkou zhruba 10% materiálu (nové objektově orientované vlastnosti popisované v kapitole 2 a 22 a pojednání o protokolu SOAP v kapitole 16) není v této knize nic, co by bylo specifické výhradně pro PHP 5. Tato kniha je o nápadech a strategiích, s jejichž pomocí bude váš kód rychlejší, chytřejší a lépe navržený. Doufáme, že přinejmenším polovinu informací obsa-žených v této knize budete moci použít pro zlepšení kódu napsaného v libovolném jazyce.

Všechno v této knize bylo napsáno a otestováno na Linuxu a mělo by bez jakýchkoliv modifikací fun-govat na systémech Solaris, OS X, FreeBSD nebo jakémkoliv jiném unixovém klonu. Většina skriptů by měla fungovat s minimálními modifikacemi ve Windows, ačkoli některé používané utility (zejména utilita pcnt1 v kapitole 5) nemusí být zcela přenositelné.

php5 - pokrocile programovani.indb 18 21.9.2004 10:33:46

Page 19: Pokročilé programování v PHP5

2

Jasně největší a neproklamovanější změnou v PHP5 je kompletní přepracování objektového modelu a značně vylepšená podpora standardních objektově orientovaných (OO) metodologií a technik. Tato kniha není zaměřena na techniku objektově orientovaného programování (OOP) a ani není o návrho-vých vzorech. Těmto tématům se věnuje mnoho jiných knih (některé jsou uvedeny na konci této kapi-toly). Místo toho se v této kapitole budeme věnovat přehledu rysů OOP v PHP5 a některým obecným návrhovým vzorům.

Já mám spíše agnostický pohled na využití OOP v PHP. Pro mnohé problémy je použití metod OOP jako použití kladiva k zabití mouchy. Úroveň abstrakce, která je zde použita, se mi jeví zbytečná pro zvládnutí jednoduchých úkolů. Metody OOP jsou vhodné pro řešení komplexnějších systémů. Již jsem pracoval na několika rozsáhlých projektech, které opravdu těžily z toho, že byly navrženy modulárně s využitím technik OOP.

V této kapitole je popsán přehled pokročilých rysů OOP, které jsou nyní podporovány v PHP. Některé zde uvedené příklady budou použity i v dalších částech knihy a budou – doufejme – sloužit jako ukázky toho, jak lze prostřednictvím OOP úspěšně řešit určité problémy.

Objektově orientované programování znamená zásadní změnu od procedurálního programování, které je tradiční technikou programátorů PHP. U procedurálního programování máte data (uložená v pro-měnných), která předáváte funkcím a ty pak provádí s těmito daty různé operace, mohou je modifi-kovat, či vytvářet z nich data nová. Procedurální programování je tradičně seznam instrukcí, které se vykonávají v daném pořadí pomocí výrazů řízení toku, funkcí apod. Zde následuje příklad takovéhoto procedurálního kódu:

<?phpfunction hello($name)

OOP prostřednictvím návrhových vzorů

php5 - pokrocile programovani.indb 49 21.9.2004 10:33:52

Page 20: Pokročilé programování v PHP5

Pokročilé programování v PHP 550

{ 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";print hello($name);print "You are ".age($bday)." years old.\n";print goodbye($name);? >

Objektově orientované programováníÚvodem je důležité poznamenat, že v procedurálním programování jsou funkce a data od sebe navzá-jem odděleny. V OOP jsou data a funkce, které s těmito daty operují, navzájem svázány do objektů. Objekty obsahují jak data (nazývané attributy nebo vlastnosti), tak i funkce, které těmito daty operují (nazývané metody).

Objekt je definován třídou, jejíž je instancí. Třída definuje atributy, které objekt obsahuje a metody, které může použít. Objekt vytvoříte vytvořením instance třídy. Vytvořením instance se vytvoří nový objekt, incializují se všechny jeho atributy a je volán jeho konstruktor, což je funkce, která provádí všechny ope-race prvotního nastavení. Konstruktor třídy bývá v PHP 5 nazýván _ _construct(), což umožňuje jeho jednoduchou identifikaci. Následující příklad definuje jednoduchou třídu User, vytváří její instan-ci a volá její dvě metody:

<?phpclass User { public $name; public $birthday; public function _ _construct($name, $birthday)

php5 - pokrocile programovani.indb 50 21.9.2004 10:33:52

Page 21: Pokročilé programování v PHP5

Kapitola 2 – OOP prostřednictvím návrhových vzorů 51

{ $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); if($ts === -1) { return "Unknown"; } else { $diff = time() – $ts; return floor($diff/(24*60*60*365)) ; } }}$user = new User('george', '10 Oct 1973');print $user->hello();print "You are ".$user->age()." years old.\n";print $user->goodbye();?>

Spuštění tohoto kódu vyvolá následující:

Hello george!You are 29 years old.Goodbye george!

Konstruktor je v tomto příkladu velmi jednoduchý – pouze inicializuje dva atributy, jméno (name) a da-tum narození (birthday). Metody jsou rovněž triviální. Poznamenejme, že parametr $this je automa-ticky vytvořen uvnitř metod třídy a reprezentuje samotný objekt User. Pro přístup k vlastnostem nebo metodám objektu použijte notaci ->.

Na první pohled zde není vidět mnoho rozdílů mezi asociačním polem a kolekcí funkcí, které ji repre-zentují. Nicméně, existují některé další důležité vlastnosti, které si popíšeme v následujících částech:

• Dědičnost – Dědičnost je schopnost odvozovat novou třídu ze stávající a zdědit nebo přepsat její atributy a metody.

php5 - pokrocile programovani.indb 51 21.9.2004 10:33:52

Page 22: Pokročilé programování v PHP5

Pokročilé programování v PHP 552

• Zapouzdření – Zapouzdření je schopnost skrýt data před uživatelem třídy.

• Speciální metody – Jak uvidíte dále, třídy umožňují, v okamžiku, kdy je nový objekt vytvářen, pomocí konstruktoru vykonat prvotní inicializaci (např. nastavení atributů). Dále jsou rovněž generovány další události, které nastávají při určitých příležitostech, jakými jsou např. kopírová-ní, rušení objektu apod.

• Polymorfismus – Když dvě třídy implementují stejné externí metody, je možné ve funkcích použít zaměnitelnost. Protože k úplnému pochopení polymorfismu je třeba větší znalostní báze než v této chvíli máme, popíšeme si ho později v této kapitole v části věnované polymorfismu.

DědičnostDědičnost můžete využít, pokud chcete vytvořit novou třídu, která má mít podobné vlastnosti nebo chování jako již existující třída. K zajištění dědičnosti podporuje PHP schopnost třídy rozšířit se o již existující třídu. Když třídu rozšíříte, nová třída zdědí všechny vlastnosti a metody svého rodiče (se dvě-ma výjimkami, které si popíšeme později v této kapitole). Dále pak můžeme buď přidat nové vlastnosti a metody nebo přepsat již existující. Dědičný vztah je definován klíčovým slovem extends. Nyní si rozšíříme třídu User, nová třída bude reprezentovat uživatele s právy administrátora. Třídu rozšíříme o vybrání uživatelského hesla ze souboru NDBM a přidáme funkci umožňující porovnání tohoto hesla se zadaným heslem:

class AdminUser extends User{ public $password; public function _ _construct($name, $birthday) { parent::_ _construct($name, $birthday); $db = dba_popen("/data/etc/auth.pw", "r", "ndbm"); $this->password = dba_fetch($db, $name); dba_close($db); } public function authenticate($suppliedPassword) { if($this->password === $suppliedPassword) { return true; } else { return false; } }}

Třebaže je to úplně krátké, třída AdminUser automaticky zdědí všechny metody třídy User, takže mů-žete volat metody hello(), goodbye() a age(). Poznamenejme, že musíte ručně volat konstruktor

php5 - pokrocile programovani.indb 52 21.9.2004 10:33:52

Page 23: Pokročilé programování v PHP5

Kapitola 2 – OOP prostřednictvím návrhových vzorů 53

rodičovské třídy: parent::_ _constructor(); PHP5 automaticky nevolá konstruktor rodiče. Parent je klíčové slovo, které vyjadřuje, že se jedná o třídu rodiče.

ZapouzdřeníUživatelé přecházející z procedurálního jazyka nebo PHP 4 si mohou myslet, že všechno kolem je ve-řejné. PHP verze 5 umožňuje rozdělit viditelnost dat atributů a metody na veřejnou, chráněnou a pri-vátní. Tyto typy jsou normálně označovány zkratkou PPP (public, protected, private) a mají standardní sémantiku:

• Public – Veřejná proměnná nebo metoda, která může být přímo přístupná jakémukoli uživateli třídy.

• Protected – Chráněná proměnná nebo metoda, která nemůže být přístupná uživatelům třídy, ale může být přístupná uvnitř podtřídy, která tuto třídu dědí.

• Private – Privátní proměnná nebo metoda, která může být přístupná pouze uvnitř třídy, ve které je definována. To znamená, že privátní proměnná nebo metoda nemůže být volána z potomka dané třídy.

Zapouzdření umožňuje definovat veřejné rozhraní, které určuje způsob, jakým může uživatel s třídou komunikovat. Metody, které nejsou veřejné můžete předělat nebo změnit bez toho, abyste se znepoko-jovali tím, že poškodíte kód, který jste zdědili z nějaké třídy. Beztrestně rovněž můžete přepsat privátní metody. Přepsání chráněných metod vyžaduje více péče, abyste se vyvarovali přerušení vazby podtříd.

Zapouzdření není v PHP nezbytné (pokud není použito, metody a proměnné jsou považovány za ve-řejné), ale pokud je to jenom trochu možné, je dobré zapouzdření využít. V jednoprogramátorském prostředí a zvláště pak při týmové práci je velkou chybou vyhýbat se veřejným rozhraním objektu a pro-vádět přímý přístup k proměnným a k metodám. Takové přístupy rychle vedou k neudržitelnosti kódu, protože místo jednoduchého veřejného rozhraní se dostanete do stavu, kdy jsou všechny metody třídy neschopné přepracování bez toho, aniž byste nezpůsobili chyby v třídě, která tyto metody používá. Po-užití PPP vás zavazuje používat tuto dohodu a zajišťuje, že v externím kódu jsou použity pouze veřejné metody bez ohledu na lákadlo přímého přístupu.

Statické (třídní) atributy a metodyV PHP mohou být navíc metody a vlastnosti deklarovány jako statické. Statická metoda je vázána přímo na třídu, ne k instanci této třídy (objektu). Statické metody jsou volány pomocí syntaxe ClassName::method(). Použití proměnné $this uvnitř statické metody není možné.

Statická vlastnost je proměnná třídy, která je spojena se třídou, ne s instancí třídy. Tzn., že když je vlast-nost změněna, projeví se tato změna ve všech instancích dané třídy. Statické proměnné jsou deklarovány klíčovým slovem static a jsou přístupné prostřednictvím syntaxe ClassName::$property. Násle-dující příklad ukazuje, jak taková statická proměnná funguje:

class TestClass { public static $counter;

php5 - pokrocile programovani.indb 53 21.9.2004 10:33:53

Page 24: Pokročilé programování v PHP5

Pokročilé programování v PHP 554

}$counter = TestClass::$counter;

Pokud chcete ke statické proměnné přistupovat uvnitř třídy, můžete rovněž použít kouzelná klíčová slova self a parent, která rozlišují, zdali se jedná o danou třídu nebo jejího rodiče. Použití klíčových slov self a parent umožňuje vyhnout se explicitní referenci na název třídy. Zde je jednoduchý příklad, který využívá statickou vlastnosti k přiřazení unikátního celočíselného ID pro každou instanci třídy:

class TestClass { public static $counter = 0; public $id; public function _ _construct() { $this->id = self::$counter++; }}

Speciální metodyTřídy v PHP mají vyhrazeny určité názvy metod jako speciální volání pro zpracování určitých událostí. Už jste se setkali s metodou _ _construct(), která je automaticky volána, když je vytvářena instan-ce objektu. Třídy využívají dalších pět speciálních metod: _ _get(), _ _set() a _ _call() ovlivňující způsob, jakým jsou vlastnosti a metody objektu volány a jsou popsány později v této kapitole. Zbylé dvě metody jsou _ _destruct() a _ _clone().

_ _destruct() je metoda volaná při rušení objektu. Destruktory jsou vhodné pro uvolnění zdrojů (např. napojení na soubor nebo databázi), které třída vytvořila. V PHP jsou proměnné referenčně po-čítány. Když je počítadlo referencí proměnné sníženo na nulu, je proměnná automaticky odstraněna ze systému. Pokud je proměnnou objekt, je volána jeho metoda _ _destruct().

Následující jednoduché zabalení funkcí pracujících v PHP se souborem ukazuje využití destruktoru:

class IO { public $fh = false; public function _ _construct($filename, $flags) { $this->fh = fopen($filename, $flags); } public function _ _destruct() { if($this->fh) { fclose($this->fh); } } public function read($length)

php5 - pokrocile programovani.indb 54 21.9.2004 10:33:53

Page 25: Pokročilé programování v PHP5

Kapitola 2 – OOP prostřednictvím návrhových vzorů 55

{ if($this->fh) { return fread($this->fh, $length); } } /* ... */}

V mnoha případech je vytváření destruktoru zbytečné, protože PHP na konci požadavku uvolňuje všechny použité zdroje. Pro dlouhotrvající skripty nebo skripty, které otevírají velký počet souborů, je však uvolňování zdrojů důležité.

V PHP 4 jsou objekty předávány svou hodnotou. Tzn. pokud v PHP 4 napíšete:

$obj = new TestClass;$copy = $obj;

vytvoříte aktuálně tři kopie dané třídy: Jedna instance objektu je vytvořena při deklaraci, druhá bě-hem přiřazení návratové hodnoty z konstruktoru do $obj a třetí, když přiřadíte $copy proměnné $obj. Tato sémantika je úplně odlišná od sémantiky ve všech ostatních objektově orientovaných jazy-cích a v PHP 5 je již od ní upuštěno.

Když v PHP 5 vytváříte objekt, vracíte handle na tento objekt, který je obdobný v pojetí s referencí s C++. Když spustíte předchozí kód v PHP 5, vytvoříte pouze jednu instanci daného objektu; nevytvo-říte žádnou jeho další kopii.

Pro vytvoření aktuální kopie objektu musíte v PHP 5 použít metodu _ _clone(). Takže předcházející příklad, ve kterém vytváříme v $copy aktuální kopii objektu $obj (a nikoliv jenom další referenci na stejný objekt), bude vypadat následovně:

$obj = new TestClass;$copy = $obj->_ _clone();

Pro některé třídy není možné použít předpřipravenou metodu _ _clone(), ale PHP dovoluje její pře-psání. Uvnitř metody _ _clone() máte proměnnou $this, která je novým objektem se všemi původ-ními vlastnostmi objektu, které byly zkopírovány. Např. ve třídě TestClass, definované již dříve v této kapitole, byste použitím defaultní metody _ _clone(), zkopírovali i její vlastní proměnnou id. Proto musíte upravit tuto třídu následovně:

class TestClass { public static $counter = 0; public $id; public $other; public function _ _construct() { $this->id = self::$counter++; }

php5 - pokrocile programovani.indb 55 21.9.2004 10:33:53

Page 26: Pokročilé programování v PHP5

Pokročilé programování v PHP 556

public function _ _clone() { $this->id = self::$counter++; }}

Stručný úvod do návrhových vzorůUrčitě jste již o návrhových vzorech někdy slyšeli, ale nevěděli jste co tento pojem vlastně znamená. Ná-vrhové vzory jsou zevšeobecněná řešení tříd problémů, se kterými se programátoři setkávají nejčastěji.

Když se programování věnujete už nějaký delší dobu, určitě máte potřebu přizpůsobit si danou knihov-nu tak, aby byla přístupná prostřednictvím různých API. Nejste sami. Toto je obecný problém. Je sice pravda, že neexistuje nějaké obecné řešení, které řeší všechny problémy – nicméně, lidé tento typ problé-mu znají a řeší jej stále znovu a znovu. Základní myšlenkou návrhových vzorů je, že problémy a jejich odpovídající řešení mají sklon k následování pomocí opakujících se šablon.

Návrhové vzory však bývají někdy nedoceňovány. Před lety jsem odmítl využít návrhové vzory bez toho, aniž bych o nich reálně uvažoval. Moje problémy byly natolik specifické a složité, že jsem si myslel, že se na ně nehodí žádná šablona. To bylo ode mně opravdu krátkozraké.

Návrhové vzory poskytují slovník pro identifikaci a klasifikaci problémů. V Egyptské mytologii měly božstva a další entity tajná jména a až když jste tato jména objevili, mohli jste ovládat jejich "božskou sílu". Problémy návrhu jsou v přírodě velmi podobné. Když můžete rozeznat správné řešení problému v přírodě a ztotožnit ho se známou množinu obdobných (řešených) problémů, dostanete se na správnou cestu k jejich řešení.

Tvrdit, že jedna kapitola o návrhových vzorech vám bude stačit, je směšná. V následujících částech si popíšeme několik vzorů, hlavně jako prostředek pro popsání některých pokročilých objektově oriento-vaných technik, které jsou v PHP k dispozici.

Adaptér vzoruAdaptér vzoru poskytuje přístup k objektu prostřednictvím specifického rozhraní. V čistě objektově ori-entovaném jazyku umožňuje adaptér vzoru provázání alternativního API s objektem; ale v PHP se často spíše setkáme s tím, jak takový adaptér vzoru poskytuje alternativní rozhraní k sadě procedur.

Poskytnutí rozhraní s objektem prostřednictvím specifického API může být užitečné z těchto dvou hlavních důvodů:

• Když různé třídy poskytují podobné služby a implementují stejné API, můžete se mezi nimi v průběhu programu přepínat. Toto je známo jako polymorfismus. Toto slovo pochází z latiny: Poly znamená "mnoho" a morph znamená "tvar" čili "mnohotvárnost".

php5 - pokrocile programovani.indb 56 21.9.2004 10:33:54

Page 27: Pokročilé programování v PHP5

Kapitola 2 – OOP prostřednictvím návrhových vzorů 57

• Může být obtížné změnit předdefinovaný systém pro využívání sady objektů. Když do projektu začleňujete třídu třetí strany, která není kompatibilní s použitým API systému, je často nejjedno-dušším řešením použít adaptér poskytující přístupu k danému API.

Nejběžnější způsob použití adaptérů v PHP není při vytváření alternativního rozhraní pro spojení jedné třídy s druhou (protože to je v komerčním kódu PHP limitováno placenou částkou a v otevřeném kódu můžete takovéto rozhraní změnit přímo). PHP má svůj původ v procedurálním jazyku, a proto jsou mnohé předpřipravené funkce PHP jsou ze své podstaty procedurální. Když je potřeba, aby byly funkce spouštěny postupně, v daném pořadí (např. když pracujete s databází, musíte použít postupně funk-ce mysql_pconnect(), mysql_select_db(), mysql_query() a mysql_fetch()), jsou pro ucho-vání dat pro napojení na databázi použity určité zdroje a vy je musíte předávat do všech těchto funkcí. Zabalením celého takového procesu do třídy se můžete zbavit stále se opakující činnosti a vyhnete se generování následných chyb.

Řekněme, že bychom chtěli vytvořit objektové rozhraní pro dvě základní zdrojové funkce MySQL: pro napojení se na databázi a pro získání výsledných dat. Naším cílem není napsat úplnou abstrakci, ale jed-noduše vytvořit dostatečně zabalený kód, který bude zpřístupňovat všechny rozšiřující funkce MySQL objektově orientovaným způsobem a přidá navíc několik dalších praktických věcí.

Zde je první pokus, jak zabalit třídu:

class DB_Mysql { protected $user; protected $pass; protected $dbhost; protected $dbname; protected $dbh; // Database connection handle 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 Exception; } if(!mysql_select_db($this->dbname, $this->dbh)) { throw new Exception; } } public function execute($query) { if(!$this->dbh) {

php5 - pokrocile programovani.indb 57 21.9.2004 10:33:54

Page 28: Pokročilé programování v PHP5

Pokročilé programování v PHP 558

$this->connect(); } $ret = mysql_query($query, $this->dbh); if(!$ret) { throw new Exception; } else if(!is_resource($ret)) { return TRUE; } else { $stmt = new DB_MysqlStatement($this->dbh, $query); $stmt->result = $ret; return $stmt; } }}

Pomocí tohoto rozhraní můžete vytvořit nový objekt DB_Mysql, při inicializaci mu předat přístupové údaje k databázi MySQL a tím se na ní napojit ( uživatelské jméno, heslo, stanice a název databáze):

$dbh = new DB_Mysql("testuser", "testpass", "localhost", "testdb");$query = "SELECT * FROM users WHERE name = '".mysql_escape_string($name)."'";$stmt = $dbh->execute($query);Tento kód vrací objekt DB_MysqlStatement, což je objekt, který zabaluje výstup z MySQL databáze: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);

php5 - pokrocile programovani.indb 58 21.9.2004 10:33:54

Page 29: Pokročilé programování v PHP5

Kapitola 2 – OOP prostřednictvím návrhových vzorů 59

} public function fetchall_assoc() { $retval = array(); while($row = $this->fetch_assoc()) { $retval[] = $row; } return $retval; }}

K vypsání jednotlivých řádků výsledku dotazu musíte místo použití funkce mysql_fetch_assoc(), použít následující konstrukci:

while($row = $stmt->fetch_assoc()) { // process row}

Následuje několik poznámek k uvedené implementaci:

• Vyhýbá se manuálnímu volání funkcí connect() a mysql_select_db().

• Při chybě vyvolá výjimku. Výjimky jsou v PHP 5 novým rysem. Zde o nich ještě nebudeme mlu-vit, ale podrobně si je popíšeme v druhé polovině kapitoly 3 popisující zpracování chyb.

• Tento kód není moc praktický. Neexistuje způsob, jak jednoduše znovu použít nebo upravit dotaz – pro jiný dotaz musíte znovu všechna data kódu přepsat.

Na základě těchto tří problémů můžete toto rozhraní rozšířit tak, aby umožňovalo automaticky zabalit všechna data, která mu pošlete. Nejjednodušší cestou, jak to udělat, je provést emulaci všech připrave-ných dotazů. Když spouštíte znovu dotaz na databázi, hrubé SQL, které zde vytváříte, musí být nejprve přeloženo do formátu, kterému databáze vnitřně rozumí. Tento krok vyžaduje zvýšené nároky na výkon a mnohé databázové systémy tento problém řeší cachováním. Uživatel si může dotaz předpřipravit, což způsobí, že databáze si provede rozbor tohoto dotazu a vrací jakýsi předkompilovaný zdroj, který je pak použit k vlastní reprezentaci dotazu. Takováto vlastnost je často spojována s pojmem vázání SQL. Vázá-ní SQL umožňuje provést rozbor dotazu v souladu s umístěním dat, se kterými budou vaše proměnné následně svázány. Následně pak můžete svázat parametry s předkompilovanou verzí dotazu ještě před jeho spuštěním. V mnoha databázových systémech (zejména v Oracle) je použití vázání dat SQL vý-znamnou výkonnostní výhodou.

Verze MySQL před verzí 4.1 neposkytuje samostatné uživatelské rozhraní k přípravě dotazů před jejich spuštěním, ani neumožňuje vázání SQL. To pro nás znamená posílat všechna měnící se data ke zpra-cování samostatně, zajistit vhodné místo k uložení proměnných a uchovávat je až do doby, než budou vloženy do dotazu. Rozhraní nové verze MySQL 4.1 je vybudováno na základě rozšíření mysqli od Georga Richtera a poskytuje nové funkčnosti.

Abychom těchto nových vlastností mohli využít, musíme rozšířit třídu DB_Mysql o metody prepare a do třídy DB_MysqlStatement vložit metody bind a execute:

php5 - pokrocile programovani.indb 59 21.9.2004 10:33:55

Page 30: Pokročilé programování v PHP5

Pokročilé programování v PHP 560

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, $this->dbh); if(!$this->result) { throw new MysqlException; } return $this; } /* ... */}

Metoda prepare() v tomto příkladu neprovádí nic, jen jednoduše vytváří nový objekt DB_Mysql-Statement, který specifikuje dotaz. Skutečná práce je prováděná třídou DB_MysqlStatement. Pokud nemáme vázané parametry, můžete tyto objekty volat takto:

$dbh = new DB_Mysql("testuser", "testpass", "localhost", "testdb");$stmt = $dbh->prepare("SELECT * FROM users WHERE name = '".mysql_escape_string($name)."'");$stmt->execute();

php5 - pokrocile programovani.indb 60 21.9.2004 10:33:55

Page 31: Pokročilé programování v PHP5

Kapitola 2 – OOP prostřednictvím návrhových vzorů 61

Skutečnou výhodou použití zabaleného objektu oproti použití normálního procedurálního volání je, že k dotazu můžete navázat parametry. To provedete tak, že do textu dotazu vložíte výraz začínající dvoj-tečkou (:), který naváže daná data dotazu až v době provádění kódu:

$dbh = new DB_Mysql("testuser", "testpass", "localhost", "testdb");$stmt = $dbh->prepare("SELECT * FROM users WHERE name = :1");$stmt->execute($name);

Výraz :1 v dotazu označuje, že se jedná o umístění první svázané proměnné. Když zavoláte metodu execute() objektu $stmt, tato metoda tento výraz rozpozná a přiřadí mu hodnotu prvního předané-ho argumentu ($name) a doplní hodnotu této proměnné za výraz :1 v dotazu.

Dokonce, i když toto navázané rozhraní nemá normální výkonnostní výhodu vázaného rozhraní, po-skytuje vhodný způsob, jak se jednoduše vyhnout přepisování všech vstupů v dotazu.

Šablona vzoruŠablona vzoru popisuje třídu, jež modifikuje logiku podtřídy, čímž ji rozšiřuje.

Šablonu vzoru můžete např. využít ke skrytí všech parametrů specifikujících napojení na databázi, které jsme používali v předchozích třídách. Při použití tříd z předchozích částí musíte specifikovat konstanty parametrů pro připojení:

<?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 now()");print_r($stmt->fetch_row());?>

Abychom se vyhnuli specifikaci konstant parametrů připojení, můžeme definovat podtřídu DB_Mysql a v ní natvrdo zadat parametry pro připojení k databázi test:

class DB_Mysql_Test extends DB_Mysql { protected $user = "testuser"; protected $pass = "testpass"; protected $dbhost = "localhost"; protected $dbname = "test"; public function _ _construct() { }}

php5 - pokrocile programovani.indb 61 21.9.2004 10:33:55

Page 32: Pokročilé programování v PHP5

Pokročilé programování v PHP 562

Obdobně můžeme to samé udělat pro ostrou databázi:

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() { }}

PolymorfismusObjekty určené pro práci s databází, popsané v této kapitole, jsou dobře obecně použitelné. Ale ve sku-tečnosti, když se podíváte na jiné databázové rozšíření vytvořené v PHP, najdete v nich vždy znovu a znovu stejnou základní funkčnost – napojení se na databázi, příprava dotazů, spuštění dotazů a zpra-cování výsledků. Když budete chtít, můžete napsat obdobné třídy DB_Pgsql nebo DB_Oracle, které za-balí knihovny pro PostgreSQL, respektive Oracle a v nich můžete použít úplně stejné základní metody.

Je ale vždy důležité použít pro metody, které provádějí stejný druh operace vždy totožné názvy metod, a to i v případě, že tyto metody jsou vnitřně nekompatibilní. To následně umožňuje polymorfismus, což je schopnost transparentně zaměnit jeden objekt za jiný, jejichž přístupové API jsou shodné.

Prakticky: polymorfismus znamená, že můžete například napsat funkci:

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(); // display entry}

Tato funkce nepracuje pouze, když je proměnná $dbh objekt typu DB_Mysql, ale bude pracovat správně s jakýmkoli typem objektu $dbh, který má implementovanou metodu prepare() a tato metoda vrací objekt, který má implementovány metody execute() a fetch_assoc().

Abychom daný databázový objekt předali do všech volaných funkcí, můžeme využít princip delega-ce. Delegace je objektově orientovaný model, kdy objekt má jako atribut jiný objekt, který používá k provádění daných úloh.

Knihovny zapouzdřující databázi jsou ideálním příkladem objektu, který využívá právě delegace. V nor-málních aplikacích mnohé třídy potřebují provádět databázové operace. Při tvorbě takovýchto tříd máte dvě možnosti:

• Všechna databázová volání můžete implementovat nativně. To je směšné. Toto řešení je jen pro nouzovou práci, kterou pak musíte dělat stále znovu a znovu a takovéto zabalení databáze je vlastně zbytečné.

php5 - pokrocile programovani.indb 62 21.9.2004 10:33:55

Page 33: Pokročilé programování v PHP5

Kapitola 2 – OOP prostřednictvím návrhových vzorů 63

• Můžete použít zabalené API databáze, ale vlastní objekty inicializovat mimo. Zde je příklad, který takovouto možnost využívá:

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(); // display entry }}

Jak můžete vidět, inicializace objektu napojení na databázi mimo, se jeví jako dobrý nápad. Můžete zde použít zabalenou knihovnu, což je dobré. Problém ale nastane, když chcete změnit databázi, která tuto třídu používá, to pak musíte vytvořit a změnit všechny funkce, které prová-dějí operace s databází.

• Použijete delegaci, a to tím, že třída Weblog obsahuje atribut, kterým je objekt zabalující databá-zi. Když je tato třída vytvářena, je vytvořen objekt zabalující databázi, který pak můžete použít pro všechny vstupně/výstupní operace. Zde je přepsána původní třída Weblog, která používá tuto techniku:

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(); // display entry }}

Nyní můžete nastavit databázi do objektu následovně:

$blog = new Weblog;$dbh = new Mysql_Weblog;$blog->setDB($dbh);

Samozřejmě můžete místo nastavení delegace databáze použít šablonu vzoru:

php5 - pokrocile programovani.indb 63 21.9.2004 10:33:56

Page 34: Pokročilé programování v PHP5

Pokročilé programování v PHP 564

class Weblog_Std extends Weblog { protected $dbh; public function _ _construct() { $this->dbh = new Mysql_Weblog; }}$blog = new Weblog_Std;

Delegace je výhodná vždy, když potřebujete provádět komplexní službu nebo službu, kterou je vhodné provádět uvnitř třídy. Dalším místem, kde se delegace běžně využívá, jsou třídy, které potřebují ge-nerovat výstup. Když je potřebné provádět výstup různými způsoby (např. v HTML, RSS [Rich Site Summary nebo také Really Simple Syndication] nebo prostém textu), vyplatí se vytvořit registr delegací umožňující generovat výstup, který chcete.

Rozhraní a kontrola typuKlíčem k úspěšné delegaci objektů je zaručení toho, že všechny třídy, které mají být použity jsou poly-morfické. Když v objektu Weblog nastavíte jako parametr $dbh třídu, která nemá implementovánu me-todu fetch_row(), vyvoláte při běhu programu fatální chybu. Takováto chyba při běhu programu se detekuje velice těžce a proto bychom měli již dopředu zajistit, že všechny objekty mají implementovány všechny požadované funkce.

K předcházení takovýchto chyb zavádí PHP 5 koncept rozhraní. Rozhraní (interface) je vlastně kostra třídy. Definuje všechny metody třídy, ale neobsahuje žádný jejich kód – pouze vzor toho, jaké má daná metoda argumenty.

Zde je základní rozhraní s názvem interface, které specifikuje metody potřebné pro napojení na da-tabázi:

interface DB_Connection { public function execute($query); public function prepare($query);}

Vždy, když pak budete chtít děděním rozšířit danou třídu, použijete toto rozhraní a protože neobsahuje kód, jednoduše rozpoznáte všechny funkce, které jsou v něm definovány a které budete muset imple-mentovat.

Např. pokud má třída DB_Mysql implementovat všechny funkce specifikované v rozhraní DB_Con-nection, musíte ji deklarovat následovně:

class DB_Mysql implements DB_Connection { /* definice třídy */}

php5 - pokrocile programovani.indb 64 21.9.2004 10:33:56

Page 35: Pokročilé programování v PHP5

16

Jednoduše řečeno, služby vzdáleného volání procedur (RPC) poskytují standardizované rozhraní pro tvorbu volání funkcí nebo metod přes síť.

Prakticky každý aspekt webového programování obsahuje služby RPC. Na bázi RPC jsou požadavky HTTP provedené webovými prohlížeči na webové servery i dotazy zaslané databázovému serveru kli-enty databáze. I když oba tyto příklady jsou vzdálenými voláními, ve skutečnosti se nejedná o protokoly RPC. Postrádají generalizaci a standardizaci volání RPC; protokoly používané webovým serverem a da-tabázovým serverem nelze například sdílet, přestože jsou vytvořeny na stejném síťovém protokolu.

Aby byl protokol RPC užitečný, měl by vykazovat následující vlastnosti:

• Generalizace – mělo by být snadné vkládat nové volatelné metody.

• Standardizace – jestliže znáte název a seznam parametrů metody, měli byste být schopni pro ni jednoduše sestavit požadavek.

• Jednoduše konvertovatelný – vrácená hodnota RPC by měla jít jednoduše zkonvertovat na příslušný nativní typ dat.

Samotný protokol HTTP nesplňuje žádné z těchto kritérií, ale poskytuje extrémně vhodnou transportní vrstvu pro zasílání požadavků RPC. V této kapitole se zaměříme na dva nejoblíbenější protokoly RPC a to XML-RPC a SOAP, které se tradičně implementují přes web.

RPC: Interakce se vzdálenými službami

php5 - pokrocile programovani.indb 407 21.9.2004 10:35:24

Page 36: Pokročilé programování v PHP5

Pokročilé programování v PHP 5408

Použití protokolů RPC v aplikacích s velkým provozemI když jsou RPC extrémně flexibilními nástroji, vnitřně jsou pomalé. Jakýkoli proces, který využívá RPC se ihned prováže s výkonem a dostupností vzdálené služby. Dokonce i v nejlepším případě zaznamenáte zdvojnásobení času služby na každé obsluhované stránce. Jsou-li na vzdáleném koncovém bodu nějaké poruchy, celý web se může viset na dotazech RPC. U administrativních nebo málo využívaných služeb to nepředstavuje velký problém, avšak u produkčních nebo velmi navště-vovaných stránek to většinou není přijatelné.

Kouzelné řešení minimalizace dopadu na produkční služby a redukce problémů latence a dostupnosti webových služeb spočívá v implementaci cachovací strategie, díky níž se zbavíme přímé závislosti na vzdálené službě. O cachovacích strategií, které lze jednoduše adaptovat pro zpracování volání RPC, jsme hovořili v kapitole 10 a 11.

XML-RPCXML-RPC je předchůdcem protokolů RPC založených na XML. XML-RPC je nejčastěji zapouzdřen v požadavku a odezvě POST protokolu HTTP, ačkoli, jak si v krátkosti řekneme v kapitole 15, není to ne-zbytně nutné. Jednoduchý požadavek XML-RPC představuje dokument XML, který vypadá asi takto:

<?xml version="1.0" encoding="UTF-8"?><methodCall><methodName>system.load</methodName><params></params></methodCall>

Tento požadavek se odešle metodou POST na server XML-RPC. Server následně vyhledá a provede spe-cifikovanou metodu (v tomto případě system.load) a předá specifikované parametry (v tomto pří-padě se nepředají žádné parametry). Výsledek se poté předá zpět volajícímu. Vrácená hodnota tohoto požadavku představuje řetězec, který obsahuje aktuální zatížení počítače, získané z výsledku unixové-ho příkazu uptime. Podívejte se na příklad výstupu:

<?xml version="1.0" encoding="UTF-8"?>< methodResponse><params><param><value><string>0.34</string></value></param></params></methodResponse>

php5 - pokrocile programovani.indb 408 21.9.2004 10:35:24

Page 37: Pokročilé programování v PHP5

Kapitola 16 – RPC: Interakce se vzdálenými službami 409

Tyto dokumenty samozřejmě nemusíte sami vytvářet a interpretovat. Pro PHP existuje celá řada růz-ných implementací XML-RPC. Obecně preferuji použití tříd PEAR XML-RPC, protože jsou distribuo-vány se samotným PHP. (Používá je instalátor PEAR.) Tudíž mají téměř 100% implementaci. Díky tomu existují pouze pramalé důvody poohlížet se po něčem jiném. Dialog XML-RPC se skládá ze dvou částí: požadavek klienta a odezva serveru.

Nejprve si něco řekneme o kódu klienta. Klient vytvoří dokument request, zašle jej na server a ana-lyzuje (parse) odezvu. Následující kód generuje dokument požadavku znázorněný dříve v této sekci a analyzuje výslednou odezvu:

require_once 'XML/RPC.php';$client = new XML_RPC_Client('/xmlrpc.php', 'www.example.com');$msg = new XML_RPC_Message('system.load');$result = $client->send($msg);if ($result->faultCode()) {print "Error\n";}print XML_RPC_decode($result->value());

Vytvořte nový objekt XML_RPC_Client, který předává vzdálené službě URI a adresu.

Poté se vytvoří XML_RPC_Message obsahující název metody, která se má vyvolat (v tomto případě sys-tem.load). Protože této metodě nejsou předány žádné parametry, není potřeba do zprávy vkládat další data.

Dále se přes metodu send() odešle zpráva na server. Výsledek se zkontroluje, zdali neobsahuje chybu. Není-li výsledkem chyba, hodnota výsledku se dekóduje z formátu XML do nativního typu PHP a vy-tiskne se pomocí XML_RPC_decode().

Pro příjem požadavku, nalezení a provedení příslušného zpětného volání a vrácení odezvy musíte pod-porovat funkcionalitu na straně serveru. Podívejte se na příklad implementace, která zpracuje metodu system.load, kterou jste požadovali v kódu klienta:

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'));}}$dispatches = array('system.load' => array('function' => 'system_uptime'));new XML_RPC_Server($dispatches, 1);

Jelikož funkce PHP potřebné pro podporu příchozích požadavků jsou definovány, stačí pouze zpraco-vat system.load request, který se implementuje přes funkci system_load(). Tato funkce spustí

php5 - pokrocile programovani.indb 409 21.9.2004 10:35:25

Page 38: Pokročilé programování v PHP5

Pokročilé programování v PHP 5410

příkaz uptime a extrahuje z počítače jednominutovou průměrnou zátěž. Dále serializuje extrahovanou zátěž do XML_RPC_Value a zabalí ji do XML_RPC_Response pro vrácení uživateli.

Dále se registruje funkce zpětného volání v odesílací mapě, která informuje server o tom, jak odeslat příchozí požadavky do specifických funkcí. Vytvoříte pole funkcí $dispatches, které se vyvolá. Tohle je pole, které mapuje názvy metod XML-RPC na názvy funkcí PHP. Nakonec se vytvoří objekt XML_RPC_Server a předá se mu odesílací pole $dispatches. Druhý parametr 1 signalizuje, že by se měl požadavek obsloužit okamžitě s pomocí metody service() (která se volá interně).

Metoda service() hledá nezpracovaná data POST HTTP, analyzuje je pro požadavek XML-RPC a poté provede odeslání. Protože se spoléhá na autoglobální proměnnou PHP $HTTP_RAW_POST_DATA, musí-te se ujistit, že máte v souboru php.ini zapnuto always_populate_raw_post_data.

Nyní byste měli obdržet následující, pokud umístíte kód serveru na www.example.com/xmlrpc.php a z libovolného počítače provedete kód klienta:

> php system_load.php0.34

případně jinou hodnotu udávající vaše průměrné jednominutové zatížení.

Tvorba serveru: implementace MetaWeblog APISíla XML-RPC spočívá v tom, že poskytuje standardizovanou metodu pro komunikaci mezi službami. To je užitečné zejména, když neovládáte oba konce požadavku služby. XML-RPC vám umožní jedno-duše vytvořit dobře definované rozhraní na poskytovanou službu. Příkladem toho je odesílací API web ogu.

K dispozici je celá řada systémů pro blogování na webu a existuje spousta nástrojů, které pomáhají li-dem uspořádat a zaslat záznamy těmto systémům. Pokud by neexistovaly standardizované procedury, každý nástroj by musel podporovat každý weblog, aby byl obecně použitelný nebo každý weblog by musel podporovat všechny nástroje. Tento druh spleti relací by nebylo možné škálovat.

I když se množina funkcí a implementace weblogových systémů značně liší, je možné definovat množi-nu standardních operací, které je potřeba provést k odeslání záznamů systémům pro blogování na webu. A následně, weblogy a nástroje musí implementovat pouze toto rozhraní, aby byly nástroje kompatibilní se všemi systémy pro blogování na webu.

Na rozdíl od velkého počtu systémů pro blogování, které jsou k dispozici, se obecně se používají pouze tři odesílací API weblogů: Blogger API, MetaWeblog API a MovableType API (což ve skutečnosti je pouze rozšíření MetaWeblog API). Všechny dostupné nástroje pro vytváření weblogů používají jeden z těchto tří protokolů, a proto, pokud implementujete tyto API, váš weblog bude schopen komunikovat s jakýmkoliv nástrojem. Tohle je obrovská výhoda pro vytváření nových snadno adaptovatelných blo-govacích systémů.

Samozřejmě nejprve musíte mít webový blogovací systém, který lze zacílit jedním z API. Vytváření ce-lého systému přesahuje rámec této kapitoly, a proto, místo jeho vytváření od úplného začátku, můžeme

php5 - pokrocile programovani.indb 410 21.9.2004 10:35:25

Page 39: Pokročilé programování v PHP5

Kapitola 16 – RPC: Interakce se vzdálenými službami 411

vložit vrstvu XML-RPC do blogovacího systému Serendipity. Zmíněné API zpracuje odeslání, a proto bude možné sestavit rozhraní s následujícími rutinami z Serendipity:

function serendipity_updertEntry($entry) {}function serendipity_fetchEntry($key, $match) {}

serendipity_updertEntry() je funkce, která buď aktualizuje stávající záznam nebo vloží nový zá-znam v závislosti na tom, zda je předáno id. Její parametr $entry je pole, které přestavuje řádkovou bránu (row gateway) (elementy pole jsou se sloupci tabulky v poměru jeden-k-jednomu) pro následující tabulku databáze:

CREATE TABLE serendipity_entries (id INT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(200) DEFAULT NULL,timestamp INT(10) DEFAULT NULL,body TEXT,author VARCHAR(20) DEFAULT NULL,isdraft INT);

serendipity_fetchEntry() načte záznam z tabulky porovnáním specifikované dvojice klíč/hodno-ta.

Cílem naší implementace je MetaWeblog API, protože poskytuje větší hloubku funkcionalit než Blogger API. MetaWeblog API implementuje tři hlavní metody:

metaWeblog.newPost(blogid,username,password,item_struct,publish) returns stringmetaWeblog.editPost(postid,username,password,item_struct,publish) returns truemetaWeblog.getPost(postid,username,password) returns item_struct

blogid je identifikátor weblogu, na který se zaměřujete (což je užitečné, pokud systém podporuje více samostatných weblogů). username a password jsou autentizační kritéria, které identifikují odesílate-le. publish je příznak, zdali záznam je koncept nebo by se měl publikovat živě.

item_struct je pole dat k odeslání.

Místo implementace nového formátu dat pro záznamy se autor specifikace MetaWeblogu – Dave Winer – rozhodl použít definici položky item ze specifikace RSS (Really Simple Syndication) 2.0, k dispozici na adrese http://blogs.law.harvard.edu/tech/rss. RSS je standardizovaný formát XML vytvo-řený pro prezentování článků a záznamů v novinách. Jeho uzel item obsahuje následující elementy:

php5 - pokrocile programovani.indb 411 21.9.2004 10:35:25

Page 40: Pokročilé programování v PHP5

Pokročilé programování v PHP 5412

Element Popis title Titulek item. link URL, které odkazuje na naformátovaný tvar položky.

description Shrnutí položky.

author Název autora položky. Podle specifikace RSS by to měla být emailová adresa, ačkoli mnohem častěji se používá přezdívka.

pubDate Datum publikace záznamu.

Specifikace rovněž volitelně poskytuje pole pro odkazy na vlákna komentáře, jedinečné identifikátory a kategorie. Navíc spousta weblogů rozšiřuje RSS definici item pro zahrnutí elementu content:en-coded obsahující úplnou poštu, nejenom tedy shrnutí pošty, které se tradičně nachází v RSS elemen-tu description.

Chcete-li zavést MetaWeblog API, musíte definovat funkce pro implementace tří zmíněných metod. Nejprve se podívejme na funkci pro zpracování zasílání nových záznamů:

function metaWeblog_newPost($message) {$username = $message->params[1]->getval();$password = $message->params[2]->getval();if(!serendipity_authenticate_author($username, $password)) {return new XML_RPC_Response('', 4, 'Authentication Failed');}$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':'false';$id = serendipity_updertEntry($entry);return new XML_RPC_Response( new XML_RPC_Value($id, 'string'));}

metaWeblog_newPost() extrahuje z požadavku parametry username a password a s pomocí meto-dy getval() deserializuje jejich XML reprezentace do typů PHP. Poté metaWeblog_newPost() au-tentizuje specifikovaného uživatele. Pokud uživatel neprokáže svou identitu, metaWeblog_newPost() vrací prázdný objekt XML_RPC_Response s chybovou zprávou "Authentication Failed".

Proběhne-li autentizace úspěšně, metaWeblog_newPost() načte parametr item_struct a s pomocí metody getval() jej deserializuje na PHP pole $item_struct. Pole $entry definující reprezentaci interního záznamu Serendipity se sestaví z $item_struct a předá se serendipity_updertEntry(). XML_RPC_Response, kterou tvoří identifikátor nového záznamu, se vrátí volajícímu.

Back-end pro MetaWeblog.editPost se velmi podobá MetaWeblog.newPost. Podívejte se na kód:

php5 - pokrocile programovani.indb 412 21.9.2004 10:35:26

Page 41: Pokročilé programování v PHP5

Kapitola 16 – RPC: Interakce se vzdálenými službami 413

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 Failed');}$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['id'] = $postid;$entry['isdraft'] = ($publish == 0)?'true':'false';$id = serendipity_updertEntry($entry);return new XML_RPC_Response( new XML_RPC_Value($id?true:false, 'boolean'));}

Provede se stejná autentizace a provede se sestavení a aktualizace pole $entry. Pokud serendipi-ty_updertEntry vrací $id, znamená to, že byla úspěšná a odezva se nastaví na hodnotu true, jinak se odezva nastaví na hodnotu false.

Poslední funkcí, kterou máme ještě implementovat, je zpětné volání MetaWeblog.getPost. Tato funk-ce používá serendipity_fetchEntry() k získání podrobných informací o poště a poté naformátuje odezvu XML obsahující item_struct. Podívejte se na implementaci:

function metaWeblog_getPost($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 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, 'struct');

php5 - pokrocile programovani.indb 413 21.9.2004 10:35:26

Page 42: Pokročilé programování v PHP5

Pokročilé programování v PHP 5414

return new XML_RPC_Response($entry);}

Všimněte si, že po načtení záznamu se v item připraví pole všech dat. XML_RPC_iso8601() se postará o formátování časové známky Unixu, kterou používá Serendipity, na formát odpovídající ISO 8601, jenž vyžaduje RSS item. Výsledné pole se poté serializuje jako struct XML_RPC_Value. Tohle představuje standardní způsob sestavování XML-RPC typu struct ze základních typů PHP.

Doposud jste viděli, že XML_RPC_Value lze předat identifikátory string, boolean, dateTime.iso8601 a struct. Podívejte se na úplný seznam všech možností:

Typ Popisi4/int 32bitové celé číslo

boolean logický typ

double číslo s plovoucí desetinou čárkou

string řetězec

dateTime.iso8601 časová známka ve formátu ISO 8601

base64 základní 64kódovaný řetězec

struct implementace asociativního pole

array neasociativní (indexované) pole

Typy struct a array mohou obsahovat jakýkoli datový typ (včetně dalších elementů struct a ar-ray). Není-li specifikován žádný typ, použije se string. I když všechna PHP data lze reprezentovat buď jako string, struct nebo array, jsou podporovány i ostatní typy, protože vzdálené aplikace napsané v jiných jazycích mohou vyžadovat specifičtější typ dat.

Chcete-li registrovat tyto funkce, vytvořte odeslání následovně:

$dispatches = array(metaWeblog.newPost' =>array('function' => 'metaWeblog_newPost'),'metaWeblog.editPost' =>array('function' => 'metaWeblog_editPost'),'metaWeblog.getPost' =>array('function' => 'metaWeblog_getPost'));$server = new XML_RPC_Server($dispatches,1);

Gratulujeme! Váš software je nyní kompatibilní s MetaWeblog API!

Informativní služby XML-RPC Uživatelé služeb XML-RPC jistě ocení možnost požádat server o podrobné informace o všech poskyto-vaných službách. XML-RPC definují tři standardní, vestavěné metody pro takovéto sebepozorování:

php5 - pokrocile programovani.indb 414 21.9.2004 10:35:26

Page 43: Pokročilé programování v PHP5

Kapitola 16 – RPC: Interakce se vzdálenými službami 415

• system.listMethods – vrací pole všech metod implementovaných serverem (všechny zpětné volání registrované v odesílací mapě).

• system.methodSignature – přijímá jeden parametr (název metody) a vrací pole možných signa-tur (prototypů) metody.

• system.methodHelp – přijímá název metody a vrací dokumentační řetězec pro metodu.

Protože PHP je dynamický jazyk a nedbá na dodržování počtu nebo typu argumentů předávaných funkci, data, která se mají vrátit pomocí system.methodSignature, musí být specifikovány uživate-lem. Metody v XML-RPC mohou mít různé parametry, proto vrácená množina je pole všech možných signatur. Každá signatura je sama o sobě polem; první element pole je návratový typ metody a zbývající elementy představují parametry metody.

Pro poskytování těchto dalších informací musí server zvětšit svou odesílací mapu, aby zahrnul další informace, jak můžete vidět níže u metody metaWeblog.newPost:

$dispatches = array('metaWeblog.newPost' =>array('function' => 'metaWeblog_newPost','signature' => array(array($GLOBALS['XML_RPC_String'],$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'),/* ... */);

Tyto tři metody můžete spolu kombinovat pro získání uceleného obrázku o tom, co implementuje ser-ver XML-RPC. Podívejte se na skript, který vypíše dokumentaci a signatury pro každou metodu na daném serveru XML-RPC:

<?phprequire_once 'XML/RPC.php';if($argc != 2) {print "Must specify a url.\n";exit;}$url = parse_url($argv[1]);$client = new XML_RPC_Client($url['path'], $url['host']);

php5 - pokrocile programovani.indb 415 21.9.2004 10:35:27

Page 44: Pokročilé programování v PHP5

Pokročilé programování v PHP 5416

$msg = new XML_RPC_Message('system.listMethods');$result = $client->send($msg);if ($result->faultCode()) {echo "Error\n";}$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";}$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";}?>

Spustíte-li skript na instalaci Serendipity, vygeneruje se následující výstup:

> xmlrpc-listmethods.php http://www.example.org/serendipity_xmlrpc.php/* ... */

php5 - pokrocile programovani.indb 416 21.9.2004 10:35:27

Page 45: Pokročilé programování v PHP5

Kapitola 16 – RPC: Interakce se vzdálenými službami 417

Method metaWeblog.newPost:Takes blogid, username, password, item_struct, publish_flagand returns the postid of the new entrySignature #0: string metaWeblog.newPost(string, string, string, struct, string)/* ... */Method system.listMethods:This method lists all the methods that the XML-RPC server knowshow to dispatchSignature #0: array system.listMethods(string)Signature #1: array system.listMethods()Method system.methodHelp:Returns help text if defined for the method passed, otherwisereturns an empty stringSignature #0: string system.methodHelp(string)Method system.methodSignature:Returns an array of known signatures (an array of arrays) forthe method name passed. If no signatures are known, returns anone-array (test for type != array to detect missing signature)Signature #0: array system.methodSignature(string)

SOAPSOAP byla původně zkratka Simple Object Access Protocol, ale od verze 1.1 se jedná už pouze o označe-ní, a nikoliv o zkratku. SOAP je protokol pro výměnu dat v heterogenním prostředí. Na rozdíl od XML--RPC, který je určen především pro zpracování RPC, je SOAP vytvořen pro generické zasílání zpráv a RPC je pouze jednou z aplikací SOAP. Je však potřeba říct, že tato kapitola pojednává o RPC, přičemž se soustředíme pouze na podmnožinu SOAP 1.1 používanou k implementaci RPC.

Jak tedy SOAP vypadá? Podívejte se na vzorek obalu SOAP, který používá xmeth-ods.net – vzorovou službu SOAP pro implementaci kanonického příkladu SOAP RPC pro získávání skladových cen IBM (kanonický příklad proto, že jde o příklad z návrhového dokumentu SOAP):

<?xml version="1.0" encoding="UTF-8"?><soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:xsi="http://www.w3.org/2001/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/"><symbol xsi:type="xsd:string">ibm</symbol>

php5 - pokrocile programovani.indb 417 21.9.2004 10:35:27

Page 46: Pokročilé programování v PHP5

Pokročilé programování v PHP 5418

</getQuote></soap:Body></soap:Envelope>Tohle je odezva:<?xml version="1.0" encoding="UTF-8"?><soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/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.25</Result></n:getQuoteResponse></soap:Body></soap:Envelope>

SOAP je vynikající příklad skutečnosti, že jednoduchý koncept nemusí vždy přinášet jednoduchou im-plementaci. Zprávu SOAP tvoří obal, který obsahuje hlavičku a tělo. V SOAP má všechno jmenný pro-stor, což je teoreticky dobrá věc, i když čtení XML je poté obtížnější.

Nejvyšší uzel je Envelope, což je kontejner zprávy SOAP. Tohle je element ve jmenném prostoru xml-soap, jak udává jeho plně kvalifikovaný název elementu <soap:Envelope> a deklarace jmenného pro-storu:

xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"

která tvoří asociaci mezi soap a jmenným prostorem URI http://schemas.xmlsoap.org/soap/envelope/.

SOAP a SchemaSOAP znesnadňuje použití Schema, což je jazyk založený na XML pro definici a validaci datových struktur. Úplný jmenný prostor elementu je obvykle (například http://schemas.xmlsoap.org/soap/envelope/) dokument Schema, který popisuje jmenný prostor. Není to však podmínkou – jmenný prostor dokonce nemusí být URL.

Jmenné prostory plní stejný účel v XML jako v jakémkoli programovacím jazyku: zabraňují možným kolizím dvou názvů. Popřemýšlejte o nejvyšším uzlu <soap-env:Envelope>. Název atributu Enve-lope je ve jmenném prostoru soap-env. Takže, pokud by z nějakého důvodu chtěl FedEX definovat formát XML, který používá Envelope jako atribut, mohlo by to být <FedEX:Envelope> a všichni by byli spokojeni.

Zde jsou čtyři jmenné prostory deklarované v SOAP Envelope:

php5 - pokrocile programovani.indb 418 21.9.2004 10:35:28

Page 47: Pokročilé programování v PHP5

Kapitola 16 – RPC: Interakce se vzdálenými službami 419

• xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" – obal SOAP definice Schema popisuje základní objekty SOAP a je standardním jmenným prostorem obsaženým v každém požadavku SOAP.

• xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" – atribut elementu xsi:type se převážně používá pro specifikaci typů elementů.

• xmlns:xsd="http://www.w3.org/2001/XMLSchema" – Schema deklaruje počet základních datových typů, které lze použít pro specifikaci a validaci.

• xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" – specifikace typu kódování použitého ve standardních požadavcích SOAP.

Element < GetQuote> má rovněž jmenný prostor – v tomto případě s následujícím extrémně dlouhým názvem:

http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote.Stock-Quote

Všimněte si, jak je použito Schema pro specifikaci typu a uspořádání skladového symbolu, který se předává:

<symbol xsi:type="xsd:string">ibm</symbol>

A podobně – v odezvě uvidíte specifický zápis skladové ceny:

<Result xsi:type="xsd:float">90.25</Result>

Výše uvedené specifikuje, že výsledek musí být číslo s plovoucí desetinou tečkou. To je užitečné, protože jsou zde sady nástrojů pro validaci Schema, která vám umožní ověřit váš dokument. Mohly by vám sdělit, že odezva v tomto formuláři není platná, protože foo není platnou reprezentací čísla s plovoucí desetinou tečkou:

<Result xsi:type="xsd:float">foo</Result>

WSDLSOAP je doplněno jazykem WSDL (Web Services Description Language). WSDL je jazyk založený na XML popisující schopností a metody interakce s webovými službami (častěji než SOAP). Zde je soubor WSDL, který popisuje službu, na kterou jsou v předchozí sekci sestaveny požadavky:

<?xml version="1.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/2001/XMLSchema"xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"

php5 - pokrocile programovani.indb 419 21.9.2004 10:35:28

Page 48: Pokročilé programování v PHP5

Pokročilé programování v PHP 5420

xmlns="http://schemas.xmlsoap.org/wsdl/"><message name="getQuoteResponse1"><part name="Result" type="xsd:float" /></message><message name="getQuoteRequest1"><part name="symbol" type="xsd:string" /></message><portType name="net.xmethods.services.stockquote.StockQuotePortType"><operation name="getQuote" parameterOrder="symbol"><input message="tns:getQuoteRequest1" /><output message="tns:getQuoteResponse1" /></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"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://66.28.98.121:9090/soap" /></port></service></definitions>

WDSL se zřetelně zapojuje do obtížného použití jmenných prostorů, přičemž uspořádání má poněkud zmatenou logiku.

php5 - pokrocile programovani.indb 420 21.9.2004 10:35:28

Page 49: Pokročilé programování v PHP5

Kapitola 16 – RPC: Interakce se vzdálenými službami 421

První částí tohoto kódu, která stojí za povšimnutí, je uzel < portType>. Tento uzel specifikuje činnosti, které lze provést, a vstupní a výstupní zprávy. Definuje to getQuote, která přijímá getQuoteRequest1 a odpovídá pomocí getQuteResponse1.

Uzly < message> pro getQuoteResponse1 specifikují, že obsahuje jediný element Result typu float. A podobně, getQuoteRequest1 musí obsahovat jediný element symbol typu string.

Dále je tu uzel < binding>. Vazba se asociuje k < portType> přes atribut type, který odpovídá ná-zvu <portType>. Vazby specifikuje protokol a podrobné informace o přenosu (například specifikace kódování pro data obsažená v těle SOAP), nicméně, ne skutečné adresy. Vazba se asociuje k jedinému protokolu, v tomto případě HTTP, viz následující:

<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />

Nakonec uzel < service> seskupí skupinu portů a specifikuje pro ně adresy. Protože v tomto příkladě je použit jediný port, odkazuje se a je provázán s http://66.28.98.121:9090/soap pomocí násle-dujícího:

<port name="net.xmethods.services.stockquote.StockQuotePort"binding="tns:net.xmethods.services.stockquote.StockQuoteBinding"><soap:address location="http://66.28.98.121:9090/soap" /></port>

Za povšimnutí stojí skutečnost, že nic nepropojí SOAP, aby fungoval pouze s HTTP, ani nezapříčiňuje, že se musí vrátit odezvy. SOAP je vytvořen tak, aby byl flexibilním univerzálním protokolem pro zasílání zpráv, přičemž RPC přes HTTP je pouze jednou implementací. Soubor WSDL vám sdělí, jaké služby jsou k dispozici, jak a kde je zpřístupnit. SOAP poté implementuje samotný požadavek a odezvu.

Naštěstí třídy PEAR SOAP zpracují téměř všechno za vás. Chcete-li spustit požadavek SOAP, vytvořte nejprve nový objekt SOAP_Client a předejte soubor WSDL službám, které chcete zpřístupnit. Objekt SOAP_Client poté vygeneruje veškerý potřebný kód proxy pro požadavky, které se mají provést přímo, alespoň v případě, kde jsou všechny vstupu jednoduché typy Schema. Následující kód představuje kom-pletní požadavek klienta na demo službu xmethods.net:

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

SOAP_Client provede všechny tajemné věci stojící za tvorbou objektu proxy, který poskytuje přímé vykonání metod specifikovaných v WSDL. Poté, co se provede volání getQuote(), výsledek se deseri-alizuje do nativních typů PHP s pomocí deserializeBody() a získáte následující:

> php delayed-stockquote.phpCurrent price of IBM is 90.25

php5 - pokrocile programovani.indb 421 21.9.2004 10:35:28

Page 50: Pokročilé programování v PHP5

Pokročilé programování v PHP 5422

Přepis system.load na službu SOAPRychlý test vašich nových znalostí SOAP spočívá v reimplementaci XML-RPC služby system.load na službu SOAP.

Pro začátek definujte službu SOAP jako specializaci SOAP_Service. Je třeba implementovat minimálně čtyři funkce:

• public static function getSOAPServiceNamespace(){} – musí vracet definovaný jmenný pro-stor.

• public static function getSOAPServiceName() {} – musí vracet název definované služby.

• public static function getSOAPServiceDescription() – musí vracet popis řetězce služby, kterou definujete.

• public static function getWSDLURI() {} – musí vracet URL, které směřuje na soubor WSDL, kde je popsána služba.

Navíc byste měli definovat všechny metody, které budete volat.

Podívejte se na definici třídy pro novou SOAP implementaci SystemLoad:

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.]+)/", $uptime, $matches)) {return array( 'Load' => $matches[1]);}}}

Na rozdíl od XML-RPC vaše metody SOAP_Service obdrží své argumenty jako normální proměnné PHP. Metoda musí vrátit pouze pole parametrů zprávy odezvy. Jmenné prostory, které si vybíráte, nejsou povinné, ale jsou ověřovány proti specifikovanému souboru WSDL, a proto musí být interně shodné.

Jakmile definujete službu, musíte ji registrovat jako u XML-RPC. V následujícím příkladě vytvoříte nový SOAP_Server, vložíte novou službu a nařídíte instanci serveru, aby zpracoval příchozí požadavky:

php5 - pokrocile programovani.indb 422 21.9.2004 10:35:29