70-480 – Użytkowanie danych

70-480

Użytkowanie danych JSON i XML; pobieranie danych przy użyciu usług sieci Web; ładowanie danych lub pobieranie danych z innych źródeł przy użyciu obiektu XMLHTTPRequest.



Jak mówi opis ze strony Microsoft – dzisiaj zajmiemy się konsumpcją danych w formatach JSON oraz XML przy użyciu obiektu XMLHTTPRequest (odpowiednik dla IE5 i IE6, to obiekt ActiveXObject).

JSON | XML

Oba formaty pozwalają na wymianę danych w łatwy sposób. Są one również czytelne dla swoich użytkowników i zapewniają sposób opisu danych. To co jednak odróżnia JSON od XML, to “lekkość”. Ilość przesyłanych danych jest często znacznie mniejsza, a format odpowiada w zasadzie obiektom JavaScript, czy bazom danych typu NoSQL, takim jak na przykład MongoDB.

Przykład notacji JSON:

{
  "glossary":{
    "title":"example glossary",
    "GlossDiv":{
      "title":"S",
      "GlossList":{
        "GlossEntry":{
          "ID":"SGML",
          "SortAs":"SGML",
          "GlossTerm":"Standard Generalized Markup Language",
          "Acronym":"SGML",
          "Abbrev":"ISO 8879:1986",
          "GlossDef":{
            "para":"A meta-markup language, used to create markup languages such as DocBook.",
            "GlossSeeAlso":[
              "GML",
              "XML"
            ]
          },
          "GlossSee":"markup"
        }
      }
    }
  }
}

Przykład opisu tych samych danych, ale notacji XML:

<?xml version="1.0" encoding="UTF-8"?>
<glossary>
  <title>example glossary</title>
  <GlossDiv>
    <title>S</title>
    <GlossList>
      <GlossEntry ID="SGML" SortAs="SGML">
        <GlossTerm>Standard Generalized Markup Language</GlossTerm>
        <Acronym>SGML</Acronym>
        <Abbrev>ISO 8879:1986</Abbrev>
        <GlossDef>
          <para>A meta-markup language, used to create markup
languages such as DocBook.</para>
          <GlossSeeAlso OtherTerm="GML" />
          <GlossSeeAlso OtherTerm="XML" />
        </GlossDef>
        <GlossSee OtherTerm="markup" />
      </GlossEntry>
    </GlossList>
  </GlossDiv>
</glossary>

Warto również nadmienić, że JavaScript posiada natywne wsparcie dla notacji JSON i pozwala transformować obiekt JSON do formatu typu String i w drugą stronę. Służą do tego dwie funkcje:

  • stringify – transformuje obiekt JSON do String
  • parse – parsuje String i stara się z niego zbudować obiekt JSON

, które znajdziemy w obiekcie JSON.

Spójrzmy na przykład:

var json = {
  "glossary":{
    "title":"example glossary",
    "GlossDiv":{
      "title":"S",
      "GlossList":{
        "GlossEntry":{
          "ID":"SGML",
          "SortAs":"SGML",
          "GlossTerm":"Standard Generalized Markup Language",
          "Acronym":"SGML",
          "Abbrev":"ISO 8879:1986",
          "GlossDef":{
            "para":"A meta-markup language, used to create markup languages such as DocBook.",
            "GlossSeeAlso":[
              "GML",
              "XML"
            ]
          },
          "GlossSee":"markup"
        }
      }
    }
  }
};

var jsonAsString = JSON.stringify(json);
console.log(jsonAsString);

var againJson = JSON.parse(jsonAsString);
console.log(againJson);

Wersja online dostępna jest na jsfiddle.net Format JSON jest niezwykle często spotykany w komunikacji z wykorzystaniem serwisów typu REST.

Przykład operacji na notacji XML znajdziecie na stronie w3schools.com. Po raz kolejny odsyłam do dobrego źródła, bo nie chcę się powtarzać.

XMLHTTPRequest

O metodzie $.ajax oraz jej aliasach $.get i $.post pisałem już w poprzednich wpisach. Całość jednak opiera się na natywnym sposobie konsumowania tak zwanych web serwisów, z użyciem obiektu XMLHTTPRequest.

