I blog di Alessioempoli

Data 1 ottobre 2018

ESTENSIONE DI PHP

Per ingrandire il testo, cliccare sul browser

Per ingrandire le foto, cliccarci sopra

                         ESTENSIONE DI PHP

Cerchiamo di capire adesso come estendere le funzionalità di PHP creando estensioni e sfruttando le API messe a disposizione dello Zend Engine.

 

 

lo Zend Engine e le API

 

Lo Zend Engine, il motore che si occupa di compilare ed eseguire i nostri script PHP, fornisce una vasta gamma di funzioni native che svolgono moltissimi compiti di comune utilità. Possiamo operare con semplicità sulle stringhe di testo, eseguire operazioni sul filesystem o gestire complesse strutture gerarchiche di array. Molte volte sorge però la necessità di estendere le capacità native del linguaggio al fine di semplificare ed organizzare correttamente i propri progetti: potremmo aver bisogno di un sistema di sessioni su database, oppure di una libreria che ci faciliti la generazione di file PDF o che ci permetta la gestione di stringhe composte da byte multipli. In questo caso la struttura stessa del linguaggio PHP, che unisce al supporto alla programmazione procedurale quello per la programmazione orientata agli oggetti, ci viene in aiuto: possiamo combinare i componenti che ci vengono forniti dal linguaggio per costruire strutture operative che si adattino alle nostre esigenze.

 

Sfortunatamente non tutte le nostre esigenze possono però essere soddisfatte in questo modo: potrebbero presentarsi casi in cui delle routine debbano essere eseguite con basso utilizzo di memoria o di cicli macchina, oppure si potrebbe voler sfruttare una libreria scritta da terzi in un linguaggio differente da PHP anziché doverla riscrivere dal nulla. In queste situazioni particolari lo Zend Engine da solo può fare ben poco; per questo motivo i creatori di PHP hanno optato per fornire un sistema che permetta allo sviluppatore esperto di estendere le potenzialità del motore fornendo funzionalità native difficilmente emulabili sfruttando solamente PHP.

 

Il motore di esecuzione di PHP è quindi capace di caricare estensioni scritte in linguaggio C/C++ (il linguaggio con cui lo Zend Engine stesso è stato sviluppato) che seguano una determinata struttura e di permettere l’utilizzo delle funzioni definite nativamente all’interno dei propri script. La scrittura di estensioni non è affatto un’operazione poco comune: l’installazione standard di PHP fornisce già molte estensioni che permettono di svolgere svariate operazioni (basti pensare al supporto per MySQL o per XML), e svariati sviluppatori si sono occupati di sviluppare e fornire estensioni personalizzate al fine di permettere ai programmatori che utilizzano PHP di affrontare varie problematiche in modo molto più semplice. È possibile trovare moltissime estensioni per PHP sulla rete: Sourceforge e PECL sono due fonti molto ricche e di sicuro interesse.

Cercheremo di spiegare in modo dettagliato ed esauriente come scrivere estensioni per PHP. Abbiamo deciso di utilizzare Linux come piattaforma di sviluppo e di testing dato che la maggior parte dei server che offrono il supporto PHP girano su piattaforma LAMP (Linux Apache Php MySQL). Lo schema seguirà una struttura prettamente didattica, anche se verranno comunque forniti alcuni esempi pratici al fine di rendere più piacevole la lettura e più comprensibili i concetti esposti. Prima di cominciare è necessario che abbiate una buona conoscenza del linguaggio C e che abbiate disponibili i sorgenti della versione di PHP per cui desiderate scrivere le vostre estensioni.

 

 

Ext_skel: generare lo scheletro dell’estensione

 

Le estensioni per PHP possono essere compilate come librerie (dll per i sistemi Windows, so per quelli Linux) linkate dinamicamente o staticamente. Affinché un’estensione funzioni correttamente è necessario che segua delle rigide regole strutturali. Al fine di facilitare lo sviluppo delle estensioni, i creatori del linguaggio PHP hanno deciso di fornirci due strumenti essenziali che permettono allo sviluppatore di focalizzarsi sulla soluzione del problema piuttosto che sulla struttura dell’estensione: un set di API molto potente che comprende svariate funzioni di utilità e macro che limitano notevolmente lo spreco di tempo in fase di sviluppo, ed un semplice programma capace di generare l’infrastruttura di un estensione partendo da un insieme di dichiarazioni molto semplici. Il programma in questione si chiama ext_skel.php, ed è reperibile nella cartella ext dei sorgenti di PHP.

 

