Programowanie Aplikacji Internetowych Wykład 7 PHP zaawansowane programowanie Opracował dr inż. Dariusz Trawicki Gdańsk 12.01.2011 1
PHP zaawansowane programowanie Wzorce projektowe Wzorzec MVC Warstwa abstrakcji baz danych Mapowanie obiektowo-relacyjne Szablony (Smarty) PEAR 2
PHP zaawansowane programowanie Wzorce projektowe Wzorzec projektowy - określony sposób rozwiązania danego problemu. Jest to swego rodzaju opis uniwersalnego rozwiązania często występującego problemu w określonym kontekście. Więcej na stronie: http://phpedia.pl/wiki/wzorce_projektowe 3
Architektura MVC Przykładem wzorca architektury aplikacji internetowej jest MVC (ang. Model-View- Controler). Wzorzec model-widok-kontroler pozwala odseparować w programie logikę sterująca aplikacją (kontroler), generowaną treść (widok) i przetwarzanie danych (model) w taki sposób by każdy z tych trzech elementów reprezentował odrębne, niezależne komponenty. Więcej np. na stronach: http://wortal.php.pl/wortal/artykuly/php/architektura/wprowadzenie_do_mvc, http://phpedia.pl/wiki/mvc. Wzorzec MVC jest często używany we frameworkach do szybkiego tworzenia aplikacji internetowych. Przykładami frameworków (stworzonych w PHP) stosujących ten wzorzec są: Code Igniter, Kohana, CakePHP, Zend Framework, Symfony. 4
Rys.7.1.Wzorzec MVC 5
Przykład (http://www.symfony-project.org/book/1_0/02-exploring-symfony-s-code) W poniższym przykładzie przedstawiona zostanie idea konwersji typowej, napisanej w PHP aplikacji internetowej do aplikacji bazującej na architekturze MVC. Poniżej przedstawiony kod jest przykładem tzw. płaskiego programowania/pliku (ang. flat programming/file). <?php // Connecting, selecting database $link = mysql_connect('localhost', 'myuser', 'mypassword'); mysql_select_db('blog_db', $link); // Performing SQL query $result = mysql_query('select date, title FROM post', $link);?> <html> <head> <title>list of Posts</title> </head> 6
<body> <h1>list of Posts</h1> <table> <tr><th>date</th><th>title</th></tr> <?php // Printing results in HTML while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { echo "\t<tr>\n"; printf("\t\t<td> %s </td>\n", $row['date']); printf("\t\t<td> %s </td>\n", $row['title']); echo "\t</tr>\n";?> </table> </body> </html> <?php // Closing connection mysql_close($link);?> 7
Przedstawiony kod charakteryzują trzy zasadnicze wady: 1. Brak kontroli występowania ewentualnych błędów w trakcie wykonywania skryptu, np. może wystąpić problem połączenia z serwerem baz danych. 2. Nagminne przeplatanie się kodu HTML z kodem PHP. 3. Skrypt jest ukierunkowany na współpracę z serwerem MySQL. Wyodrębnienie warstwy prezentacji Przytoczony kod zostanie podzielony na dwie części. Pierwsza zawierająca kod PHP odpowiedzialna jest za tzw. logikę biznesową tworzyć będzie tzw. kontroler aplikacji. 8
Kod kontrolera (plik index.php) <?php // Connecting, selecting database $link = mysql_connect('localhost', 'myuser', 'mypassword'); mysql_select_db('blog_db', $link); // Performing SQL query $result = mysql_query('select date, title FROM post', $link); // Filling up the array for the view $posts = array(); while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { $posts[] = $row; // Closing connection mysql_close($link); // Requiring the view require('view.php');?> Logika odpowiedzialna za działanie aplikacji jest realizowana w kontrolerze. Plik kontrolera zawiera jedynie kod PHP (brak HTMLa), co umożliwia wykorzystanie go, np. do aplikacji prezentującej wyniki w postaci pliku pdf czy też xml. 9
Natomiast kod HTML, wzbogacony o kod PHP prezentujący wyniki/dane uzyskane z kontrolera, zostanie zapisany w wydzielonym pliku (view.php), generującym tzw. widok rozważanej aplikacji. Kod widoku (plik view.php) <html> <head> <title>list of Posts</title> </head> <body> <h1>list of Posts</h1> <table> <tr><th>date</th><th>title</th></tr> <?php foreach ($posts as $post):?> <tr> <td><?php echo $post['date']?></td> <td><?php echo $post['title']?></td> </tr> <?php endforeach;?> </table> </body> </html> UWAGA: Kod PHP zawarty w widoku należy ograniczać do niezbędnego minimum. Zaleca się zawęzić wykorzystywane w widoku instrukcje PHP do: echo, if/endif, foreach/endforeach. 10
Wyodrębnienie modelu Znacząca część kodu zawartego w kontrolerze związana odpowiada za manipulowania danymi. Należy się zastanowić czy jest to rozwiązanie dobre, pojawiają się pytania: 1. Co w przypadku, gdy w przypadku bardziej rozbudowanej aplikacji inna jej część (inny kontroler) wymagać będzie tych samych danych z bazy? 2. Czy nie wygodniej przechowywać kod odpowiedzialny za komunikacje z bazą w jednym "miejscu" (pliku, klasie, itp.) aplikacji? 3. Co się stanie w przypadku gdy powstanie koniecznośc zmiany sposobu przechowywania danych, np. zmiana serwera baz danych z MySQL na PostgreSQL? Wychodząc naprzeciw wymienionym problemom warto wyodrębnić odpowiedni kod z pliku kontrolera i umieścić go w wydzielonym pliku (model.php), który nazywać będziemy modelem. Model jest częścią MVC, odpowiedzialną za tzw. logikę biznesową. Termin logika biznesowa odnosi się do funkcjonalności związanej ze sposobem, w jaki aplikacja przechowuje dane. 11
Kod modelu (plik model.php) <?php function getallposts() { // Connecting, selecting database $link = mysql_connect('localhost', 'myuser', 'mypassword'); mysql_select_db('blog_db', $link); // Performing SQL query $result = mysql_query('select date, title FROM post', $link); // Filling up the array $posts = array(); while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { $posts[] = $row; // Closing connection mysql_close($link); return $posts;?> 12
Wprowadzone modyfikacje skutkują tym, iż kod kontrolera (plik index.php) znacznie się upraszcza. Plik index.php <?php // Requiring the model require_once('model.php'); // Retrieving the list of posts $posts = getallposts(); // Requiring the view require('view.php');?> W rozważanej aplikacji zadaniem kontrolera jest przekazanie pobranych z modelu danych do widoku. W przypadku bardziej złożonych aplikacji zadaniami obsługiwanymi przez kontroler są m.in. autoryzacja użytkownika, obsługa sesji, odbiór przesyłanych danych itp. 13
Podsumowując należy podkreślić fakt, iż w efekcie końcowym uzyskaliśmy aplikację spełniającą założenia architektury MVC, która wymaga podziału aplikacji na trzy zasadnicze warstwy: modelu, widoku i kontrolera. W MVC stosowana jest zasada oddzielenia logiki biznesowej od warstwy prezentacji. Pozwala to na niezależne prowadzenie prac (separację) programistów (kod funkcjonalny) oraz projektantów (odpowiednia prezentacja wyświetlanych danych). Zalety oddzielenia logiki biznesowej od warstwy prezentacji: zwiększoną czytelność kodu HTML i PHP, łatwiejsze zarządzanie dużymi projektami, łatwiejsza konserwacja projektów, programista nie musi konsultować każdej zmiany z projektantem (i na odwrót), programista może modyfikować kod PHP bez konieczności dokonywania zmian w szablonach, specjalizacja - wzrost wydajności. 14
Warstwa abstrakcji baz danych Kolejnym problemem jest sposób implementacji modelu. Podstawową zaletą wyodrębnienia modelu jest możliwość jego wielokrotnego użycia, unikając przy tym zbędnego powielania kodu odpowiedzialnego za manipulację wykorzystywanych w aplikacji danych. W tym miejscu warto jednakże wprowadzić jeszcze jeden termin/mechanizm jakim jest warstwa abstrakcji baz danych [WABD] (ang. Database Abstraction Layer [DAL]). Jej użycie wprowadza do aplikacji cechę odporności na ewentualne zmiany sposobu przechowywania danych. Warstwa modelu zostanie podzielona na dwie części: warstwę dostępu do danych, warstwę abstrakcji baz danych. Zawarte w warstwie dostępu do danych funkcje (operujące na danych) nie zawierają kodu zależnego od używanego źródła danych, np. serwera baz danych. Wywołują one natomiast odpowiednie funkcje (warstwę abstrakcji baz danych) odpowiedzialne bezpośrednio za manipulację danymi, np. komunikację z bazą danych (przesyłanie kwerend SQL). 15
Kod (część modelu) implementujący warstwę abstrakcji baz danych <?php function open_connection($host, $user, $password) { return mysql_connect($host, $user, $password); function close_connection($link) { mysql_close($link); function query_database($query, $database, $link) { mysql_select_db($database, $link); return mysql_query($query, $link); function fetch_results($result) { return mysql_fetch_array($result, MYSQL_ASSOC);?> 16
Kod (część modelu) implementujący warstwę dostępu do danych <?php function getallposts() { // Connecting to database $link = open_connection('localhost', 'myuser', 'mypassword'); // Performing SQL query $result = query_database('select date, title FROM post', 'blog_db', $link); // Filling up the array $posts = array(); while ($row = fetch_results($result)) { $posts[] = $row; // Closing connection close_connection($link); return $posts;?> 17
WABD umożliwia korzystanie z różnych sterowników baz danych, co oznacza, że aplikacja pracująca na bazie MySQL mogłaby równie dobrze pracować pod każdą inną bazą danych SQL, czyli WABD ujednolica interfejs API. Jest to możliwe dzięki połączeniu ze sobą: - warstwy abstrakcji dla interfejsu bazy danych (ujednolicenie połączeń i przesyłu danych z bazą), - warstwy abstrakcji dla kodu SQL (w razie potrzeby emulującą kod SQL do zgodnego z wybraną bazą danych), - warstwy abstrakcji typów danych. Przykładami warstwy abstrakcji baz danych są: MDB2 (dostępna w ramach PEAR), Creole, PDO (PHP Data Objects ). 18
Mapowanie obiektowo-relacyjne Kolejnym krokiem prowadzącym do wydajnej i wygodnej obsługi baz danych jest tzw. mapowanie obiektowo-relacyjne (ang. Object-Relational Mapping ORM). Pozwala ono na dostęp do bazy danych za pomocą zestawu obiektów, dostarczając proste API do odczytywania i zapisywania danych. Przykładem biblioteki PHP implementującej ORM jest biblioteka o nazwie Propel. Propel, jako aplikacja, posiada dwa główne komponenty (całkowicie rodzielne): 1. Generator służący do budowy klas i plików z kodem SQL (propel-generator) 2. Środowisko, które pozwala budować i wykonywać zapytania SQL oraz narzędzia do zarządzania symultanicznymi połączeniami do serwerów bazodanowych (propel-runtime) Środowisko uruchomieniowe dostarcza warstwę abstrakcji i enkapsulacji bazy danych dla logiki biznesowej. Klasy Propela reprezentują warstwę modelu w tradycyjnym MVC. Model ten został zaprojektowany tak, aby zawierał możliwość walidacji na każdym poziomie Twojej aplikacji. Poniższy diagram (źródło: http://propel.phpdb.org/docs/pl/user_guide/chapters/introduction.html) pokazuje, jak Propel współpracuje z Creole. Najwyższy poziom, to aplikacja napisana w PHP i może zawierać dowolną ilość innych warstw. 19
20
Przykład ze strony: http://wortal.php.pl/wortal/artykuly/bazy_danych/propel_czyli_wydajna_i_wygodna_obsluga_ baz_danych_w_php5/podstawowe_operacje_na_obiektach Poniższy kod realizuje prosty sposób obsługi przykładowego formularza <?php $link = mysql_connect('localhost', 'root', ''); if (!$link) { die('nie mogę się połączyć do DB: '. mysql_error()); $db_selected = mysql_select_db('bookstore', $link); if (!$db_selected) { die('nie mogę użyć DB "bookstore": '. mysql_error()); //Wydawca $sql = 'INSERT INTO publisher (name) VALUES '.'("'.mysql_escape_string($_post['publisher_name']).'")'; $result = mysql_query($sql); if (!$result) { die(mysql_error()); $publisher_id = mysql_insert_id(); 21
c.d. //Autor $sql = 'INSERT INTO author (first_name, last_name) VALUES '.'("'.mysql_escape_string($_post['firs_name']).'","'.mysql_escape_string($_post['last_name']).'")'; $result = mysql_query($sql); if (!$result) { die(mysql_error()); $author_id = mysql_insert_id(); //Książka $sql = 'INSERT INTO book (publisher_id, author_id, title, isbn)'.'values '.'('.$publisher_id.','.$author_id.','.'"'.mysql_escape_string($_post['title']).'","'.mysql_escape_string($_post['isbn']).'")'; $result = mysql_query($sql); if (!$result) { die(mysql_error()); $book_id = mysql_insert_id(); mysql_close($link); echo "Dodano książkę o ID: $book_id";?> 22
Obsługa przykładowego formularza tym razem zrealizowany w Propel <?php $old_inc_path = ini_get('include_path'); ini_set('include_path',$old_inc_path.';f:/www/phpsolmag/propel/propel- 1.0.0/runtime/classes'); require_once('propel/propel.php'); Propel::init(dirname( FILE ).'/conf/bookstore-conf.php'); include_once 'bookstore/publisher.php'; include_once 'bookstore/author.php'; include_once 'bookstore/book.php'; $pub = new Publisher(); $pub->setname($_post['publisher_name']); $author = new Author(); $author->setfirstname($_post['firs_name']); $author->setlastname($_post['last_name']); $book = new Book(); $book->settitle($_post['title']); $book->setisbn($_post['isbn']); $book->setpublisher($pub); $book->setauthor($author); $book->save();?> 23
PHP zaawansowane programowanie System szablonów pozwala usprawnić wspólną pracę projektantom i programistom aplikacji PHP oraz oddzielić logikę biznesową od warstwy prezentacji danych. Szablon jest jedynie szkieletem nie zawierającym treści. W miejscach, w których powinna znaleźć się treść, umieszczone są zmienne szablonu, które określają, w jaki sposób szablon wypełnić treścią, by otrzymać kompletną stronę WWW. Przykładem systemu szablonów dla języka PHP jest Smarty. 24
PHP zaawansowane programowanie PEAR (ang. PHP Extension and Application Repository - zbiór rozszerzeń i aplikacji PHP) jest biblioteką oferującą ponad 500 uporządkowanych i podzielonych na kategorie pakietów klas, których kod został wielokrotnie zbadany przez programistów z całego świata pod kątem bezpieczeństwa i szybkości wykonywania. Pakiety dostępne w PEAR zawierają gotowe rozszerzenia umożliwiające wykonanie niemal wszystkich standardowych operacji w PHP. Rozszerzenia te przechodzą przez ścisły system kontroli jakości, a ich autorzy muszą stosować się do określonych zaleceń. Dlatego pisanie programów z wykorzystaniem pakietów jest nie tylko szybsze, ale prowadzi też do powstawania lepszych i bardziej spójnych aplikacji. Przykłady pakietów PEAR: Auth - uwierzytelnianie użytkownika, Database - połączenie z bazą danych, Mail - współpraca z POP, IMAP i SMTP, Calendar - praca z datami. 25