var xmlhttp = new XMLHttpRequest();

xmlhttp.onreadystatechange = function() {
  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    console.log(JSON.parse(xmlhttp.responseText));
  }
}
xmlhttp.open('GET', 'http://somefancyapi.com/returnjsonstring.json', true);
xmlhttp.send();

Całość wydaje się bajecznie prosta. Tworzymy obiekt, ustawiamy obsługę callback dla onreadystatechange, a następnie otwieramy połączenie i wysyłamy do serwera. Po odebraniu danych są one parsowane do JSON. Możemy jednak posłużyć się określeniem responseType, a odpowiedź zostanie nam zwrócona (jeśli nie wystąpi błąd) w żądanym formacie. Spójrzmy na kolejny przykład:

var xmlhttp = new XMLHttpRequest();

xmlhttp.onreadystatechange = function() {
  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    console.log(xmlhttp.response);
  }
}
xmlhttp.responseType = 'json';
xmlhttp.open('GET', 'http://somefancyapi.com/returnjsonstring.json', true);
xmlhttp.send();

Nie ma konieczności korzystania z funkcji parse obiektu JSON. Całość wydaje się być jeszcze prostsza.

O XMLHTTPRequest pisałem już w poprzednich postach, a będzie o nim także mowa w kolejnych, więc nie ma co przedłużać. Zapraszam do własnych prób i przejrzenia API, które oferuje nam XMLHTTPRequest.

RSS i SimplePie

SimplePie RSS parser

Google Reader nie jest dostępne już prawie dwa miesiące, a na rynku pojawiło się sporo rozwiązań, które skutecznie wypełniły lub nadal wypełniają lukę pozostawioną przez ten największy jak do tej pory serwis. Niektóre rozwiązania starają się bardzo przypominać rozwiązanie od wujka Google, inne z kolei zmieniają nieco nasze przyzwyczajenia i udostępniają nam nowatorskie interfejsy. Nie chcę jednak pisać o obecnych rozwiązaniach, a o możliwości stworzenia własnego.

SimplePie

Narzędzie, które pozwoli nam konsumować kanały RSS oraz w łatwy sposób wyświetlać treści się tam znajdujące nazywa się SimplePie. Budowane i rozwijane w duchu Open Source na Github, zainstalowane może być za pomocą wspominanego już wielokrotnie tutaj Composera (SimplePie Packagist).

Podstawy

Zakładając, że mamy już zainstalowane SimplePie poprzez Composer, możemy przystąpić do wyboru pierwszego kanału RSS, który będziemy chcieli sparsować i wyświetlić. Posłużę się oczywiście swoim kanałem blogowym i pokażę Wam przykład, gdzie stworzę nowy obiekt SimplePie, ustawię adres URL kanału RSS (set_feed_url()) oraz zainicjalizuję nasz RSS reader poprzez wywołanie metody init() na obiekcie $feed.

<?php

require_once 'vendor/autoload.php';

$url = 'http://mrzepinski.pl/feed';
$feed = new SimplePie();
$feed->set_feed_url($url);
$feed->init();

UWAGA: SimplePie może rzucić wyjątkiem Warning: ./cache is not writeable. Wystarczy w takim wypadku utworzyć folder cache i w przypadku środowiska [L]inux nadać mu odpowiednie uprawnienia (chmod 755 cache).

Tym samym – jeżeli URL, który podaliśmy jest dostępny i jest to kanał RSS / Atom – możemy wykonywać kolejne metody, które pozwolą pobrać dane z kanału RSS.

...
echo '<h1>'.$feed->get_title().'</h1>';
echo '<p>'.$feed->get_description().'</p>';

Dzięki metodom get_title() oraz get_description() pobierzemy odpowiednio tytuł oraz opis naszego kanału RSS.

Jako, że kanały RSS posiadają standard, który zapewnia, że format XML kanału musi być zawsze taki sam, możemy bez problemu pobierać także konkretne informacje o poszczególnych obiektach, które udostępniane są przez RSS. Pobranie pierwszego obiektu wyglądać może następująco:

...
$item = $feed->get_item(0);
echo '<p>Title: <a href="'.$item->get_link().'">'.$item->get_title().'</a></p>';
echo '<p>Author: '.$item->get_author()->get_name().'</p>';
echo '<p>Date: '.$item->get_date('Y-m-d H:i:s').'</p>';
echo $item->get_content(true);

Ważnym czynnikiem jest także ilość dostępnych elementów. Taką informację osiągniemy poprzez wywołanie metody get_item_quantity() w następujący sposób:

...
$itemQty = $feed->get_item_quantity();
for ($i = 0; $i < $itemQty; $i++) {
    ...
}

Rozwiązanie to nie pozwoli nam jednak na paginację pobieranych wyników. Dużo łatwiej będzie nam to osiągnąć poprzez wykorzystanie metody get_items(), która jako pierwszy argument przyjmuje tak zwany offset, (miejsce od którego chcemy zacząć pobierać kolejne elementy), a jako drugi liczbę elementów do pobrania.

...
foreach ($feed->get_items(10, 10) as $item) {
    ...
}

Podsumowanie

Tym samym zbudowaliśmy prosty czytnik RSS. Nie było to specjalnie trudne prawda? Pełną dokumentację dostępnych klas oraz metod znajdziecie w oficjalnym API opisującym wszystkie dostępne funkcje. Do Was należy pobudzenie swojej kreatywności oraz realizacja własnego projektu.

Dane geograficzne na podstawie adresu IP w PHP

geolocation

Dzisiejszym wpisem chciałbym Wam pokazać, w jaki sposób uzyskać można dane geograficzne na podstawie adresu IP.

Wykorzystam w tym celu serwis locatorhq.com, który udostępnia API pozwalające na identyfikację danych geograficznych.  Usługa jest darmowa i może z niej skorzystać każdy po założeniu konta.

Początek

Pierwszym krokiem jest rejestracja w serwisie: http://www.locatorhq.com/ip-to-location-api/signup.php poprzez podanie kilku podstawowych danych. Po chwili – na podany adres email przyjdzie wiadomość, której zawarte będą dane – API_KEY i nazwa użytkownika, które będą potrzebne podczas wysyłania zapytań do API.

Użycie

Jeżeli posiadasz już swój API_KEY i nazwę użytkownika, to jesteś w stanie wykonać swoje pierwsze zapytanie do API serwisu.

http://api.locatorhq.com/?user={YOURUSERNAME}
  &key={YOURAPIKEY}&ip={IPADDRESSTOLOOKUP}

, gdzie podać musimy następujące parametry:

  • YOURUSERNAME – nazwa użytkownika
  • YOURAPIKEY – API_KEY otrzymane w email z serwisu po rejestracji
  • IPADDRESSTOLOOKUP – adres IP, który chcemy “sprawdzić”

Istnieje jeszcze 4 parametr, a mianowicie format danych, który ma nam zostać zwrócony po wykonaniu zapytania.

  • &format=text
  • &format=xml
  • &format=json – dostępne wkrótce

Domyślna wartość parametru to: text.

Przykład

Czas na prosty przykład wykorzystania. Sprawdzę w ten sposób adres IP jednego z serwerów Google: 74.125.236.196.

Wykonując:

http://api.locatorhq.com/?user=Username&key=APIKEY&ip=74.125.236.196

otrzymamy taką oto odpowiedź:

US,United States,California,Mountain View,34.305,-86.2981
Kod krajuUS
KrajUnited States
StanCalifornia
MiejscowośćMountain View
Szerokość geograficzna34.305
Długość geograficzna-86.2981 

Parsowanie surowego tekstu nie jest zbyt wygodne, dlatego też wykorzystam format XML do pobrania danych i sparsowania ich po stronie kodu PHP.

Url zapytania będzie miał teraz postać:

http://api.locatorhq.com/?user=Username&key=APIKEY
  &ip=74.125.236.196&format=xml

Otrzymamy w ten sposób odpowiedź o treści:

<ip2locationapi>
  <countryCode>US</countryCode>
  <countryName>United States</countryName>
  <region>California</region>
  <city>Mountain View</city>
  <lattitude>34.305</lattitude
  <longitude>-86.2981</longitude>
</ip2locationapi>

Mając już strukturę XML mogę przejść do właściwego kodu, który będzie w stanie pobrać i sparsować wszystkie dane.