Ext_skel esegue il parsing di un documento contenente le definizioni dei prototipi delle funzioni che desideriamo implementare nella nostra estensione, e produce lo scheletro di quest’ultima. Il file contenente i prototipi è formato da una serie di righe, ognuna delle quali dovrà seguire questo schema:

 

tipo_restituito nome_funzione ([tipo parametro [, tipo parametro]*]?) commento?

 

Il file di prototipi così generato potrà essere passato al programma per essere processato. Ext_skel accetta diversi parametri, elencati qui di seguito:

 

proto=file: percorso al file che contiene la definizione dei prototipi;

xml: informa il programma di generare anche della documentazione XML;

skel=dir: percorso nel quale verranno salvati i file generati;

no-help: informa il programma di tralasciare la scrittura di commenti di aiuto nei file generati;

stubs=file: tralascia la generazione di elementi specifici per un modulo, permettendo così l’integrazione dei prototipi all’interno di estensioni già esistenti;

extname=name: il nome dell’estensione;

 

Ext_skel genera anche i file necessari per la corretta compilazione delle estensioni, dei file necessari per effettuare il testing e qualche file aggiuntivo, che verranno posizionati nella cartella specificata come valore dell’opzione –skel. È da ricordare che il file .m4 prodotto dovrà essere editato manualmente al fine di permettere la corretta compilazione dell’estensione.

 

 

La nostra prima estensione

 

Creiamo la prima sempplice estensione PHP e implementiamone le funzioni

 

Per comprendere al meglio l’utilità di ext_skel per la produzione di estensioni, procederemo con l’implementazione di due semplici funzioni. Una volta compreso il meccanismo utilizzato per creare, completare e compilare i sorgenti prodotti da ext_skel per un’estensione di piccole dimensioni, fare il passo verso strutture più complesse non risulterà affatto difficile.

Per prima cosa creiamo un file di testo contenente i prototipi delle nostre funzioni chiamandolo prototipi.txt:

int countuniquechars(string input [,bool case_sensitive])

Restituisce il numero di caratteri univoci nella stringa

string invertcase(string input)

Inverte maiuscole e minuscole

 

Queste due funzioni non saranno di molta utilità nello sviluppo di applicazioni complesse, ma ci aiuteranno a comprendere il meccanismo di creazione delle estensioni. Con l’aiuto di ext_skel, generiamo i file necessari:

 

1-

 

Se tutto va per il verso giusto, il programma verrà eseguito correttamente, e verranno stampate a video le istruzioni necessarie per procedere con la compilazione dell’estensione. I comandi generati possono essere seguiti alla lettera, senza alcun problema. Il primo comando permette di modificare il file di config.m4; questo file è utilizzato per modificare automaticamente il file configure di PHP. Se non dovesse essere apportata nessuna modifica a questo file, verrebbe utilizzata la configurazione di default. Il file prodotto è abbastanza lungo, e per motivi di spazio non viene mostrato. È necessario modificare il file rimuovendo i commenti (eliminando la parola dnl che precede la riga) delle righe seguenti in modo che includa automaticamente l’estensione che andremo a compilare:

 

2-

 

Una volta applicate le modifiche ed eseguito il backup del file configure di PHP, eseguiamo in sequenza i seguenti comandi:

 

3-

 

L’estensione è stata creata e compilata correttamente, ma se cercassimo di eseguire le funzioni in essa definite, verrebbe visualizzato un’avvertenza (warning) che ci informa che l’implementazione della funzione non è stata ancora effettuata. Apriamo quindi il file html_it_ext.c e procediamo con un’analisi superficiale della struttura e con l’implementazione delle funzioni.

Se cerchiamo all’interno dei sorgenti, dovremmo trovare la definizione della funzione countuniquechars:

 

4-

 

Come potete notare la struttura base di una funzione PHP è molto semplice. Tralasciando le righe relative alla definizione delle variabili utilizzate, abbiamo una funzione che si occupa di fare il parsing degli argomenti passati e di salvarli nello scope corrente, ed una funzione che restituisce un warning. Procediamo con l’implementazione della funzione:

 

5-

 

