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”