<?php

$ipAddress = $_SERVER['REMOTE_ADDR']; // adres IP
$user = MYUSERNAME;
$apiKey = MYAPIKEY;
$locationUrl = "http://api.locatorhq.com/?user={$user}&key={$apiKey}&ip={$ipAddress}&format=xml";

// pobieramy xml zwrocony przez API LocatorHQ
$xml = simplexml_load_file($locationUrl); 

$countryCode = $xml->countryCode; // kod kraju
$countryName = $xml->countryName; // kraj
$region = $xml->region;           // stan
$city = $xml->city;               // miejscowosc
$lattitude = $xml->lattitude;     // szerokosc geograficzna
$longitude = $xml->longitude;     // dlugosc geograficzna

?>

Już od Was zależy co zrobicie z tym dalej. Możliwości jest jednak wiele.

  • personalizacja naszej strony pod konkretny język
  • analiza wejść na naszą stronę – analiza demograficzna
  • geotargetowanie reklam
  • automatyczne wypełnianie formularzy na podstawie danych geograficznych

To tylko kilka przykładów.

Sama usługa LocatorHQ nie jest idealna. Czasami zwraca ona niepoprawne wyniki. Wszystko zależy tak naprawdę od ustawień sieci, w której znajduje się dany adres IP. API nadaje się jednak świetnie do eksperymentowania z usługami GEO.

Parsowanie XML z SimpleXML w PHP

XML SimpleXML PHP

Parsowanie składni XML oznacza zwykle nawigowanie po całej strukturze dokumentu XML. Duża liczba dzisiejszych serwisów zwraca dane w formacie JSON, ale jest jeszcze sporo takich, które używają do tego celu XML.

W dzisiejszym HowTo wykorzystam rozszerzenie SimpleXML, które wprowadzone zostało do PHP 5.0. Pozwala ono na łatwe nawigowanie po dokumencie XML.

Przejdźmy do pierwszego przykładu wykorzystania. Mamy do dyspozycji plik languages.xml:

<?xml version="1.0" encoding="utf-8"?>
<languages>
 <lang name="C">
  <appeared>1972</appeared>
  <creator>Dennis Ritchie</creator>
 </lang>
 <lang name="PHP">
  <appeared>1995</appeared>
  <creator>Rasmus Lerdorf</creator>
 </lang>
 <lang name="Java">
  <appeared>1995</appeared>
  <creator>James Gosling</creator>
 </lang>
</languages>

Powyższy dokument opisuje w pewien sposób trzy języki programowania i podaje ich kilka charakterystyk.

Do odczytania tego dokumentu wykorzystam funkcję simple_load_file().

<?php
$languages = simplexml_load_file("languages.xml");

Istnieje jeszcze funkcja simplexml_load_string(), która jak nie trudno się domyślić, ładuje do pamięci łańcuch znaków, który jest poprawną składnią dokumentu XML. Obie funkcje wczytują dokument DOM do pamięci tworząc tym samym obiekt SimpleXMLElement.

Wykonując var_dump($languages) lub print_r($languages) otrzymamy:

SimpleXMLElement Object
(
    [lang] => Array
        (
            [0] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [name] => C
                        )
                    [appeared] => 1972
                    [creator] => Dennis Ritchie
                )
            [1] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [name] => PHP
                        )
                    [appeared] => 1995
                    [creator] => Rasmus Lerdorf
                )
            [2] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [name] => Java
                        )
                    [appeared] => 1995
                    [creator] => James Gosling
                )
        )
)

Widać tutaj główny, publiczny element lang (SimpleXMLElement), który jest tablicą i zawiera trzy elementy.

By dostać się do konkretnych elementów trzeba wykorzystać operator ->

<?php
$languages->lang[0]->appeared;
$languages->lang[0]->creator;

Można także iterować po liście za pomocą metody foreach

<?php
foreach ($languages->ang as $lang) {
    echo "Language: ".$lang["name"];
    echo "Appeared: ".$lang->appeared;
    echo "Creator: ".$lang->creator;
}

Należy zwrócić uwagę, że do wartości atrybutu lang docieramy za pomocą parametru w tablicy, a do publicznych elementów appeared i creator za pomocą operatora ->.