L’implementazione del metodo è molto semplice: prendiamo ogni carattere della stringa in input, lo convertiamo in minuscolo se necessario, e controlliamo che questo carattere non sia presente all’interno di un buffer. In caso affermativo aggiungiamo il carattere al buffer ed incrementiamo un contatore. Il valore del contatore viene infine restituito come risultato. È necessaria una precisazione sulle funziona utilizzate per l’allocazione della memoria: nelle estensioni PHP vengono utilizzate delle implementazioni alternative alle funzioni per la gestione della memoria. Queste implementazioni hanno lo stesso nome delle versioni standard, preceduto da una ‘e’. Tramite PHP è possibile utilizzare emalloc, efree, estrdup ecalloc.

 

Procedendo con la seconda funzione, l’implementazione è subito fatta:

 

6-

 

Come possiamo notare, la struttura rimane pressoché identica. Varia solamente la logica applicativa ed il sistema utilizzato per restituire un valore. In questo caso è necessario restituire una copia della stringa creata, quindi utilizzeremo la macro RETVAL_STRING con i parametri appropriati.

 

Riprendiamo nuovamente i passi di configurazione e compilazione, e procediamo con il testing delle funzioni implementate. Otterremo i risultati sperati.

 

 

Accettare argomenti

 

Come analizzare e gestire gli argomenti passati alla funzione da PHP

Dopo aver introdotto brevemente un esempio di implementazione, passiamo a trattare in modo più approfondito le diverse parti che compongono un’estensione e le API che possono essere utilizzate per lavorare correttamente. Come prima cosa analizzeremo gli strumenti che PHP mette a disposizione per accettare e gestire gli argomenti.

Affinché si possano gestire correttamente gli argomenti passati alla funzione da PHP è necessario che questi siano convertiti e trasformati in tipi di dato corretti. La funzione zend_parse_parameters viene utilizzata per questa finalità. Il suo prototipo è il seguente:

 

7-

 

Il primo parametro è il numero di argomenti di cui la funzione dovrà eseguire il parsing. La macro TSRMLS_DC viene utilizzata al fine di aggiungere il supporto threaqd safe a PHP, che non verrà discusso in questa sezione. Il secondo parametro è una stringa, che fornisce alla funzione informazioni sulla struttura degli argomenti accettati dalla funzione. È possibile specificare una stringa di dimensioni variabili, ed ogni singolo carattere di questa stringa avrà un preciso significato:

 

8-

 

Il restante numero di parametri rappresentano dei riferimenti a variabili nei quali verranno salvati i valori relativi agli argomenti estratti. Va specificato che al formato ‘s’ devono corrispondere due parametri: il primo di tipo char* che conterrà un puntatore alla stringa estratta, ed il secondo di tipo int, contenente la lunghezza di quest ultima.

Questa funzione risulta molto comoda, e permette anche di recuperare un numero parziale di argomenti lasciando al programmatore il compito di recuperare manualmente i valori degli altri parametri sfruttando altre api.

La funzione restituisce un valore identificato dalla costante FAILURE in caso di fallimento, altrimenti SUCCESS.

Lo zend engine fornisce altri strumenti utili per la gestione dei parametri:

 

9-

 

È necessario fare una nota particolare per la funzione zend_get_parameters_array_ex: questa funzione accetta come primo parametro un intero rappresentante il numero di parametri che si desidera salvare nel secondo argomento, un array di zval**

 

10-

 

Il tipo zval rappresenta un oggetto generico PHP, e viene utilizzato in tutti i casi nei quali è necessario lavorare a basso livello con argomenti e valori; normalmente viene utilizzato in PHP come puntatore di puntatore, e può essere interrogato direttamente per recuperare svariate informazioni su un oggetto. Le API di PHP forniscono un insieme di funzioni convert_to_*_ex dove l’asterisco è uno tra boolean, long, double, string, array, object e null, che convertono lo zval nel tipo specificato.

 

 

I tipi di dato scalari – I

 

La gestione dei valori di tipo scalare e le macro per accedervi

Nel caso in cui si preferisca lavorare con oggetti zval e non si desideri (o non si possa) utilizzare la funzione zend_parse_parameters, è necessario conoscere gli strumenti necessari per la corretta gestione dei valori rappresentati. In questo e nel paragrafo successivo tratteremo in modo approfondito i tipi di dato scalari, ed introdurremo i sistemi utilizzati per gestire e restituire questi valori.

