AJAX, JavaScript, PHP e Google maps: Georeferenziazione indirizzi

 

Questo post ha più di 1 anno. Il contenuto potrebbe essere obsoleto, non più completamente accessibile o mancante di alcune informazioni.

screenshot del programma georeferenziazione indirizziPer concludere il panorama dei possibili utilizzi della classe PHP per georeferenziare indirizzi multipli, vediamo l’impiego della tecnologia AJAX vera e propria.
Come avevo accennato nel precedente articolo, l’approccio non è così lineare per la necessità di invocare più volte l’oggetto XMLHttpRequest, e ancora peggio, per dover gestire le risposte multiple ed asincrone del server. Si rende infatti necessario, non solo creare tanti oggetti AJAX quante sono le richieste, ma anche altrettanti oggetti che gestiscano separatamente le risposte, mantenendo l’handle della chiamata.
Una buona soluzione mi è sembrata quella illustrata in questo articolo, ed è quella che ho adottato per questo esempio.

La pagina html iniziale comprende un form per l’immissione degli indirizzi e un semplice script javascript che, per ogni tag di tipo input, invoca una richiesta AJAX. Ecco il codice della funzione:

<script type="text/javascript" src="MultipleAjaxClassSimple.js"></script> 
<script type="text/javascript">
 
function getFormValues() 
{
  document.getElementById("divResults").innerHTML = '';
  var myForm = document.getElementById("myForm").getElementsByTagName('input');
  var len = myForm.length;
  var params = '';
  for (i = 0; i < len; i++) {
    if(myForm[i].type == 'text'){
      params = myForm[i].value; 
      ajaxSaveTag(params);         
    }        
  }
}    
</script>

Lo script AJAX è composto da quattro funzioni:

  • ajaxSaveTag()
  • httpRequest()
  • initRequest()
  • ReturnSaveTag()

La prima funzione prende come parametro l’indirizzo da georeferenziare, compone una url con la querystring da passare allo script server e infine chiama la seconda funzione httpRequest() che si occupa di instanziare l’oggetto XMLHttpRequest. La terza funzione, initRequest() ha il compito di gestire le risposte del server, invocando i metodi della funzione di callback (la quarta: ReturnSaveTag()), che in realtà è un oggetto. Questo permette di creare tante istanze di callback quante sono le chiamate AJAX.

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
// JavaScript Document
function ajaxSaveTag(tag) {
  var url  = 'ajaxSaveTags.php';
  url += '?query='+encodeURIComponent(tag);
  httpRequest("GET",url,1,new ReturnSaveTag());
}
function httpRequest(type,url,asynch,respHandle) {
  var request;
  // mozilla
  if (window.XMLHttpRequest) {
    request = new XMLHttpRequest();
  }
  // internet explorer
  else if (window.ActiveXObject) {
    request = new ActiveXObject("Msxml2.XMLHTTP");
    if (!request) {
      request = new ActiveXObject("Microsoft.XMLHTTP");
    }
  }
  // send request | error
  if (request) {
    initRequest(request,type,url,asynch,respHandle);
  }
  else {
  // ERROR!
  }
}
function initRequest(request,type,url,asynch,respHandle) {
  try {
    respHandle.setReqObj(request);
    request.onreadystatechange = respHandle.goResponse;
    request.open(type,url,asynch);
    request.send(null);
  } catch (errv) {
    // ERROR!
  }
}
function ReturnSaveTag() {
  var reqObj = null;
  this.setReqObj = setReqObj;
  this.goResponse = goResponse;
  function setReqObj(myVal) { reqObj = myVal; }
  function goResponse() {
    var request = reqObj;
    if (request.readyState == 4) {
      if (request.status == 200) {
        // Success!
        var mytext = request.responseText;
        var myDiv = document.getElementById("divResults");
        // display the HTML output
        myDiv.innerHTML = myDiv.innerHTML + mytext;
      } else {
        // ERROR (couldn't find ajax file)
      }
    }
    return true;
  } // end method
}

Lo script lato server ajaxSaveTags.php crea un header che non consenta al browser di utilizzare la cache, raccoglie i dati dalla querystring, utilizza la classe PHP geocodeaddr
per georeferenziare l’indirizzo, ed infine stampa il risultato formattato.

1
2
3
4
5
6
7
<!--?php // make sure the user's browser doesn't cache the result header('Expires: Wed, 23 Dec 1980 00:30:00 GMT'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); header('Cache-Control: no-cache, must-revalidate'); header('Pragma: no-cache'); require("geocoder4.class.php"); $data = array(); if (!empty($_GET['query'])) { $toGeocode = $_GET['query']; $data = array($toGeocode); } //localhost key: $gmpKey = "ABQIAAAAzr2EBOXUKnm_...";// Inserire la Google key corretta! $test = &amp;amp; new geocodeaddr($data,$gmpKey); $geocodeResults = $test-&gt;getAddress();
$res = $geocodeResults[0]['address'].' lat: '.
       $geocodeResults[0]['lat'].' long: '.
       $geocodeResults[0]['lng'].'&lt;br ?-->';
