Yary Ribero Matricola 106145 Monografia SVILUPPO DI
Transcript
Yary Ribero Matricola 106145 Monografia SVILUPPO DI
Yary Ribero Matricola 106145 Monografia SVILUPPO DI UN FRAMEWORK PER APPLICAZIONI WEB ORIENTED Referente Accademico: prof. Maurizio Morisio Referente Aziendale: Davide Bonino Politecnico di Torino Facoltà di Ingegneria dell’Informazione Corso di Laurea in Ingegneria Informatica Anno Accademico 2006-2007 Indice Indice.................................................................................................................................. 2 Sommario........................................................................................................................... 3 1 Introduzione ................................................................................................................... 4 1.1 Criteri attuali nello sviluppo di applicazioni web .................................................... 4 1.2 Gli strumenti di lavoro.............................................................................................. 4 2 L’organizzazione del lavoro di squadra....................................................................... 6 2.1 Il server Subversion ed il client TortoiseSVN ........................................................... 6 2.2 Configurazione di Subversion e TortoiseSVN........................................................... 7 2.3 Funzionamento di base di TortoiseSVN.................................................................. 10 2.4 Gestione dei conflitti tra versioni ........................................................................... 10 3 Il lavoro di sviluppo ..................................................................................................... 12 3.1 I componenti basilari del framework “Vergilius”.................................................. 12 3.2 Il Kernel .................................................................................................................. 13 3.2.1 Le variabili strutturali e la costruzione dinamica del kernel .......................... 14 3.2.2 La gestione centralizzata degli errori.............................................................. 15 3.2.3 La gestione degli eventi ................................................................................... 16 3.3 I plugin .................................................................................................................... 18 3.3.1 La gestione delle proprietà .............................................................................. 20 3.3.2 Caricamento dei dati ed inizializzazione ......................................................... 21 3.3.3 I files relativi alla lingua.................................................................................. 22 3.4 I modelli .................................................................................................................. 22 3.5 La configurazione mediante un file in formato XML.............................................. 24 4 Listati da cui sono stati tratti gli esempi di codice .................................................... 25 4.1 Svnserve.conf .......................................................................................................... 25 4.2 Authz ....................................................................................................................... 25 4.3 Passwd .................................................................................................................... 25 4.4 Kernel.inc................................................................................................................ 25 4.5 Plugin_session.inc .................................................................................................. 27 4.6 Session_ita.xml........................................................................................................ 30 4.7 Html.inc................................................................................................................... 30 4.8 Config.xml............................................................................................................... 31 5 La struttura del Framework Vergilius (di Davide Bonino) ..................................... 32 5.1 Introduzione ............................................................................................................ 32 5.2 Il progetto................................................................................................................ 32 5.3 La realizzazione pratica.......................................................................................... 33 5.4 La situazione attuale ............................................................................................... 35 5.5 Cosa manca ancora ................................................................................................ 36 5.6 Cosa ci serve ........................................................................................................... 37 5.7 Dove vogliamo arrivare.......................................................................................... 37 Conclusioni ...................................................................................................................... 39 Ringraziamenti ................................................................................................................ 40 Indice delle figure............................................................................................................ 40 Siti Internet...................................................................................................................... 40 Sommario Lo scopo di questa monografia è spiegare e documentare la mia esperienza presso l’A.S.L. n. 15 di Cuneo, durante la quale ho potuto osservare le tecniche, gli strumenti e l’organizzazione del lavoro necessarie ad affrontare il problema dell’accorpamento di tre aziende sanitarie provinciali in una sola con tutte le conseguenze che un’operazione di questo tipo comporta. Da alcuni anni l’esiguità degli investimenti a fronte della crescita di richiesta dei servizi ha spinto il settore informatico a cercare soluzioni economiche, efficienti, riadattabili e di facile utilizzo per il personale non specializzato, soluzioni che hanno sortito l’effetto sperato cioè di accrescere i servizi senza aumentare la spesa attraverso l’ottimizzazione. Da queste esigenze è nato il progetto proposto dal mio referente aziendale, Davide Bonino, che ha lo scopo di fornire i servizi necessari attraverso strumenti già in dotazione all’azienda oppure attraverso i migliori strumenti open source attualmente disponibili, di cui avrò occasione di parlare. 1 Introduzione 1.1 Criteri attuali nello sviluppo di applicazioni web La buona organizzazione di un’impresa attualmente dipende moltissimo dalla qualità del suo sistema informatico, la cui misura dipende da diverse caratteristiche, come la scalabilità, l’efficienza, o la chiarezza della sua struttura. D’altro canto però altri criteri influenzano, in maniera anche più pressante della qualità, le scelte alla base di un sistema informatico: il criterio di costo e la capacità di rimpiazzare pezzi e personale senza che il cambiamento crei particolari problemi. Per poter soddisfare tutte quante queste necessità, è necessario disporre di strumenti estremamente flessibili, facili da usare, strutturati per natura, efficienti e ricchi di servizi integrati che possono essere esclusi o inclusi a seconda delle necessità. 1.2 Gli strumenti di lavoro Le applicazioni web oriented più semplici devono interfacciarsi con un database, elaborare dati e fornirli agli utenti mediante interfacce grafiche. In molti casi sono richiesti dei servizi più sofisticati: stampa, traduzione delle informazioni in documenti ufficiali e via dicendo. In quasi tutti i casi un programmatore non ha bisogno di spendere troppo tempo per imparare ad usare questi strumenti, ma può sfruttare con facilità le API con cui questi si interfacciano al suo ambiente di sviluppo ignorando così le loro problematiche e la loro complessità. Partendo da questi presupposti il mio referente aziendale ha deciso di utilizzare WAMP server come ambiente di sviluppo per le sue applicazioni, dal momento che include il server http Apache, un buon interprete PHP (linguaggio interpretato lato server con ottime prestazioni) ed il DataBase Manager MySQL. Inoltre il linguaggio PHP è open source, ed avuto un successo tale che ormai la comunità di sviluppatori che se ne servono (e che mettono il loro software gratuitamente a disposizione degli altri) è estesa al punto che è facile trovare software già scritto adattabile alle proprie esigenze. Le caratteristiche naturali del linguaggio (orientato agli oggetti, facile da imparare ed efficiente), la grande quantità di servizi forniti di base dall’ambiente, la facile reperibilità delle interfacce a servizi meno comuni, la semplicità di implementazione delle interfacce grafiche in HTML costituiscono i pregi di WAMP e rispondono adeguatamente alle necessità di cui ho parlato nei capitoli precedenti. Un grande aiuto nella creazione della struttura del framework è arrivato dallo standard XML: un linguaggio di descrizione dei documenti naturalmente strutturato ad albero. Due aspetti hanno influenzato questa scelta: l’interfaccia tra XML e php, e la disponibilità di programmi di convalida. In php sono disponibili alcune funzioni in grado di caricare ed elaborare un file XML: è possibile reperire la documentazione sul sito della zend nel capito “SimpleXML functions”. La convalida di un documento è molto utile in quanto è possibile imporre alcune regole come la presenza di alcune informazioni oppure generare degli avvertimenti nel caso manchino informazioni non strettamente necessarie ma di solito presenti. Nel nostro caso abbiamo usato XML solo per la sua struttura mentre l’aspetto della convalida l’abbiamo rimandato allo sviluppo futuro. In conclusione, questo progetto trae beneficio dalla disponibilità di un gran numero di strumenti e software, che in alcuni casi abbiamo integro all’interno del framework, provenienti dal mondo dell’open source e, proprio per questo è esso stesso disponibile ad altri utenti nella stessa modalità. Un mondo, quello dell’open source, a cui occorre guardare sia per la diffusione della conoscenza che per il contenimento dei costi, e che non ha niente da invidiare alla controparte proprietaria. 2 L’organizzazione del lavoro di squadra Il referente mi ha chiesto di sviluppare intere parti del progetto per lui, insieme a lui o anche in parallelo, così che siamo incorsi nella necessità di coordinare il lavoro con mezzi più efficaci dello scambio di file di codice e della comunicazione verbale (che resta in ogni caso fondamentale). Lo strumento che abbiamo scelto è il server Subversion, il quale, mediante il protocollo svn, mette a disposizione degli strumenti di controllo di versione utili a sviluppatori che lavorano allo stesso progetto o anche alla stessa parte di progetto contemporaneamente. 2.1 Il server Subversion ed il client TortoiseSVN Subversion è un software con molte risorse per l’organizzazione del lavoro di squadra, che permette di sfruttare con facilità i servizi di base. L’idea di base è che esiste una versione di partenza che tutti i partecipanti usano come riferimento; questa versione è l’unica valida e deve rimanere sempre in uno stato valido. È una buona idea lasciare la versione di riferimento su un computer remoto accessibile soltanto al responsabile del progetto oppure sul computer del responsabile stesso. Subversion può comunicare con il mondo esterno attraverso un server apache (se presente sulla macchina) e quindi utilizzare il protocollo http, oppure utilizzando l’applicativo svnserve, fornito dall’installazione, con il quale si può cominciare mediante il protocollo svn. Nel nostro caso, pur disponendo di un server http, abbiamo utilizzato il protocollo svn, per provarne il funzionamento, e non abbiamo incontrato problemi. Per quanto sia possibile utilizzare i servizi offerti da Subversion attraverso righe di comando, risulta molto più comoda un’interfaccia grafica come quella che fornisce TortoiseSVN. TortoiseSVN è uno strumento che si integra con l’applicativo Explorer, al quale aggiunge delle funzioni come la possibilità di estrarre dati condivisi in remoto, di aggiornarli, di confrontarli e così via. Una volta installato, è possibile accorgersi del cambiamento facendo click con il destro del mouse su un file qualsiasi: si noterà che al menù sono state aggiunte due voci relative a TortoiseSVN. 2.2 Configurazione di Subversion e TortoiseSVN Io ed il mio referente lavoravamo nello stesso ufficio per cui, per quanto il lavoro dovesse essere suddiviso logicamente tra utenti remoti, potevamo risolvere i conflitti tra le versioni personalmente, senza dover ricorrere a tutte le possibilità offerte da Subversion. La scelta della macchina di riferimento è caduta naturalmente sul desktop del referente, su cui abbiamo creato il repository del progetto ed abbiamo lanciato il server svn in modalità demone. Il repository è una cartella in cui si trovano tutte le informazioni di configurazione relative ad un progetto, quali gli utenti abilitati, le loro password, la suddivisione in gruppi e via dicendo. Per creare il repository con TortoiseSVN basta scegliere una cartella vuota, fare click con il destro del mouse e selezionare l’opzione “Create repository here” dal menù TortoiseSVN; successivamente l’applicativo richiede il file system utilizzare ed è bene specificare quello nativo della macchina perché il repository sia utilizzabile da remoto. Posto che il repository si trovi nella cartella : C:\repository È possibile raggiungerlo da locale con il percorso, oppure all’indirizzo: svn://localhost/repository Mentre per accedervi da remoto basta sostituire a “localhost” l’indirizzo della macchina o, molto meglio, il nome della macchina. È fortemente sconsigliato utilizzare direttamente l’indirizzo IP nel caso non sia fisso, perché altrimenti sarebbe necessario configurare nuovamente il client. Creato il repository, abbiamo selezionato dai files di configurazione il file conf/svnserve.conf contenuti nella cartella repository ed abbiamo rimosso il simbolo di commento (#) dalle linee che ci interessavano: anon-access = read auth-access = write In questo modo abbiamo dato il permesso agli utenti anonimi di leggere ed agli utenti autorizzati di leggere e modificare (in questo caso il possesso di un diritto garantisce tutti i diritti di livello inferiore). Un’altra riga di grande interesse è quella relativa al file dove saranno immagazzinate le autorizzazioni: authz-db = authz Così abbiamo informato il server che, se dovesse essere contattato per il repository in questione (in realtà può gestirne più di uno) può gestire gli utenti in base alle autorizzazioni specificate nel file conf/authz. Il file authz può essere anche molto complicato perché con esso è possibile suddividere gli utenti in gruppi, fare in modo che utenti possano appartenere a gruppi diversi con autorizzazioni diverse, e specificare permessi particolari per aree specifiche di un progetto. Tra le linee che abbiamo inserito cito: [/] *= yary = rw Con queste specifiche vietiamo a tutti gli utenti (contrassegnati dal carattere *) di accedere alla root del repository, in seguito assegniamo all’utente yary il diritto di lettura e scrittura. È importante osservare che le linee relative ai permessi vengono interpretate una ad una, per cui, se scambiate di posizione, possono essere interpretate in maniera diversa: nel nostro caso, revocando i privilegi a tutti gli utenti dopo averli assegnati all’utente yary, li revocheremmo anche a yary. Nonostante la complessità di organizzazione che è possibile ottenere, la configurazione del file authz è intuitiva e non presenta particolari difficoltà. Nel nostro caso non è stato necessario sfruttare tutte le possibilità, non abbiamo dovuto far altro che inserire i nostri nomi tra gli utenti autorizzati e fornirci l’autorizzazione di massimo livello (write) per tutto il progetto. Tornando al file svnserve.conf, un’altra riga molto importante è quella relativa al database delle password: password-db = passwd Il file passwd contiene le coppie utente = password, con cui l’applicativo svnserve.exe convaliderà l’autenticazione. Dopo aver modificato opportunamente i files di configurazione, abbiamo creato un file batch (svnserve.bat) contenente questa riga: C:\Subversion\bin\svnserve -d la quale specifica, mediante il parametro “-d”, che il server si deve comportare come un demone. Infine abbiamo spostato il file batch nella cartella “esecuzione automatica” della macchina di riferimento, ottenendo il servizio pronto ad ogni avvio, senza doverci più pensare. Completata la fase di configurazione del server abbiamo aggiunto la cartella del progetto seguendo la procedura standard del manuale di TortoiseSVN: basta fare click con il tasto destro del mouse sulla cartella originaria del progetto, puntare “TortoiseSVN” dal menù e fare click con il sinistro su “import”. A questo punto viene richiesto l’indirizzo del repository, che può essere espresso come un percorso nel file system: file:///C:\repository Il nome di una macchina nella rete locale: svn://localhost/repository Un indirizzo remoto: svn://41.1.14.90/repository A questo punto il progetto è raggiungibile dal mondo esterno e protetto da un sistema di autenticazione e verifica dei privilegi. Come ultimo passo abbiamo scaricato una copia del progetto su ogni macchina: per questa operazione è necessario installare TortoiseSVN poi bisogna scegliere una cartella vuota, fare click con il destro del mouse su di essa, selezionare la voce “SVN checkout …” e fornire l’indirizzo remoto del repository. Se l’autenticazione va a buon fine ed i privilegi dell’utente sono sufficienti, il server invierà i dati che il client copierà nella cartella scelta, insieme alle informazioni necessarie a svolgere le operazioni di aggiornamento senza disturbare ulteriormente l’utente. Al termine dell’operazione la cartella di lavoro cambierà icona, così come ogni suo file, e le comparirà un segno di spunta che indica che la cartella è in linea con quanto presente nella cartella originale del progetto. 2.3 Funzionamento di base di TortoiseSVN Lavorare con TortoiseSVN è facile e comodo grazie agli strumenti integrati che permettono di trattare le cartelle remote quasi come se fossero locali. La difficoltà sta tutta nel configurare correttamente l’ambiente al primo avvio, dove è necessario conoscere la posizione del repository nella macchina remota e ricordare di non fornire l’indirizzo IP ma quello logico, nel caso il primo sia variabile. Se la fase di autenticazione va a buon fine (consiglio di scegliere l’opzione “save authentication”) e la configurazione è stata fatta correttamente, non ci sarà più bisogno di preoccuparsi del livello di rete e sarà possibile cominciare a lavorare. Le operazioni fondamentali di TortoiseSVN sono la “update”, con cui si richiede di sincronizzare la propria versione con quella sul server, e la “commit”, con cui si inviano le modifiche effettuate nella copia locale al server. Per effettuare queste operazioni basta fare click con il destro del mouse sulla cartella locale del progetto e selezionare “SVN Update” o “SVN Commit...” ed aspettare che la sincronizzazione termini. Update e Commit possono essere effettuate anche su singoli files o cartelle nel progetto, in modo tale da alleggerire il lavoro del server. 2.4 Gestione dei conflitti tra versioni Una normale operazione di modifica che non crea problemi è costituita da una fase di update, una di modifica in locale ed infine una commit. Un conflitto tra versioni ha origine quando due utenti, partiti dalla stessa versione del progetto, inviano al server due copie diverse che dovrebbero avere la stessa numerazione. TortoiseSVN offre diverse opzioni per gestire questi casi, la prima delle quali è la prevenzione. Quando un utente vuole modificare una parte specifica del progetto, può mettere un lucchetto su di essa selezionando la voce “Get lock” del menù TortoiseSVN che viene visualizzato facendo click con il tasto destro del mouse sulla risorsa di cui vuole ottenere l’uso esclusivo. In questo modo utente impedisce a chiunque altro di modificare la risorsa bloccata fino a quando non selezionerà l’opzione “Release lock”. Lavorare in questo modo può essere utile, soprattutto nel caso in cui gli utenti suddividano il lavoro in compartimenti stagni; proprio per questo motivo TortoiseSVN mette a disposizione un altro strumento: “merge” con cui ottimizzare le modifiche a parti separate. Nel caso in cui due utenti inviino due versioni modificate, la funzione merge valuterà per prima cosa se le modifiche sono compatibili, poi provvederà automaticamente a fonderle insieme nella nuova versione. Nel caso di modifiche parallele non compatibili, cioè due utenti lavorano sulla stessa risorsa, è disponibile una funzione che permette ad un amministratore di scegliere quali parti lasciare e quali rimuovere, ma questo è un caso critico di difficile gestione. Capita a volte che per un problema si presentino soluzioni così diverse da condizionare il progetto a portarne avanti soltanto una per volta. Anche per questo caso esiste una funzione apposita: “branch” che permette di ramificare il progetto in maniera ordinata. Nel nostro caso non abbiamo avuto necessità di creare branche del progetto visto il nostro numero e soprattutto il fatto che lavoravamo nello stesso ufficio, quindi, per approfondire questo aspetto rimando alla guida sul sito indicato al fondo della monografia. 3 Il lavoro di sviluppo Il lavoro di sviluppo ha richiesto la mia collaborazione sia dal lato strutturale, dei componenti e delle rifiniture. Durante la progettazione strutturale ho contribuito alla realizzazione di tutti i controlli centralizzati, come la gestione degli errori, l’inizializzazione dei plug-in o la gestione della lingua. Per quanto riguarda i componenti, ho rinnovato i plug-in access, profile e session, in misura diversa. 3.1 I componenti basilari del framework “Vergilius” Il framework Vergilius ha un cuore, kernel.inc, che gestisce in maniera centralizzata tutte le parti di un applicativo. In una macchina si possono avere più istanze del framework per usi diversi senza dover duplicare il cuore: è sufficiente modificare il file di configurazione config.xml in maniera appropriata. Figura 1: Elementi basilari del framework “Vergilius” Per sfruttare in maniera ordinata gli elementi del framework, è opportuno che la cartella dove si trova l’applicativo web (la cui pagina principale si chiama, di solito, index.php), sia strutturato come nella figura 2. Figura 2: L'albero della cartella www 3.2 Il Kernel Il kernel, motore principale del framework, è una classe che esegue le funzioni di interfaccia ai vari plugin. Per servirsene è sufficiente creare un file (ad esempio index.php) il quale contenga una classe che estende cls_kernel. class mia_classe extends cls_kernel A questo punto è sufficiente creare un’istanza della classe, passando il nome del file di configurazione come parametro. Il file di configurazione è proprio di ogni singola istanza, sebbene sia possibile riciclare lo stesso per fornire di due servizi simili. Una riga di codice per creare un’istanza della classe cls_kernel può essere questa: $p = new mia_classe("C:/wamp/www/framework/config.xml"); Un possibile codice sorgente che sfrutta le potenzialità basilari del framework potrebbe essere quello nella pagina seguente. 1 <?php 2 require_once dirname(__FILE__) . "/lib/kernel.inc"; 3 class cl extends cls_kernel{ 4 function __construct ($file = ""){ 5 parent::__construct($file); 6 } 7} 8 $p = new cl("C:/wamp/www/framework/config.xml"); 9 $p->react_to_event(); 10 ?> La riga #1 e #10 informano il server che il codice contenuto al loro interno deve essere interpretato come codice php. La riga #2 costruisce il percorso assoluto di kernel.inc a partire dalla posizione del file che lo lancia. La riga #5 costruisce la classe utente allo stesso modo del kernel, richiamando il costruttore della classe padre. La riga #8 istanza la classe e la riga #9 lancia uno dei servizi opzionali, di cui parlerò in seguito. 3.2.1 Le variabili strutturali e la costruzione dinamica del kernel Come si vede da questo estratto del file kernel.inc è costituito da quattro variabili: l’albero relativo al registro di configurazione generale, il registro di configurazione dei plugin, il registro dei plugin, e l’array degli eventi. 4 5 6 7 var $sysreg; var $plugin_register=array(); var $plugin = array(); var $event = array(); Il registro di configurazione contenuto in $sysreg, riproduce la struttura ad albero del file config.xml, il quale viene caricato in memoria dalla istruzione alla riga #20. 18 19 20 if ($filename=="") $filename = "config.xml"; $this->sysreg=simplexml_load_file($filename); Il registro dei plugin viene generato a partire dal registro di configurazione in maniera tale da alleggerire la notazione al programmatore. Il registro dei plugin dipende dalle impostazioni di configurazione e dalle istanze dei plugin inserite dal programmatore del codice. Gli eventi sono i comandi inviati da utente con un form: il registro degli eventi contiene i nomi degli eventi a cui rispondere e le funzioni ad essi associate. 3.2.2 La gestione centralizzata degli errori La gestione centralizzata degli errori è stata resa molto semplice dagli sviluppatori del server che hanno messo a punto un sistema in grado di intercettare un errore e di tracciare tutta la sua storia. Per sfruttare questa caratteristica abbiamo creato una funzione che adatta l’output del backtrace in modo da essere visualizzato in maniera comprensibile sul browser che riceve la risposta: 9 function catch_error($errno, $errstr, $errfile, $errline){ 10 echo "<pre>" ; 11 debug_print_backtrace(); 12 echo "</pre>" ; 13 exit(-1); 14 } Quindi abbiamo impostato la nostra funzione come handler di tutti gli errori (E_ALL) all’interno del costruttore della classe: 17 set_error_handler ( array ("cls_kernel","catch_error"),E_ALL); L’output di un errore è un listato che contiene la lista delle funzioni coinvolte nell’errore, dalla funzione chiamata al primo chiamante, come si può vedere da questo esempio: #0 cls_kernel->catch_error(2, cls_kernel::require_once(c:\wamp\www\framework\lib/plugin/plugin_adodb.inc) [function.cls-kernelrequire-once]: failed to open stream: No such file or directory, C:\wamp\www\lib\kernel.inc, 93, Array ([path] => SimpleXMLElement Object ([0] => c:\wamp\www\framework\),[p] => SimpleXMLElement Object ([@attributes] => Array ([name] => adodb,[class] => adodb,[install] => FALSE),[default_db] => yary,[databases] => SimpleXMLElement Object ([main] => main;mysql;siacun03;framework;root;masterkey,[internet] => internet;mysql;siacun03;internet;root;masterkey,[fisiatria] => fisiatria;mysql;srv08;internet;root;masterkey,[igea] => igea;mysql;srvrad;asl15_pv;ced_pv;pv_ced,[pegaso] => pegaso;oci8;;SCEREV15.WORLD;cupsl;slcup,[srvsiasw] => srvsiasw;mssql;srvsiasw;ASL15;sa;daberoridabo,[yary] => yary;mysql;siabsd82;framework;root;masterkey)),[class] => adodb,[name] => adodb)) #1 cls_kernel::load_plugin() called at [C:\wamp\www\lib\kernel.inc:93] #2 cls_kernel->load_plugin() called at [C:\wamp\www\lib\model\html.inc:34] Il risultato, per quanto estremamente coinciso, è ricco di informazioni e sufficientemente leggibile: questa funzionalità del php ci ha aiutati molto in fase di sviluppo. 3.2.3 La gestione degli eventi La gestione degli eventi è una caratteristica opzionale che permette di elaborare un form inviato da utente come un comando che ha delle funzioni e dei dati associati ad esso. L’interprete php fornisce l’array associativo $_REQUEST, il quale contiene tutte le variabili del form html inviato da utente, indirizzabili attraverso un’etichetta, che è il nome della variabile. Per esempio, se un form contiene un tag html di questo tipo: <input type='hidden' name='command' id='command' value='inserisci'> un programmatore può accedere al valore di command in questo modo: $_REQUEST[“command”] Per servirsi degli eventi è necessario che nel form sia presente una variabile “command”, per poter discriminare il tipo di azione che l’utente richiede. Un altro requisito è aver creato una classe che estende il kernel ed aver dichiarato le funzioni che si intendono utilizzare per reagire agli eventi, per esempio con questo codice: class cl extends cls_kernel{ function __construct ($file = ""){ parent::__construct($file); } function show_profile($k){ if ($k->plugin["access"]->user->user_id!="") $k->body_detail = $k->plugin["profile"]->load_profile(); } function show_groups($k){ $k->body_detail=$k->plugin["profile"]->load_groups(); } } A questo punto bisogna associare le funzioni agli eventi: ogni evento può essere gestite da più funzioni, in ordine di inserimento, ed ogni funzione può gestire più eventi. $p->add_event("profile_info",array("cl","show_profile")); $p->add_event("login",array("cl","show_profile")); In questo modo la funzione add_event, molto semplice, può aggiungere il riferimento alla funzione all’array di funzioni, creando l’array se non esiste già. Da queste righe di codice si può capire la necessità di creare una nuova classe: infatti in php per passare una funzione come parametro bisogna in realtà passare un arrey che contiene il nome della ed il nome della funzione che si vuole chiamare. 62 63 64 65 66 function add_event ($command,$function){ if(!isset($this->event[$command])) $this->event[$command]=array(); $this->event[$command][]=$function; } Dopo aver dichiarato tutti gli handler, il programmatore deve inserire l’istruzione: $p->react_to_event(); Tenendo presente che è importante posizionare questa funzione nel punto giusto del programma: infatti, posizionando del codice prima, sarà eseguito prima dell’elaborazione dei dati dell’utente, cosa che potrebbe essere utile, oppure una svista che causa errori o bachi. La funzione react_to_event() permette diverse possibilità: è possibile associare del codice agli eventi speciali “before_any_event” (riga #70), le cui funzioni associate vengono eseguite prima di tutte le altre, “after_any_event” (riga #89), che serve nel caso opposto, e l’evento “no_event” (riga #75), che permette di gestire il caso in cui “command” non è stato valorizzato. 68 69 70 71 72 73 74 function react_to_event(){ if(isset( $this->event["before_any_event"])){ $function_list =$this->event["before_any_event"]; foreach ($function_list as $handler) call_user_func ($handler,$this); $this->event["before_any_event"]=null; } 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 } if (!isset($_REQUEST["command"])){ if (isset( $this->event["no_event"])){ $function_list = $this->event["no_event"]; foreach ($function_list as $handler) call_user_func ($handler,$this); $this->event["no_event"]=null; } } else if (isset( $this->event[ $_REQUEST["command"] ])){ $command = $_REQUEST["command"]; $function_list = $this->event[$command]; foreach ($function_list as $handler) call_user_func ($handler,$this); } if(isset( $this->event["after_any_event"])){ $function_list =$this->event["after_any_event"]; foreach ($function_list as $handler) call_user_func ($handler,$this); } return; } Sebbene gli eventi siano molto utili per rendere il codice più leggero e leggibile, li abbiamo usati con criterio durante la fase di sviluppo dei plugin, perché nella loro configurazione a runtime, soprattutto in casi delicati come la gestione di un log in, l’analisi dei dati utente ha priorità assoluta e non può attendere di essere effettuata alla fine. 3.3 I plugin I plugin, contenuti nella cartella ./lib/plugin, offrono un’interfaccia standard per il kernel verso un servizio. I plugin sono strutturati in modo da funzionare senza che il kernel debba essere aggiornato dopo l’aggiunta di un nuovo componente: il kernel non deve dipendere da nessuno di essi, e nemmeno conoscere la loro esistenza prima del caricamento. Esistono tuttavia dei plugin che vengono utilizzati praticamente sempre, come avviene nel nostro caso per adodb, e dei plugin la cui funzione è potenziare le funzioni di base, per esempio aiutando il debug ed il monitoraggio del kernel (è il caso del plugin monitor). Per capire meglio come è fatto un plugin, basta guardare l’albero del plugin profile in figura 3. Figura 3: L'albero del plugin profile Come si può osservare, il plugin si trova nell’apposita cartella e fa riferimento ad un’insieme di risorse contenute in una cartella privata. Il contenuto e la struttura di un plugin sono del tutto trasparenti a chi semplicemente utilizza i servizi che offre, mentre uno sviluppatore deve tenere presente che la struttura ha dei punti fissi: il plugin si deve trovare nella cartella ./lib/plugin e deve chiamarsi plugin_NOMESERVIZIO.inc, deve avere almeno un file di lingua inglese, che deve chiamarsi NOMESERVIZIO_eng.xml nella cartella NOMESERVIZIO, la quale deve anche contenere la documentazione. Tutti gli altri files necessari al funzionamento devo essere inseriti dentro la stessa cartella in maniera ordinata e strutturata, in modo tale da rendere comprensibile la struttura globale. Infine è obbligatorio inserire un file di documentazione per ogni plugin: per quanto non sia obbligatorio nessun tipo di formato, nel nostro caso ci siamo serviti di un software, phpDocumentor, il quale produce documentazione a partire dal codice sorgente arricchito di commenti in cui sono presenti speciali TAG, per la cui documentazione rimando al sito al fondo della monografia. 3.3.1 La gestione delle proprietà Le proprietà di un plugin sono inserite dentro un array particolare, $_properties: private $_properties = array ( //for compatibility with _get and _set "kernel"=>NULL, "message"=>NULL, "cookie_name"=>NULL, "session_id" => NULL ); Questo array è particolare per due motivi: il primo è che il programmatore non deve scrivere il percorso delle proprietà per intero, ma può servirsi di una stringa come questa per leggere la proprietà default_db da un’istanza del plugin adodb. $this->db_alias = $this->kernel->plugin["adodb"]->default_db; anziché dover usare la notazione più pesante: $this->db_alias = $this->kernel->plugin["adodb"]->_properties[“default_db”]; La seconda caratteristica importante è la possibilità di proteggere le proprietà dalla scrittura e dalla lettura diretta, infatti, quando un programmatore richiama una di esse con la notazione semplificata, viene richiamato un dei due metodi speciali __get e __set: 15 function __get ($propertyName){ 16 if (!array_key_exists($propertyName,$this->_properties)) 17 trigger_error($this->_properties["message"]->m1,E_USER_WARNING); 18 if (method_exists($this,"get" . $propertyName)) 19 return call_user_func (array($this,"get" . $propertyName)); 20 else 21 return $this->_properties[$propertyName]; 22 } 23 24 function __set($propertyName, $value){ 25 if (!array_key_exists($propertyName, $this->_properties)) 26 trigger_error($this->_properties["message"]->m1, E_USER_WARNING); 27 if (method_exists($this,"set" . $propertyName)) 28 call_user_func (array($this,"set" . $propertyName), $value); 29 else 30 $this->_properties[$propertyName] = $value; 31 } Il funzionamento di questi due metodi, che lavorano in maniera simmetrica uno in lettura ed un in scrittura, è davvero notevole: nel momento in cui un programmatore accede ad una proprietà dall’esterno (anche dall’interno, ma è un caso meno rilevante), prima di tutto il metodo verifica se esiste poi, in caso affermativo, cerca un metodo definito dall’utente dal nome getNomeDellaProprietà (oppure setNomeDellaProprietà) definito da utente, infine, se non lo trova, svolge il lavoro per il chiamante. 3.3.2 Caricamento dei dati ed inizializzazione Il caricamento dei dati di un nuovo plugin avviene all’interno della funzione init: function init($config, $kernel_pointer){ $this->kernel = $kernel_pointer; $path= $this->kernel->sysreg->kernel->path; $language = $this->kernel->sysreg->kernel->language; $file = $path . "lib/plugin/session/session_" . $language . ".xml"; if (file_exists($file)){ $this->message = simplexml_load_file ($path . "lib/plugin/session/session_" . $language . ".xml"); } else { $this->message = simplexml_load_file ($path . "lib/plugin/session/session_eng.xml"); } } La funzione init permette al kernel di conoscere il nuovo plugin installato e vice versa così grazie ad esso, riesce a comunicare con gli altri plugin installati in precedenza. Questa caratteristica è importantissima perché molti plugin dipendono da altri, come ad esempio capita al plugin session che dipende da adodb. In questo caso, chi configura il framework deve avere bene presente in mente questo problema, quindi la documentazione di ogni plugin deve essere molto chiara sui suoi requisiti. Come si può vedere dal codice, ogni plugin riceve due parametri, uno dei quali, appunto, è proprio il puntatore al kernel e può essere usato per accedere agli altri plugin. L’altro parametro, $config, è quella parte dell’albero ricavato dal file config.xml relativa al plugin interessato. Oltre al puntatore al kernel esiste un’altra proprietà obbligatoria: $message, che contiene l’insieme dei messaggi che verranno utilizzati nell’output. Il kernel riceve la lingua dal file di configurazione e cerca di instanziare tutti i plugin in quella lingua, tuttavia, se non è supportata, il plugin provvederà a caricare la versione inglese (che deve essere prensente obbligatoriamente). 3.3.3 I files relativi alla lingua I files relativi alla lingua che, come detto, si trovano nella cartella relativa al plugin cui si riferiscono, sono in formato XML. Abbiamo usato il formato XML anche in questo caso perché, grazie alla sua struttura ad albero, si presta molto bene alle nostre necessità, inoltre, dal momento che si sta imponendo come standard, esistono diverse funzioni in php che ne facilitano l’utilizzo e la validazione. La struttura interna dei files di lingua è semplice: 1 <?xml version="1.0" ?> 2 <document> 3 <m1>Hai richiesto una proprieta' che l'oggetto non possiede</m1> 4 <m2>Hai inserito come predefinito un database sconosciuto</m2> 5 <m3>Errore in creazione sessione</m3> 6 </document> Come si vede dal codice, all’interno dei TAG <document> e </document> vengono racchiusi i messaggi il cui nome è progressivo. Il nome di un file di lingua è univocamente definito dall’identificativo della lingua (LId) e dal nome del plugin (NOME) in questo modo: NOME_LId.xml Come per esempio si può osservare in sessio_ita.xml: è necessario che il nome sia esatto perché la init del plugin non è interessata al contenuto della cartella ma cercherà di caricare un file il cui nome viene costruito come specificato. 3.4 I modelli I modelli sono delle classi che estendo kernel allo scopo di offrire un particolare servizio finale. Come esempio porto il modello html, il quale, appunto, offre alcune funzioni che aiutano il programmatore a scrivere l’output dell’applicativo in formato leggibile dal browser, operazione che molto spesso da origine ad una gran massa di codice poco chiaro e difficilmente mantenibile. Creare ed utilizzare un modello è semplice, per la creazione è sufficiente realizzare un file, ad esempio html.inc, in cui è presente una classe che estende il kernel(che è necessario includere), come è possibile osservare nel listato 4.7: 2 require_once dirname(__FILE__) . "/../kernel.inc"; 3 4 class cls_html extends cls_kernel{ Come si può vedere dal codice, questo modello è in grado di reperire kernel.inc perché sa di essere in una sottocartella, la cartella model; sarebbe possibile salvare il modello altrove, tuttavia la cartella model è stata creata apposta a questo scopo. L’utilità del modello html incluso nel framework è la sua struttura, infatti, grazie alle variabili riproduce esattamente il modello di una pagina html: 5 6 7 8 9 10 11 12 var $url; var $lib; var $action; var $page; var $header; var $body; var $footer; var $title; // url del sito // url del framework // handler di default dei form // buffer di pagina // intestazione della pagina // corpo della pagina // fine della pagina // titolo pagina Un’altra caratteristica interessante dei modelli è la possibilità di configurarli nel file config.xml: 23 24 25 $this->lib = $this->sysreg->model->html->lib; $this->url = $this->sysreg->model->html->url; $this->action = $this->sysreg->model->html->action; Tuttavia, il caricamento dei dati relativi ai modelli non viene fatto dal kernel allo stato attuale delle cose, ma prevediamo di sviluppare questo servizio in futuro. Tornando al modello html, esso slega tra loro le parti principali che costituiscono la pagina, legandole assieme solo alla fine, quando il programmatore lo richiede: 30 function execute(){ 31 parent::execute(); 32 $this->page = $this->header . $this->body . $this->footer; 33 return TRUE; Questo permette di poter sviluppare le parti della pagina separatamente, così da poter aggiungere degli elementi nell’header dopo aver cominciato a lavorare al corpo della pagina, se se ne presenta la necessità: 36 37 38 function add_css($url){ $this->header .= "<link rel='stylesheet' type='text/css' href='" . $url . "'>"; } Questa funzione, per esempio, è in grado di includere un foglio di stile anche dopo aver cominciato a lavorare al body. 3.5 La configurazione mediante un file in formato XML Il file di configurazione è composto di tre parti che servono a configurare rispettivamente il kernel, i modelli ed il plugin. Un file di configurazione definisce un’istanza del framework: due applicazioni che utilizzano lo stesso file di configurazione di solito svolgono lo un lavoro simile, pertanto, è consigliabile usare un file di configurazione diverso per ogni istanza. Tutto il codice descrittivo deve essere racchiuso entro i TAG <doc> e </doc> come si può osservare nel config.xml d’esempio nel listato 4.8. La parte di kernel è la più semplice: contiene soltanto tre attributi che definiscono il percorso assoluto del framework, l’esecuzione in modalità debug. La parte che riguarda i modelli non è ancora completa: allo stato attuale i modelli non dispongono ancora di una funzione standard di accesso per estrarrei dati di configurazione, per cui l’unico vincolo è chiudere il codice descrittivo entro due TAG che corrispondono al nome del modello (<NomeModello> e </NomeModello>), il formato degli attributi può essere ricavato dalla documentazione del modello. La parte relativa ai plugin passa attraverso un’interfaccia standard (la add_plugin() del kernel e la init() del plugin) per cui ha una forma definita: ogni istanza di plugin deve essere racchiusa entro i TAG <component> e </component> in cui sono definiti i parametri relativi al nome del plugin, class, al nome dell’istanza, name, e all’installazione, install. Il parametro install è importante perché definisce se il plugin debba essere instanziato subito oppure a runtime, una selta che può incidere dal punto di vista delle prestazioni. 4 Listati da cui sono stati tratti gli esempi di codice 4.1 Svnserve.conf 1 [general] 2 anon-access = read 3 auth-access = write 4 password-db = passwd 5 authz-db = authz 6 realm = My First Repository 4.2 Authz 1 [groups] 2 workgroup = yary,davide 3 4 [/] 5*=r 6 yary = rw 7 davide = rw 8 9 [/repository] 10 davide = rw 4.3 Passwd 1 [users] 2 3 yary = politecnico 4 davide = dabon 4.4 Kernel.inc 1 <?php 2 3 class cls_kernel{ 4 var $sysreg; 5 var $plugin_register=array(); 6 var $plugin = array(); 7 var $event = array(); 8 9 function catch_error($errno, $errstr, $errfile, $errline){ 10 echo "<pre>" ; 11 debug_print_backtrace(); 12 echo "</pre>" ; 13 exit(-1); 14 } 15 16 function __construct($filename = ""){ 17 set_error_handler ( array ("cls_kernel","catch_error"),E_ALL); 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 63 64 65 66 67 68 69 70 71 72 73 if ($filename=="") $filename = "config.xml"; $this->sysreg=simplexml_load_file($filename); } function load_plugin(){ $path = $this->sysreg->kernel->path; foreach($this->sysreg->plugin->component as $p){ $class = (string)$p["class"]; $name = (string)$p["name"]; $this->plugin_register[ $name ] = $p; require_once $path . "lib/plugin/plugin_" . $class . ".inc"; if ((isset($p["install"]))&&((string)$p["install"]=="TRUE")) $this->add_plugin($name); } } function is_plugin($name){ return array_key_exists($name, $this->plugin); } function add_plugin($model,$name=""){ if ($name=="") $name=$model; if (isset($this->plugin_register[$model])){ $register = $this->plugin_register[$model]; $class = (string)$register["class"]; } else{ $path = $this->sysreg->kernel->path; $register=""; $class=$name; require_once $path . "lib/plugin/plugin_" . $class . ".inc"; } if ($this->is_plugin($name) == FALSE){ eval ("\$this->plugin[\$name] = new cls_plugin_" . $class . "();"); $this->plugin[$name]->init($register,$this); return TRUE; } else{ return FALSE; } } function add_event ($command,$function){ if(!isset($this->event[$command])) $this->event[$command]=array(); $this->event[$command][]=$function; } function react_to_event(){ if(isset( $this->event["before_any_event"])){ $function_list =$this->event["before_any_event"]; foreach ($function_list as $handler) call_user_func ($handler,$this); $this->event["before_any_event"]=null; 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 } 97 ?> } if (!isset($_REQUEST["command"])){ if (isset( $this->event["no_event"])){ $function_list = $this->event["no_event"]; foreach ($function_list as $handler) call_user_func ($handler,$this); $this->event["no_event"]=null; } } else if (isset( $this->event[ $_REQUEST["command"] ])){ $command = $_REQUEST["command"]; $function_list = $this->event[$command]; foreach ($function_list as $handler) call_user_func ($handler,$this); } if(isset( $this->event["after_any_event"])){ $function_list =$this->event["after_any_event"]; foreach ($function_list as $handler) call_user_func ($handler,$this); } return; } 4.5 Plugin_session.inc 1 <?php 2 class cls_plugin_session{ 3 private $_properties = array ( //for compatibility with _get and _set 4 "kernel"=>NULL, // puntatore alla classe padre 5 "cookie_name"=>NULL, // nome del cookie 6 "session_id" => NULL, // l'id di sessione 7 "session_time" =>NULL, // la durata della sessione 8 "session_gc_time"=>NULL, // il tempo per la garbage collection 9 "session_vars"=>NULL, // variabili di sessione 10 "db_alias" => null, 11 "message"=>NULL, 12 "status"=>NULL 13 ); 14 15 function __get ($propertyName){ 16 if (!array_key_exists($propertyName,$this->_properties)) 17 trigger_error($this->_properties["message"]->m1,E_USER_WARNING); 18 if (method_exists($this,"get" . $propertyName)) 19 return call_user_func (array($this,"get" . $propertyName)); 20 else 21 return $this->_properties[$propertyName]; 22 } 23 24 function __set($propertyName, $value){ 25 if (!array_key_exists($propertyName, $this->_properties)) 26 trigger_error($this->_properties["message"]->m1, E_USER_WARNING); 27 if (method_exists($this,"set" . $propertyName)) 28 call_user_func (array($this,"set" . $propertyName), $value); 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 else $this->_properties[$propertyName] = $value; } function __construct(){ } function init($config, $kernel_pointer){ $this->kernel = $kernel_pointer; $this->db_alias = (string)$config->db_alias; if($this->db_alias == "") $this->db_alias = $this->kernel->plugin["adodb"]->default_db; $this->cookie_name = (string)$kernel_pointer->plugin_register["session"]->cookie_name; $this->session_time = (string)$kernel_pointer->plugin_register["session"]->session_time; $this->session_gc_time=(string)$kernel_pointer->plugin_register["session"]->session_gc_time; $path= $this->kernel->sysreg->kernel->path; $language = $this->kernel->sysreg->kernel->language; $file = $path . "lib/plugin/session/session_" . $language . ".xml"; if (file_exists($file)){ $this->message=simplexml_load_file($path ."lib/plugin/session/session_". $language. ".xml"); } else { $this->message = simplexml_load_file ($path . "lib/plugin/session/session_eng.xml"); } $this->create(); $this->status=$this->read(); } function create(){ if (!isset($_COOKIE[$this->cookie_name])) $this->session_id=md5(uniqid(microtime())) else $this->session_id=$_COOKIE[$this->cookie_name]; $cookie_expire = ($this->session_time > 0) ? (time() + $this->session_time) : 0; if(!isset($_COOKIE[$this->cookie_name])){ setcookie($this->cookie_name, $this->session_id, $cookie_expire,"/"); $query="INSERT INTO tbl_sessions VALUES('" . $this->session_id . "', '', " . time() . ")"; $db = $this->kernel->plugin["adodb"]->connect($this->db_alias); if ($db->Execute($query) === FALSE){ trigger_error($this->message->m3,E_USER_WARNING); $return = FALSE; } else $return = TRUE; } else{ if($this->session_time > 0){ setcookie($this->cookie_name, $this->session_id, $cookie_expire); $return = TRUE; } else{ $return = FALSE; } } $this->clear(); return $return; 85 } 86 87 function write($name, $value){ 88 $_MY_SESSION = array(); 89 $query = "SELECT session_vars FROM tbl_sessions WHERE sessid = '" . $this->session_id . "'"; 90 $db = $this->kernel->plugin["adodb"]->connect($this->db_alias); 91 $result = $db->Execute($query); 92 if(!$result->EOF){ 93 $_session = unserialize($result->fields['session_vars']); 94 $_session[$name] = $value; 95 $query = "UPDATE tbl_sessions SET session_vars='" . serialize($_session) 96 $query .= "'WHERE sessid='" . $this->session_id . "'"; 97 $result = $db->Execute($query); 98 $return = TRUE; 99 } 100 else{ 101 $return = FALSE; 102 } 103 $result->close(); 104 return $return; 105 } 106 107 function read($key = ''){ 108 $query = "SELECT * FROM tbl_sessions WHERE sessid = '" . $this->session_id . "'"; 109 $db = $this->kernel->plugin["adodb"]->connect($this->db_alias); 110 $result = $db->Execute($query); 111 if(!$result->EOF){ 112 $this->session_vars = unserialize($result->fields['session_vars']); 113 $result->close(); 114 if($key <> "") 115 $return = (isset($key) && $key) ? $this->session_vars[$key] : $session_vars; 116 else 117 $return = TRUE; 118 } 119 else{ 120 $return = FALSE; 121 } 122 return $return; 123 } 124 125 function destroy(){ 126 $query="UPDATE tbl_sessions SET session_vars = '' WHERE sessid = '" . $this->session_id . "'"; 127 $db = $this->kernel->plugin["adodb"]->connect($this->db_alias); 128 $result = $db->Execute($query); 129 $result->close(); 130 } 131 132 function clear(){ 133 $query="DELETE FROM tbl_sessions WHERE session_date<".(time()-$this->session_gc_time); 134 $db = $this->kernel->plugin["adodb"]->connect($this->db_alias); 135 $result = $db->Execute($query); 136 } 137 } 138 ?> 4.6 Session_ita.xml 1 <?xml version="1.0" ?> 2 <document> 3 <m1>Hai richiesto una proprieta' che l'oggetto non possiede</m1> 4 <m2>Hai inserito come predefinito un database sconosciuto</m2> 5 <m3>Errore in creazione sessione</m3> 6 </document> 4.7 Html.inc 1 <?php 2 require_once dirname(__FILE__) . "/../kernel.inc"; 3 4 class cls_html extends cls_kernel{ 5 var $url; // url del sito 6 var $lib; // url del framework 7 var $action; // handler di default dei form 8 var $page; // buffer di pagina 9 var $header; // intestazione della pagina 10 var $body; // corpo della pagina 11 var $footer; // fine della pagina 12 var $title; // titolo pagina 13 14 function __construct($filename=""){ 15 parent::__construct($filename); 16 $this->page = ""; 17 $this->header = ""; 18 $this->body = ""; 19 $this->footer = ""; 20 21 $this->title = "untitled"; 22 23 $this->lib = $this->sysreg->model->html->lib; 24 $this->url = $this->sysreg->model->html->url; 25 $this->action = $this->sysreg->model->html->action; 26 27 $this->load_plugin(); 28 } 29 30 function execute(){ 31 parent::execute(); 32 $this->page = $this->header . $this->body . $this->footer; 33 return TRUE; 34 } 35 36 function add_css($url){ 37 $this->header .= "<link rel='stylesheet' type='text/css' href='" . $url . "'>"; 38 } 39 40 function set_title($title){ 41 $this->title = $title; 42 } 43 44 45 46 47 48 49 } 50 ?> function output(){ $this->execute(); print $this->page; return TRUE; } 4.8 Config.xml 1 <?xml version="1.0" ?> 2 <doc> 3 <kernel> 4 <path>c:\wamp\www\framework\</path> 5 <language>ita</language> 6 <debug>FALSE</debug> 7 </kernel> 8 <model> 9 <html> 10 <lib>http://siabsd82/framework/lib/</lib> 11 <url>http://siabsd82/framework/</url> 12 <action>http://siabsd82/framework/index.php</action> 13 </html> 14 </model> 15 <plugin> 16 <component name="adodb" class="adodb" install="FALSE"> 17 <default_db>yary</default_db> 18 <databases> 19 <main>main;mysql;siacun03;framework;root;masterkey</main> 20 <internet>internet;mysql;siacun03;internet;root;masterkey</internet> 21 <fisiatria>fisiatria;mysql;srv08;internet;root;masterkey</fisiatria> 22 <igea>igea;mysql;srvrad;asl15_pv;ced_pv;pv_ced</igea> 23 <pegaso>pegaso;oci8;;SCEREV15.WORLD;cupsl;slcup</pegaso> 24 <srvsiasw>srvsiasw;mssql;srvsiasw;ASL15;sa;daberoridabo</srvsiasw> 25 <yary>yary;mysql;siabsd82;framework;root;masterkey</yary> 26 </databases> 27 </component> 28 <component name="session" class="session" install="FALSE"> 29 <cookie_name>asl15</cookie_name> 30 <session_time>0</session_time> 31 <session_gc_time>14400</session_gc_time> 32 </component> 33 </plugin> 34 </doc> 5 La struttura del Framework Vergilius (di Davide Bonino) 5.1 Introduzione Negli ultimi 10 anni l’ASL 15 ha subito una notevole evoluzione, dal punto di vista informatico. Sia dal lato hardware che da quello software l’azienda ha saputo dotarsi di strumenti tecnologici per poter incrementare in modo esponenziale la propria efficienza pur dovendo ridimensionare sia le risorse economiche che umane. Oggi ci troviamo con una rete distribuita su un territorio di 55 comuni e quasi 40 sedi con oltre 500 PC e circa 20 server. Gli applicativi prodotti da terzi utilizzati a livello aziendale sono oltre 15 e quelli sviluppati internamente altrettanti. Le prospettive sono di espansione continua, tanto che si comincia a parlare di integrazione tra i vari applicativi. Ad esempio all’applicativo della Protesica serve dialogare con quello della Ragioneria per come quello del controllo di gestione riceve dati, dalle cure domiciliari e da altri servizi. Entrambi sono collegati al database del centro prenotazioni per l’anagrafica degli assistiti. Dato che ogni applicativo necessita di manutenzione continua (aggiornamento delle anagrafiche, modifiche alle logiche procedurali, backup, ecc.), si rende necessario cercare strumenti che siano in grado di soddisfare il maggior numero di servizi al minor costo. La scelta di affidarsi ad un consulente esterno risolve il problema delle risorse umane ma non quello finanziario. Viceversa lo sviluppo interno risulta economico ma eccessivamente oneroso per le risorse umane disponibili. L’unica soluzione percorribile sembra essere una via di mezzo tra le due, ossia uno sviluppo congiunto tra le risorse interne e consulenti esterni. 5.2 Il progetto Un progetto di questo tipo, non avendo tempi e risorse illimitati, deve essere snello, modulare e facilmente mantenibile, ossia si deve poter creare la struttura di un applicativo semplicemente mettendo insieme una serie di componenti già pronti e collegabili tra di loro. Un aspetto importante di questo metodo e’ che in internet si possono trovare parecchi componenti già pronti a costo zero; devono solo essere integrati nel sistema. Per sistema si intende un framework che fa da base di appoggio all’applicativo, contiene i componenti e li mette in comunicazione tra loro. L’ambiente di sviluppo scelto per la realizzazione di questo framework è web oriented e si è scelto il linguaggio PHP in abbinamento con MySQL come database primario. La filosofia che si intende seguire per questo progetto è quella dell’Open Source per diversi motivi: primo, si vuole realizzare qualcosa che non sia un semplice prodotto commerciale, ossia basato su architetture proprietarie, ma qualcosa di aperto e condiviso che possa anche evolvere verso sviluppi inizialmente non previsti; secondo, la possibilità di poter sfruttare ulteriori risorse per arrivare a dei prodotti completi che uniscano esperienze e competenze differenti; terzo, un meccanismo come questo è economico, sperimentato e affidabile. 5.3 La realizzazione pratica Come detto il framework sovrintende il tutto mettendo a disposizione un contenitore di componenti. Questo contenitore si appoggia su una classe astratta che gestisce i componenti tramite dei plug-in. Questi ultimi per essere installati nel sistema sfruttano una classe wrapper che fa da interfaccia tra la libreria e il framework. Questa classe serve ad effettuare eventuali conversioni, controlli e impostazioni per una corretta trasmissione e ricezione dei parametri. Tra questi esiste un componente particolare che si chiama config. Quest’ultimo non è localizzato insieme agli altri ma dovrebbe essere situato nella root di ogni applicativo. Come si può facilmente immaginare il suo compito è quello di inizializzare tutti i parametri dei componenti e dell’applicativo. La classe astratta sarà poi ereditata da dei modelli di script. I modelli ereditando tutte le funzionalità del padre, cioè la possibilità di installare i plug-in e connetterli creeranno la struttura di una pagina web, un report pdf, un e-mail, un file batch ecc. A loro volta, questi modelli, essendo delle classi, potranno essere ereditati per poter creare altri modelli specializzati, ad esempio la struttura di una pagina di un applicativo che contiene una testata con un menu contestuale e un piè di pagina con delle informazioni di stato. Il concetto è che tutto, componenti e modelli, devono essere riutilizzabili ma anche aggiornabili o addirittura sostituibili nel modo più trasparente e indolore possibile. Figura 4: Il modello del Framework "Vergilius" 5.4 La situazione attuale Attualmente è stata sviluppata la classe framework e sono stati installati diversi componenti : • ADO DB: una libreria open source che permette il collegamento a diversi database utilizzando un’API comune, la classe wrapper aggiunge un array contenente i parametri di accesso ai database utilizzati dall’applicativo. L’array è caricato dalla classe config. • FPDF: una libreria open source che permette la creazione di file in pdf • JSCalendar: un componente javascript open source per la selezione delle date • Form_Field: una libreria che restituisce elementi GUI con configurazione semplificata (es. combo caricata con elementi restituiti da un SQL) • Box: un componente che permette di creare comprimibili stile dinamic tabs di windows xp contenitori • Table: un componente PHP che permette di creare tabelle personalizzabili a partire da un SQL • Session: un componente che gestisce le sessioni e le variabili di ambiente Oltre a questi ce ne sono altri pronti che devono solo essere installati (FCK Editor, LDAP, JsTree, PHP Layers Menu, Rico Ajax, ecc.). Verranno integrati nel sistema solo quando realmente necessari. Figura 5: Servizi in fase di sviluppo 5.5 Cosa manca ancora I componenti possono essere anche utilizzati come base per la creazione di altri plug-in più complessi; ad esempio ADO DB viene utilizzato da Table per l’accesso ad una generica base dati. Pensando ad un ambiente di sviluppo completo viene subito in mente una parte, comune a tutti gli applicativi, che semplifichi l’amministrazione del sistema, ossia la gestione degli utenti e dei gruppi, controllo degli accessi alle risorse, il collegamento con i dipendenti, le strutture, gli ambulatori, i medici di base e i pediatri ecc. Questa parte è stata sviluppata solo per quanto riguarda l’autenticazione di un utente e il controllo dell’accesso alle risorse. Un componente, o meglio una serie di componenti che si stanno rivelando indispensabili per le nostre necessità sono quelli che consentono di amministrare le agende. Per agende si intende la possibilità di amministrare i calendari degli appuntamenti degli assistiti, presso i nostri servizi/ambulatori per ottenere delle prestazioni sanitarie. In sostanza serve un meccanismo che sia in grado di prelevare le informazioni dal CUP aziendale e di inserirle negli applicativi specifici di ogni servizio per arrivare ad ottenere le cartelle degli assistiti integrate in un unico sistema. 5.6 Cosa ci serve Come preannunciato sono necessari dei componenti per la gestione dei calendari. Dopo aver effettuato alcune ricerche su ciò che esiste in Internet si è trovata una libreria in PHP per la visualizzazione di file ics. I file ics sono uno standard creato dall’IETF (Internet Engineering Task Force) il gruppo che gestisce gli standard e i protocolli di Internet. In particolare la RFC (Request For Comments) 2445 che tratta del Internet Calendaring and Scheduling Core Object Specification (iCalendar). Questo protocollo definisce il formato di un file che tratta la gestione di calendari utilizzato dall’agenda di Mac OS X, da quasi tutti i palmari e supportata da Outlook. PHP Icalendar è una libreria completa per la visualizzazione di di questi file in stile outlook. Quello che manca è la possibilità di creare delle agende, modificare gli appuntamenti ecc. Quindi quello che serve è un componente per gestione dei file ics che si interfacci ad un sistema analogo su database e ne estragga solo i dati necessari. Se possibile la modifica della libreria per far puntare un appuntamento ad un link, ad esempio che punti ad una pagina che consenta la modifica dell’appuntamento e il relativo aggiornamento. Questi componenti serviranno da subito per l’utilizzo in un applicativo già in produzione: la fisiatria. Questo applicativo gestisce le visite fisiatriche e le prestazioni fisioterapiche con la relativa lista di attesa. La parte delle agende andrebbe utilizzata per visualizzare gli appuntamenti dei fisiatri, scaricati dall’applicativo Pegaso e i cicli di prestazioni eseguite dai vari fisioterapisti. 5.7 Dove vogliamo arrivare Nonostante il framework sia uno strumento general purpose, nell’ambito dell’ASL15 questo ambiente di sviluppo sarà utilizzato principalmente per lo sviluppo di cartelle cliniche ambulatoriali e per altri servizi di tipo sanitario e amministrativo. L’obiettivo è quello di riuscire a creare, in modo graduale, una complesso di applicativi che siano in grado di condividere tra loro le informazioni. Anche la comunicazione verso il mondo esterno è tra le nostre priorità per cui uno dei nostri obiettivi è la creazione di un componente HL7 che ci interfacci col mondo … o almeno con qualche applicativo chiave. In ambito regionale o anche solo in ottica di possibile fusione con le altre ASL è strategico possedere un sistema “aperto” verso tutti. Le aree ancora scoperte, dal punto di vista software, sono parecchie (Formazione, Prevenzione e protezione, medico competente, SPRESAL, ecc.). Anche la connessione con i pediatri e i medici di base non sarà ritardata ancora per molto e da qui si apriranno molte strade. Proprio nella comunicazione con i medici l’utilizzo di uno strumento Open Source e l’adesione agli standard può rivelarsi decisivo. Conclusioni In questa monografia ho trattato approfonditamente gli aspetti organizzativi e strutturali del lavoro che ho svolto tralasciando quasi del tutto il codice che ho sviluppato. Ho scelto di privilegiare questi aspetti perché più interessanti e significativi, mentre una trattazione esaustiva avrebbe richiesto molto più spazio di quello a mia disposizione per esporre quanto fatto. Ho inoltre incluso il punto di vista del mio referente aziendale con lo scopo di fare maggiore chiarezza su tutte le tematiche, non solo informatiche, che influenzano lo sviluppo di un lavoro di questo tipo. In realtà l’aspetto più interessante del tirocinio è stata la collaborazione con Davide Bonino, che ringrazio, con il quale ho condiviso la prima esperienza lavorativa in squadra, grazie alla quale ho potuto imparare molto. Un’esperienza, dal mio punto di vista, del tutto positiva da entrambi i lati che ritengo non solo un’opportunità, ma addirittura un necessità per uno studente. Devo anche rendere conto del fatto che grazie all’aiuto del prof. Mangiantini abbiamo ricevuto la visita di S. Duretti e S. Longo per conto del CSP Piemonte, assieme ai quali abbiamo discusso della possibilità di un finanziamento regionale al progetto. Nonostante il finanziamento non sia andato in porto, è stata un’esperienza interessante ed ha aperto alcune possibilità future per il progetto, a cui auspico di poter ancora prendere parte in futuro. Ringraziamenti Ringrazio il dott. Ortolano per la sua disponibilità, per il tempo che ha dedicato affinché il tirocinio potesse avere luogo e per aver saputo cogliere l’occasione che si è presentata ed infine per avermi assegnato ad un buon progetto. Ringrazio Davide Bonino per tutto il tempo in cui abbiamo collaborato, per avermi dato così tanto spazio nel suo progetto e per essere stato così attivo nel seguirmi e nel mostrarmi gli strumenti di sviluppo che si usano in ambito professionale. Ringrazio il prof. Mangiantini per averci sostenuto contattando alcuni responsabili del CSP al fine di valutare il progetto per un eventuale finanziamento. Indice delle figure Figura 1: Elementi basilari del framework “Vergilius”.................................................... 12 Figura 2: L'albero della cartella www............................................................................... 13 Figura 3: L'albero del plugin profile ................................................................................. 19 Figura 4: Il modello del Framework "Vergilius" .............................................................. 34 Figura 5: Servizi in fase di sviluppo ................................................................................. 36 Siti Internet http://www.zend.org/ - Il sito degli sviluppatori del linguaggio php http://devzone.zend.com/manual/view/page/ - Un manuale di php http://www.w3.org/ - Per la validazione e riferimenti per html e css http://www.wampserver.com/ - Gli sviluppatori di WAMP http://tortoisesvn.tigris.org/ - Un client svn http://subversion.tigris.org/ - Il server subversion http://www.phpdoc.org/ - Dove reperire php documentor http://www.asl15.it/ - Il sito dell’ A.S.L. 15 di Cuneo che si appoggia sul framework “Vergilius”