La tabella seguente riassume brevemente le macro utilizzate per impostare, accedere e restituire i valori di un oggetto zval. Le macro aventi alcune particolarità saranno discusse in seguito:

 

11-

 

 

I tipi di dato scalari – II

 

Le macro ZEND_SET_SYMBOL, Z_TYPE, ZVAL_STRING e ZVAL_STRINGL

Tutte le macro utilizzate per impostare e recuperare i valori accettano come primo parametro un puntatore a zval (estratto facendo precedere la variabile contenente lo zval da un asterisco).

Una nuova variabile di tipo zval può essere creata attraverso la macro MAKE_STD_ZVAL, che si occupa dell’allocazione e dell’inizializzazione. Questi valori sono disponibili solamente all’interno del modulo C che definisce l’estensione. È possibile rendere globale o inserire una variabile nello scope corrente del blocco di codice che richiama la nostra estensione utilizzando la macro ZEND_SET_SYMBOL. Questa macro permette di accedere in modo trasparente la tabella dei simboli locale o globale, modificando il valore di un determinato simbolo:

 

12-

 

La funzione qui definita lavora assegnando ad una variabile chiamata con la stringa passata come primo argomento il valore passato come secondo:

 

13-

 

Al fine di salvare la variabile nel namespace globale, è possibile utilizzare &EG(symbol_table) anziché EG(active_symbol_table). Bisognerà comunque assicurarsi che il nome utilizzato nella tabella dei simboli globali sia importato precedentemente utilizzando la parole chiave global.

Una volta che si ha a disposizione uno zval, è possibile modificare il suo tipo ed il suo valore attraverso un apposito le macro sopra riportate. È da ricordare che le macro ZVAL_STRING e ZVAL_STRINGL accettano come ultimo parametro un valore che indica se effettuare la copia della stringa passato o se salvarne solamente un riferimento.

Spesso risulta necessario conoscere il valore contenuto da uno zval. Questo può avvenire in quei casi in cui all’utente è lasciata la possibilità di passare valori di diverso tipo, oppure quando lo zval ci proviene da una funzione che restituisce un oggetto che può avere valori differenti. In questo caso ci viene in aiuto la macro Z_TYPE, che restituisce il tipo contenuto nello zval passato come argomento. I possibili valori restituiti sono accessibili attraverso queste macro autoesplicative:

 

14-

 

 

Gli array: creazione e restituzione

 

Come gli array vengono creati, gestiti e restituiti in PHP4

In PHP4 il concetto di array è molto importante: il cuore stesso dello zend engine sfrutta gli array di PHP per salvare alcune informazioni molto importanti, quali tabelle dei simboli e delle funzioni. In questa sezione ci occuperemo di trattare il sistema con cui gli array possono essere creati, gestiti e restituiti.

Prima di addentrarmi nella discussione delle API fornite dallo Zend Engine per la gestione degli array, vi mostro un esempio. La funzione definita si occupa semplicemente di restituire un array:

 

15-

16-

17-

 

La funzione test_array restituisce un elemento di tipo zval rappresentante un array. Lo Zend Engine restituisce a PHP il valore contenuto nella variabile return_value, occupandosi di apportare le dovute trasformazioni. Questa variabile viene passata come argomento alla funzione da noi definita, anche se la macro PHP_FUNCTION ci nasconde i dettagli sull’implementazione, e non deve essere inizializzata. Essendo un normale zval, possiamo sfruttare tutte le funzioni che lavorano su questo tipo di dato. La funzione array_init è fondamentale affinché l’area di memoria associata al valore di tipo array dello zval sia correttamente allocata ed inizializzata, quindi prima di eseguire qualsiasi altra operazione di inserimento, eliminazione o ricerca, è necessario richiamarla sul valore che su cui si intende agire.

Le funzioni che permettono di aggiungere elementi ad un array sono ben documentate, anche se qualche appunto è comunque necessario:

 

18-

 

 

Come abbiamo già visto in qualche esempio precedente, è possibile utilizzare queste funzioni per operare sullo scope locale e globale.

 

 

Le API di PHP per gestire gli array: una carrellata di funzioni utili

 

PHP fornisce un set di API apposite (sfortunatamente poco documentate) che si occupano di gestire gli array ricevuti come parametri e di operare sulle variabili superglobali di PHP ($_SESSION, $_SERVER, …). Questo insieme di funzioni è molto utile, e può essere utilizzato anche per lavorare sui normali array creati manualmente.

