Jednou za čas se může stát, že se setkají dvě ne úplně kompatibilní technologie, mezi kterými je ale potřeba vytvořit komunikační kanál. V tomto článku budeme řešit problém, kdy máme webovou stránku v PHP, která využívá MySQL databázi, a k této stejné databázi potřebujeme přistupovat i z desktopové aplikace, napsané třeba v C#. Kvůli bezpečnostním omezením hostingu není ve většině případů možné přímé připojení k databázi odjinud než z hostované stránky, a proto si musíme napsat webovou službu, která nám komunikaci zprostředkuje.
Hned na úvod vysvětlím, proč budu popisovat tvorbu SOAP služby pomocí knihovny NuSOAP a ne pomocí PHP5 SOAP Extension (což je knihovna pro generování SOAPu dostupná přímo v PHP5), abych se vyvaroval otázkám tohoto typu v komentářích.
Důvod je prostší, než by se mohlo na první pohled zdát - nefungovalo to. Jednoduše se mi nepodařilo vložit do své C# aplikace referenci na službu napsanou pomocí PHP5 SE, ale když jsem vytvořil její ekvivalent pomocí NuSOAP, šlo vše relativně hladce, viz níže. Řeknete si, že smyslem univerzálních protokolů typu SOAP by měla být možnost komunikace čehokoliv s čímkoliv, problém ale nastane v případě, kdy jedna strana dodržuje daný standard trochu jinak než ta druhá, veškerá snaha o vzájemné porozumění je rázem pryč.
Co nás čeká?
Cílem tohoto článku tedy bude, jak už jste jistě pochopili, vytvořit ukázkovou C# aplikaci, která bude schopná si vytáhnout data z databáze běžící někde na hostingu/localhostu prostřednictvím webové služby. Předpokladem je jistá znalost C#, PHP, MySQL a ke všem těmto technologiím nakonfigurovaná příslušná vývojová a běhová prostředí. Věcmi jako je vytvoření databáze se zde zabývat nebudu.
V prvním díle si napíšeme samotnou službu, v díle druhém pak službu zakomponujeme do C# aplikace.
NuSOAP
Začneme vytvořením adresáře pro nový projekt v rootu Apache, říkejme mu třeba Programek. Pokračujeme stažením samotného NuSOAP toolkitu ze SourceForge. Stažený archiv obsahuje dva adresáře - samples a lib, potřebujeme extrahovat druhý jmenovaný, první obsahuje jen ukázkové kódy. Vyextrahujeme ho do adresáře Programek a nějak vhodně přejmenujeme, třeba na nusoap.
Dále budeme potřebovat soubor, ve kterém bude náš samotný kód, pojmenujme si ho například service.php a umístěme ho adresáře Programek. V tomto souboru si nadeklarujeme údaje pro připojení k databázi, vytvoříme referenci na knihovnu NuSOAP a nadefinujeme si vlastní datové typy a funkce služby. Jednoduchý kód, který obsahuje funkci HelloWorld, vracející string, může vypadat takto:
<?php
$conn = mysql_connect("localhost", "root", "");
mysql_select_db("db") or die("Databáze nedostupná!");
mysql_query('SET CHARACTER SET utf8');
require_once('nusoap/nusoap.php');
$server = new soap_server();
$wsdl_addr = 'http://localhost/Programek/service.php';
$server->configureWSDL('MyService', $wsdl_addr);
$server->register('sayHello', // Název funkce
array('name'=>'xsd:string'), // Vstup
array('return' =>'xsd:string'), // Výstup
'uri:mywsdl', // namespace
'uri:mywsdl#sayHello', // soapaction
'rpc', // rpc/document
'encoded', // encoded/literal
'Pozdraví uživatele' // Dokumentační řetězec
);
function sayHello($name)
{
return "Ahoj, $name";
}
// Spuštění služby
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service(utf8_encode($HTTP_RAW_POST_DATA));
?>
Prvních několik řádků asi není nutné rozebírat, zajímavé to začne být až při registraci nové funkce SayHello. Webová služba je totiž popsána tzv. WSDL dokumentem (napsaným v XML syntaxi), který vystavíme na internetu a naše aplikace si z něj přečte, jaké funkce nám služba umožňuje volat. V kódu výše voláme funkci register, kterou musíme použít vždy, když chceme do služby přidat novou funkci. Funkci register předáme informace o tom, jaké datové typy může nová funkce přijmout atd., a podle nich se vygeneruje WSDL.
U WSDL rozlišujeme dva typy "stylu" (RPC a document) a dva typy "bindingu" (encoded a literal). Získáváme čtyři kombinace těchto hodnot, přičemž pokaždé vypadá výsledné WSDL trochu jinak. Při volání služby v C# se mi osvědčilo používat kombinaci RPC a encoded, při jiných kombinacích se aplikace chovala občas podivně.
Nyní můžeme v prohlížeči otevřít adresu http://localhost/Programek/service.php?wsdl a uvidíme vygenerované WSDL. Pokud adresu otevřeme bez ?wsdl na konci, ukáže se nám stránka, ve které můžeme pohodlně prohlížet jednotlivé funkce služby.
Složitější funkce s daty z databáze
Nechat se pozdravit od webové služby je sice hezké, ale v praxi spíše potřebujeme pracovat s daty z databáze. Nestačí jen spustit dotaz SELECT, takže si ukážeme na příkladu, co vše je potřeba.
Nejprve je nutné zaregistrovat ve službě nový typ, ve kterém nadefinujeme jednotlivé proměnné. Řekněme, že máme v databázi tabulku People se sloupci ID, Name, Surname. Tento typ bychom v NuSOAP zapsali následovně:
$server->wsdl->addComplexType(
'Person',
'complexType',
'struct',
'all',
'',
array(
'id_person' => array('name' => 'id_person', 'type' => 'xsd:int'),
'name' => array('name' => 'name', 'type' => 'xsd:string'),
'surname' => array('name' => 'surname', 'type' => 'xsd:string')
)
);
Ještě je potřeba si nadefinovat pole tohoto nového typu, protože nejspíše budeme chtít vracet více než jednoho člověka:
$server->wsdl->addComplexType(
'PersonArray',
'complexType',
'array',
'all',
'SOAP-ENC:Array',
array(),
array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:Person[]')),
'tns:Person'
);
A to je vše! Tento krok bude bohužel nutné zopakovat pro každou tabulku nebo kus dat, se kterými budeme chtít nějak pracovat. Na druhou stranu budeme odměněni tím, že v C# vše pěkně uvidíme jako třídy a budeme s nimi moci pracovat tak, jak jsme zvyklí.
Teď si zaregistrujeme funkci getPeople a naprogramujeme ji:
$server->register('getPeople',
array(), // žádné vstupní parametry
array('return' => 'tns:PersonArray'),
'uri:mywsdl',
'uri:mywsdl#getPeople',
'rpc',
'encoded',
'Získá lidi z databáze'
);
function getPeople()
{
$vysledek=mysql_query("SELECT * FROM people");
while ($zaznam = mysql_fetch_array($vysledek))
{
$result[] = array('id_person'=>$zaznam['ID_Person'],
'name'=>$zaznam['Name'],
'surname' =>$zaznam['Surname']
);
}
return $result;
}
A jednoduchou službu máme hotovou! V příštím díle si ukážeme, jak ji vložit do projektu C# a získávat tak data z databáze.