// output the result
echo $res;
?&gt;

Questa è la demo

Conclusioni:

L’utilizzo della tecnologia AJAX ha richiesto un codice un po’ più sofisticato e meno facilmente intuibile rispetto alla soluzione pseudo-AJAX. Sarebbe interessante fare un confronto anche in altre situazioni in cui sia necessario effettuare richieste asincrone al server, e verificare efficienza e robustezza di entrambi con carichi gravosi. Un’ultima curiosità: poiché le risposte del server sono asincrone, può capitare che l’ordine dei risultati non sia lo stesso delle chiamate.

Riferimenti ed approfondimenti:

Pseudo-AJAX, JavaScript, PHP e Google maps: Georeferenziazione indirizzi

 

Questo post ha più di 1 anno. Il contenuto potrebbe essere obsoleto, non più completamente accessibile o mancante di alcune informazioni.

Ajax: no grazie!Nelle conclusioni del precedente post, dicevo che “… sarebbe interessante utilizzare la tecnologia Ajax per recuperare in modo asincrono le coordinate …”, ebbene, fatti alcuni test, mi sono ritrovato ad affrontare due difficoltà: compatibilità dell’oggetto XMLhttprequest e soprattutto la gestione di richieste multiple contemporanee. Cercando sul web, ho trovato diverse soluzioni, alcune molto complicate, altre troppo “deboli”, una di queste interessante, ma alla fine ne ho trovata una, completamente diversa, che mi ha davvero incuriosito: la simulazione di un comportamento AJAX senza utilizzare… AJAX! La soluzione prospettata dall’autore di questo interessante post su W3 Facile, è tanto semplice quanto geniale: utilizzare il tag <script> per includere nella nostra pagina html, porzioni di codice javascript generati dinamicamente da script server side PHP. In questo modo risulta possibile elaborare richieste a server remoti e ricevere serialmente le risposte, vediamo l’applicazione di questa soluzione al nostro caso:

la pagina iniziale contiene un form per impostare gli indirizzi da georeferenziare e questo codice javascript che effettua le chiamate allo script lato server 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
<script type="text/javascript">
// Ottieni la base url
url = document.location.href;
xend = url.lastIndexOf("/") + 1;
var base_url = url.substring(0, xend);
function chiama_ajax(url) 
{
  //Inizia l'url con http?
  if (url.substring(0, 4) != 'http') 
  {
    url = base_url + url;
  }
  // Crea un nuovo elemento JS
  var jsel = document.createElement('SCRIPT');
  jsel.type = 'text/javascript';
  jsel.src = url;
  //Appende l'elemento JS e quindi esegue la 'chiamata pseudo-AJAX'
  document.body.appendChild (jsel);
}
function getFormValues() 
{
  document.getElementById("divResults").innerHTML = '';
  var myForm = document.getElementById("myForm").getElementsByTagName('input');
  var len = myForm.length;
  var params = '';
  for (i = 0; i < len; i++) {
    if(myForm[i].type == 'text'){
      params = myForm[i].value; 
      //per ogni elemento del form, includo lo script js che
      // viene creato dinamicamente dallo script php
      chiama_ajax('pseudoajax.php?query='+params);         
    }        
  }
}
</script>

In pratica, per ogni elemento input di tipo text del form viene richiamata la funzione chiama_ajax() passandogli la url del file php e il valore impostato in querystring.

Lo script lato server pseudoajax.php:

1
2
3
4
5
6
7
<!--?php require("geocoder4.class.php"); $data = array(); if (!empty($_GET['query'])) { $toGeocode = $_GET['query']; $data = array($toGeocode); } //localhost key: $gmpKey = "ABQIAAAAzr2EBOXUKnm_..."; // Inserire la Google key corretta! $test = &amp;amp; new geocodeaddr($data,$gmpKey); $geocodeResults = $test-&gt;getAddress();
$res = $geocodeResults[0]['address'].' lat: '.
       $geocodeResults[0]['lat'].' long: '.
       $geocodeResults[0]['lng'].'&lt;br ?-->';
?&gt;
div = document.getElementById('divResults');
div.innerHTML += '<!--?php echo($res); ?-->';

Lo script raccoglie il parametro passato in querystring, valorizza l’array da passare alla classe geocodeaddr, formatta il risultato e lo rispedisce come elemento code>innerHTML
al client HTML sotto forma di script js. Fatto!

Questa è la demo

Conclusioni:

Concordo pienamente con l’autore che, nei commenti al suo articolo dice “Diciamo che come metodo ha i suoi pro e contro…i pro sono un’enorme leggerezza, compatibilità e semplicità d’uso, i contro dei limiti nel suo utilizzo e l’esclusione per la lettura di valori XML, non utilizzando l’XMLhttprequest”, e conclude dicendo: “…nella programmazione non esiste una soluzione migliore di un’altra SEMPRE, dipende dal problema che dobbiamo risolvere”

Riferimenti ed approfondimenti:

PHP: una classe per trovare le coordinate geografiche di una lista di localita’ con Google Maps

 

Questo post ha più di 1 anno. Il contenuto potrebbe essere obsoleto, non più completamente accessibile o mancante di alcune informazioni.

planisferoHo ricavato il codice di questa classe PHP modificando leggermente un chiarissimo esempio disponibile sul sito di Google Maps. L’esempio mostra come sia possibile inserire in un database MySQL le coordinate geografiche relative ad un archivio di località o indirizzi. Ho pensato di renderlo più versatile traducendolo in una classe che prende come input un array di indirizzi/località e restituisce in output un array di coordinate geografiche. Le API di Google Maps mettono a disposizione un servizio per la georeferenziazione che può essere utilizzato attraverso due differenti modalità:

  1. Attraverso una richiesta HTTP
  2. Attraverso l’oggetto GClientGeocoder

Le operazioni di georeferenziazione attraverso GClientGeocoder sono piuttosto onerose in termini di tempo e risorse, per questo motivo Google stesso suggerisce di utilizzare le richieste server side via HTTP.
La URI per accedere al servizio è: http://maps.google.com/maps/geo? e i parametri per completare la richiesta:

  • q – è l’indirizzo o località da georeferenziare
  • key – è la chiave per l’accesso a Google Maps (attivabile qui)
  • output – è il formato dell’output generato, i valori possibili sono: xml, kml, csv, json.

Il codice base è praticamente lo stesso dell’esempio fornito da Google al quale ho soltanto eliminato le parti di accesso al database MySQL. La classe compatibile con PHP5 (che è mostrata qui sotto), utilizza come output un file xml, mentre quella compatibile con PHP4 utilizza un output csv.

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
class geocodeaddr
{
  public $mapsHost = "maps.google.com";
  public $googleKey = "myGoogleMapsKey";
  public $dataArray;
  public $errorMsg = "";
 
  public function __construct($dataArray, $googleKey)
  {
    $this-&gt;dataArray = $dataArray;
    $this-&gt;googleKey = $googleKey;
  }
  public function getAddress()
  {
    $results = array();
    // Initialize delay in geocode speed
    $delay = 0;
    $base_url = "http://" . $this-&gt;mapsHost . "/maps/geo?output=xml" . "&amp;key=" . 
                $this-&gt;googleKey . "&amp;oe=utf-8";
    // Iterate through the rows, geocoding each address
    foreach ($this-&gt;dataArray as $address) {
      $geocode_pending = true;
        while ($geocode_pending) {
          $request_url = $base_url . "&amp;q=" . urlencode($address);
          $xml = simplexml_load_file($request_url) or die("url not loading");
          $status = $xml-&gt;Response-&gt;Status-&gt;code;
          if (strcmp($status, "200") == 0) {
            // Successful geocode
            $geocode_pending = false;
            $coordinates = $xml-&gt;Response-&gt;Placemark-&gt;Point-&gt;coordinates;
            $coordinatesSplit = split(",", $coordinates);
            // Format: Longitude, Latitude
            $lng = $coordinatesSplit[0];
            $lat = $coordinatesSplit[1];
            $results[] = array("address" =&gt; $address, "lat" =&gt; $lat, "lng" =&gt; $lng);
          } else if (strcmp($status, "620") == 0) {
            // sent geocodes too fast
            $delay += 100000;
          } else {
            // failure to geocode
            $geocode_pending = false;
            $results[] = array("address" =&gt; $address, "lat" =&gt; 0, "lng" =&gt; 0);
            $this-&gt;errorMsg .= "Address ".$address.
                  " failed to geocoded. Received status ".$status."\n";
          }
        usleep($delay);
      }
    }
    return $results;
  }
}

Per comprendere meglio come utilizzare la struttura xml generata dal servizio di Google riporto un esempio utilizzando “Roma” come indirizzo di ricerca:

1
2
3
4
5
6
7
<!--?xml version="1.0" encoding="UTF-8"?-->
 
 
  Roma
 
 
    <code>200</code> geocode
Roma, Roma (Lazio), Italy

ITLazioRomaRoma12.482324,41.895466,0

Notate che ho utilizzato un piccolo hack non documentato per evitare “warning” con caratteri particolari, tipo le lettere accentate, che si verifica scegliendo il formato xml. Il trucco consiste nel forzare l’encoding dei caratteri nel formato utf-8 aggiungendo alla querystring questo parametro: &oe=utf-8