Tra le funzioni definite (i cui prototipi sono reperibili in Zend/zend_hash.h all’interno della cartella dei sorgenti PHP), le seguenti risultano molto utili:

 

int zend_hash_update(HashTable*, char*, uint, void**, uint, void**)

Aggiunge o modifica un valore contenuto in un array associativo. Il primo argomento è l’array sul quale si vuole operare (Z_ARRVAL(*array) oppure una delle hashtable speciali dello Zend Engine). Il secondo argomento è una stringa rappresentate la chiave di riferimento ed il terzo la sua lunghezza. Il quarto e quinto elemento rappresentano il dato che si intende aggiungere e le sue dimensioni, mentre l’ultimo argomento è solitamente settato a NULL, e rappresenta la destinazione del valore associato alla chiave. Di questa funzione esiste anche la versione che opera su un valore indicizzato da un intero, zend_hash_index_update, che accetta l’intero rappresentante l’indice al posto del secondo e terzo parametro. Esiste anche la funzione zend_hash_add, che le stesse operazioni ed accetta gli stessi argomenti.

 

int zend_hash_find(HashTable*, char*, uint, void**)

Localizza un elemento all’interno di un array associativo. L’elemento, indicato dalla chiave passata come secondo parametro, viene eventualmente salvato nel quarto parametro. Anche per questa funzione esiste la versione che opera su indici interi, zend_has_index_find.

 

int zend_has_move_forward(Hashtable*), int zend_has_move_backward(Hashtable*)

Muovono rispettivamente il puntatore interno dell’array in modo che punti all’elemento successivo o precedente.

 

int zend_has_del(HashTable*, char*, uint)

Elimina una chiave e l’elemento ad essa associato. Anche per questa funzione esiste la versione che opera su indici interi, zend_hash_index_del.

 

int zend_hash_get_current_key(HashTable*, char**, ulong*, zend_bool)

Trova la chiave a cui punta il puntatore interno dell’array ed in caso sia una stringa la assegna al primo parametro, altrimenti al secondo. L’ultimo parametro server per indicare se duplicare o meno la chiave.

 

int zend_hash_get_current_data(HashTable*, void**)

Salva nel secondo parametro il dato a cui punta il puntatore interno dell’array.

 

int zend_hash_num_elements(HashTable*)

Restituisce il numero di elementi contenuti nella hashtable.

 

int zend_hash_internal_pointer_reset(HashTable*), int zend_hash_internal_pointer_end(HashTable*)

Muovono rispettivamente il puntatore interno dell’array in modo che punti al primo o all’ultimo elemento.

 

Le funzioni qui descritte operano tutte sul tipo di dato HashTable, che è per l’appunto la struttura che lo Zend Engine utilizza per salvare gli array ed alcuni dati interni in formato tabellare.

Il seguente esempio chiarisce l’utilizzo di alcune delle funzioni sopra descritte:

 

19-

20-

 

Questa semplice funzione accetta un array come parametro, e restituisce una rray avente due elementi: il primo indicato dalla chiave elements count rappresentante il numero di elementi dell’array, il secondo, informations, rappresentante un array contenente la rappresentazione di ogni elemento in formato stringa. Viene effettuato un controllo sul valore restituito da zend_hash_get_current_key per conoscere se la chiave restituita è una stringa o un intero, e successivamente si opera di conseguenza. Ricordo che il suffisso _P alle macro serve per recuperare il valore di un puntatore, ed il suffisso _PP il valore di un puntatore a puntatore.

 

 

Gli oggetti

 

Arricchire le nostre estensioni con gli oggetti

 

Lo Zend Engine permette di operare anche con tipi di dato che verranno esposti come oggetti quando la nostra estensione verrà utilizzata. Il motore di PHP rappresenta internamente gli oggetti sfruttando le HashTable, allo stesso modo di come vengono salvati gli array (da PHP 5, con l’introduzione dello Zend Engine 2, gli oggetti hanno una rappresentazione differente in memoria, molto più efficiente; per la loro gestione possono comunque essere sfruttate le stesse API che vedremo in questo paragrafo). È possibile quindi sfruttare le stesse API messe a disposizione per gli array anche sugli oggetti. Viene fornito comunque un set di API atte ad aggiungere proprietà ad un oggetto; queste proprietà possono contenere qualsiasi tipo di dato ed in base a questo dovranno essere utilizzate delle funzioni differenti per manipolare l’oggetto.

