Creare un web service con SOAP e PHP

Ai tempi dell’università, nel corso di programmazione in rete tenuto dal Prof. Matteo Baldoni, avevamo realizzato un ottimo progetto in Java di agenti che interagivano tra di loro.
Era anche questo una specie di servizio web anche se era realizzato con RMI ovvero Remote Method Invocation, ossia una funzione di java che consente di invocare metodi di oggetti remoti da altre java virtual machine.

Oggi visto che sono ancora a casa per l’influenza, ho deciso di documentarmi sui web services in php, dato che probabilmente dovrò realizzare un progetto in cui potrebbero tornarmi utili.

Seguendo l’ottima guida “PHP 6, Guida per lo sviluppatore” di Lecky-Thompson, Novicki e Myer (ve la consiglio vivamente se siete interessati all’argomento, è davvero ottima), ho deciso di riprodurre l’esempio di un client-server SOAP(Simple Object Access Protocol). Innanzitutto SOAP è un protocollo per lo scambio di messaggi tra agenti software, ossia un client tramite soap può richiamare funzioni di un server inviando e ricevendo parametri. E fin qui nulla di strano mi direte voi… Infatti, la cosa interessante deve ancora arrivare! La peculiarità di scrivere software implementando SOAP è che client e server possono essere scritti in linguaggi diversi, essendo quest’ultimo appunto un protocollo e non una componente software. Tutti i linguaggi più comuni hanno delle classi che implementano SOAP, sia lato server sia lato client. Diventa così facile capire che una volta scritto un server SOAP che implementa alcune funzioni, possono esserci N client che sfruttano queste funzioni per leggere dati comuni o interagire con applicazioni sul web.

Ecco allora che ci spieghiamo come sia possibile che tutte le applicazioni web più famose, da quelle di google, a facebook fino a twitter, mettano a disposizione metodi per interagire dall’esterno con i loro servizi (le famose API, Application Programming Interface).

Come vi dicevo ho messo in pratica l’esempio che ho trovato sulla guida di PHP, potete raggiungerla a questo indirizzo. L’esempio non fa altro che realizzare un server che ha un metodo che prende in input un nome e restituisce un saluto, comprensivo del nome inviato. Il client ovviamente preso in input il nome invia la richiesta al server e stampa il saluto ricevuto. Nulla di complicato, ma si riescono ad intuire le potenzialità di questo protocollo.

I due script in php come vedrete fra poco, sono molto semplici, e non richiedono più di una decina di righe di codice ciascuno, la parte fondamentale di questo esempio è però il file wsdl, ossia il file xml di dichiarazione dei metodi messi a disposizione dal server. Questo file non è indispensabile ma è utile a chi implementa i client per sfruttare tutte le funzioni messe a disposizione da SOAP.

Come vi dicevo dal lato server il codice php è semplice:

function sayHello($name){
    $salutation="Ciao ".$name.", sarai lieto di sapere che sto funzionando!";
    return $salutation;
}
$server= new SoapServer("test.wsdl");
$server->addFunction("sayHello");
$server->handle();

Il file xml per le dichiarazioni è invece un po’ più complesso ma con un po’ di attenzione non sarà difficile comprenderne il significato:

<?xml version="1.0" encoding="UTF-8" ?>
<definitions name="test"
targetNamespace="http://URL_CARTELLA_SERVER_SOAP"
xmlns:tns="http://URL_CARTELLA_SERVER_SOAP"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/">

    <message name="sayHelloRequest">
        <part name="name" type="xsd:string"/>
    </message>
    <message name="sayHelloResponse">
        <part name="salutation" type="xsd:string"/>
    </message>

    <portType name="sayHelloPortType">
        <operation name="sayHello">
            <input message="tns:sayHelloRequest" />
            <output message="tns:sayHelloResponse" />
        </operation>
    </portType>

    <binding name="sayHelloBinding" type="tns:sayHelloPortType">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
        <operation name="sayHello">
            <soap:operation soapAction="" />

            <input>
                <soap:body use="encoded" namespace="" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
            </input>
            <output>
                <soap:body use="encoded" namespace="" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
            </output>
        </operation>
    </binding>
    <documentation>UNA VOSTRA DESCRIZIONE DEL SERVIZIO</documentation>
    <service name="sayHelloService">
        <port name="sayHelloPort" binding="sayHelloBinding">
            <soap:address location="http://URL_SCRIPT_PHP_SERVER_SOAP" />
        </port>
    </service>