Un altro possibile errore potrebbe verificarsi utilizzando la classe versione PHP4. Infatti la funzione utilizzata per “recuperare” il file csv via HTTP è file_get_contents(). Questa funzione è compatibile solo con le versioni di PHP 4.3.x e superiori, e solo se è abilitata la direttiva del php.ini: allow_url_fopen. Per questo motivo nel codice della classe geocoder4.class.php, ho inserito un metodo che utilizza le librerie cURL in alternativa a file_get_contents(). Il metodo deve essere decommentato insieme alla riga di codice che lo richiama nel caso in cui si voglia usare cURL (verificate che almeno cURL sia abilitato!!).

Questo, invece, è uno spezzone di codice che mostra come instanziare la classe in PHP5 e generare una tabella di risultati: (un esempio di utilizzo della classe è visibile qui)

1
2
3
4
5
6
7
8
9
10
11
...
require("geocoder5.class.php");
$data = array("Roma, RM, Italy", 
              "Milano, MI, Italy", 
              "Bologna, BO, Italy", 
              "Napoli, NA, Italy", 
              "Palermo, PA, Italy");
$gmpKey = "ABQIAAAAzr2EBOXUKnm..."; // Inserire la Google key corretta!
$test = new geocodeaddr($data,$gmpKey);
$geocodeResults = $test-&gt;getAddress();
echo("

“); echo(”
“); foreach ($geocodeResults as $rowResult){ echo(”); } echo(”

IndirizzoLatitudineLongitudine
‘.$rowResult[‘address’].’‘.$rowResult[‘lat’]. ‘‘.$rowResult[‘lng’].’

“); echo $test->errorMsg; …

Download

Il pacchetto completo delle classi sia per PHP5 che per PHP4 ed il file di esempio è scaricabile qui.

Conclusioni:

Non è stato difficile adattare l’esempio fornito da Google Maps alle classi, ci è voluto un po’ tempo, invece per assicurarsi che la classe per il PHP4 funzionasse senza errori con diversi tipi di configurazione dei server. Per questo motivo, consiglio a tutti di passare al PHP5 se e ove possibile!
Infine, sarebbe interessante utilizzare la tecnologia Ajax per recuperare in modo asincrono le coordinate, ed eliminare in questo modo il fastidioso stato di attesa del browser finché non sono state georeferenziate tutte le località. Tempo permettendo, è un miglioramento che vorrei implementare.

Riferimenti ed approfondimenti:

P4A framework: utilizzare il widget P4A Google Maps per implementare uno store locator

 

Questo post ha più di 1 anno. Il contenuto potrebbe essere obsoleto, non più completamente accessibile o mancante di alcune informazioni.

P4A esempio di Store LocatorEcco un esempio di utilizzazione del widget P4A Google Maps per creare una mappa di riferimenti (negozi, magazzini, ecc) che siano compresi in un certo raggio di distanza in Km, a partire da un indirizzo prestabilito.
Ho cercato di utilizzare il widget di Alberto Galanti senza effettuare nessuna modifica al codice originale. Ho scelto, quindi di implementare un metodo che fornisce la georeferenza della località che funge da centro, per poi effettuare, attraverso una query, la selezione degli indirizzi compresi nel raggio di distanze prestabilito.
L’esempio utilizzato, per comodità, è lo stesso indicato dalla guida di Google Maps in questo articolo.

I passi principali prima di procedere:

  • Scaricare il widget P4A Google Maps qui
  • Creare un archivio di riferimenti che abbiano un indirizzo nel formato accettato da Google Maps (Es.: “Piazza Venezia, Roma, Italia”)
  • Georeferenziare tutti gli indirizzi (trovare latitudine e longitudine)

Utilizzando MySQL, la struttura dell’archivio suggerita da Google è questa:

1
2
3
4
5
6
7
8
CREATE TABLE `markers` (
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(60) NOT NULL,
  `address` VARCHAR(80) NOT NULL,
  `lat` FLOAT(10,6) NOT NULL,
  `lng` FLOAT(10,6) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

Per comodità potete scaricare l’sql completo dei dati di esempio di Google qui

Nel caso non si conoscano già le coordinate dei riferimenti, è possibile utilizzare il metodo suggerito in questo articolo sempre da Google. Questo sistema permette di aggiornare direttamente i dati di un database MySQL con le coordinate fornite da Google Maps per tutti gli indirizzi memorizzati.

La selezione dei record che sono compresi in un reggio di distanza in Km prefissato, avviene tramite una query che utilizza la formula della triangolazione sferica:
cos p = cos a cos b + sen a sen b cos φ,
per maggiori dettagli potete leggere questo post.

Ecco la query in pratica:

1
2
3
4
5
6
7
8
SELECT address, name, lat, lng, 
( 6371 * acos( cos( radians('Center_latitude')) * cos( radians( lat )) 
* cos( radians( lng ) - radians('Center_longitude')) 
+ sin( radians('Center_latitude'))
* sin( radians( lat )))) AS distance
FROM markers
HAVING distance &lt; 'Radius'
ORDER BY distance

dove 'Center_latitude' è la latitudine del centro, 'Center_longitude' è la longitudine del centro e 'Radius' è il raggio in km dal centro.

Ed ecco dunque, il codice della maschera P4A:

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
class GoogleMap extends P4A_Mask
{
  function GoogleMap()
  {
    parent::P4A_Mask();
    $this-&gt;setTitle("Google Map Store Locator Test");
    // Frame
    $this-&gt;build("p4a_frame", "frame");
    $this-&gt;frame-&gt;setWidth(800);
    //la chiave per localhost è disponibile nel file p4a_map.php
    $coordArray = $this-&gt;getLocationCoordinates("Mountain View, CA", 
                  "Inserire_chiave_Google"); 
    $radius = 12;
    // DB Source
    $this-&gt;build("p4a_db_source", "source");
    $query = "SELECT address, name, lat, lng, 
     CONCAT_WS('
', name, address) AS info,
     (6371 * acos(cos(radians('$coordArray[2]')) * cos(radians(lat))
     * cos( radians(lng) - radians('$coordArray[3]')) 
     + sin( radians('$coordArray[2]'))
     * sin( radians(lat)))) AS distance
     FROM markers
     HAVING distance &lt; '$radius' ORDER BY distance"; $this-&gt;source-&gt;setQuery($query);
    $this-&gt;source-&gt;setPk("id");
    $this-&gt;source-&gt;load();
    // map
    $map =&amp; $this-&gt;build("p4a_map","mappa");
    $map-&gt;setCenter("Mountain View, CA");
    $map-&gt;setZoom(10);
    $map-&gt;setWidth(750);
    $map-&gt;setHeight(500);
    // markers from datasource
    $map-&gt;setSourceAddressField("address");
    //$map-&gt;setSourceDescriptionField("distance");
    $map-&gt;setSourceDescriptionField("info");
    $map-&gt;setSource($this-&gt;source);
    // Layout
    $this-&gt;frame-&gt;anchorCenter($map);
    // Display
    $this-&gt;display("main", $this-&gt;frame);
   }
 /*
  * Return the coordinates of the location.
  * @param $location		valid Google Maps address of a location.
  * @param $googleMapKey		key for accessing Google Maps.	 
  * @return array  element[0] = status, element[2] = lat, element[3] = long
  */
 function getLocationCoordinates($location, $googleMapKey)
 {
  $base_url = "http://maps.google.com/maps/geo?output=csv&amp;key=".$googleMapKey;
  $request_url = $base_url."&amp;q=".urlencode($location);
  $csv = file_get_contents($request_url);
  if ($csv) {
    $csvSplit = split(",", $csv);
  }
  else{
    $csvSplit = array();
  }
    return $csvSplit;
  }
}

Note:

La query crea la sorgente dati per i markers, che vengono evidenziati automaticamente dalla classe p4a_map.
Al posto delle info, potete visualizzare la distanza di ogni marker dal centro (nell’esempio: “Mountain View, CA”) utilizzando l’istruzione: $map->setSourceDescriptionField("distance");.
Il metodo getLocationCoordinates() recupera le coordinate del centro per poter poi comporre la query.
La chiave per l’accesso a Google Map è quella valida per un server tipo “localhost” e la trovate all’interno del file: p4a_map.php che si trova nella directory libraries. Per avere quella per un dominio pubblico è necessario farla generare a Google Maps qui.
Non ho fatto controlli di errore, ad esempio per quanto riguarda la risposta del metodo getLocationCoordinates() sarebbe opportuno controllare che l’array non sia vuoto e anche lo status della risposta di Google Maps, e si dovrebbe anche limitare il risultato della query al numero di markers massimo gestiti dal widget (17 se non ricordo male…).

Download

Il widget con la maschera sopra descritta è scaricabile qui

Conclusioni:

Il risultato è già soddisfacente, anche se un po’ grezzo, ma credo ci siano molti margini di miglioramento per questo tipo di utilizzo di Google Maps in P4A. Spero di avere tempo per affinare ulteriormente sia questo programma che il widget, per renderlo utilizzabile anche per idee diverse! Suggerimenti e consigli sono molto ben accetti!!

Riferimenti ed approfondimenti:

PHP: Usare la libreria cURL per l’upload di file tramite FTP e la gestione sincrona del risultato

Logo librerie cURLLa libreria cURL è stata scritta da Daniel Stenberg
per la connessione attraverso diversi tipi di protocolli fra i quali FTP, http, https, telnet a server remoti. Di particolare interesse è la possibilità di effettuare l’upload via FTP con l’estensione ftp di PHP. Il test che propongo riguarda l’utilizzo di alcune funzioni di questa libreria per l’invio di un file su un server remoto e la gestione sincrona dei dati inviati attraverso il file.
Più semplicemente, invio un file utilizzando il protocollo FTP, attendo che il file sia arrivato senza errori, e poi effettuo il parsing del suo contentuto lanciando uno script sul server passandogli come parametro POST il nome ed il percorso di destinazione del file stesso.

Per prima cosa bisogna accertarsi che il PHP integri le librerie cURL. L’installazione non sembra essere troppo complessa, vi rimando al manuale PHP nella sezione installazione.
I più fortunati, (come me!) che utilizzano WAMP5 non dovranno far altro che abilitare in Impostazioni PHP -> Estensioni PHP -> php_curl.

La sostanza delle funzioni cURL è tutta in questi pochi step:

  • Inizializzare una sessione usando curl_init()
  • Impostare le opzioni per il trasferimento tramite curl_setopt()
  • Eseguire la sessione usando curl_exec()
  • Terminare la sessione con curl_close()

Vediamo il codice dello script testcurlftp.php che effettua l’upload del file:

1
2
3
4
5
6
7
8
<!--?php // ------- Settings --------------------------- $local_path = "c:/wamp/www/test/"; //local path of the file to send $file_name = "test.txt"; //file name $ftp_user = 'user'; // ftp username $ftp_pass = 'password'; // ftp password $ftp_location = "ftp.yoursite.com/httpdocs"; // ftp site destination $http_path = "/files/"; // relative http path //server script location: $server_script_url = "http://yoursite.com/files/testscript.php"; // ------- End Settings ------------------------ $fileToSend = $local_path.$file_name; $ftp_url = "ftp://".$ftp_user.":".$ftp_pass."@" .$ftp_location.$http_path.$file_name; $errorMsg = ''; // ------- Upload file through FTP --------------- if (is_file($fileToSend)){ $ch = curl_init(); $fp = fopen ($fileToSend, "r"); // we upload a TXT file curl_setopt($ch, CURLOPT_URL, $ftp_url); curl_setopt($ch, CURLOPT_UPLOAD, 1); curl_setopt($ch, CURLOPT_INFILE, $fp); // set size of the file, which isn't _mandatory_ but // helps libcurl to do extra error checking on the upload. curl_setopt($ch, CURLOPT_INFILESIZE, filesize($fileToSend)); $res = curl_exec ($ch); $errorMsg = curl_error($ch); $errorNumber = curl_errno($ch); curl_close ($ch); // Run server script with the uploaded file name passed as a // POST parameter if ($errorNumber==0){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $server_script_url); //Return a string as the result of curl_exec() curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, 'fileuploaded='.$http_path.$file_name); $res = curl_exec ($ch); $errorMsg = curl_error($ch); $errorNumber = curl_errno($ch); curl_close ($ch); echo $res; //DEBUG } if ($errorNumber&gt;0){
    print 'cURL error: '.$errorMsg." - n.".$errorNumber; // DEBUG
  }
}
else{
  print 'Cannot find file:'.$fileToSend; //DEBUG
}
?-->