Un breve esempio:

 

21-

 

Il codice precedente si occupa di creare una funzione che restituisce un oggetto al chiamante. Questo oggetto, se stampato con la funzione print_r, restituirà questa struttura:

22-

Come possiamo notare dal codice utilizzato per definire la semplice funzione d’esempio, l’aggiunta di una proprietà ad un oggetto viene effettuata utilizzando le seguenti funzioni

23-

Tutte le funzioni qui definite accettano come primo parametro un riferimento all’oggetto sul quale operare, e come secondo parametro una stringa rappresentante il nome della proprietà da aggiungere. I restanti parametri definiscono il valore da associare alla proprietà, e variano in base al tipo di dato. Seguono comunque le stesse regole utilizzate dalla funzioni add_next_index_* descritte nel paragrafo precedente.

Come già indicato, le operazioni di ricerca e di interrogazione dell’oggetto possono essere effettuate come se questi fosse un array. Vediamo un semplice esempio che modifica leggermente la funzione test_zend_hash definita precedentemente al fine di permetterle di operare su un oggetto:

24-

25-

Come possiamo notare sono state sfruttate le stesse API utilizzate nella funzione definita precedentemente, ma sono state fatte operare su oggetti anziché array.

Gestire le risorse

 

Arricchire le nostre estensioni con le risorse

 

Le risorse rappresentano un tipo di dato molto importante in PHP, utilizzato quando è necessario mantenere qualche tipo di informazione che non può essere esposta a PHP. Per esempio le funzioni per la gestione della immagini, quelle per l’interrogazione dei database e le funzioni per la creazione di parser SAX sfruttano le risorse.

Lo Zend Engine salva le risorse in una lista interna che viene interrogata utilizzando un identificatore associato alla risorsa per recuperare il tipo di dato base. Quando la risorsa non ha più riferimenti, il motore di PHP si occupa di richiamare una funzione distruttore che solitamente libera la memoria allocata precedentemente. Il distruttore deve essere registrato in modo che PHP conosca quale funzione richiamare per liberare una risorsa. È da precisare che PHP permette l’utilizzo di due tipi di risorse: le risorse normali, le quali vengono distrutte quando lo script che le ha create termina o quando l’engine viene fermato manualmente, e le risorse persistenti, che persistono in memoria finché non vengono distrutte esplicitamente o finché l’engine non viene terminato manualmente.

 

Procediamo con un semplice esempio che mostra una libreria utilizzata per leggere dei dati da un file utilizzando le API del sistema operativo. È da notare che l’esempio utilizza una variabile statica in cui contenere il riferimento alla risorsa. Questa operazione è corretta per sistemi a thread singolo. I sistemi multithread su cui viene eseguito PHP condividerebbero la stessa variabile per ogni risorsa creata, generando problemi a livello di esecuzione. Per una corretta implementazione sarebbe necessario utilizzare il sistema per la gestione della variabili globali, descritto nel paragrafo 11.

 

26-

27-

28-

 

L’estensione espone tre funzioni che si occupano, nell’ordine, di aprire, leggere e chiudere un file utilizzando le API fornite dal sistema operativo. La prima riga definisce il prototipo del distruttore della risorsa. Tutti i distruttori dovranno seguire questo prototipo, anche se potranno fornire nomi differenti. Successivamente viene definito un nuovo tipo di dato che rappresenta la nostra risorsa: il nuovo tipi di dato non è altro che una struttura contenente un puntatore ad un file aperto. Viene anche definita una variabile statica che conterrà il tipo associato dallo Zend Engine alla risorsa nel momento in cui questa verrà registrata. La funzione definita utilizzando la macro PHP_MINIT_FUNCTION (discussa nei paragrafi successivi) viene richiamata in automatico da PHP durante il processo di inizializzazione. Nel nostro caso si occupa di registrare la risorsa da noi definita in modo che possa essere utilizzata dal modulo. La risorsa viene registrata utilizzando la funzione zend_register_list_destructors_ex che accetta i seguenti parametri:

 

