luni, 21 decembrie 2009

Servicii Web publice. Exemplu de parsare REST cu PHP şi SimpleXML (YouTube, Flickr & Twitter API)

Serviciile Web reprezintă un mod de schimb de date pe Web (pe Internet) prin protocolul HTTP. Acest schimb de date se poate face între mai calculatoare, chiar dacă au arhitecturi şi sisteme de operare diferite. Deci, serviciile Web sunt cross-platform.
Pentru a putea comunica mai uşor, se foloseşte un limbaj de tip markup (de tip plain text) precum XML sau JSON. Datele transmise cu XML sau JSON nu necesită criptare/decriptare (pot fi percepute şi de către om, nu doar maşină :) ), iar mesajul (un şir de caractere de dimensiune relativ mică) ajunge foarte repede la destinaţie.
Există mai multe tipuri de servicii Web, cele mai utilizate fiind XML-RPC, SOAP şi REST. Prin intermediul lor, o companie permite accesul utilizatorilor la baza sa de date. În dependenţă de informaţia pe care o pune la dispoziţie, compania decide dacă serviciul Web va fi privat (acces autorizat) sau public (acces tuturor utilizatorilor).
În acest articol voi descrie doar codul PHP de parsare a REST-urilor, mai multe informaţii despre fiecare serviciu Web în parte găsiţi în articolul anterior - Follow-up LEC Technology Meetings - editia a II-a .

YouTube API

Spre exemplu compania Google pune la dispoziţie accesul public la resursele sale prin intermediul serviciilor Web publice. Acestea pot fi accesate cu XML-RPC, SOAP şi REST. Mai jos voi exemplifica parsarea unui XML accesat prin REST de pe YouTube API cu limbajul PHP şi librăria SimpleXML. XML-ul primit prin REST de la Google este de tip Atom. Mai multe specificaţii despre Atom Feed Type găsiţi pe Google Code Data API.

Pentru a căuta o listă de filme pe YouTube voi accesa un URI, unde se află un XML (REST) cu rezultatul căutării. REST-ul parsat se află la următorul URI: http://gdata.youtube.com/feeds/api/videos . La acest URI se adaugă parametri tip HTTP_GET, URI-ul final devenind:
http://gdata.youtube.com/feeds/api/videos?q=cat&orderby=viewCount&max-results=10&start-index=1
.

Clasa YoutubeParser:

/**
* YoutubeParser
* parseaza REST de pe YouTube
*
* by Dumitru Glavan
*
*/
class YoutubeParser
{
/**
* URL curent de parsare
*/
protected $_url = '';
/**
* XML-ul curent returnat de catre server
*/
protected $_xml;
/**
* Adresa la care se vor adauga parametrii si metoda de REST
* si se va accesa serverul pentru cautare
*/
protected $_youtubeSearchUrl = 'http://gdata.youtube.com/feeds/api/videos';

/**
* Functia constructor
*
* @param string $get - parametrii pentru search
*
*/
public function __construct($get = '')
{
$this->setUrl($get);
$this->_xml = new SimpleXmlElement($this->_url, null, true);
}
/**
* Setter pentru URL-ul de search
*
* @param array $get
*
*/
public function setUrl($get = '')
{
$this->_url = $this->_youtubeSearchUrl . '?' . $get;
}
/**
* Getter pentru URL-ul de search
*
* @param null
*
*/
public function getUrl()
{
return $this->_url;
}
/**
* Parsare REST si decodare statistici
*
* @param null
*
*/
public function getStats()
{
return $this->_xml->children('http://a9.com/-/spec/opensearchrss/1.0/');
}
/**
* Parsare REST si decodare mesaje
*
* @param null
*
*/
public function getVideos()
{
$videos = array();
foreach ($this->_xml->entry as $entry) {
$video = array();
//noduri din mamespace-ul :media
$media = $entry->children('http://search.yahoo.com/mrss/');
$video['title'] = $media->group->title;
$video['description'] = $media->group->description;
// URl pentru player-ul Video
$attrs = $media->group->player->attributes();
$video['url'] = $attrs['url'];
// Video thumbnail
$attrs = $media->group->thumbnail[0]->attributes();
$video['thumbnail'] = $attrs['url'];
// nodul pentru lungimea filmului
$yt = $media->children('http://gdata.youtube.com/schemas/2007');
$attrs = $yt->duration->attributes();
$length = $attrs['seconds'];
$video['length'] = sprintf("%0.2f", $length/60);
// nodul pentru rating-ul filmului
$gd = $entry->children('http://schemas.google.com/g/2005');
if ($gd->rating) {
$attrs = $gd->rating->attributes();
$video['rating'] = $attrs['average'];
} else {
$video['rating'] = 0;
}
$videos[] = $video;
}
return $videos;
}

}