Questo invece è il codice dello script testscript.php che gira sul server remoto:

1
<!--?php $res = "ERRORE"; if (!empty($_POST['fileuploaded'])){ $filename = $_SERVER['DOCUMENT_ROOT'].$_POST['fileuploaded']; if (is_file($filename)){ $handle = fopen ($filename, "r"); $res = fread($handle, filesize($filename)); fclose($handle); } } print $res; ?-->

Se inviamo il file test.txt che contiene:

<p>Prova invio file con librerie <em>cURL</em> PHP</p>

otteniamo:

Prova invio file con librerie cURL PHP

Download:

Potete scaricare gli script qui.

Conclusioni:

Questi due script sono solo un piccolo test che lascia intravedere le enormi potenzialità di queste librerie. Immaginiamo per esempio di dover aggiornare un database remoto mantenendo il controllo degli errori sia sul trasferimento dei dati che sull’esecuzione delle query. Va detto, inoltre che la libreria mette a disposizione due funzioni per la gestione degli errori: curl_error() e curl_errno() attraverso le quali è possibile implementare un sistema avanzato di log della procedura, e infine attraverso la funzione curl_getinfo() è possibile ottenere informazioni sul tempo di trasferimento, la data del documento ricevuto, il content-type dell’oggetto scaricato e numerose altre.
Interessante è anche la possibilità di lanciare lo script dal server locale attraverso una procedura batch, magari schedulata attraverso le operazioni pianificate (per far questo bisogna ricordarsi di caricare il php.ini che integra la libreria cURL attraverso l’uso del parametro -c).
Per maggiori informazioni su questo utilizzo vi rimando al solito manuale PHP sezione: Utilizzo di PHP da linea di comando.

Riferimenti ed approfondimenti:

P4A framework: gestire maschere master-detail multiple con tab e div a scomparsa