# un puntatore alla funzione utilizzata per liberare la risorsa nel caso questa sia di tipo normale. Ricordo che la funzione passata deve seguire il prototipo specificato all’inizio del file;

# un puntatore al distruttore che si occuperà di liberare la risorsa nel caso questa sia di tipo persistente. Può avere anche valore nullo, come nel nostro caso;

# una stringa che specifica il nome univoco da associare alla risorsa;

# un valore interno utile allo Zend Engine per operare la registrazione correttamente. Le funzioni definite con la macro PHP_MINIT_FUNCTION espongono il parametro module_number, che può essere usato come argomento.

 

Una volta operata la registrazione ci viene restituito un intero che rappresenta l’identificativo univoco del nuovo tipo di risorsa. Nella funzione html_it_open viene aperto il file che si trova al path passato come argomento (ricordo che le funzioni sono solo d’esempio, e non dovrebbero essere utilizzate in un contesto reale che necessiterebbe un controllo sulla sicurezza e per lo meno sui parametri passati). Il puntatore al file viene salvato nella struttura da noi definita, che poi viene trasformata in una risorsa ed assegnata al valore di ritorno utilizzando ZEND_REGISTER_RESOURCE che accetta come parametri la destinazione, un puntatore alla risorsa e l’indice del tipo di risorsa registrato precedentemente.

Nella funzione html_it_read viene effettuata la lettura di una porzione di testo dal file. La funzione accetta come parametro la risorsa da utilizzare, che viene salvata in arg . Al fine di recuperare dallo zval passato come argomento la risorsa a noi necessaria, viene utilizzata la macro ZEND_FETCH_RESOURCE che accetta i seguenti parametri:

 

# un puntatore alla risorsa sulla quale opereremo. Deve essere del tipo di dato registrato precedentemente;

# il tipo di dato associato alla risorsa;

# l’indirizzo dello zval contenente l’ID della risorsa a cui fa riferimento l’argomento passato;

# l’ID di default utilizzabile se la risorsa non può essere recuperata;

# il nome della risorsa;

# l’indice associato al tipo di risorsa registrato precedentemente;

 

La macro si occupa di verificare che la risorsa sia corretta: in caso non lo sia si occupa da sola di restituire il controllo allo script chiamante restituendo NULL. Dopo la chiamata a questa macro, è possibile operare correttamente su resource.

L’ultima funzione si occupa di chiudere la risorsa, eliminandola dalla lista interna delle risorse delle zend engine attraverso la funzione zend_list_delete, che accetta come parametro l’indice della risorsa da eliminare. La chiamata a questa funzione eseguirà di conseguenza il distruttore associato alla risorsa, che si occuperà di chiudere il file aperto e di eliminare la memoria allocata.

 

 

Alcune funzionalità che può svolgere lo Zend Engine

 

Attraverso lo Zend Engine è possibile effettuare svariate operazioni di routine che risultano molto utili quando si sviluppano estensioni. In questo paragrafo mi occuperò di descriverle brevemente, lasciando al lettore il compito di sperimentare.

La funzione zend_printf si occupa di stampare del testo indirizzandolo all’output stream di PHP. La funzione è sintatticamente identica a printf.

La funzione zend_error (che funziona allo stesso modo della funzione php_error) genera dei messaggi di errore e li indirizza al motore di PHP. Accetta due parametri:

 

29-

 

Per aggiungere delle informazioni relative all’estensione all’output generato dalla funzione phpinfo, è possibile definire una funzione utilizzando la macro PHP_MINFO_FUNCTION. Questa funzione può utilizzare le API per aggiungere all’output di phpinfo informazioni relative all’estensione. Un breve esempio:

 

30-

 

le funzioni php_info_print_table_* vengono utilizzate per la costruzione della tabella da aggiungere all’output di phpinfo:

 

# php_info_print_table_start() e php_info_print_table_end() si occupano di stampare rispettivamente l’inizio e la fine di una tabella;

# php_info_print_table_header accetta un numero variabile di argomenti (il numero di colonne ed il valore di ogni colonna) e stampa l’header della tablla;

# php_info_print_table_row funziona come la funzione precedente ma stampa una normale riga.

 

Utilizzano zend_get_executed_filename() è possibile recuperare il nome del file in cui una funzione definita nella nostra estensione è richiamata, mentre utilizzando zend_get_executed_lineno() è possibile recuperare il numero della linea in cui la funzione è stata richiamata.