Wszystko fajnie, pięknie, ale co jeżeli trzeba sobie poradzić z przestrzenią nazw, która zdefiniowana została dla poszczególnych elementów? Jest i na to sposób!

Zmodyfikuję teraz plik languages.xml do postaci:

<?xml version="1.0" encoding="utf-8"?>
<languages
 xmlns:dc="http://purl.org/dc/elements/1.1/">
 <lang name="C">
  <appeared>1972</appeared>
  <dc:creator>Dennis Ritchie</dc:creator>
 </lang>
 <lang name="PHP">
  <appeared>1995</appeared>
  <dc:creator>Rasmus Lerdorf</dc:creator>
 </lang>
 <lang name="Java">
  <appeared>1995</appeared>
  <dc:creator>James Gosling</dc:creator>
 </lang>
</languages>

Element creator posiada teraz namespace, który kieruje na http://purl.org/dc/elements/1.1/. Dostęp do tego elementu poprzez wykorzystanie operatora -> nie jest już możliwy.

By poradzić sobie z tym problemem trzeba zrobić coś takiego:

<?php
$dc = $languages->lang[1]- >children("http://purl.org/dc/elements/1.1/");
echo $dc->creator;

Metoda children() pobiera namespace i zwraca wartość elementu dla którego dc jest prefixem.

Istnieje inna możliwość odczytania wartości elementu creator:

<?php
$namespaces = $languages->getNamespaces(true);
$dc = $languages->lang[1]->children($namespaces["dc"]);

echo $dc->creator;

Pobieramy w tym celu wszystkie namespace użyte w dokumencie XML i za pomocą elementu w tablicy odwołujemy się do konkretnego namespace i pobieramy wartość dla którego jest on prefixem.

I podobnie jak wcześniej – przykład iterowania po wszystkich elementach:

<?php
$languages = simplexml_load_file("languages.xml");
$ns = $languages->getNamespaces(true);

foreach ($languages->lang as $lang) {
    $dc = $lang->children($ns["dc"]);
    echo "Language: ".$lang["name"];
    echo "Appeared: ".$lang->appeared;
    echo "Creator: ".$dc->creator;
}

Czas na praktyczny przykład. Parsowanie kanału YouTube.

W tym celu należy wykorzystać API dostępne pod adresem: http://gdata.youtube.com/feeds/api/users/{channel}/uploads, gdzie w miejscu {channel} podana zostanie nazwa konkretnego użytkownika.

W danych, które zostaną zwrócone w przypadku podania poprawnej nazwy użytkownika, dla każdego filmu zdefiniowane będą:

  • jego URL
  • miniatura
  • tytuł
<?php
$channel = "channelName";
$url = "http://gdata.youtube.com/feeds/api/users/".$channel."/uploads";
$xml = file_get_contents($url);

$feed = simplexml_load_string($xml);
$ns = $feed->getNameSpaces(true);

Spoglądając w strukturę otrzymanego XML możemy zauważyć, że ważne dla nas dane znajdują się w elemencie group, który poprzedzony jest namespace media.

<entry>
   …
   <media:group>
      …
      <media:player url="video url"/>
      <media:thumbnail url="image url" height="height" width="width"/>
      <media:title type="plain">Title…</media:title>
      …
   </media:group>
   …
</entry>

Wystarczy, że ponownie wykorzystana zostanie metoda foreach, dzięki której będziemy w stanie wyświetlić odpowiednie dla nas dane.

<?php
foreach ($feed->entry as $entry) {
	$group = $entry->children($ns["media"]);
	$group = $group->group;
	$thumbnailAttrs = $group->thumbnail[1]->attributes();
	$image = $thumbnailAttrs["url"];
	$player = $group->player->attributes();
	$link = $player["url"];
	$title = $group->title;
	
	echo "<p><a href=".$link."><img src=".$image." alt=".$title."></a></p>";
}

Podsumowując. Operowanie na dokumentach XML jest bardzo proste. Dzięki wykorzystaniu rozszerzenia SimpleXML, drzewo DOM nie jest już dla nas problemem.

Pełną dokumentację SimpleXML znajdziecie pod adresem: http://php.net/manual/en/book.simplexml.php