Swój pierwszy wpis chciałbym poświęcić usługom sieciowym, w tym aplikacjom typu Mashup. Na pierwszy ogień polecimy z usługami REST (ze względu na dużą prostotę).
Na bazie usług sieciowych można konstruować rozproszone systemy i aplikacje. Aplikacje komunikują się z usługami sieciowymi z wykorzystaniem internetowych protokołów i formatów danych.
W notce tej przyjrzymy się podstawom pobierania danych z usług wykorzystujących protokoł REST oraz opracujemy prostą klasę jeszcze bardziej ułatwiając to zadanie.
Zanim przejdziemy do konkretów, chciałbym wyjaśnić podstawowe pojęcia, których znajomość przyda się w zrozumieniu notki. Oto one:
- Mashup – strona internetowa łącząca w sobie treść innych aplikacji sieci Web. Przy pobieraniu danych wykorzystywane są różne protokoły, w tym SOAP, XML-RPC i REST (opisywany w tym wpisie).
-
REST – wzorzec architektury oprogramowania opierający się na bezstanowej wymianie informacji w środowisku rozproszonym. Jako nośnik informacji wykorzystuje m.in. formaty XML i JSON (wikipedia.org).
Mówiąc prościej, usługa REST to odpowiednio zapisany ciąg znaków w formie adresu URI, który następnie przysyłamy do zewnętrznej aplikacji metodą GET, co skutkuje zwrotem żądanych danych.
- API – (ang. Application Programming Interface), interfejs programowy aplikacji. Spis procedur i funkcji, które mogą wywoływać zewnętrzne aplikacje.
- PEAR – obszerny zbiór bibliotek i rozszerzeń dla języka PHP. Rozszerzenia te znacząco ułatwiają pracę programisty, wykonując za Ciebie wiele trudnej i żmudnej pracy ;) Zobasz spis pakietów PEAR.
Skoro już rozumiemy czym są usługi REST i na jakiej zasadzie działają aplikacje typu Mashup, przejdźmy do rzeczy!
Korzyści z aplikacji Mashup
Wiele popularnych serwisów społecznościowych, tj. Amazon, Last.fm, Allegro, Google i innych udostępnia swój interfejs API.
Powodów i zastosowań jest multum. Z usług tych korzystają nawet najwięksi gracze na Polskim rynku, np. nasza-klasa skorzystała z WebAPI Allegro, dając możliwość połączenia swego profilu z allegrowym kontem. Podobnych przykładów jest naprawdę wiele i nietrudno je znaleźć.
We wpisie tym przyjrzymy się, w jaki sposób pobrać dane z usług typu REST oraz jak zaanalizować w ten sposób pobrane pliki XML.
Let’s do this!
Czas przystąpić do działania. Aby ułatwić sobie pracę wykorzystamy repozytorium PEAR. Potrzebujemy zainstalować następujące moduły PEAR:
- XML_Serializer,
- XML_Util,
- XML_Parser.
Jeśli nie posiadasz jeszcze repozytorium PEAR lub nie potrafisz instalować kolejnych bibliotek, zobacz jak instalować pakiekty PEAR.
Zacznijmy więc od utworzenia podstawowej klasy, która wykona połączenie GET i pobierze dane z zewnętrznego serwera, a następnie dokona deserializacji do tablicy PHP.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | abstract class REST_Parser { protected $api_url; protected $xml_uri; protected $unserialized_data; protected $params_array; private $params_string; public function __construct($params=array()) { // save parameters values $this->params_array = $params; } protected function generateUriData() { // check parameters exist if (!empty($this->params_array) and is_array($this->params_array)) { foreach ($this->params_array as $param => $value) { $this->params_string .= '&' . $param . '=' . rawurlencode($value); } } // REST: Uri for API Methods $this->xml_uri = $this->api_url . '?' . $this->params_string; } public function getUnserializedData() { // either the file name of an XML document or a string containing the XML document $this->generateUriData(); // Instantiate the serializer $unserializer = &new XML_Unserializer(); // Global settings $options = array( 'parseAttributes' => false, 'encoding' => 'utf-8' ); // Using serialization $unserializer->setOptions($options); $unserializer->unserialize($this->xml_uri, true); // Check whether serialization worked if (PEAR::isError($status)) { die($status->getMessage()); } // Get results $this->unserialized_data = $unserializer->getUnserializedData(); } protected function isErrorData() { return ( !is_array($this->unserialized_data) || empty($this->unserialized_data) ); } public function setUrlToApi($url) { $this->api_url = $url; } } |
Powyższa klasa jest abstrakcyjna, ponieważ bezcelowe jest tworzenie instancji parsera „od niczego”. Dopiero klasy dziedziczące będą zajmowały się konkretnymi zadaniami, więc abstrakcja zostanie wtedy zdjęta.
Przejdźmy do krótkiej analizy kodu. Klasa posiada pięć metod, w tym jedna magiczna (konstruktor). Mają one następujące zadania:
- __construct – metoda ta przyjmuje i zapisuje do zmiennej tablicę klucz-wartość.
- generateUriData() – zadaniem metody jest utworzenie adresu usługi REST.
- getUnserializedData() – wywołanie metody generateUriData(), a następnie dokonanie głównej deserializacji pobranej odpowiedzi (w formacie XML) do tablicy. Odpowiedź zostaje zapisana we właściwości unserialized_data, do której dostęp mają wszystkie klasy podrzędne (wykorzystane to zostanie w celach manipulacyjnych).
- isErrorData – zadaniem metody jest wykrycie, czy pobrane dane są w prawidłowym formacie i czy całość operacji przebiegła bezbłędnie.
- setUrlToApi – prosta metoda dostępowa, której zadaniem jest określenie głównego adresu URL do usługi REST.
Ok, udało się stworzyć podstawową klasę parsera. Czas zabrać się za dedykowane klasy. Przykład oprzemy o API Last.fm:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | class LastFM_Parser extends REST_Parser { protected $api_url = 'http://ws.audioscrobbler.com/2.0/'; /** * * getAlbumList * get unserialized data and display albums list * * @param string total total records to show * **/ public function getAlbumList($total=0) { // Run unserialization process and gettin data $this->getUnserializedData(); if ($this->isErrorData() OR empty($this->unserialized_data['topalbums']['album'])) { die('Zwrócono nieprawidłowe dane. Być może nie ma takiego artysty.'); } // Processing results - get album titles $result = $this->unserialized_data['topalbums']['album']; $albums = array(); // Total records to show $total_records = count($result); if ( ($total > 0) AND ($total < = $total_records) ) { $total_records = intval($total); } // fill an array with all items from a unserialized array for ($i=0; $i <= $total_records-1; $i++) { array_push($albums, $result[$i]['name']); } // Display albums list echo '<p><strong>' . $this->params_array['artist'] . '</strong> - najpopularniejsze albumy:'; echo '<ul>'; foreach ($albums as $value => $name) { echo '<li>' . $name . '</li>'; } echo '</ul>'; } /** * * searchTracksByTag * get unserialized data and display tracks list by tag * * @param string total total records to show * **/ public function searchTracksByTag($total=0) { // Run unserialization process and gettin data $this->getUnserializedData(); if ($this->isErrorData() OR empty($this->unserialized_data['toptracks']['track'])) { die('Zwrócono nieprawidłowe dane lub dla podanego tagu niczego nie znaleziono.'); } // Processing results - get album titles $result = $this->unserialized_data['toptracks']['track']; $tracks = array(); // Total records to show $total_records = count($result); if ( ($total > 0) AND ($total < = $total_records) ) { $total_records = intval($total); } // fill an array with all items from a unserialized array for ($i=0; $i <= $total_records-1; $i++) { array_push($tracks, array('artist' => $result[$i]['artist']['name'], 'track' => $result[$i]['name'])); } // Display tracks list echo '<p><strong>' . $total_records . '</strong> najpopularniejszych utworków oznaczonych tagiem <strong>' . $this->params_array['tag'] . '</strong>:</p>'; echo '<ul>'; foreach ($tracks as $value => $item) { echo '<li>' . $item['artist'] . ' - ' . $item['track'] . '</li>'; } echo '</ul>'; } } |
Powyżej stworzyliśmy krótką klasę dziedziczącą z klasy REST_Parser. Na samym początku zdefiniowaliśmy adres URL usługi REST (możemy tego dokonać także poza klasą, używając publicznej metody setUrlToApi).
Następnie utworzyliśmy dwie przykładowe metody pobierające listę popularnych albumów danego wykonawcy oraz wyszukujące najpopularniejszych tytułów piosenek dla podanego słowa kluczwego.
Obie metody są bardzo podobne, dlatego opiszemy je razem. W metodach tych pierwszym krokiem powinno być wywołanie innej metody getUnserializedData odpowiedzialnej za odczyt i transformację pobranych danych.
Pobrane w ten sposób dane przekazujemy metodzie isErrorData, której zadaniem jest sprawdzenie czy wszystko przebiegło bez błędów. Metoda isErrorData jest rozszerzona o dodatkowe warunki poprawności danych. Dzięki temu dodajemy dodatkowe, dedykowane wykrywanie błędów pochodzących z konkretnego API (w tym przypadku Last.fm). Moglibyśmy rozszerzyć jeszcze bardziej tę metodę, analizują poszczególne kody błędów opisane w API Last.fm.
Następnie możemy już pobierać i wyświetlić dowolne dane (za co odpowiada pozostały kod).
Czas więc przetestować dotychczasowy kod:
1 2 3 4 5 6 7 | $last_fm = new LastFM_Parser(array( 'method' => 'artist.gettopalbums', 'artist' => 'Afroman', 'api_key' => 'b25b959554ed76058ac220b7b2e0a026' )); $last_fm->getAlbumList(3); |
W wyniku czego powinniśmy ujrzeć:
Afroman – najpopularniejsze albumy:
- Because I Got High
- Sell Your Dope
- Afroholic…The Even Better Times
Jak widać, wykorzystanie stworzonej klasy jest bardzo proste i podręczne. Kolejną możliwością tak stworzonej klasy jest możliwość wyszukiwania piosenek przypisanych do tagów.
1 2 3 4 5 6 7 | $last_fm = new LastFM_Parser(array( 'method' => 'tag.getTopTracks', 'tag' => 'love', 'api_key' => 'b25b959554ed76058ac220b7b2e0a026' )); $last_fm->searchTracksByTag(10); |
W wyniku czego powinniśmy ujrzeć:
10 najpopularniejszych utworków oznaczonych tagiem love:
- Jason Mraz – I’m Yours
- Owl City – The Bird and The Worm
- Taylor Swift – Love Story
- The Pussycat Dolls – Jai Ho! (You Are My Destiny)
- Colbie Caillat – Fallin’ For You
- Death Cab for Cutie – I Will Follow You Into the Dark
- Panic at the Disco – Northern Downpour
- James Blunt – Goodbye My Lover
- Anberlin – Inevitable
- Paramore – The Only Exception
Mam nadzieję, że powyższymi przykładami udało mi się zobrazować prostotę wykorzystania usług REST oraz że dalsze rozszerzanie klasy LastFM_Parser nie będzie stanowiło najmniejszego problemu.
Podsumowanie
Jak widać, wykorzystanie usług REST jest bardzo proste i podręczne. Wystarczy kilkanaście linijek kodu, by pozyskiwać logiczne i często przydatne treści.
Zastosowań jest wiele, m.in. tworzenie zaplecza z unikalną treścią (powiązaną z kilku różnych serwisów, przez co możliwe stałoby się uniknięcie Duplicate Content, w pewnym sensie), tworzenie serwisów typu Mashup (Google Maps + zdjęcia z Flickr dot. wybranych miejsc + artyści z tego samego miejsca, etc).
Warto też dodać, iż dane pobrane w ten sposób powinny być w miarę możliwości cachowane, tzn. zapisywane w lokalnych plikach lub bazie danych. Dzięki temu można uniknąć wykonywania niepotrzebnych połączeń do zewnętrznych zasobów (co mogłoby przy zwiększonym ruchu okazać się zgubne).
To by było tyle na początek. Dziękuję za przeczytanie tego tutorialu, który z założenia miał być zajawką dot. aplikacji typu Mashup ;)
Przydało by się bardziej ideowe rozpisanie REST, a nie od razu zabierać się za kod.
Nie chciałem by notka była teoretyczna, a raczej by przedstawiała jeden ze sposób praktycznego podejścia do usług sieciowych, w tym przypadku do REST.
Przy swoich kolejnych wpisach nt. usług sieciowych postaram bardziej ideowo rozpisać poszczególne metody :)
Nie przesadzajmy, po prostu REST jako taki mnie interesuje.
[…] Procedure Call).Uzupełnieniem tego artykułu, dla początkujących, może być wcześniejszy wpis: Usługi sieciowe w PHP: REST. Znajduje się tam słowniczek pojęć, wspomniana jest instalacja pakietów PEAR, omówiona jest z […]
Hej! W metodzie getUnserializedData() powinno być przypisanie do zmiennej $status :)
@Kansuke: no ładnie odkopałeś dinozaura :P patrząc w ten mój kod sprzed 2 lat to znalazłoby się dużo więcej rzeczy, które wypadałoby pozmieniać :)