Lo Zend Engine fornisce anche un sistema che permette di esporre delle costanti allo script PHP che utilizza la nostra estensione. La creazione di costanti viene effettuata tramite le seguenti macro:

 

# REGISTER_LONG_CONSTANT(name, value, flags);

# REGISTER_DOUBLE_CONSTANT(name, value, flags);

# REGISTER_STRING_CONSTANT(name, value, flags);

# REGISTER_STRINGL_CONSTANT(name, value, length, flags);

Il parametro flgs può essere una combinazione di una delle seguenti costanti:

 

# CONST_CS, che obbliga lo Zend Engine a trattare il nome della costante in modo case sensitive;

# CONST_PERSISTENT che mantiene la costante in memoria anche dopo che il processo che l’ha creata viene terminato;

 

L’engine PHP ci permette di definire ed utilizzare variabili globali in modo thread safe. Le variabili generate e gestite utilizzando le API ora descritte saranno locali per ogni thread che utilizza un’estensione.

Le macro ZEND_BEGIN_MODULE_GLOBALS e ZEND_END_MODULE_GLOBALS si occupano di generare una struttura contenente le variabili globali. La definizione delle variabili globali viene eseguita solitamente in un file header, seguendo la seguente struttura:

 

31-

 

Le macro generano una struttura zend_(nome_modulo)_globals e definisce una variabile (nome_modulo)_globals che può essere utilizzata per accedere alle variabili globali. Solitamente si opta per definire una macro per l’accesso agli elementi della struttura.

 

#define VAR_G(v) (nome_modulo_globals.v)

 

In caso si stia compilando l’estensione per un sistema multithread, è necessario accedere alla variabile recuperandola dalla pool delle variabili di thread:

 

32-

 

L’inizializzazione delle variabili avviene utilizzando un’apposita macro che richiama una funzione definita dall’utente:

 

33-

 

La macro in questione è ZEND_INIT_MODULE_GLOBALS, che richiama il metodo passato come secondo argomento al fine di popolare correttamente la struttura delle variabili globali.

Un’altra funzionalità importante fornitaci dallo Zend Engine è quella di definire funzioni speciali da richiamare nel momento in cui un modulo viene caricato in memoria, inizializzato, distrutto e scaricato. Per poter definire queste funzioni è necessario operare sulla struttura che definisce la nostra estensione e sfruttare delle macro apposite:

 

34-

 

Come possiamo notare le righe dalla sette alla undici permettono aggiungere al modulo delle funzioni da richiamare in casi particolari. In caso non siano presenti delle funzioni speciali, è necessario specificare il parametro della struttura zend_module_entry come NULL.

 

Le seguenti macro possono essere utilizzate per dichiarare funzioni speciali:

 

# PHP_MINIT_FUNCTION (nome_modulo), che permette di definire la funzione richiamata all’inizializzazione del modulo;

# PHP_MSHUTDOWN_FUNCTION (nome_modulo), che permette di definire la funzione richiamata quando il modulo viene scaricato;

# PHP_RINIT_FUNCTION (nome_modulo), che permette di definire la funzione richiamata quando viene effettuata una richiesta;

# PHP_RSHUTDOWN_FUNCTION (nome_modulo), che permette di definire la funzione richiamata quando viene terminata una richiesta;

# PHP_MINFO_FUNCTION (nome_modulo), che permette di definire la funzione richiamata per aggiungere all’output della funzione phpinfo informazioni sull’estensione.

 

 

Conclusione

 

Le ultime raccomandazioni per chi vuole scrivere le proprie estensioni

 

Si chiude qui la prima infarinatura di un argomento complesso, che ruberà ancora molto spazio. Data l’impronta fortemente introduttiva e teorica della guida, ho preferito tralasciare alcuni argomenti complicati e fornire un piccolo numero di esempi, al fine di permettere all’utente di sperimentare le conoscenze acquisite. Data la poca documentazione riguardante l’argomento, è molto importante che chiunque si dedichi allo sviluppo di estensioni per PHP impari a saper leggere il codice sorgente dell’engine ed a studiare i sorgenti delle estensioni definite all’interno del core. Molte funzionalità non sono documentate, e molte altre hanno comportamenti anomali in situazioni leggermente differenti. Mi auguro di aver invogliato il lettore ad approfondire l’argomento.

Lascia una risposta