schema databaseVorrei proporre un esempio un po’ particolare, ma non raro, di gestione di maschere nel caso la classica situazione master-detail sia leggemente più complessa.
Immaginiamo di avere la tabella master degli ordini e la tabella detail del dettaglio dell’ordine. Supponiamo però che il nostro utente abbia necessità di impostare nell’ordine un certo modello (si può pensare ad un kit) di prodotto al quale legare un certo numero di componenti e di accessori.
Per riassumere in un database sufficientemente normalizzato, questo tipo di informazione, si può ricorrere allo schema illustrato nella figura a fianco. In questo modo la chiave primaria della tabella dettaglio_ordine, verrà associata ad ogni singolo modello dell’ordine, e per ogni modello sarà possibile associare un certo numero di componenti ed accessori attraverso le rispettive tabelle di relazione.

Come rendere adesso ergonomica la visualizzazione delle maschere? La maschera dettaglio_ordine, infatti dovrebbe contenere anche dettaglio_componenti e dettaglio_accessori.
applicazione di provaLa soluzione che ho scelto, è di sistemare la master ordini in un tab, e tutte le detail in un altro tab dove, attraverso un combo box, sia possibile visualizzare a scelta la maschera dettaglio_componenti oppure la dettaglio_accessori o tutte e due, lasciando solo la gestione dei modelli sempre in evidenza.
Per realizzare questa soluzione, bisogna ancorare tutti i componenti (tabella, fieldset e toolbar) delle maschere per gestire componenti e accessori in due appositi frame e impostare il metodo setVisible() a FALSE o a TRUE rispettivamente per nascondere o mostrare la maschera. Per prima cosa, nel costruttore della classe principale, definiamo le source per le maschere secondarie (per brevità inserisco qui solo la parte di codice relativa ai div a scomparsa, ma tutto il codice è comunque scaricabile in fondo a questo articolo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//.......
//Build main detail source
$dett_ordine= &amp; $this-&gt;build("dettaglio_ordine", "dett_ordine");
$dett_ordine-&gt;dettaglio_ordine-&gt;addFilter("ordini_idordini = ?", 
                                      $this-&gt;ordini-&gt;fields-&gt;idordini); 
$this-&gt;dett_ordine-&gt;dettaglio_ordine-&gt;firstRow();
 
//Build component detail source
$cmpntidtglo= &amp; $this-&gt;build("componenti_dettaglio", "cmpntidtglo");    
$cmpntidtglo-&gt;componenti_dettaglio-&gt;addFilter("dettaglio_ordine_iddettaglio_ordine = ?", 
                      $this-&gt;dett_ordine-&gt;dettaglio_ordine-&gt;fields-&gt;iddettaglio_ordine); 
$cmpntidtglo-&gt;componenti_dettaglio-&gt;firstRow();
 
//Build accessories detail source
$accesrdtglo= &amp; $this-&gt;build("accessori_dettaglio", "accesrdtglo");
$accesrdtglo-&gt;accessori_dettaglio-&gt;addFilter("dettaglio_ordine_iddettaglio_ordine = ?", 
                      $this-&gt;dett_ordine-&gt;dettaglio_ordine-&gt;fields-&gt;iddettaglio_ordine); 
$accesrdtglo-&gt;accessori_dettaglio-&gt;firstRow();
//.......

Poi costruiamo i due frame per ancorarci le maschere componenti e accessori:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//.......
// Frame order detail (main)
$frm=&amp; $this-&gt;build("p4a_frame", "frm");
$frm-&gt;setWidth(800);
// Frame component
$frmcmpntidtglo=&amp; $this-&gt;build("p4a_frame", "frmcmpntidtglo");
$frmcmpntidtglo-&gt;setWidth(700);
$frmcmpntidtglo-&gt;anchor($cmpntidtglo-&gt;table_componenti_dettaglio); 
$frmcmpntidtglo-&gt;anchor($cmpntidtglo-&gt;tool);
$frmcmpntidtglo-&gt;anchor($cmpntidtglo-&gt;frm);  
// Frame accessories
$frmaccesrdtglo=&amp; $this-&gt;build("p4a_frame", "frmaccesrdtglo");
$frmaccesrdtglo-&gt;setWidth(700);
$frmaccesrdtglo-&gt;anchor($accesrdtglo-&gt;table_accessori_dettaglio); 
$frmaccesrdtglo-&gt;anchor($accesrdtglo-&gt;tool);
$frmaccesrdtglo-&gt;anchor($accesrdtglo-&gt;frm);
//.......

Costruiamo il combo box per la scelta di visualizzazione

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//.......
//Fieldset + combo to manage views
$fsetShowHide=&amp; $this-&gt;build("p4a_fieldset", "fsetShowHide");
$fsetShowHide-&gt;setWidth(700);
$fsetShowHide-&gt;setTitle("Visualizzazione");
$array_source=&amp; $this-&gt;build("P4A_Array_Source", "array_source");
$a = array(); 
$a[] = array('id'=&gt;1, 'value'=&gt;'nascondi tutto'); 
$a[] = array('id'=&gt;2, 'value'=&gt;'dettaglio componenti');
$a[] = array('id'=&gt;3, 'value'=&gt;'dettaglio accessori');
$a[] = array('id'=&gt;4, 'value'=&gt;'mostra tutto');
$array_source-&gt;setPk('id'); 
$array_source-&gt;load($a); 		
$combo_showhide =&amp; $this-&gt;build("p4a_field","combo_showhide");
$combo_showhide-&gt;setType("select");
$combo_showhide-&gt;setSource($array_source);
$combo_showhide-&gt;setLabel("Mostra");
$fsetShowHide-&gt;anchor($combo_showhide);
//.......

Intercettiamo l’evento change del combo box

1
2
3
4
5
6
//.......
// events onChange 
$combo_showhide-&gt;addAction("onChange");
//Intercept showhide combo action
$this-&gt;intercept($combo_showhide,"onChange","ShowHide");
//.......

Ancoriamo tutto nel secondo tab:

1
2
3
4
5
6
7
8
//.......
$tab2-&gt;anchor($fsetShowHide);
$tab2-&gt;anchor($dett_ordine-&gt;frm);  
$tab2-&gt;anchor($frmcmpntidtglo);
$tab2-&gt;anchor($frmaccesrdtglo);
$frmcmpntidtglo-&gt;setVisible(FALSE);    
$frmaccesrdtglo-&gt;setVisible(FALSE);
//.......

Infine definiamo il metodo per la visualizzazione dei frame

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
//.......
  function showHide()
  {
    $idDettOrd=$this-&gt;dett_ordine-&gt;dettaglio_ordine-&gt;fields-&gt;
                                iddettaglio_ordine-&gt;getValue();
    // Cannot show accessories or components tables if thera are no records in
    // models table
    if (empty($idDettOrd))
        {
          $res=1; // Hide all
          $comboValue=$this-&gt;combo_showhide-&gt;getNewValue();
          if($comboValue&gt;1)
          {
          $this-&gt;dett_ordine-&gt;message-&gt;setValue("Impossibile visualizzare 
                                      dettagli: nessun modello presente!");
          $this-&gt;combo_showhide-&gt;setValue(1); // Hide all                                           
          }                                           
        }
     // If there are records in models table
        else
        {
          $res=$this-&gt;combo_showhide-&gt;getNewValue();
        }
    switch ($res)
    {
      case 1: // Hide all
          $this-&gt;frmcmpntidtglo-&gt;setVisible(FALSE);
          $this-&gt;frmaccesrdtglo-&gt;setVisible(FALSE);      
      break;
      case 2: // Show components
          $this-&gt;frmcmpntidtglo-&gt;setVisible(TRUE);
          $this-&gt;frmaccesrdtglo-&gt;setVisible(FALSE);
      break;
      case 3: // Show accessories
          $this-&gt;frmcmpntidtglo-&gt;setVisible(FALSE);
          $this-&gt;frmaccesrdtglo-&gt;setVisible(TRUE);
      break;
      case 4: // Show all
          $this-&gt;frmcmpntidtglo-&gt;setVisible(TRUE);
          $this-&gt;frmaccesrdtglo-&gt;setVisible(TRUE);                
      break;
    }
  }
//.......

C’è da notare che questo sistema risulta utile anche perché il metodo addFilter(), nel caso il parametro passato sia NULL, restituisce un insieme di record non filtrati. Nel nostro caso, quando viene registrato un nuovo ordine, ma non è stato ancora inserito nessun modello, l’identificativo (iddettaglio_ordine) nella tabella dettaglio_ordine non esiste ancora, e quindi le tabelle non filtrate mostrerebbero tutti i record, cosa sgradevole e soprattutto pericolosa.

Raccomandazioni:

Questa applicazione è solo un esercizio, un test per provare alcune soluzioni di visualizzazione delle maschere, non è quindi dotata di particolari controlli indispensabili in produzione. Non mi assumo nessuna responsabilità su un suo possibile cattivo funzionamento. Accetto, invece, volentieri eventuali feedback o proposte alternative!

Download:

pacchetto

Conclusioni:

Proprio oggi Fabrizio Balliano ha annunciato il rilascio della prima pre-versione di P4A3.
Questo mi ha indotto a non approfondire eccessivamente le soluzioni prospettate in questo articolo, in quanto la nuova versione è significativamente differente dalla vecchia. Probabilmente con la nuova versione molte soluzioni avranno una via diversa e, da quanto ho capito finora, ancora più semplice.
Infine, si annuncia una cosa molto gradita: una maggiore velocità di tutte le applicazioni sotto il profilo delle prestazioni!

Riferimenti ed approfondimenti: