Programmazione a oggetti con PHP – 2
I metodi magici di PHP
Impariamo ad utilizzare i metodi magici di PHP, utilizzati per reagire a diversi eventi e scenari all’interno degli oggetti. Conoscere questi metodi è di fondamentale importanza per lo sviluppo di applicazioni OOP avanzate. Un metodo magico può avere un ruolo fondamentale nelle gerarchie di oggetti di qualsiasi tipologia di applicazione, poiché permette di controllare con precisione situazione e proprietà altrimenti inaccessibili.
Come anticipato in precedenza, PHP fornisce alcuni metodi magici, i quali ricoprono un ruolo molto importante nella programmazione OOP di PHP. A questo punto della guida, abbiamo già analizzato dei metodi magici: __construct, __destruct() e __clone(). In questa lezione analizzeremo anche questi metodi, attivati al verificarsi di determinati eventi e contrassegnati dal doppio underscore __ iniziale(“__”) .
Attenzione: PHP tratta tutte le funzioni che iniziano con “__” come metodi magici. Si consiglia quindi di non implementare nomi di funzioni con doppio underscore __ iniziale.
Introduzione ai metodi magici in PHP
Di seguito un elenco dei principali metodi magici disponibili con PHP, essi verranno analizzanti dettagliatamente nei prossimi paragrafi:
__construct e __destruct
__construct(), è il metodo magico che PHP invocata per creare istanze di oggetti di classe. Accetta qualsiasi numero di argomenti. Il metodo __destruct, viene chiamato automaticamente dal motore PHP prima che l’oggetto sia distrutto. Risulta molto utile, ad esempio, per chiudere gli handle dei files, le connessioni al database o altre operazioni da eseguire prima che l’oggetto venga distrutto.
__call e __callStatic
Il metodo __call viene chiamato ogni volta che si cerca di eseguire un metodo che non esiste. Accetta 2 parametri: il nome della funzione (metodo chiamato) e l’array contenente i parametri passati al metodo. Per i metodi in un contesto statico si consiglia di utilizzare __callStatic().
__set e __get
Il metodo __set, viene chiamato quando si cerca di modificare una proprietà inesistente , mentre, il metodo __get, viene chiamato quando si tenta di leggere una proprietà inesistente. Il seguente esempio illustra l’applicazione dei 2 metodi.
__isset e __unset
Oltre ai metodi __set e __get ci sono altri due metodi utili per esaminare l’impostazione di una proprietà inesistente: __isset e __unset.
__isset() controlla se la proprietà è stata impostata o meno. Accetta un argomento, ossia la proprietà che si desidera testare. Si chiama il metodo __isset anche per la verifica di proprietà vuote, utilizzando la funzione empty(). __unset() svolge la funzione opposta a quella del metodo __isset(), riceve quindi un argomento che è il nome della proprietà che si vuole disinserire o eliminare.
__sleep e __wakeup
I metodi magici __sleep() e __wakeup() sono chiamati durante la serializzazione di oggetti. Questi forniscono un metodo per ripulire e ripristinare un oggetto prima della serializzazione; quindi in caso di serializzazione e deserializzazione di oggetti, PHP controllerà automaticamente, la presenza nella classe dei due metodi.
__sleep() viene chiamato quando l’oggetto di una classe sta per essere serializzato. Questo metodo non accetta alcun parametro e restituisce un array contenente i nomi delle proprietà da serializzare e si usa generalmente per operazioni di cleanup.
__wakeup () svolge la funzione opposta a quella del metodo __sleep(). Viene Chiamato quando l’oggetto di una classe sta per essere deserializzato, ad esempio, per riaprire le connessioni ai database o alle risorse esterne. Questo metodo non accetta alcun parametro né restituisce nulla. Si analizzi un esempio a riguardo:
__toString
Questa funziona viene utilizzata per restituire la rappresentazione come stringa di un oggetto.
Senza l’utilizzo del metodo __toString() il tentativo di visualizzare un oggetto come stringa restituirebbe un errore fatale.
Catchable fatal error: Object of class Person could not be converted to string in…
__set_state e __invoke
__set_state viene azionato quando si esporta un oggetto tramite la funzione var_export() ed accetta un array che avrà le coppie key/value impostate ai nomi/valori delle proprietà esportate. Il secondo, __invoke, viene richiamato quando si usa un oggetto come una funzione.
__clone
Come visto nella lezione dedicata alla clonazione degli oggetti, il metodo __clone fornisce tutte le funzionalità per la clonazione completa e indipendente di un oggetto: crea un nuovo oggetto identico all’originale copiando tutte le variabili membro.
Non appena PHP eseguirà la clonazione del nuovo oggetto, verrà invocato il metodo __clone() e l’oggetto clonato sarà accessibile.
Classi anonime in PHP
Cosa sono e come si utilizzano le classi anonime in PHP, delle classi prive di nome che non possono essere estese da altre classi.
Le funzioni anonime, come abbiamo visto, sono supportate dalla versione 5.3.0 di PHP e forniscono una serie di vantaggi, tanto è vero che sono presenti in molti linguaggi di programmazione come ad esempio Javascript (si pensi ad un handler per un evento) e Python (le funzioni lambda).
Oltre a queste PHP ha introdotto le classi anonime che hanno in comune con le funzioni il fatto di non essere associate ad un nome e quindi non poter essere istanziate più di una volta tramite il costrutto new. Inoltre non possono essere estese da altre classi comportandosi essenzialmente come delle classi final e questa è una limitazione importante per l’utilizzo che se ne può fare, anche se è intuitivo pensare che una classe anonima non nasce per essere estesa.
Per il resto possiedono tutte le caratteristiche di una classe comune, per cui è possibile aggiungere metodi e proprietà pubblici, privati e protetti, possono fare uso di traits, estendere una classe base o implementare un’interfaccia. L’implementazione di un’interfaccia è probabilmente l’ambito per cui meglio si presta una classe anonima, ad esempio per finalità di testing o di prototipazione.
Dal punto di vista tecnico infatti una classe anonima in PHP è una normale classe il cui nome viene generato in maniera casuale a runtime dall’interprete PHP e che quindi cambia da un’esecuzione alla successiva. Per questo il nome della classe deve essere considerato un dettaglio sul quale non fare affidamento.
Definire una classe anonima
A differenza delle funzioni anonime non si tratta di callable in senso stretto, quindi una classe anonima non può essere assegnata di per sé ad una variabile mentre è consentito, ovviamente, assegnarvi l’oggetto istanziato da essa. Di fatto la fase della definizione e dell’istanza avvengono in contemporanea e questo spiega l’incapacità di istanziare più di una copia della stessa classe anonima.
Per la definizione dobbiamo fare ricorso alla stessa sintassi utilizzata comunemente nella programmazione ad oggetti con la differenza dell’assenza del nome:
Come detto precedentemente è possibile fare ricorso a tutte le caratteristiche di una classe comune, quindi le seguenti sono tutte possibilità valide:
Passare variabili al costruttore
Gli oggetti vengono istanziati contestualmente alla definizione, quindi per passare variabili al costruttore è necessario seguire una sintassi particolare che può risultare poco intuitiva:
Istanze multiple
In realtà non è del tutto vero che non possa essere creata più di un’istanza della stessa classe anonima. Esistono infatti almeno due metodi per ottenere istanze multiple:
# clonare l’oggetto restituito dal costrutto new e modificare successivamente il nuovo oggetto;
# usare il pattern factory per la creazione degli oggetti.
Il pattern factory è sicuramente la soluzione più elegante e concettualmente corretta: è possibile arrivare a livelli molto complessi nella sua architettura ma l’esempio più semplice consiste nel creare una funzione simile alla seguente.
All’interno della stessa esecuzione tutti gli oggetti istanziati da una classe anonima condividono la stessa classe base: è possibile quindi confrontare due oggetti derivati da classi anonime per controllare se sono dello stesso tipo. Al contrario oggetti istanziati da classi anonime diverse non condividono la stessa classe base come si può comprendere da questo esempio:
Iterazione sugli oggetti in PHP
Perché rendere un oggetto iterabile? Analizziamo le interfacce Iterator e IteratorAggregate che permettono di rendere un oggetto compatibile con il costrutto foreach come se si trattasse di un array.
Rendere un oggetto iterabile
Dalla versione 5.0 PHP ha integrato all’interno della propria distribuzione standard la componente Standard PHP Library (SPL) che fornisce una serie di interfacce e classi per risolvere problemi comuni. Questa scelta è nata con la volontà di migliorare il supporto per la programmazione orientata agli oggetti nel linguaggio.
Tra le interfacce disponibili troviamo Iterator e IteratorAggregate che permettono di rendere un oggetto compatibile con il costrutto foreach come se si trattasse di un array mantenendo però l’essenza di oggetto che può integrare la propria logica business. Questa tecnica viene comunemente utilizzata dagli ORM (framework per l’accesso ai database) per eseguire un ciclo foreach su un set di risultati continuando ad aver accesso a metodi specifici.
Il codice mostra come iterare su un array presente in una classe che ha rapporti con diverse istanze di un’altra classe.
Implementare l’interfaccia Iterator
In questo caso però stiamo dando anche accesso in scrittura all’array presente nella classe e in generale si tratta di una soluzione poco elegante, ma possiamo implementare l’interfaccia Iterator aggiungendo 5 metodi (tutti public e senza parametri) alla nostra classe oltre ad una variabile per tenere traccia della posizione attuale durante l’iterazione.
Durante un ciclo foreach viene richiamato il metodo rewind() portando il cursore alla posizione iniziale, quindi viene chiamato il metodo valid() per controllare se la posizione attuale è disponibile, in caso positivo vengono chiamati i metodi key() e current() per ottenere l’indice e il valore attuali. A questo punto viene chiamato il metodo next() e il ciclo ricomincia dal metodo valid() fino a che non si ottiene un valore false.
Leggendo attentamente l’ordine delle chiamate ci si rende conto che alla fine di un ciclo foreach il cursore interno è spostato all’ultima posizione e non viene reimpostato sullo stato iniziale. Un esempio concreto di classe che implementa Iterator è il seguente:
Semplificare il costrutto con l’interfaccia IteratorAggregate
Benché sia semplice implementare l’interfaccia Iterator, questa caratteristica ha un utilizzo prevalentemente in situazioni standard come l’iterazione su un array interno. Per evitare di reinventare la ruota è possibile sfruttare un’altra interfaccia, IteratorAggregate, insieme alle classi di PHP SPL.
L’interfaccia IteratorAggregate espone un singolo metodo, getIterator(), da richiamare senza parametri che restituisce un oggetto che implementa l’interfaccia Traversable (l’interfaccia che viene estesa da Iterator). Quindi l’esempio precedente si semplifica così:
Funzioni anonime e classe Closure
Scopriamo cosa sono e come vengono definite le funzioni anonime in PHP per poi imparare come utilizzare la classe Closure.
Diversi linguaggi di programmazione supportano costrutti per la definizione a runtime di una funzione anonima: con questo termine si intende una funzione comune in tutte le sue caratteristiche ad ogni altra funzione ma senza un’associazione esplicita con un nome. Concettualmente si tratta di funzioni che dovrebbero essere considerate one shot, cioè da chiamare una sola volta nel punto stesso della definizione.
Un linguaggio dove questo paradigma è estremamente comune è Javascript per via del suo ampio utilizzo delle metodologie event-driven, cioè azioni da avviare in base agli eventi che si sono verificati. In senso lato queste funzioni prendono il nome di callback.
L’implementazione in PHP
Dalla versione 5.3.0 è possibile definire funzioni anonime utilizzandole direttamente come callback all’interno di una chiamata di funzioni o assegnarle ad una variabile per poterle richiamare più di una volta. La sintassi è estremamente semplice e richiede l’uso della parola chiave function omettendo il nome della funzione e facendo seguire, dove necessario, il carattere “;” alla dichiarazione della funzione.
Una volta acquisita la funzione anonima in una variabile è possibile eseguirla attraverso l’operatore () a cui si possono passare i parametri ordinati. In alternativa è possibile usare le funzioni predefinite call_user_func e call_user_func_array. Queste funzioni accettano come primo parametro la variabile contenente la funzione anonima ma differiscono per il modo in cui devono essere specificati gli argomenti da passare: nel primo caso saranno gli argomenti della funzione predefinita mentre nel secondo caso saranno raccolti all’interno di un array. Queste tre modalità sono quindi valide:
Come si può vedere dall’esempio, in realtà la sintassi è la stessa che si utilizzerebbe dichiarando una funzione non anonima e passando il nome di quella funzione all’interno della variabile $sum.
Accedere alle variabili esterne
Le funzioni anonime hanno uno scope estremamente rigido e di default possono accedere esclusivamente ai parametri che vengono loro passati, non possono cioè usare nessuna variabile dichiarata all’esterno della stessa. L’accesso alle variabili esterne deve essere reso esplicito tramite la parola chiave use in questo modo:
In realtà il valore della variabile esterna viene copiato all’interno della funzione anonima nel momento in cui questa viene definita (e non nel momento in cui viene chiamata). Per poter accedere al valore attuale della variabile esterna è necessario passarla per riferimento tramite l’operatore & che precede il nome del parametro nella dichiarazione della funzione.
La classe Closure
Nonostante questa sintassi specifica, in PHP, dalla versione 5.4.0, una funzione anonima è in realtà un’istanza della classe Closure, il cui costruttore è privato rendendo impossibile usare l’operatore new. Ogni oggetto derivato da questa classe ha due metodi: bindTo e call (questo solo dalla versione 7.0).
Il metodo bindTo permette di associare la closure ad uno specifico oggetto con accesso alle proprietà e ai metodi dello stesso, indipendentemente dal livello di visibilità: è possibile quindi modificare o leggere proprietà pubbliche, protette o private. Questo metodo accetta due parametri: il primo è il contesto della variabile, in pratica il valore che assumerà la variabile $this all’interno della funzione mentre il secondo è lo scope che sarà imposto alla funzione permettendo o negando l’accesso ai vari livelli di visibilità .
Come si può notare dall’esempio lo scope può essere il nome della classe o, in alternativa, l’oggetto istanziato. La chiamata al metodo bindTo restituisce una copia della funzione anonima modificata per essere associata all’oggetto indicato.
Dalla versione 7.0 di PHP la classe Closure presenta anche il metodo call che accetta come primo parametro l’oggetto a cui la funzione deve essere temporaneamente associata, seguito da un numero variabile di parametri corrispondenti agli argomenti da passare alla funzione anonima. In questo modo è possibile ottenere il risultato della chiamata alla funzione anonima senza passare per un binding temporaneo. L’esempio precedente ad esempio può essere riscritto in questo modo:
La classe Closure presenta anche un metodo statico per associare una funzione anonima ad un oggetto: bind. Usando lo stesso esempio precedente:
Introspection in PHP
Impariamo ad utilizzare le funzioni per l’Introspection (“introspezione”) di PHP, o capacità introspettiva degli oggetti, al fine di analizzare e ottenere informazioni utili su classi, interfacce, proprietà e metodi.
L’Introspection (introspezione) è, come suggerisce il nome, la capacità introspettiva degli oggetti, grazie alla quale possiamo analizzare e/o reperire informazioni utili su classi, interfacce, proprietà e metodi.
Alcune funzioni di Introspection
L’introspezione in PHP, permette di estrarre le informazioni di base sulle classi, come il loro nome, il nome della loro classe genitore e così via.
PHP offre un grande numero di funzioni che è possibile utilizzare per realizzare questo compito. Di seguito analizzeremo un esempio su come utilizzare alcune delle funzioni di introspezione del PHP.
Analizziamo innanzitutto alcune di queste funzioni:
Per capire meglio il funzionamento dell’Introspection e le varie funzioni ad essa ricollegate è possibile proporre l’esempio seguente:
L’output generato dal codice, proposto in precedenza, sarà il seguente:
Classe: Sample
Classe di derivazione: Introspezione
Si, Sample sottoclasse di Introspezione
Classe: Introspezione
Nell’esempio, controlliamo se la classe data è stata definita con il metodo class_exists(). Questo metodo, accetta un argomento stringa che rappresenta il nome della classe da controllare e un valore booleano (opzionale) il quale indica se chiamare o meno __autoload per default.
I metodi get_class() e get_parent_class() restituiscono, rispettivamente, il nome della classe di un oggetto o il nome della classe del suo genitore. Entrambi accettano come argomento un’istanza di un oggetto.
Il metodo is_subclass_of() accetta, come primo argomento, un’istanza di un oggetto e, come secondo argomento, una stringa che rappresenta il nome della classe genitore. Questa funzione restituisce TRUE se l’oggetto “$sample” appartiene ad una classe che è una sottoclasse di “Introspezione”.
Si analizzi un secondo esempio basato questa volta sull’utilizzo delle seguenti funzioni:
L’output generato dal codice sarà il seguente:
Possiamo notare come il metodo interface_exists() sia molto simile al metodo class_exists() presente nel primo esempio. Infatti, questo metodo, controlla se l’interfaccia è stata definita e accetta un argomento stringa che rappresenta il nome dell’interfaccia e un valore booleano (opzionale) il quale indica se chiamare o meno __autoload di default.
Il metodo get_declared_classes() non accetta argomenti e restituisce un array con i nomi di tutte le classi definite (Il numero delle classi visualizzate varia in base a quali librerie sono state compilate/caricate in PHP).
Entrambi i metodi get_class_method() e get_class_vars() prendono in ingresso il nome della classe; mentre il primo restituisce un array con i nomi dei metodi della classe, il secondo restituisce un’array con le proprietà di default pubbliche della classe.
Conclusioni
Abbiamo visto come utilizzare alcune delle funzioni (le più utili) di introspezione in PHP, rafforzando quindi le basi dell’OOP. Nel prossimo analizzeremo un’API che offre funzionalità simili all’introspezione: l’API Reflection.
Reflection in PHP
Come utilizzare le funzioni di Reflection in PHP, uno strumento molto potente in grado di esaminare le caratteristiche e le funzionalità di un oggetto estraendo informazioni da esso.
Abbiamo analizzato l’utilizzo di alcune delle funzioni di introspezione in PHP.
Adesso parleremo di Reflection in PHP, ovvero una metodologia con la quale chiedere ad un oggetto delle informazioni riguardanti i suoi metodi ed i suoi attributi.
PHP, supporta la riflessione mediante l’API Reflection, la quale mette a disposizione un gran numero di metodi e di classi, utili alla Reflection.
PHP, a partire dalla versione 5, fornisce la classe ReflectionClass, con cui effettuare operazioni di riflessione su classi, metodi e interfacce e per estrarre le informazioni su tutti i componenti di una classe.
Esempio pratico di Reflection in PHP
In questo esempio utilizzeremo le stesse dichiarazioni delle classi Sample e Student
e dell’interfaccia Person impiegate nel precedente capitolo:
Nell’esempio precedente, abbiamo poi istanziato la ReflectionClass, passando come parametro nel costruttore
il nome della classe. Poi, abbiamo richiamato i metodi (alcuni dei tanti a disposizione) relativi alla classe ReflectionClass.
Il metodo getInterfaceNames() restituisce un array con il nome dell’interfaccia
che implementa la classe. Per visualizzare il nome dell’oggetto ReflectionClass si utilizza, come visto nel precedente capitolo,
il metodo GetName(). Il metodo getMethods() restituisce un array di oggetti di tipo ReflectionMethod e
un parametro (opzionale) per filtrare i risultati in base a metodi con determinati attributi:
ReflectionMethod::IS_STATIC, IS_PUBLIC, IS_PROTECTED, IS_PRIVATE, IS_ABSTRACT, e IS_FINAL.
Infine, il metodo getConstants, resituisce un array associativo contenente i nomi e i valori delle costanti.
PHPDoc: documentare una classe
Utilizzare PHPDoc per la documentazione delle classi con lo scopo di rendere il nostro codice PHP maggiormente leggibile e condivisibile.
Lo scopo principale della programmazione ad oggetti è rappresentato dalla necessità di riutilizzare e condividere codice, ma scrivere codice ben formattato non è sufficiente.
Nominare in maniera descrittiva le classi, i metodi e le variabili aiuta certamente ma non fornisce un contributo importante nella comprensione immediata dei parametri richiesti da un certo metodo o da un risultato. Ciò è ancora più vero in un linguaggio loosely typed come PHP dove le variabili non possono essere totalmente definite come appartenenti ad uno tipo (integer, string, array, etc.), anche se nella versione 7 del linguaggio sono stati fatti passi da gigante in questa direzione.
Oltre alla necessità di rendere il codice più comprensibile ad altri componenti del team o ad utilizzatori futuri delle classi, la documentazione permette agli IDE di aumentare le capacità di autocompletamento e di segnalare eventuali errori nelle chiamate di metodi, ad esempio perché è stato fornito un parametro del tipo sbagliato.
PHPDoc
Per rispondere a questa esigenza è nato uno standard informale per la documentazione delle classi inizialmente supportato solo da software per la generazione di documentazione come phpDocumentor e in seguito da vari IDE. Oggi esistono molte soluzioni per la generazione di documentazione che si differenziano per l’aspetto finale del risultato e, molto marginalmente, per le sintassi riconosciute.
Dal punto di vista pratico si tratta di un adattamento di Javadoc, sistema analogo per il linguaggio Java, e ogni direttiva PHPDoc deve essere inserita all’interno di un commento di tipo DocBlock. Un commento DocBlock si riferisce alla prima dichiarazione valida seguente dove per dichiarazione s’intende la definizione di una classe, la definizione di un metodo, la dichiarazione di una variabile, la definizione di una costante, etc. Fa eccezione il primo DocBlock che, se posizionato all’inizio del file, rappresenta una documentazione per l’intera pagina quando la prima dichiarazione successiva ha un proprio DocBlock, anche vuoto.
La sintassi generale di un commento DocBlock prevede l’apertura dello stesso con la sequenza /**, la presenza di un carattere * all’inizio di ogni riga seguente e la chiusura dell’intero blocco con */. Ogni blocco è costituito da un commento valido per quanto riguarda il linguaggio di programmazione. All’interno del DocBlock è prevista la presenza di una descrizione breve, rappresentata dalla prima riga di testo (indicativamente della lunghezza di una linea di testo) e di una descrizione facoltativa più lunga separata dalla prima per mezzo di una riga vuota.
Tag
Oltre alle semplici descrizioni testuali, molto utili per gli sviluppatori, ogni DocBlock può contenere un numero indefinito di tag. Ogni tag preceduto dal simbolo @ prevede una sintassi specifica e descrive una certa funzionalità della definizione a cui è associata.
Alcuni tag prevedono di specificare il tipo di parametro a cui si riferiscono scegliendolo tra una serie di valori:
Tipologie di valori
I valori predefiniti disponibili sono:
Se il parametro accettato o restituito può essere di diversi tipi è possibile indicarlo con mixed o elencare i tipi accettati separandoli con il carattere |. In questo modo è possibile anche indicare che in alcuni casi una variabile può assumere il valore nullo in modo che lo sviluppatore sappia che può aspettarsi questo risultato indicandolo con il tipo null.
Per i parametri di tipo array o per collezioni (che implementano le interfacce per le iterazioni) esiste una doppia sintassi: indicare il parametro facendo seguire al tipo contenuto all’interno dell’array i caratteri [] o indicare il nome della collezione e il suo contenuto all’interno di < e >. Di seguito le alternative valide:
Per quanto riguarda i parametri di tipo oggetto, in aggiunta rispetto al tipo predefinito object è possibile indicare il nome della classe di cui sono istanza eventualmente completa di namespace (fully qualified domain name).
Se si tratta di un parametro restituito è possibile usare i tipi predefiniti speciali:
PHPDoc: documentare i metodi di una classe
Dopo aver analizzato i criteri generali per l’utilizzo di PHPDoc e la modalità con cui si documentano i parametri e le variabili, vediamo come documentare le altre parti del codice e in particolare i metodi.
Metodi e proprietà magiche
La documentazione di una classe da rendere pubblica dovrebbe sempre essere presente per permettere agli sviluppatori che ne vorranno beneficiare di poterne comprendere facilmente il funzionamento. In questo caso il compito è molto semplice dato che di base l’unica parte necessaria è la descrizione breve, che non dovrebbe mai mancare, mentre una descrizione più estesa è auspicabile dove abbia senso.
Se nella nostra classe facciamo uso dei metodi magici __get(), __set() e __call() sarà più complicato per un utilizzatore finale comprendere rapidamente che cosa si può aspettare, dato che per definizione una classe che utilizza questi metodi non dichiara esplicitamente il nome degli stessi. Per questo motivo possiamo avvalerci dei tag @property e @method.
Il tag @property inserito nel DocBlock principale della classe permette di indicare in maniera esplicita le proprietà non dichiarate, ma accessibili tramite i magic methods, che la classe stessa si aspetta di avere a disposizione. Ovviamente in questo caso non si richiede che vengano elencate tutte le proprietà accessibili (verrebbe meno lo scopo stesso del costrutto). La sintassi, molto semplice, è cosi composta: un tag property per ogni elemento da rendere esplicito, seguito tipo, nome e da una descrizione facoltativa dello scopo.
In maniera analoga il tag @method permette di dichiarare metodi non definiti ma invocabili tramite la funzione magic __call(). In questo caso la sintassi prevede, dopo il tag, il tipo di valore che viene restituito dal metodo, il suo nome e, tra parentesi tonde, l’elenco dei parametri attesi con i rispettivi tipi. Anche in questo caso è possibile aggiungere una descrizione facoltativa.
Se non si utilizzano i namespace, scelta oggi sconsigliata, è possibile indicare l’appartenenza di una classe ad un gruppo più ampio tramite il tag @package. Questo è applicabile all’intero file o alla singola classe, ma la prassi moderna richiede la dichiarazione di una sola classe per file, rendendo quindi superflua questa scelta.
Nel complesso la documentazione di una classe con metodi o proprietà magiche assomiglierà quindi a questa:
Documentare correttamente un metodo o una funzione
PHPDoc tratta un metodo e una funzione nella stessa maniera e prevede un costrutto che ne permette una documentazione esaustiva. Per prima cosa anche in questo caso è consigliabile iniziare con la descrizione sintetica del metodo, eventualmente aggiungere quella prolissa e quindi indicare i tag.
I tag che non dovrebbero mai mancare sono @param, per indicare i parametri del metodo, e return per il valore restituito dallo stesso: entrambi possono essere omessi se la funzione non ha parametri o se non restituisce valori. Ovviamente il tag @param deve essere ripetuto tante volte quanti sono i parametri richiesti. Nella lezione precedente sono elencati i tipi validi.
Se all’interno di un metodo viene emessa un’eccezione è possibile indicarlo tramite il tag @throws che deve essere seguito dal nome di classe completo dell’eccezione lanciata. In linea teorica sarebbe possibile usare il tipo padre da cui l’eccezione deriva, ma è più corretto indicare quest’ultima. Nel caso di chiamate a funzioni che a loro volta possono emettere eccezioni è necessario aggiungere un tag @throws per ognuna di esse in modo che l’utilizzatore sappia quali eccezioni aspettare.
Sia le funzioni che i metodi pubblici sono accessibili dal codice estraneo alla libreria distribuita avendo una visibilità esterna universale, ma in alcuni casi questa è rivolta esclusivamente ad un utilizzo interno. Per definire un metodo che è stato pensato per essere invocato direttamente dall’utilizzatore della libreria è possibile usare il tag @api, in caso contrario il tag da usare è @internal.
La documentazione di un metodo assomiglierà al seguente esempio:
Attribuzione di proprietà
Se l’intenzione è quella di distribuire il codice è consigliabile specificare le informazioni sulla proprietà intellettuale tramite i tag:
Ognuno di questi tag può essere associato all’intero file, ad un classe o ad un metodo specifico. In questo modo è possibile tributare il giusto riconoscimento intellettuale all’autore di un frammento di codice che utilizziamo all’interno della nostra libreria (se per il codice che abbiamo utilizzato avevamo i diritti necessari).
Utilizzi avanzati di PHPDoc
Analizziamo i tag di PHPDoc e le annotazioni per documentare classi, metodi e funzioni utilizzati in un progetto PHP sviluppato seguendo il paradigma ad oggetti.
L’idea alla base di una libreria di classi è quella dell’evoluzione nel tempo: alcuni metodi vengono aggiunti, altri modificati o eliminati. Anche la documentazione deve poter tenere traccia delle modifiche in relazione alle versioni del progetto.
Il primo tag di questo tipo da considerare è @version che permette di indicare le versioni di singole parti ma è consigliabile utilizzarlo per intere librerie, documentando interi file. Per semplificare tale task è possibile riferirsi a variabili del sistema di versioning che verranno rimpiazzate con la versione corrente dal software usato per generare la documentazione.
Per ottenere questo effetto è necessario usare i vettori nella forma nome-del-sistema-di-versioning: $vettore$. Ad esempio per ottenere la versione da un repository GIT la sintassi diventa:
@since indica invece la prima versione della libreria in cui è apparsa la funzionalità a cui si riferisce (una classe, una costante o un metodo). E’ possibile inserire per lo stesso elemento commentato più di un tag @since con cui illustrare, ad esempio, le modifiche dell’interfaccia. In questo modo con l’ultima versione della documentazione si può avere un’idea di cosa è disponibile in una release precedente.
Eliminare un metodo da un’interfaccia pubblica non è mai una soluzione facile perché introduce incompatibilità con i software che adottavano versioni precedenti, si consiglia quindi di dichiarare come deprecato il metodo, mantenerlo così fino alla prossima major release e solo dopo eliminarlo.
Dichiarare un metodo deprecato equivale a dire “continua ad usarlo a tuo rischio e pericolo, in una prossima versione verrà eliminato“. Per questi casi è possibile usare il tag @deprecated indicando facoltativamente la prima versione in cui non sarà più presente ed inserendo un’eventuale descrizione che specifica i motivi dell’indicazione o le alternative preferibili.
Riferimenti esterni
Data l’ereditarietà dell’OOP possono ricorrere casi in cui la documentazione di un metodo della classe figlia è uguale a quello della classe parent, anche se quest’ultimo è stato sovrascritto. In tali casi una soluzione potrebbe essere copiare il DocBlock della classe padre al posto di quello della classe figlia, in modo che la documentazione generata contenga anche tali informazioni. Vi è però la necessità di aggiornare periodicamente la versione copiata con il rischio di perdere alcune modifiche nella documentazione.
Per questo motivo è possibile usare il tag @inheritDoc con cui indicare un riferimento all’elemento corrispondente della classe padre. La sintassi in questo caso l’elemento deve essere inserito come tag inline, circondato da parentesi graffe:
Benché nella maggior parte dei casi questa sintassi preveda il tag come unico elemento nel DocBlock, in maniera facoltativa è possibile aggiungere un’ulteriore descrizione da riferire esclusivamente al metodo della classe figlia. In questo modo è possibile ad esempio dettagliare un funzionamento particolare pur mantenendo la stessa interfaccia, senza doverla ridefinire.
Quando il riferimento è invece ad un elemento diverso, è possibile usare il tag @see che richiede come parametro obbligatorio il nome del metodo o della proprietà a cui riferirsi con la notazione tipica delle chiamate statiche (nome della classe, doppio carattere “:” e nome della proprietà o del metodo).
In alternativa, lo stesso tag può essere usato anche per indicare un riferimento ad una pagina web dove mostrare il funzionamento con un esempio concreto o un tutorial; in questo caso l’URL deve essere indicato ovviamente come assoluto. Entrambi gli utilizzi del tag consentono un terzo parametro per specificare il motivo del riferimento.
Per indicare un esempio esiste un tag ancora più indicato: @example; la sintassi è la stessa di @see utilizzato con URL esterno e una descrizione aggiuntiva.
Tag vs. Annotazioni
Da alcuni anni si è diffuso anche per varie librerie PHP il concetto di annotazione, la libreria Doctrine e il framework Symfony ne fanno ad esempio un uso massiccio.
Un’annotazione è un’indicazione inserita in un commento, nel caso di PHP all’interno di un DocBlock, con cui aggiungere una funzionalità (funzione, metodo o classe) ad un blocco di codice. Questo pattern è più comune in linguaggi di programmazione che devono passare per un compilatore o uno pseudo compilatore, e il vantaggio principale consiste nel rendere il corpo di una funzione più asciutto, limitandolo al codice utile e nascondendo le parti ripetitive. Concettualmente un’annotazione può essere considerata come una decorazione.
Il problema principale risiede nel fatto che le annotazioni in PHP hanno la stessa forma dei tag di PHPDoc, quindi è possibile che un software (o un IDE) restituisca un errore non riconoscendo i tag. Per questo motivo ogni annotazione deve avere come prefisso il namespace a cui si riferisce, relativo alla libreria da cui dipende.
Generare la documentazione
Vista la diffusione di PHP oggi esistono diversi framework di testing o generatori di documentazione open source. Tra questi il primo a raggiungere una forma matura è stato phpDocumentor usato da Zend Framework, PropelOrm ed eZ publish. Sono poi nati altri progetti come Sami, usato da Symfony o ApiGen, Doctrine e CakePHP.
I tag supportati sono sostanzialmente gli stessi, a maggior ragione ora che PHPDoc si sta avviando a diventare un possibile PSR. La differenza principale tra i vari software è data dal formato dell’esecuzione, in genere personalizzabile tramite template.
Di base questi progetti generano file HTML statici da caricare per la consultazione, la generazione della documentazione per un progetto di grandi dimensioni può quindi richiedere qualche minuto. In genere i template, pur variando nell’aspetto, mantengono una struttura simile con una colonna contenente l’elenco dei namespace e delle classi presenti in ordine gerarchico, al click sul nome di una classe viene visualizzata la documentazione creata.
I pattern nella programmazione ad oggetti in PHP
Un’introduzione ai principali design pattern nella programmazione ad oggetti in PHP, schemi progettuali che rispondono a problemi ricorrenti offrendo soluzioni riproducibili.
La programmazione orientata ad oggetti è un paradigma particolarmente adatto alla modellazione della realtà nel contesto di un software. Un vantaggio consiste nella modularità del codice che deriva dall’applicazione del modello con conseguente miglioramento nella gestione di manutenzione e sviluppo.
Nella progettazione di software sviluppato in ottica OOP ricorrono spesso paradigmi e relazioni noti tra i vari modelli. Alcuni dei paradigmi sono stati studiati e codificati per fornire una base teorica al loro utilizzo, identificandone vantaggi e svantaggi.
Nonostante nella maggior parte dei casi l’utilizzo dei pattern codificati equivalga all’adozione di buone prassi, è necessario evitare di over ingegnerizzare il codice per non complicarne la leggibilità. Dobbiamo cercare infatti di raggiungere un equilibrio tra necessità dell’applicazione e pattern che permettono di aumentare manutenibilità, testabilità e modularità .
Classificazione dei design pattern
I design pattern rispondono a problemi ricorrenti offrendo soluzioni riproducibili: essi sono stati classificati all’interno di diverse categorie da Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, autori del libro “Design Patterns: Elements of Reusable Object-Oriented Software“, il testo che ha contribuito alla diffusione di questi concetti.
Le tre categorie individuate da questi autori sono:
# pattern comportamentali;
# pattern creazionali;
# pattern strutturali.
I pattern comportamentali identificano soluzioni alle interazioni tra oggetti diversi rendendo l’implementazione irrilevante per l’utilizzatore che si riferisce esclusivamente ad una o più interfacce. Un esempio è dato dal pattern Iterator che permette di ottenere una serie di risultati da parte di un oggetto all’interno di un ciclo, senza conoscere da dove provenga o come sia mantenuta questa serie.
I pattern creazionali identificano modalità per istanziare oggetti allo scopo di rendere facilmente modificabile il processo di inizializzazione (ad esempio il pattern Factory) oppure per sostituire il comportamento di default del linguaggio (ad esempio il pattern Singleton). Il campo di applicazione più immediato è costituito dalla creazione di oggetti complessi dipendenti da altri oggetti senza che l’utilizzatore abbia conoscenza della fase d’istanza.
Infine i pattern strutturali hanno lo scopo di adattare il codice esistente a interfacce nuove e per loro natura sono i più versatili e utilizzati in ambito Web. Il vantaggio principale nell’utilizzo di questi pattern consiste nella possibilità di utilizzare librerie esterne in un progetto senza legare il codice utilizzatore a quella particolare libreria: per mantenere lo stesso funzionamento è sufficiente creare una nuova implementazione dell’interfaccia attesa con una libreria diversa.
Non bisogna però fare l’errore di considerare i pattern come un set definito e stabile nel tempo: oltre a quelli identificati dalla Gang of four, come vengono definiti i quattro autori del testo citato, esistono altri pattern introdotti successivamente. Inoltre la scelta di un pattern rispetto ad un altro che risolve un problema simile può essere dettata da ragioni contingenti non necessariamente stabili nel tempo: ad esempio per l’accesso ad una base dati i due pattern principali utilizzati sono Active Record e Repository, con il primo che ha avuto un enorme impatto sugli ORM di tutti i linguaggi di programmazione, spinto anche dalla sua adozione da parte di Rails, mentre il secondo sta guadagnando il favore degli sviluppatori per i vantaggi che comporta in termini di testabilità.
Applicazione dei concetti a PHP
PHP, nato come linguaggio puramente procedurale, si è evoluto notevolmente per quanto riguarda il supporto OOP, pur non raggiungendo, per il momento, il livello di altri linguaggi nativamente OOP. Ad oggi è possibile applicare la maggior parte dei pattern codificati allo sviluppo in PHP e i framework più utilizzati come Symfony e Laravel ne fanno uso.
I pattern su cui concentreremo l’attenzione nei prossimi capitoli sono quelli associati ad un’interfaccia: sono infatti le interfacce il concetto principale da comprendere per accedere ad uno studio proficuo dei pattern. In quest’ambito il loro funzionamento è il seguente: il nostro software definisce una serie di interfacce che rappresentano il contratto che è necessario rispettare per poter fornire una determinata funzione, quindi definiamo un’implementazione specifica che rispetti tale contratto.
I vantaggi principali nell’applicazione di queste tecniche sono:
Introduzione alla OOP
Che cosa è e quali vantaggi porta al programmatore PHP 5 la programmazione orientata agli oggetti
La programmazione orientata agli oggetti (Object Oriented Programming, da cui l’acronimo OOP) è uno stile fondamentale di programmazione (o paradigma) che si basa principalmente sul raggruppamento all’interno di un’unica entità (la classe) delle strutture dati e delle procedure che operano su di esse.
Istanziando la classe, che rappresenta fondamentalmente una struttura astratta, è possibile creare “oggetti” concreti (le cosiddette istanze) dotati di proprietà (dati/variabili) e metodi (procedure/funzioni) che operano sui dati dell’oggetto stesso.
Per quanto complessa possa essere questa definizione, potete rassicurarvi sapendo che quest’ultima racchiude la maggior parte dei concetti fondamentali della OOP.
OOP e PHP
Prima della nascita della versione 5, PHP era un linguaggio sostanzialmente procedurale, ovvero fortemente basato sulle funzioni, interne o user-defined. Nonostante fosse dotato di un singolare modello ad oggetti, questo non poteva assolutamente essere considerato completo e non permetteva di mettere in pratica i veri concetti della programmazione OOP. Di conseguenza, il vecchio modello non consentiva di usufruire di tutti i vantaggi di questo paradigma.
Con il rilascio della versione 5, le cose sono decisamente cambiate. Ora PHP dispone di un solido modello OOP completo di tutti i più importanti strumenti che questo offre. Dunque, anche se PHP continua a non essere un linguaggio nativamente orientato agli oggetti (come ad esempio Java o altri), permette ora di realizzare solide applicazioni OOP.
I vantaggi del modello ad oggetti
Nel mio precedente articolo denominato Codice procedurale vs OOP sono ben elencati tutti i vantaggi derivanti dall’utilizzo e dalla padronanza del modello ad oggetti (compresi, ovviamente, gli eventuali svantaggi) con tanto di dettagliate descrizioni e confronti con il modello procedurale. Riporto qui solo i principali benefici che lo sviluppatore PHP trae adottando una struttura OOP:
# presenza di regole precise da seguire
# migliore qualità dello sviluppo di applicazioni realizzate in team
# modularità dell’applicazione
# manutenibilità del progetto
# estensibilità dell’applicazione
# possibilità di usufruire dei design patterns
# possibilità di usufruire dei principali web services
# presenza di nuovi costrutti
In sostanza, oltre a garantire maggiore modularità ed estensibilità rispetto alla controparte procedurale, il modello ad oggetti consente di sviluppare applicazioni PHP di nuova generazione usufruendo degli strumenti (web services) e delle modalità logiche (design patterns) utili alla loro nascita.
Creare le classi
Come creare le classi, gli elementi fondamentali della programmazione ad oggetti con cui raggruppare variabili e funzioni
Come abbiamo visto nella lezione precedente, tramite il modello orientato agli oggetti è possibile centralizzare più funzionalità all’interno di un’unica postazione. Questa postazione prende il nome di classe. Una classe è una sorta di “involucro” che racchiude variabili e funzioni che condividono funzionalità e risorse, che rimangono in attesa di essere utilizzate tramite le istanze della classe stessa. In PHP una classe è dichiarata tramite la keyword class seguita dal nome della classe e dall’implementazione della stessa racchiusa tra parentesi graffe:
Una volta dichiarata la classe, possiamo passare all’inserimento delle funzionalità, tramite la dichiarazione di variabili e funzioni. Nello snippet successivo verrà utilizzata la parola chiave public che precede le varie componenti della classe: per ora basta sapere che questa keyword è necessaria ai fini della corretta compilazione dello script, proprio come nel caso di class o function.
Ora che abbiamo creato le funzionalità della nostra classe, ci occorre un modo per richiamarle: questo diventa possibile tramite le istanze della classe (o oggetti). Per dichiarare un’istanza si usa l’operatore new seguito dal nome della classe ed opzionalmente da una lista di parametri richiesti dalla funzione costruttore (che studieremo tra qualche lezione). Nel nostro caso non abbiamo il costruttore, dunque la creazione di un’istanza avviene nel seguente modo:
Ovviamente, per ogni classe può essere istanziato un numero illimitato di oggetti, ognuno con i propri parametri impostati al valore definito dalla classe nella sua dichiarazione iniziale:
Proprietà e metodi
Come specificare all’interno delle classi le proprietà e i metodi: le informazioni che caratterizzano la classe
Nelle precedenti lezioni abbiamo utilizzato le parole “variabili” e “funzioni” per indicare le diverse componenti di una classe. In realtà, anche se concettualmente corrette, queste definizioni sono errate. Nella OOP queste componenti vengono chiamate rispettivamente proprietà (o membri) e metodi. Ecco un semplice snippet che chiarisce questo concetto:
Questa separazione è molto importante: mentre le normali variabili e funzioni sono componenti libere o di primo livello, le proprietà ed i metodi di una classe appartengono esclusivamente a questa classe (ed eventualmente alle classi che ereditano).
Uso delle proprietà
Come abbiamo potuto notare nel corso degli esempi precedenti, è possibile assegnare un valore alle proprietà di una classe direttamente nella fase di dichiarazione delle stesse. Ciò che è importante sapere inoltre, è che non si è limitati ai valori semplici (interi, float e boolean), ma è possibile utilizzare anche i valori composti, come gli array, oppure le costanti. Ecco un esempio:
Non si ha però a disposizione tutta la libertà che si avrebbe nel caso delle semplici variabili. Le seguenti operazioni sono infatti errate:
Uso dei metodi
I metodi di una classe si comportano esattamente allo stesso modo delle funzioni di primo livello, tranne per il fatto che i primi riconoscono tutte le keyword appartenenti alla classe (come nel caso di $this che abbiamo studiato nella lezione precedente) mentre, ovviamente, i secondi no. È dunque possibile dichiarare liste variabili di argomenti, impostare valori di default agli argomenti e restituire riferimenti al posto dei valori:
Istanziare una classe: il costruttore
Il metodo costruttore: decidere il comportamento dell’oggetto al momento della sua creazione
Come ho accennato in precedenza, è possibile passare dei parametri dopo il nome della classe nella fase di creazione delle istanze. Cercheremo ora di capire perché ciò avviene ed in che modo è possibile personalizzare la creazione delle istanze.
Prendiamo come esempio la classe MyClass :
Come possiamo vedere, la lista dei parametri è vuota in entrambe le dichiarazioni: addirittura è possibile omettere le parentesi se la classe non presenta un costruttore pubblico. Ciò significa che in questo caso non abbiamo modo di influire sui parametri della classe mentre stiamo istanziando un oggetto: tutte le istanze avranno inizialmente le stesse proprietà impostate agli stessi valori. Questo rappresenta ovviamente un limite, ma grazie al metodo costruttore, è possibile decidere quale comportamento deve assumere l’oggetto quando viene creato.
Il metodo costruttore infatti, viene richiamato nella fase di creazione dell’oggetto e si dichiara tramite la funzione (o metodo magico) __construct (esistono svariati metodi magici nella OOP di PHP, che sono contrassegnati dal doppio underscore “__” iniziale e che verranno analizzati dettagliatamente in seguito). Ecco un esempio di classe avente un costruttore:
La funzione __construct viene richiamata in fase di creazione delle istanze, in questo modo, è possibile fare assumere alla classe qualsiasi comportamento si voglia: nel nostro caso abbiamo dato la possibilità di specificare i valori delle proprietà a e b. Dato che nella funzione non abbiamo inserito nessun controllo sul tipo dei parametri, possiamo passare numeri (40, 40), stringhe (“a”, “b”) e così via.
Ovviamente in questo caso, non possiamo omettere i parametri nella fase di creazione delle istanze, altrimenti PHP genererebbe un E_WARNING che ci avvisa della mancata presenza dei parametri richiesti dal costruttore:
Sfruttando la potenza del metodo costruttore, possiamo essere veramente creativi compiendo svariate operazioni ed imponendo controlli al suo interno:
Conclusione
Tramite l’uso del metodo costruttore, che rappresenta il metodo più importante in assoluto nella OOP, è possibile personalizzare il comportamento della classe in fase di creazione delle istanze e dunque favorire l’impostazione dei parametri direttamente tramite un’unica linea di codice. Ma se esiste un metodo che viene richiamato quando si creano gli oggetti, è altrettanto vero che ne esiste un altro che viene richiamato quando si distruggono: quest’ultimo prende il nome di distruttore e che analizzeremo di seguito.
Distruzione di un oggetto
Il metodo distruttore: come cancellare un oggetto e ripulire il sistema
Dopo avere imparato ad utilizzare il metodo costruttore ed a personalizzare la fase di creazione delle istanze tramite la lezione precedente, è ora arrivato il momento di capire come e quando avviene la fase opposta, ovvero la distruzione degli oggetti.
Il metodo distruttore
Se il metodo magico __construct viene richiamato quando si creano gli oggetti, il rispettivo metodo magico __destruct viene richiamato nella fase di distruzione degli stessi. Prima di capire come avviene la distruzione degli oggetti, vediamo come è possibile dichiarare un distruttore all’interno di una classe:
Come possiamo notare, la dichiarazione del metodo distruttore è assai simile a quella del metodo costruttore, tranne per il fatto che il primo non accetta argomenti.
Quando vengono eliminati gli oggetti
A differenza della fase di creazione degli oggetti, che è assolutamente esplicita, la fase di distruzione non lo è altrettanto. PHP infatti, chiama il metodo distruttore solo quando è davvero sicuro che tutti i riferimenti all’oggetto siano stati cancellati oppure quando l’oggetto è distrutto/cancellato manualmente (per cancellare un oggetto, o in via generale una variabile, è possibile richiamare la funzione unset oppure settare esplicitamente l’oggetto ad un nuovo valore). Ecco un esempio di distruzione di un oggetto molto semplice:
Oltre alle situazioni elencate nel precedente paragrafo, occorre sapere che tutti gli oggetti, come del resto tutte le variabili, vengono distrutti automaticamente da PHP al termine dello script. Ciò significa che il metodo distruttore verrà sicuramente richiamato automaticamente al termine dell’esecuzione della nostra applicazione:
Conclusione
Abbiamo potuto osservare la potenza e soprattutto l’utilità del metodo distruttore. Anche se quest’ultimo non verrà utilizzato spesso come il metodo costruttore, rappresenta sempre uno strumento dall’indubbia potenza: viene utilizzato in molti scenari reali, come ad esempio quando occorre chiudere la connessione ad un database, terminare l’handle ad un file o chiudere la connessione a risorse esterne. Insomma, si può affermare che generalmente il distruttore viene richiamato quando occorre effettuare il clean up delle risorse.
Ottimo sito, continua il buon lavoro!
È straordinario visitare questa pagina Web e leggere le visualizzazioni
di tutti i colleghi riguardanti questo post, mentre sono anche desideroso di ottenere conoscenza ed informazioni.TY
My brother suggested I might like this website. He was once entirely right.
This publish truly made my day. You can not imagine simply
how a lot time I had spent for this information! Thanks!
Always follow your heart.