Sursa finală a aplicaţiei se află în repository la: http://code.assembla.com/public_rest_parser_example/subversion/nodes/flickr?rev=1 .

Flickr API

Pentru accesarea pozelor de pe Flickr voi folosi specificaţiile REST de pe Flickr API. Pentru a accesa acest API este nevoie de a crea un Yahoo developer key la http://www.flickr.com/services/api/misc.api_keys.html. Cu această cheie voi putea accesa XML-urile prin REST de pe Flickr API. Mai multe informaţii despre Flickr REST şi API găsiţi la http://www.flickr.com/services/api/

Pentru a căuta poze pe Flickr voi parsa un XML de la un URI de forma: http://api.flickr.com/services/rest/ .
Dupa adăugarea parametrilor HTTP_GET voi obţine URI-ul: http://api.flickr.com/services/rest/?api_key=XXX&method=flickr.photos.search&text=cat&per_page=20&page=1 . Clasa pentru lucrul cu Flickr API e asemanatoare cu cea de YouTube API:

Clasa FlickrParser:

/**

* FlickrParser
* parseaza REST de pe Flickr
*
* by Dumitru Glavan
*
*/
class FlickrParser
{
/**
* URL curent de parsare
*/
protected $_url = '';


/**
* XML-ul curent returnat de catre server
*/
protected $_xml;


/**
* Adresa la care se vor adauga parametrii si metoda de REST
* si se va accesa serverul pentru cautare
*/
protected $_flickrRestUrl = 'http://api.flickr.com/services/rest/';


/**
* Flickr Personal API Key - trebuie modificat cu o cheie proprie
*/
protected $_apiKey = 'XXX';


/**
* Metode de accesare API Flickr
*/
protected $_apiMethods = array('search' => 'flickr.photos.search',
'photo_info' => 'flickr.photos.getInfo',
);


/**
* Functia constructor
*
* @param string $get - parametrii pentru search
*
*/
public function __construct($get = '')
{
$this->setUrl($get);
$this->_xml = new SimpleXmlElement($this->_url, null, true);
}


/**
* Setter pentru URL-ul de search
*
* @param array $get
*
*/
public function setUrl($get = '')
{
$this->_url = $this->_flickrRestUrl . '?api_key=' . $this->_apiKey . '&method=' . $this->_apiMethods['search'] . '&' . $get;
}


/**
* Getter pentru URL-ul de search
*
* @param null
*
*/
public function getUrl()
{
return $this->_url;
}


/**
* Parsare REST si decodare statistici
*
* @param null
*
*/
public function getStats()
{
return $this->_xml->photos->attributes();
}


/**
* Parsare REST si decodare mesaje
*
* @param null
*
*/
public function getPhotos()
{
$photos = array();
foreach ($this->_xml->photos->children() as $entry) {
$attr = $entry->attributes();
$photo = array('id' => (string)$attr->id,
'owner' => (string)$attr->owner,
'secret' => (string)$attr->secret,
'server' => (string)$attr->server,
'farm' => (string)$attr->farm,
'title' => (string)$attr->title,
);
$photo['url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}.jpg";
$photo['photopage'] = "http://www.flickr.com/photos/{$photo['owner']}/{$photo['id']}/";


$photos[] = $photo;
}


return $photos;
}

}


Varianta finală a codului o puteţi descărca de la: http://code.assembla.com/public_rest_parser_example/subversion/nodes/flickr?rev=1


Twitter API

Twitter.com pune la dispoziţie un API public pentru a accesa mesajele unui utilizator sau după anumite keywords. Mai multă informaţie despre specificaţiile REST se găsesc la http://apiwiki.twitter.com/Twitter-API-Documentation .

Pentru căutare după keywords se va accesa un URI de forma http://search.twitter.com/search.atom . După adăugarea parametrilor HTTP_GET: http://search.twitter.com/search.atom?q=cat&rpp=15&page=2 .

Clasa TwitterParser:
/**
* TwitterParser
* parseaza REST de pe Twitter
*
* by Dumitru Glavan
*
*/
class TwitterParser
{
/**
* URL curent de parsare
*/
protected $_url = '';

/**
* XML-ul curent returnat de catre server
*/
protected $_xml;

/**
* Adresa la care se vor adauga parametrii si metoda de REST
* si se va accesa serverul pentru cautare
*/
protected $_twitterSearchUrl = 'http://search.twitter.com/search.atom';
/**
* Adresa la care se vor adauga parametrii si metoda de REST
* si se va accesa serverul pentru preluarea mesajelor user-ului
*/
protected $_twitterUserTimelineUrl = 'http://twitter.com/statuses/user_timeline.atom';

/**
* Functia constructor
*
* @param string $params - parametrii pentru search
*
*/
public function __construct($params = array())
{
$this->setUrl($params);
$this->_xml = new SimpleXmlElement($this->_url, null, true);
}
/**
* Setter pentru URL-ul de search
*
* @param array $params
*
*/
public function setUrl($params)
{
if (isset($params['tip']) && ($params['tip'] == 'user')) {
$this->_url = $this->_twitterUserTimelineUrl . '?';
} else {
$this->_url = $this->_twitterSearchUrl . '?';
}
unset($params['tip']);
$get = '';
foreach ($params as $param => $val) {
$val = urlencode($val);
$get .= "$param=$val&";
}
$this->_url .= $get;
return $this->_url;
}
/**
* Getter pentru URL-ul de search
*
* @param null
*
*/
public function getUrl()
{
return $this->_url;
}
/**
* Parsare REST si decodare statistici
*
* @param null
*
*/
public function getStats()
{
return $this->_xml->children('http://a9.com/-/spec/opensearchrss/1.0/');
}
/**
* Parsare REST si decodare mesaje
*
* @param null
*
*/
public function getTwitts()
{
//Container temporar mesaje
$twitts = array();
//Parcurgem nodurile cu mesaje
foreach ($this->_xml->entry as $entry) {
$twitt = array('date' => $this->formatDate((string)$entry->published),
'title' => (string)$entry->title,
'link' => '',
'content' => (string)$entry->content,
'avatar' => '',
'author' => array('name' => (string)$entry->author->name,
'uri' => (string)$entry->author->uri,
)
);
$attr = $entry->link[0]->attributes();
$twitt['link'] = (string)$attr->href;
$attr = $entry->link[1]->attributes();
$twitt['avatar'] = (string)$attr->href;
$twitts[] = $twitt;
}
return $twitts;
}
/**
* Formateaza o data in format human readable
*
* @param string $date
*
*/
public function formatDate($date)
{
return date('d, M Y H:i');
}

}


Clasele pentru parsare au fost create de mine pentru a exemplifica simplitatea lucrului cu SimpleXmlElement şi implementează funcţionalitatea minimă pentru aceasta. Ele pot fi modificate şi completate cu noi funcţionalităţi de către oricine. Alte idei şi sugestii sunt binevenite. :)

3 comentarii:

  1. Foate util articolul. Exemplele de cod sunt foarte bine venite. Multumesc

    RăspundețiȘtergere
  2. La ce curs se studiaza despre Web Services, la cursul de incepatori sau la avansati ?

    RăspundețiȘtergere
  3. Va multumesc pentru feedback!

    Despre Web Services se studiaza la cursul de avansati, Cursul 6. E si curriculum aici - http://www.leconline.ro/curs-it/php-advanced.html - care inca nu e definitiv, va suferi unele mici schimbari pe alocuri.

    RăspundețiȘtergere