</definitions>

Fatto ciò, non ci resta che scrivere il codice per il client (tralascio il banale form html, ma metto a disposizione lo zip con tutto dentro):

$wsdl_url="http://URL_AL_DEL_FILE_WSDL";
if(isset($_POST['name'])){
    if($_POST['name']!=null){
        $client=new SoapClient($wsdl_url);
        print_r($client->sayHello(htmlentities($_POST['name'])));
    }
}

La cosa fondamentale per poter utilizzare SOAP è che il vostro php abbia l’estensione php_soap abilitata, ma se ho capito bene, da php6 viene inclusa di default tra le funzioni del core di php. Tra l’altro essendo inclusa nelle funzioni default di php è scritta in C per cui molto più veloce rispetto a scrivere codice php, di conseguenza, in queste applicazioni l’unico collo di bottiglia potrebbe essere la lentezza della linea o i tempi di ping/risposta di client e/o server.

Se siete pigri e volete testare il codice senza dovervi sbattere, eccolo qui, pronto per voi già impacchettato ;-)

Prossimamente testerò anche REST un altro metodo per realizzare web service con meno rogne, non serve infatti nessun file di dichiarazione, ma anche meno rigido e controllato.

58 Risposte a “Creare un web service con SOAP e PHP”

  1. impervio ha detto:

    Ciao, davvero uno script molto interessante!

    C’è una cosa però che non ho capito… (premetto che non sono un esperto!)

    Ma come posso fare io per utilizzare lo script?

    Cosa devo fare per poter usare il client server Soap?

    Dove posso trovarlo?

    Sono interessato ad usare lo script per un mio progetto su una pagina web.

    • davide ha detto:

      Ciao, grazie per il commento, mi fa sempre piacere vedere che i miei articoli vengono letti. :-)
      Lo script se non lo hai ancora scaricato lo trovi qui. C’è uno zip con tutti i sorgenti all’interno.
      Per poter personalizzarti lo script dovresti per prima cosa scrivere delle tue funzioni (e fargli fare quello che a te interessa), e poi aggiungere i dettagli delle funzioni che hai creato nel file WSDL.
      SOAP è una tecnica che ti permette di scrivere funzioni lato server che possono essere richiamate lato client da programmi scritti in linguaggi anche diversi dal php.
      Spero di averti dato delucidazioni, se avessi ancora bisogno di ulteriori chiarimenti, rispondi pure qui, oppure alla mail che trovi nella sezione “Info”.
      Attendo tue notizie ;-)

  2. impervio ha detto:

    Ti sarei grato se mi potessi fare luce su questa cosa!
    Grazie…

  3. impervio ha detto:

    Scusa la mia ignoranza, ma dovresti usare termini meno tecnici…
    in particolare ti chiedo: Cos’è il file WSDL?

    Ho scaricato il file ZIP e l’ho provato, ma non mi funziona!
    Volevo sapere in particolare quale pezzo di codice php devo inserire nella mia pagina(oppure dove devo inserirlo?), forse quello chiamato “test.wsdl”?

    Gli altri tre, cioè i due file index.php e il file do action_php dove devo metterli e qual è la loro funzione all’interno di tutto?

    Poi ho scaricato il programma apache_soap 2.3.1-windows-bin. Può essermi utile?

    • davide ha detto:

      Il file WSDL è il file xml per le dichiarazioni, quello di cui parlo nell’articolo e che vedi nel secondo riquadro di codice…
      Lo zip scaricato così com’è contiene già la struttura dei file giusta, ma ovviamente i percorsi ai file sono sbagliati, sia nel file WSDL che nel file do_action.php.
      Mi chiedo una cosa, se posso permettermi: qual’è il risultato che tu vorresti ottenere? Questi script sono abbastanza tecnici e di conseguenza per un estraneo alla programmazione credo sia difficile comprenderne il funzionamento. Questo non vuol dire che non si riesca, ma bisogna approfondire alcuni dettagli prima di buttarsi su questo esempio ;-)

  4. impervio ha detto:

    Il risultato che vorrei ottenere è quello di fare una pagina personalizzata con scritto in primo piano un saluto di benvenuto seguito dal nome dell’utente che accede alla pagina. Per farti un esempio, vorrei che venga fuori una cosa di questo genere: l’utente (Pippo) clicca ed entra nella pagina dove viene accolto dalla frase “Ciao Pippo, benvenuto in questa pagina ecc ecc…”

    Che ne dici, si può fare?

    Grazie…

    • davide ha detto:

      Ora ci capiamo meglio ;) Direi che in questo caso non è necessario scomodare SOAP ;)
      Nella pagina “index.html” basta inserire un form, ossia:
      [form method=”post” action=”saluta.php”]
      [input type=”text” name=”persona” /]
      [input tyep=”submit” value=”Saluta” /]
      [/form]

      NB: Sostituisci le parentesi quadre con i maggiori e i minori corrispondenti…

      Nella pagina “saluta.php” ti basterà fare:
      < ?php if(isset($_POST['persona'])){ echo "Ciao ".$_POST['persona'].", benvenuto!"; } else{ echo "Inserire un nome"; } ?>

      E il gioco è fatto ;-)

  5. impervio ha detto:

    Bene Davide, forse era proprio il codice che cercavo. Ho girato tamto in rete per cercare qualcosa con questi parametri, ma non ho trovato nulla. Adesso proverò il codice che hai scritto con i miei dati e ti farò sapere se è quello che volevo.

    Grazie!…

  6. Gerry ha detto:

    Grazie Davide, il tuo post è stato prezioso per cominciare a roientarmi nel mondo delle SOAP.

    Saluti

  7. impervio ha detto:

    Davide, ho provato il codice che mi hai scritto.

    Ho inserito il codice del form e mi appare 2 campi di testo: uno vuoto e l’altro con dentro scritto (saluta). Riguardo a questa parte di codice del form c’è una cosa che non capisco.
    Nel campo vuoto l’ipotetico utente deve scrivere il suo nome?
    Poi, devo aggiungere un pulsante al form stesso per mandare l’utente nella pagina dove ho inserito l’altra parte di codice che tu hai chiamato “saluta.php”?

    oppure il codice fa tutto da solo rimandando l’utente automaticamente nella pagina?(perché se è così il tutto non mi funziona…)

    Ti ringrazio!

  8. impervio ha detto:

    Ok Davide, mandami pure il file via email.

    Prima però volevo farti una domanda!

    Ieri per caso mi sono accorto rileggendo il codice che forse c’è un errore di trascrizione
    nel codice che mi hai scritto, ovvero: (input tyep) invece che (input type).

    Potrebbe essere che non funziona per questo piccolo particolare?

    Non ho provato il codice inserendo questa correzione, preferisco aspettare che mi invii il codice
    completo via email.

    Grazie mille!…

  9. impervio ha detto:

    Ok! Siccome però il codice è incompleto, mi potresti mandare se puoi al mio indirizzo email il file completo, così vedo le parti e gli spazi che devo modificare.

    Ciao… grazie!

  10. impervio ha detto:

    Ti ringrazio Davide.

    Adesso il codice funziona!

    Se avro qualche dubbio te lo farò sapere…

    Grazie ancora!…

  11. matteo ha detto:

    ciao.
    Cercando informazioni sul funzionamento dei client sono arrivato su questa pagina.
    Ho letto il tuo articolo e volevo farti una domanda.
    per creare uno scambio di dati limitato a un circuito chiuso di utenti che usano una sola applicazione, come faccio a far comunicare il client con il database, attreverso un sito esterno che contiene i tracker. ma come faccio a limitare il download e che programmi devo usare per la struttura del client.
    grazie

  12. matteo ha detto:

    scusami se sono confusionario.
    comunque no, sul client in genere.

  13. adam ha detto:

    ciao ottimo tutorial…mi sto addentrando anche io nel mondo dei web service,e il tuo script,breve ma conciso mi ha tolto parecchi dubbi..sono un web developer anche io PER PASSIONE…continua cosi ;)

  14. marco ha detto:

    Ciao, ho letto il tuo articolo ma non ho capito ancora alcune cose.
    Dovrei passare delle coppie nome valore con SOAP 1.1 secondo lo schema che inserisco sotto. Dovrei farlo con php, ma non mi è chiaro come devo strutturare lo script.

    POST /xxxxxxxx/xxxxxxxxxxxxx.asmx HTTP/1.1
    Host: dominio.xx
    Content-Type: application/soap+xml; charset=utf-8
    Content-Length: length

    ….
    ….
    ….
    grazie

  15. marco ha detto:

    Non so come ma il testo è stato troncato provo a reinserirlo:

    ((

    ))

  16. Fabio ha detto:

    ciao, complimenti per la chiarezza!

  17. nicola ha detto:

    Ciao,ottimo articolo,
    io sono abbastanza nubbio sull’argomento
    e dovrei fare un server in php e un client in delphi.
    Ho provato il tuo esempio e il client php funziona correttamente, mentre quello in delphi non funziona con il tuo server
    ma funziona correttamente con questo
    http://www.abundanttech.com/WebServices/bnprice/bnprice.asmx

    Dopo vari tentativi ho risolto rimuovendo il namespace=””
    nei tag e del file wsdl

    Ora provo a creare il mio server con relativo wsdl ma mi puoi spiegare a che serve il namespace?

    grazie

  18. nicola ha detto:

    opss si sono persi i tag??
    quelli che intendevo sono input e output,
    infatti sono le tue sezioni che contengono il namespace=””

    grazie

    • davide ha detto:

      Ciao grazie per i complimenti.
      Sul Delphi proprio non saprei aiutarti l’ho usato parecchi anni fa in ambito scolastico e da lì non ho mai più fatto niente in quel linguaggio.
      Per quanto riguarda il namespace invece dovrebbe essere l’ambito a cui si riferisce quel tag, come se fosse una variabile “nome” che può avere più significati e con il namespace gli dici che fa riferimento al nome di una persona piuttosto che di un’automobile, ma nel caso specifico non saprei dirti a cosa serve. Spero di essermi riuscito a spiegare ;)
      ps: i tag non te li visualizza perchè in genere sui blog come questo vengono disattivati in automatico perchè potrebbero essere veicolo di codice malevolo

  19. Guido ha detto:

    Ciao Davide, grazie per il tuo sorgente, lo sto leggendo :)
    Non mi e’ chiaro con cosa sviluppi il servizio server e sembra strano partire dal wsdl: ipotizzando di fare un servizio complesso, con molti metodi ed oggetti di discreta complessita’, non e’ possibile certo utilizzare un semplice editor di testo!

    Sono abituato a farlo in java, ma vorrei sperimentare le stesse funzionalita’ in php :P

  20. Jacop ha detto:

    Ciao il tuo articolo e molto interessante, volevo pero chiederti una cosa, se volessi fa funzionare questo esempio sul mio server per iniziare a capire il funzionamento, devo modificare i path, quindi in test.wsdl modifico:
    targetnamespace= con il path del file per esempio localhost/soap/server.
    modifico in maniera simile anche xmlns:tns e acnhe il location di soap:address.
    sul client invece devo modificare do_action con:
    ip/path cartella dove trovo il file.wsdl

    ho fatto tutto cio ma nn mi funziona mi da un errore: Uncaught SoapFaul exception:[WSDL]…….e cosi via….
    mica ptresti aiutarmi a capire il perche? grazie e scusa per il disturbo

    • davide ha detto:

      Ciao, teoricamente non dovresti avere problemi a personalizzare lo script. Però dall’errore sembra che ci sia qualche problema con il file WSDL. Sei sicuro di aver corretto i path in tutti i punti del file WSDL? Dal PHP richiami file con il percorso giusto?

  21. jacopo ha detto:

    Si sono sicuro ho provato anche a lasciare invariati i path e poi ho ricreato la stessa situazione sulla stessa macchina quindi ho messo tutto nella cartella htcdocs/test/soap/ rispettivamente server e client e poisfruttando xampp ho provato a farlo funzionare ma niente mi da lo stesso errore……

  22. Michele ha detto:

    Ciao Davide,

    riguardo i metodi per l’autenticazione hai testato mai qualcosa?

    Io non riesco ad autenticarmi ad un servizio in .NET con la mia app in php.

  23. Roberto ha detto:

    Buonasera Davide,

    vorrei proporti un lavoro, ma non so come contattarti. Il mio indirizzo email è grppcommedia28@gmail.com

  24. moroboshi ha detto:

    Ciao Davide ho messo le due cartelle dove ho installato php, ma quando inserisco il nome mi restituisce una pagina vuota, cosa può essere?

    • davide ha detto:

      Con così poche informazioni non so come aiutarti.. Per prima cosa verifica di avere il server disponibile. Poi le due cartelle vanno messe nella directory pubblica del server e non dove è stato installato php.. Poi potrebbero esserci altri mille motivi per cui non funziona, ma così non saprei aiutarti. :-/

  25. Sorcerer ha detto:

    Articolo molto interessante, ne prenderò spunto per approfondire l’argomento. Il mio obiettivo è inviare email tramite un programma in visual basic .net, e visto che la rete (o il firewall) aziendale mi bloccano l’accesso all’smtp, pensavo di ovviare il problema tramite un webservice.

  26. […] tre articoli più popolari per il momento sono nell’ordine “Creare un web service con soap e php” con 401 visualizzazioni, “Sincronizzare Google Calendar con iCal su Mac e […]

  27. Sorcerer ha detto:

    L’ho provato e non funziona, mi dà questo errore:

    Fatal error: Uncaught SoapFault exception: [HTTP] Could not connect to host in /home/mhd-01/www.androidblogitalia.com/htdocs/_soap/do_action.php:10 Stack trace: #0 [internal function]: SoapClient->__doRequest(‘__call(‘sayHello’, Array) #2 /home/mhd-01/www.androidblogitalia.com/htdocs/_soap/do_action.php(10): SoapClient->sayHello(‘prova’) #3 {main} thrown in /home/mhd-01/www.androidblogitalia.com/htdocs/_soap/do_action.php on line 10

    Sono sicuro che i path siano corretti, ho provato sia con localhost che mettendo l’indirizzo completo.
    in _soap ho messo i file client, in _soap/server quelli server.

    Qualche suggerimento? grazie

  28. Sorcerer ha detto:

    adesso funziona ;)
    mah misteri dell’informatica

  29. Enrico Raspadori ha detto:

    Salve,
    grazie per il tutorial molto chiaro.
    Esiste un generatore di wsdl (così come accade nella libreria NuSoap) per la generazione dinamica del documento wsdl a partire dalle funzioni dichiarate nel file php del server?

  30. bertolottipf ha detto:

    Ciao… Scusa, non è che potresti mettere i link posto??? GRAZIE.

  31. Ciao Davide ha detto:

    Grazie Mille per il tuo articolo, semplice e chiaro.
    Faccio questo mestiere da 25 anni ma non sempre si riesce a vedere tutto quello che c’è nel panorama informatico, poi tutto insieme viene fuori un progetto e ti occorre colmare le lacune , e spesso questo richiede abbastanza tempo.
    Grazie al tuo articolo ho risolto il problema in una mezza giornata ed ho potuto proseguire il lavoro senza ritardi.

    Grazie mille di nuovo Collega.

  32. Paolo Camponeschi ha detto:

    Saranno pure i miei 45 anni ma alle 1.30 di notte qualche cavolata la faccio!!!!
    Il mio nome è Paolo non “Ciao Davide”
    Scusate tutti sto vecchietto.

    Saluti
    Paolo

  33. Andrea Nenci ha detto:

    ciao, un articolo molto interessante.

    Ora però mi trovo di fronte ad un’ulteriore passaggio.

    Devo fare in modo che tutte le comunicazioni passino attraverso un TCP TunnelGUI, sai come funziona questo passaggio?

  34. elio ha detto:

    grazie mille Davide;-) vale molto più di mille guide presenti online
    ciao
    elio

  35. Maxv78 ha detto:

    Ciao Davide, una domanda….se avessi la necessità di utilizzare HTTPS anziché HTTP?
    Cosa è necessario modificare nel tuo script?

    Grazie 1000!
    Il tuo articolo è utilissimo e spiegato in modo molto chiaro! Complimenti!

  36. Sebastian ha detto:

    Ciao Davide, complimenti per la guida ho già le idee piu chiare mà….

    Se volessi pescare dal DB un valore da scrivere nel wsdl ed inviarlo al client invece che scriverlo nel codice a manina?

  37. corrado ha detto:

    Stessa cosa, ottima guida funziona… vorrei prendere i dati da un db ma questo è accessibile solo tramite ajax quindi dovrei modificare index.php del server in qualcosa in javascript è fattibile? Grazie mille.

  38. Joshua ha detto:

    Ciao, ho provato il tuo esempio e funziona alla grande!
    Utilizzando però dei tipi complessi nel file wsdl la routine non sembra ricevere più nulla. Come si potrebbe fare per fare il binding di un tipo complesso nel wsdl con una variabile PHP ?
    Grazie, Giuseppe

  39. Bob ha detto:

    Scusa, ma nei sorgenti dell’esempio manca il riferimento (require) alla libreria Soap (es. NuSoap); come fa a funzionare? infatti a me non funziona. Il problema è che non mi funziona neanche linkando la libreria.
    L’errore che incontro è: Call to undefined method soapclient::sayHello() ecc.

    • davide ha detto:

      Ciao, se leggi al fondo dell’articolo spiego che serve un’estensione del php.. Più precisamente la php_soap, devi installarla e poi tutto inizierà a funzionare! ;)

  40. Lucio ha detto:

    Ciao, ho provato ad utilizzare il tuo codice sul mio server e tutto funziona correttamente.
    Se però provo ad inviare la richiesta soap da un client remoto (ho utilizzato PandaSoap dal mio PC) il sistema non funziona. Credo che il motivo sia dovuto alla configurazione di qualche autorizzazione sul server dato che quando tutto è su localhost non c’è problema.

    Hai suggerimenti da darmi a tal proposito?

    Grazie

  41. Benny ha detto:

    Salve ragazzi, ho urgente bisogno di aiuto.
    Sono anni che programmo in php ma da poco mi sono avvicinato al mondo dei webservice, e ho un pò di difficoltà a comprenderli…spero mi possiate aiutare.
    Innanzitutto ho provato a copiare ed incollare lo script proposto sul mio server ma non vuole saperne di funzionare, mentre se utilizzo wsdl di servizi esterni tutto funziona correttamente….per caso c’è bisogno di qualche configurazione particolare del server per funzionare da “lato server”?

    Inoltre con un pò di fatica sono riuscito a fare webservice semplicissimi, ma ora mi ritrovo ad avere un’esigenza particolare….Un server esterno mi invia un file (xml) in parti in base_64 e io dovrei leggerlo e salvarlo sul server in un’unico file decriptato.
    Come faccio a ricevere il file?Posso leggerlo normalmente come se fosse un $_file?

    E se poi dovvessi trovarmi nella situazione di doverlo inviare un file xml (quindi fungere da server) come mi comporto?

    Spero mi possiate aiutare con degli esempi perchè stò davvero in difficolta.

    Grazie mille

  42. Nilo ha detto:

    Ciao, ma quindi io potrei sviluppare una cosa del genere con php…
    Spiego.
    Un operatore inserisce tramite una form dei dati, che vengono inseriti in un db mysql. Un’altra societa è interessata alla estrazione di tali dati tramite chiamate SOAP (il file WSDL è stato fornito dal tale azienda). E fattibile fare tutto con php?

    Grazie mille

  43. Riccardo Ciciriello ha detto:

    Ciao Davide, non riesco a far funzionare il tuo pacchetto:mi viene restituito il seguente errore:

    Fatal error: Uncaught SoapFault exception: [SOAP-ENV:Server] Procedure ‘ns1:sayHello’ not present in /var/www/vhost/wclient/htdocs/do_action.php:10 Stack trace: #0 /var/www/vhost/wclient/htdocs/do_action.php(10): SoapClient->__call(‘sayHello’, Array) #1 /var/www/vhost/wclient/htdocs/do_action.php(10): SoapClient->sayHello(‘riccardo’) #2 {main} thrown in /var/www/vhost/wclient/htdocs/do_action.php on line 10

Lascia un commento