I blog di Alessioempoli

Data 2 ottobre 2018

Programmazione a oggetti con PHP – 1

Per ingrandire il testo, cliccare sul browser

Per ingrandire le foto, cliccarci sopra

          Programmazione a oggetti con PHP – 1

 

Creare applicazioni Web utilizzando la programmazione orientata ad oggetti e il linguaggio PHP, dalla teoria alla pratica. Una guida completa all’Object Oriented Programming in PHP per imparare a sviluppare applicazioni modulari, facili da implementare e da mantenere utilizzando meno codice e migliorando il livello delle prestazioni.

 

PHP è un linguaggio Object Oriented dalla versione 5 e interpreta questo paradigma in modo completo ma con le peculiarità date dalla sua natura web oriented

 

Prima della versione 5, PHP è stato un linguaggio sostanzialmente procedurale. Era in realtà dotato di un singolare modello ad oggetti, ma assolutamente incompleto e tale da non permettere l’applicazione dei concetti base della OOP: la Programmazione Orientata agli Oggetti.

 

Ora PHP dispone di un solido modello OOP, completo di tutti i più importanti strumenti che questo paradigma è in grado di offrire, anche se con alcuni limiti di carattere strutturale.

 

A differenza di altri linguaggi che sfruttano il paradigma OOP come Java, che nascono per applicazioni general purpose, PHP è un linguaggio di tipo Web oriented e presenta dei limiti sia nell’architettura che nella sintassi. Questo è il motivo per il quale, quando si tratta di PHP, sarebbe meglio non parlare di “Programmazione Orientata agli Oggetti” ma di “programmazione in stile OOP“.

 

Cosa è la programmazione ad oggetti?

 

La Programmazione Orientata agli Oggetti, è un paradigma di programmazione in cui, a differenza di quello “procedurale”, il flusso del programma non è gestito tramite chiamate a procedura sequenziali, ma è rappresentato dalla interazione e dallo scambio di messaggi tra oggetti.

 

Questo stile di programmazione, utilizza le classi per organizzare le strutture dei dati e le procedure che operano su di esse. Questo permette di creare un “oggetto” composto da due entità:

 

1) metodi (le procedure);

2) attributi (i dati).

Il paradigma OOP, risulta molto utile nella strutturazione di programmi di grandi dimensioni, permettendo la separazione tra dati e logica applicativa e utilizzando il principio di progettazione DRY (Do not Repeat Yourself), criterio secondo il quale andrebbe evitata ogni forma di ripetizione e ridondanza logica nell’implementazione del sorgente.

 

Alcune peculiarità della OOP, come l’architettura, la maggiore chiarezza e linearità del codice ad oggetti rispetto a quello procedurale, permettono di ottenere un sorgente modulare e facile da modificare. Inoltre la OOP consente di gestire meglio gli errori e semplifica il debugging.

Tra gli altri vantaggi della programmazione ad oggetti vi sono:

 

1-

 

Questa guida ha lo scopo di descrivere tutti i concetti fondamentali della programmazione ad oggetti in PHP, consentendo allo sviluppatore di realizzare applicazioni object oriented con il linguaggio PHP. La trattazione verrà sostanzialmente divisa in due parti:

 

1) una parte teorica dove si studieranno i concetti, le teorie, i namespaces ed il funzionamento fondamentale della OOP.

2) una parte pratica, finale, dove affronteremo lo sviluppo di un’applicazione completa in PHP tramite l’OOP.

 

 

OOP vs Codice procedurale in PHP

 

Scopriamo le differenze fra la programmazione orientata agli oggetti e il più semplice modello procedurale realizzando il medesimo progetto in PHP. Osserviamo come nonostante la curva di apprendimento iniziale più ripida l’OOP si dimostri preferibile nello sviluppo di applicazioni complesse.

 

Prima di addentrarci nello studio vero e proprio dei concetti e delle teorie della programmazione ad oggetti, è bene avere le idee chiare sulle differenze che questo modello di programmazione pone rispetto al più semplice modello procedurale.

 

Questo perché molti sviluppatori PHP hanno probabilmente a disposizione un solido bagaglio di conoscenze sul codice procedurale, soprattutto chi ha lavorato a lungo con la versione 4. Per una completa analisi delle differenze teoriche, dei vantaggi e degli svantaggi tra modello ad oggetti e modello procedurale, l’articolo Codice procedurale vs OOP è un ottimo punto di riferimento.

 

Un wrapper per la stampa

 

Partiamo subito con un esempio pratico che ci permetterà di cogliere le rispettive differenze concettuali dei due modelli: un wrapper per stampare un paragrafo HTML contente del testo.

 

2-

 

La prima, lampante differenza che emerge dal confronto tra questi due snippet è la diversa quantità di codice necessaria per giungere allo stesso fine: il modello OOP ne richiede infatti di più. La seconda differenza è che, mentre nel primo caso si può usare direttamente la funzione e quindi stampare il codice, nel secondo si deve prima istanziare un oggetto della classe HTML (il cui nome è scelto a nostro piacimento) e successivamente richiamare il metodo denominato ph.

 

Ma vediamo cosa accade se successivamente decidiamo di aggiornare le funzionalità volendo creare un ulteriore wrapper al paragrafo risultante, ovvero un elemento div, ed ottenere il seguente markup: <div><p>This is a paraghrap</p></div>:

 

3-

 

Come è possibile notare, nel caso del modello procedurale si deve procedere alla creazione di una nuova funzione che richiama la precedente, mentre nel modello OOP tutta l’implementazione è centralizzata in un unico oggetto: la classe HTML. Le istanze di questa classe (nel nostro caso l’oggetto $html) possono usufruire di tutte le funzioni pubbliche dichiarate nella stessa.

 

 

Le basi della OOP in PHP

 

I concetti fondamentali riguardanti proprietà metodi e oggetti, componenti fondamentali che ci permetteranno di creare la nostra prima classe e comprendere il significato del concetto di istanza nella OOP.

 

Come abbiamo visto in precedenza, 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 “struttura” con al suo interno variabili e funzioni che condividono funzionalità e risorse, che rimangono in attesa di essere utilizzate tramite le istanze della classe stessa.

 

Classi, oggetti, proprietà, metodi

 

Prima di addentrarci nell’argomento è necessaria una conoscenza di base delle differenze tra gli oggetti, le classi, le proprietà e i metodi.

Definizione base dell’OOP:

 

4-

 

Definiamo quindi la nostra prima classe

 

5-

 

Nell’esempio abbiamo creato la classe Person, utilizzando la parola chiave class, seguita dal nome della classe e una serie di parentesi graffe {}.

 

Proprietà

Per aggiungere proprietà (variabili) alla classe, utilizzeremo il seguente codice:

 

public $name;

 

Nell’esempio abbiamo creato un unico attributo: $name. La keyword public, di cui parleremo nei prossimi capitoli, determina invece il livello di visibilità delle proprietà.

 

I metodi

Una volta create le proprietà possiamo definire il nostro metodo getName per accedere al contenuto della proprietà $name:

 

6-

 

L’oggetto

La classe è una dichiarazione di metodi e proprietà che lavorano su di essa e per questo motivo non può essere usata direttamente. Per poterla utilizzare bisogna invece istanziare un oggetto.

Per creare una nuova istanza, basterà scrivere:

 

7-

 

La chiave new, crea una nuova istanza di person e la assegna a $customer. In questo metodo, tutti i valori dati dai parametri sono configurati esattamente per questa istanza; tali valori sono inoltre unici per ogni istanza. La stessa sintassi, utilizzata per istanziare gli attributi dell’oggetto, viene usata anche per richiamare un metodo della classe su un oggetto; quindi, per richiamare getName() sull’oggetto $customer1 della classe person, utilizziamo:

 

8-

 

Nota: Nel nostro esempio abbiamo usato il metodo __construct, utile per inizializzare la variabile interna $name. I parametri passati tra parentesi (se previsti) sono quelli richiesti dalla funzione __construct. Del metodo __construct e degli altri metodi magici di PHP parleremo più avanti.

 

 

Classi, costruttori e distruttori

 

Impariamo a creare una classe con PHP e la OOP, scopriamo come definirla e utilizzarla analizzando nel dettaglio il ruolo svolto da componenti come il costruttore, il distruttore e la variabile $this, analizziamo la fase fondamentale dell’istanza.

Come abbiamo visto , 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 “struttura” con al suo interno proprietà e metodi che condividono funzionalità e risorse, che rimangono in attesa di essere utilizzate tramite le istanze della classe stessa.

 

Creazione di una classe

La creazione di una classe, come in altri linguaggi di programmazione, viene definita tramite la keyword class seguita dal nome della classe.

 

9-

 

Una volta dichiarata la classe, possiamo passare all’inserimento delle funzionalità, tramite la dichiarazione di proprietà e metodi.

 

10-

 

Osservando la classe proposta, possiamo notare l’unica proprietà name con la parola chiave private. Della parola chiave private e degli altri indicatori di visibilità parleremo in dettaglio nei prossimi capitoli. Per il momento ci basterà sapere che la proprietà name può essere utilizzata solo dai metodi interni alla classe e quindi non è visibile all’esterno della classe. Infatti, per accedere al contenuto di questa proprietà è stato creato un metodo ad hoc getName().

 

Variabile $this

Per istanziare gli attributi/proprietà della classe, e in generale per accedere ad essi, si usa la sintassi:

 

11-

 

dove $this rappresenta l’oggetto costruito a runtime e quindi avrà dei valori all’interno della proprietà. La stessa sintassi, inoltre, si utilizza per richiamare un metodo della classe su un oggetto.

Infatti, l’obiettivo del metodo getName(), è restituire una copia del valore contenuto nella proprietà $name, utilizzando l’istanza dell’oggetto rappresentata all’interno della classe da $this e recuperando il valore della proprietà attraverso il simbolo ->.

 

Costruttore

Nella classe Person abbiamo definito il metodo (o metodo magico) __construct, che è il costruttore della classe.

 

12-

 

Disponibile dalla versione PHP 5, questo metodo magico a differenza del PHP4 e di altri linguaggi di programmazione, non ha lo stesso nome della classe. Se in PHP5, una classe non dovesse avere questo metodo, si potrà utilizzare il vecchio stile di programmazione e verrà quindi creato un metodo con lo stesso nome della classe.

I diversi “metodi magici” nella OOP di PHP, attivati al verificarsi di determinati eventi e contrassegnati dal doppio underscore __ iniziale.

Il costruttore, come ogni altro metodo, può contenere parametri. Nel nostro caso, infatti, il parametro che viene passato al costruttore viene utilizzato per inizializzare la proprietà $name, grazie all’istanza a runtime dell’oggetto, cioè $this.

 

Distruttore

Con PHP 5 è stato introdotto anche il concetto del distruttore (__destruct()). Questo metodo, già presente in altri linguaggi OOP, viene chiamato automaticamente dal motore di 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. Nel caso del nostro esempio il distruttore non fa nulla, e quindi potremmo anche non definirlo.

 

Istanza della classe

Una volta visti i principali elementi che compongono una classe, possiamo vedere come questa può essere utilizzata. Nel nostro esempio, non solo abbiamo definito una classe, ma anche creato un’istanza utilizzando la seguente sintassi:

 

13-

 

La parola chiave new restituisce un riferimento all’oggetto creato. Ora, utilizzando l’oggetto $costumer1 possiamo accedere a qualsiasi proprietà o metodo utilizzando la sintassi vista in precedenza: $customer1->getName();

 

Conclusioni

Lo scopo di questa parte  è quello di mostrare un primo approccio alla programmazione ad oggetti in PHP fornendo un semplice esempio. Più avanti  ci soffermeremo sugli indicatori di visibilità: public, protected e private per proprietà e metodi.

 

 

Costruttori multipli in PHP

 

Linguaggi di programmazione con un supporto più avanzato per la programmazione ad oggetti (come ad esempio Java) permettono alle classi di avere costruttori multipli con sequenze diverse di parametri. In questo modo, ad esempio, una classe per rappresentare il tempo può essere istanziata passando come parametro il timestamp oppure una stringa rappresentante una data oppure i parametri singoli per giorno, mese e anno. Questa caratteristica, che si chiama method overloading, è molto potente e permette allo sviluppatore una grande flessibilità nell’utilizzo delle proprie classi.

 

PHP purtroppo non supporta nativamente i costruttori multipli nelle sue classi: come abbiamo visto ogni classe può avere un unico metodo costruttore chiamato __constructor(). Per fortuna è possibile aggirare questa limitazione in almeno tre diverse maniere.

 

Soluzioni per il supporto dei costruttori multipli

La prima possibilità consiste nell’impostare dei parametri come facoltativi o gestirli dal costruttore senza dichiararli tramite le funzioni func_num_args(), func_get_args() e func_get_arg() con dei costrutti if/else. Un esempio può essere il seguente:

 

14-

 

Come si può vedere il codice risultante è difficile da leggere e da mantenere, inoltre manca di flessibilità infatti non è possibile, ad esempio, cambiare l’ordine dei parametri per giorno, mese e anno. Una soluzione migliore è l’utilizzo del pattern factory che consiste nel creare ulteriori classi (minimo una) che si occupa di istanziare la classe di nostro interesse. In questo caso la nostra classe MyTime conterrebbe solo un costruttore con unico parametro il timestamp, mentre la classe factory tramite vari metodi può calcolare il timestamp con cui istanziarla.

Il vantaggio evidente è nella flessibilità e leggibilità: basta aggiungere un metodo alla classe factory per poter istanziare la classe MyTime con parametri diversi. Questo pattern è molto potente e permette anche di sfruttare oggetti terzi per istanziare la classe di sua competenza (ad esempio per fare query ad un database o per calcoli complessi). Per tale ragione in questo caso specifico si tratterebbe di una soluzione sovradimensionata che comporta lo svantaggio di dover mantenere due classi diverse.

PHP ci offre una terza via che permette di avere i vantaggi di entrambi i metodi: dichiarare il costruttore come privato senza un corpo e usare metodi statici per istanziare la classe. Sempre relativamente al nostro esempio potremmo scrivere un codice simile a questo:

 

15-

 

Da notare che all’interno dei metodi statici viene impostato un attributo privato del costruttore, una cosa che non sarebbe possibile normalmente. PHP permette questa modifica perché un oggetto può modificare qualsiasi attributo, anche quelli privati, di oggetti istanziati a partire dalla propria classe.

Un altro dettaglio da notare è il modo in cui la classe viene istanziata all’interno dei metodi statici, infatti la scelta di usare static non è casuale. L’utilizzo di static (disponibile dalla versione 5.3 di PHP) si riferisce alla classe che viene richiamata dall’esterno, per cui il suo impiego rende disponibili i metodi statici anche in classi che estendono MyTime. Usando self, invece, le chiamate ai metodi statici restituirebbero sempre un’istanza di MyTime, anche se in realtà li abbiamo richiamati da una classe figlia.

 

 

Indicatori di visibilità: public, protected e private

 

Analizziamo il ruolo svolto dagli indicatori di visibilità public, protected e private dei membri di una classe; scopriamo come proprietà e metodi divengono più o meno accessibili all’interno e dall’esterno di una classe a seconda degli indicatori associati ad essi e come utilizzare questi ultimi per evitare errori.

Abbiamo visto come definire una classe con l’uso del costrutto class, le sue proprietà e metodi. L’esempio di classe Person, era composta da una proprietà $name privata ed una serie di metodi con visibilità pubblica. Un aspetto molto importante nella programmazione ad oggetti, riguarda proprio l’incapsulamento e la visibilità di proprietà e metodi.

 

Public, protected e private

In PHP, così come in altri linguaggi orientati agli oggetti (C++ e Java), è possibile stabilire le restrizioni su proprietà e metodi tramite i seguenti indicatori di visibilità:

 

16-

 

Tramite gli indicatori di visibilità, possiamo introdurre nuove regole all’interno delle nostre applicazioni, rendendole più robuste e maggiormente mantenibili. A questo proposito vediamo ora degli esempi pratici che ci aiuteranno a capire le differenze che si ottengono dal comportamento di proprietà e metodi quando vengono dichiarati con uno specifico indicatore di visibilità.

Prima di dichiarare il membro di una classe bisognerà specificare uno dei 3 indicatori di accesso. Nel caso in cui non si specifichi la visibilità associata ad un membro, il livello impostato di default sarà public.

 

Public

Le proprietà e i metodi dichiarati public, non presentano nessun tipo di restrizione, e sono utilizzabili da tutte le classi. L’esempio seguente mostra il codice necessario per svolgere una semplice funzione, la stampa di un nominativo:

 

17-

 

Private

I membri dichiarati private sono accessibili e modificabili soltanto all’interno delle classi che li dichiarano, cosi come i metodi privati sono richiamabili solo al loro interno.

Infatti, questi sono simili ai membri protected con la sola differenza che, le proprietà private, sono invisibili alle classi che ereditano. Estendiamo l’esempio effettuato in precedenza allo scopo di gestire un maggior numero di informazioni, ma adottando indicatori di visibilità differenti per i membri:

 

18-

 

In questo caso, l’unico modo che abbiamo per accedere al valore della proprietà $age, è tramite un metodo pubblico (se vogliamo accedervi dall’esterno) oppure tramite un metodo protetto (se vogliamo accedervi solo dalle classi che ereditano). Nel nostro esempio abbiamo utilizzato il metodo pubblico print_age().

Invece, l’istruzione:

 

19-

 

Protected

Uno specificatore di accesso protetto, consente alla classe derivata di accedere alle funzioni membro dati o membri della classe base, mentre disabilita l’accesso globale ad altri oggetti e funzioni. Modifichiamo l’esempio già proposto introducendo un’ulteriore classe:

 

20-

21-

 

 

Namespace in PHP

 

Dichiarazione del namespace

 

Fino ad ora abbiamo analizzato la creazione di singole classi in PHP tenendo in considerazione solo due caratteristiche:

 

1) il nome della classe;

2) il file in cui veniva dichiarata la classe.

Questo è stato lo standard fino all’introduzione di PHP 5.3.0 che ha rivoluzionato il linguaggio introducendo il concetto di namespace. Tale paradigma non è nuovo nella programmazione ad oggetti ed è presente in molti dei linguaggi (ad esempio Java). In sostanza si tratta di creare un proprio spazio tramite una dichiarazione arbitraria in cui faremo risiedere le nostre classi.

La definizione viene effettuata tramite la parola chiave namespace che deve essere inserita prima di ogni altra istruzione e quindi deve precedere la dichiarazione della classe:

 

22-

 

Concettualmente un namespace è assimilabile alla struttura delle cartelle di un filesystem: dobbiamo dichiarare un’origine per il nostro namespace che corrisponde alla directory root, o al nome del disco su sistemi Windows, e possiamo inserire in maniera sequenziale una serie di suddivisioni che in questa similitudine rappresenterebbero le sottocartelle.

 

Questa caratteristica dei namespace offre due vantaggi rilevanti:

 

# possiamo organizzare il codice in modo da facilitare la mantenibilità, ad esempio tramite lo standard PSR-4;

# non dobbiamo preoccuparci dei problemi relativi alle omonimie tra classi, ad esempio una classe con un nome molto comune come Connection non sarà più un problema.

 

Richiamare una classe interna ad un namespace

 

Una volta dichiarata una classe all’interno di un namespace è possibile richiamarla utilizzando il suo nome completo nel punto in cui la utilizziamo oppure il solo nome della classe se questa viene dichiarata, dopo la parola chiave namespace, tramite la keyword use. Entrambi i metodi sono validi ma il secondo ci permette di utilizzare due classi con lo stesso nome tramite gli alias. Per chiarire il concetto ecco un esempio:

 

23-

 

Le classi utilizzate devono essere state precedentemente caricate in memoria tramite i relativi comandi PHP (include, include_once, require, require_once) oppure possiamo semplificare la procedura ricorrendo ad un autoloader come quello generato automaticamente da Composer.

 

Usare classi senza namespace

 

Nonostante i namespace siano stati introdotti da diversi anni in PHP, alcune librerie anche se ad oggetti non fanno ricorso ai namespace. Esempi di questo tipo con una diffusione notevole sono Twig (libreria per il templating), SwiftMailer (per inviare messaggi email) o PHPExcel (per leggere e creare fogli di lavoro del popolare software).

Utilizzare direttamente queste librerie in un file in cui viene dichiarato un namespace (ad esempio quello del listato precedente) non è possibile perché l’interprete PHP si aspetta di trovare una classe priva del namespace completo sotto il namespace dichiarato all’inizio del file o, in alternativa, tra le classi importate tramite use. In questo caso infatti si riceverebbe un errore perché la classe non è stata precedentemente importata. Esistono due metodi per aggirare questo impedimento:

 

# far precedere in tutto il codice il nome della classe da un carattere di backslash;

# inserire la classe tra quelle dichiarate per l’utilizzo sempre preceduta da un carattere di backslash.

 

La seconda versione è più semplice perché permette anche di copiare parti di codice non pensate per la programmazione ad oggetti con namespace, ma non esiste una differenza tra le due:

 

24-

 

Questa modalità deve essere usata anche per le classi stardard PHP. Non è invece necessaria per richiamare funzioni o costanti globali (dichiarate al di fuori di un namespace) come quelle standard del linguaggio.

 

 

Le costanti

Il ruolo delle costanti nelle applicazioni scritte in PHP, le loro differenze sintattiche e funzionali con variabili e proprietà e il loro utilizzo dalle basi fino all’impiego alla loro adozione all’interno delle classi nella programmazione orientata agli oggetti.

 

In PHP, come in altri linguaggi di sviluppo e programmazione, è possibile definire delle costanti, ovvero dei valori fissi che non possono cambiare e vengono quindi mantenuti per tutta la durata dello script o dell’applicazione. Rispetto alle variabili, dalle quali si differenziano sia sintatticamente che funzionalmente, le costanti devono rispettare le seguenti regole:

 

# il nome delle costanti non inizia mai con il simbolo $, ma deve sempre iniziare con una lettera o con un underscore, seguita da caratteri alfanumerici o underscore;

# esse non vengono definite mediante assegnazione, ma tramite la funzione define(), quest’ultima accetta due parametri: il nome della costante e il valore da associare ad essa;

# sono case-sensitive, per un fattore di consuetudine nella programmazione, il nome di una costante è sempre maiuscolo;

# i valori ammessi per le costanti devono essere di tipo scalare (quindi integer, float, string o boolean) o null;

# la visibilità delle costanti è globale. Possono essere utilizzate in qualsiasi punto del programma;

# come anticipato, una volta definite non possono essere modificate.

 

Le costanti sono utilizzate, ad esempio, nei file di configurazione, dove le stesse conterranno le informazioni necessarie per la connessione ad un database, ma gli ambiti di adozione sono molteplici; di seguito un esempio riguardante la definizione e l’utilizzo di una costante (SITE):

 

25-

 

Le costanti predefinite

 

Le costanti predefinite, fornite dal core PHP o dai suoi moduli (estensioni), sono utili per accedere a informazioni (di sola lettura) in qualsiasi script eseguito. A differenza delle costanti normali, le quali si definiscono con define(), le costanti predefinite hanno un nome che richiama la libreria associata.

Ad esempio, le costanti per la gestione degli errori (Error Handling), iniziano tutte con E_* e sono utilizzate insieme alla funzione error_reporting() per impostare il livello di errori da visualizzare.

Di seguito, un esempio di alcune costanti predefinite (costanti magiche) messe a disposizione da PHP:

 

26-

 

Le costanti di classe

 

A partire da PHP 5, è possibile definire, tramite la parola chiave const seguita dal nome e dal valore della costante stessa, una costante all’interno di una classe.

A differenza delle proprietà e dei metodi, le costanti, appartengono alla classe e non alle sue istanze. Quindi, per riferirsi alle costanti dall’interno della classe non è possibile utilizzare la keyword $this, né si può ricorrere all’operatore di deferenziamento (“->”) per riferirsi dall’esterno.

 

27-

 

In questo esempio, abbiamo visto come accedere alla costante sia all’interno del metodo getConsts() tramite la parola chiave self seguita dall’operatore di risoluzione di scope (::)

 

28-

 

Conclusioni

Affronteremo le strutture complesse che sono formate dalla combinazione di più classi che prendono il nome di gerarchie di classi, ed osserveremo come sia possibile condividere informazioni e risorse per creare applicazioni modulari.

 

 

Ereditarietà: estendere le classi in PHP

 

Come osservato nei precedenti capitoli, uno dei vantaggi del paradigma object orientied, è il riutilizzo del codice. In questo capitolo parleremo dell’ereditarietà che ha un ruolo fondamentale nella programmazione OOP.

Tramite l’ereditarietà (inheritance), una classe (sottoclasse o classe figlia), può ereditare sia i metodi che le proprietà da un’altra classe (superclasse o classe padre). Inoltre, la nuova sottoclasse, può definire nuove proprietà e metodi, oppure, ridefinire metodi della classe padre (tecnica dell’overriding della quale parleremo in dettaglio nei prossimi capitoli).

A differenza di altri linguaggi di programmazione, come ad esempio nel C++, in PHP, non è prevista l’ereditarietà multipla. Cioè, non è possibile che una classe estenda contemporaneamente più di una classe. In alternativa all’utilizzo dell’ereditarietà multipla, in PHP, si utilizzano le Interfacce di cui parleremo successivamente.

 

Estendere una classe

 

Anche in PHP, come nel linguaggio Java, si utilizza la parola chiave extends per creare la relazione di ereditarietà tra due classi.

 

29-

 

L’esempio riportato successivamente, mostra come la classe Person viene ereditata dalla nuova classe Employees:

 

30-

 

Nell’esempio abbiamo utilizzato una classe Person e una sottoclasse Employees, la quale eredita tutti i metodi e gli attributi della classe genitore.

In Employees abbiamo aggiunto un’ulteriore funzionalità, inserendo la proprietà $profile utile, ad esempio, per definire il ruolo del nostro “Employee” all’interno di un’azienda.

 

31-

 

All’interno di una sottoclasse, possiamo utilizzare tutti i metodi e le proprietà dichiarate public e private nella classe genitore. Notiamo che la sottoclasse accede a tutti i membri della superclasse attraverso la parola chiave $this.

 

32-

 

Nella superclasse abbiamo utilizzato il modificatore di accesso protected per la proprietà $id_rif, tutti i membri protetti di una classe sono visibili nella superclasse e nella sottoclasse. In questo caso possiamo utilizzare:

 

33-

 

Il membro $password, dichiarato privato, non è visibile nella sottoclasse: quindi nel costruttore della sottoclasse Employee è necessario usare setPassword() per impostare la password. La sottoclasse accede alle proprietà private soltanto tramite i metodi pubblici della superclasse.

 

34-

 

Conclusioni

Abbiamo visto i concetti fondamentali dell’ereditarietà in PHP e nella programmazione orientata agli oggetti. Vedremo come ereditare il costruttore/distruttore e la tecnica dell’overriding.

 

 

Overriding e keyword final in PHP

 

Impariamo ad utilizzare l’overriding per sovrascrivere i metodi della classe padre in una classe figlia facendo in modo che le funzioni ridefinite abbiano precedenza su quelle della classe padre. Nello stesso tempo, scopriamo l’utilità della keyword final per impedire che i metodi all’interno della classe o l’intera classe sia estesa per ereditarietà o overriding.

Come abbiamo appreso, una classe (sottoclasse o classe figlia) può ereditare sia i metodi che le proprietà da un’altra classe (superclasse o classe padre). In questa lezione approfondiremo l’ereditarietà in PHP: come ereditare il costruttore/distruttore e la tecnica dell’overriding o ridefinizione.

 

Overriding

 

I metodi definiti in una classe padre, possono essere sovrascritti (overriding) in una classe figlia e, in questo caso, le funzioni ridefinite avranno precedenza su quelle della classe padre. Questo metodo è utile se si vuole utilizzare la funzionalità del genitore o semplicemente aggiungere ulteriori funzionalità nel metodo della sottoclasse. Per ridefinire un metodo della superclasse in una sottoclasse, si crea semplicemente un metodo nella classe figlia con lo stesso nome del metodo della classe padre.

 

35-

 

Come possiamo notare in questo caso, il metodo viewHello di Employees va a sovrascrivere quello di Person. Praticamente, il metodo viewHello di Employees ignora quello di Person.

 

Per utilizzare l’istanza $employee al fine di accedere al metodo viewHello di Person, utilizziamo la keyword parent, seguita dall’operatore di risoluzione di scope (“::”) e dal nome dell’elemento desiderato. Questo permette di richiamare gli elementi della classe genitore direttamente nelle sottoclassi:

 

36-

 

Quindi, grazie all’uso della keyword parent, è possibile condividere informazioni tra le classi, senza la necessità di riscrivere un metodo, nel caso in cui questo venga ridefinito.

 

Ereditare il costruttore e il distruttore

 

In PHP, il costruttore e il distruttore possono essere ereditati nelle sottoclassi, così da evitare la riscrittura del codice. Infatti, di default, viene richiamato automaticamente quello della classe padre.

Invece, nell’esempio utilizzato precedentemente, la classe effettua un overriding del costruttore della classe base,   per inizializzare la proprietà $profile. Per impostare anche l’attributo $name richiamiamo il costruttore della classe genitore tramite la parola chiave parent, che identifica appunto la classe padre Person.

 

37-

 

La keyword final

 

A volte, possono verificarsi situazioni in cui non si voglia attuare l’overriding o l’ereditarietà. Questo per evitare problemi di sicurezza, instabilità dell’applicazione, codice eccessivamente complesso e così via. In tali situazioni, è possibile impedire che i metodi all’interno della classe o addirittura l’intera classe sia estesa, utilizzando la parola chiave final prima della definizione del metodo e della classe.

Ad esempio:

 

38-

 

Se proviamo ad estendere la classe MyClass e/o sovrascrivere il metodo viewHello() verranno generati i seguenti errori:

 

39-

 

 

I traits in PHP

 

Impariamo a simulare l’eredità multipla in PHP con i traits, porzioni di codice che vengono incluse all’interno di una classe per aggiungere delle funzionalità.

Dalla versione 5.4 PHP offre un’ulteriore possibilità per aggiungere funzionalità a più di una classe mantenendo un’unica fonte di codice, i traits. In termini tecnici i traits permettono di ottenere un effetto simile all’ereditarietà multipla senza la complessità di quest’ultima ma con dei limiti.

In termini pratici un trait è uno snippet di codice che viene incluso all’interno di una classe per aggiungere delle funzionalità in maniera analoga a quanto avviene all’interno di uno script richiamando un file esterno con include o require. Di fatto non c’è alcuna differenza pratica tra richiamare un trait all’interno di una classe e copiare/incollare lo stesso codice. L’unica differenza è che il codice che abbiamo scritto è presente in un unico punto che possiamo modificare portando la stessa modifica in tutte le classi che utilizzano il trait.

Concettualmente quindi un trait serve per aggiungere un behavior (alla lettera “comportamento”) ad una serie di classi. Ad esempio possono essere usati per aggiungere funzioni alle classi controller in un modello MVC in maniera modulare.

 

La struttura del trait

 

Un trait viene definito con la parola chiave trait seguita dal nome assegnato e da parentesi graffe che contengono lo snippet vero e proprio di codice, in maniera analoga alle classi. A differenza di queste però un trait non può essere istanziato.

 

40-

 

Nel corpo del trait è possibile definire tutto quello che verrebbe definito in una classe quindi sia metodi che proprietà che risulteranno accessibili dalle classi che useranno il trait. I metodi possono essere definiti come public, protected e private. Possono essere definiti come static e perfino come abstract. Chiaramente i metodi astratti dovranno essere ridefiniti all’interno delle classi. Anche per quanto riguarda le proprietà è possibile decidere la visibilità .

 

41-

 

Da notare che è possibile ridefinire nella classe utilizzatrice un metodo precedentemente definito all’interno del trait mentre non è possibile fare la stessa cosa con le proprietà.

 

Richiamare un trait

 

L’utilizzo del trait è molto semplice, basta usare la parola chiave use all’interno del corpo di una classe seguita dal nome del trait stesso. Se la classe usa più di un trait è possibile indicarli nella stessa riga con un unico use seguito dall’elenco dei nomi dei trait separati da una virgola.

All’interno della classe utilizzatrice possono essere richiamati i metodi e le proprietà definite nel trait come se fossero definiti nella stessa classe, quindi utilizzando la variabile $this.

 

42-

 

Utilizzo avanzato dei trait

 

Due trait utilizzati nella stessa classe potrebbero definire metodi o proprietà con lo stesso nome: in questo caso l’interprete PHP restituirebbe un errore. Per evitarlo è possibile modificare la modalità di importazione con un costrutto più complesso di use. Infatti alla lista di trait usati è possibile far seguire un blocco racchiuso tra parentesi graffe per specificare la sorgente di importazione di un metodo o per rinominarlo.

 

43-

 

Infine è possibile cambiare la proprietà di visibilità di un metodo o una proprietà definita in un trait usando la parola chiave as, come specificato nell’esempio precedente, seguita dalla nuova visibilità e dall’eventuale nuovo nome da attribuire.

 

 

Le classi astratte

 

Un’analisi completa su cosa sono le classi astratte (abstract class) in PHP, come si utilizzano, come evitare errori nel loro impiego, come estenderle e qual’è il loro ruolo nel paradigma orientato agli oggetti in PHP.

Abbiamo analizzato i concetti basilari dell’ereditarietà in PHP. Ora tratteremo invece delle classi astratte, le quali ricoprono un ruolo fondamentale nella programmazione orientata agli oggetti (OOP).

 

Cosa sono le classi astratte (abstract class)

 

Una classe astratta è definibile come un particolare tipo di classe la quale non può essere istanziata, cioè non è possibile creare un oggetto da una classe astratta, ma può solo essere estesa da un’altra classe.

Possiamo considerare una classe astratta come una superclasse, in cui è possibile aggiungere uno o più metodi astratti i quali dovranno essere (necessariamente) ridefiniti da una sottoclasse per poter essere utilizzati. Quindi, quando si aggiunge un metodo astratto in una classe astratta, non si inserisce nessun codice all’interno del metodo, da qui la definizione di “metodo senza corpo”; al contrario, si lascia l’implementazione del metodo alle classi figlie che ereditano dalla classe astratta.

Per questo motivo possiamo considerare questa tipologia di classe come una “base di partenza” o “modello” per la creazione di classi.

Una classe derivata da una classe astratta, per poter essere istanziata, deve implementare tutti i metodi astratti della classe astratta; se non lo facesse, costringerebbe PHP a generare un errore). Nello specifico, una classe astratta è contrassegnata dalla parola chiave abstract nella dichiarazione della classe e in tutti i metodi. a tal proposito si osservi l’esempio seguente:

 

44-

 

Nell’esempio, abbiamo dichiarato la classe astratta Person, aggiungendo la parola chiave abstract prima della definizione di classe. La classe contiene due proprietà $name e $lastname, così come i metodi, setName e getName (ereditati dalle classi figlie), per impostare e recuperare queste proprietà.

La classe Person, contiene anche un metodo astratto, viewHello, per la visualizzazione di un messaggio di benvenuto. Siccome parliamo di un metodo astratto, si tratta soltanto di una dichiarazione e non contiene nessuna implementazione.

Come detto in precedenza, una classe astratta non può essere istanziata. In altre parole, non è consentito creare una nuova istanza di una classe astratta. Infatti, provando ad istanziare la classe Person avremo un risultato come il seguente:

 

45-

 

dove viene generato un errore di runtime interrompendo l’esecuzione dello script:

 

46-

 

 

Ora potremo creare la nostra classe Employees la quale eredita da Person

 

47-

 

Come possiamo vedere, la nostra classe, implementa il metodo astratto viewHello di Person, più un metodo standard setId(). Questo è e l’output visualizzato dopo l’esecuzione dello script:

 

Hello Giuseppe Rossi

 

Dichiarando Employee senza la definizione di viewHello(), avremmo ottenuto il seguente errore:

 

48-

 

 

Polimorfismo, Overloading e Late-Binding

 

Analizziamo le metodologie di PHP per l’implementazione del meccanismo chiamato polimorfismo, tipico della programmazione object oriented; una soluzione realizzabile tramite la risoluzione dei metodi che si rivela ideale per la creazione di codice riutilizzabile.

Un altro elemento fondamentale nella programmazione ad oggetti basata sull’ereditarietà, è il polimorfismo. Insieme all’incapsulamento e all’ereditarietà quest’ultimo fa parte dei concetti fondamentali del paradigma object oriented, i quali, facilitano il riutilizzo e la flessibilità del codice.

Nella programmazione, quando si parla di polimorfismo, si intende la possibilità di accedere a più funzioni tramite la stessa interfaccia. Quindi, una stessa funzione, potrebbe comportarsi in modo diverso a seconda del contesto in cui viene eseguita. Questo succede perché l’ereditarietà, come visto nei capitoli precedenti, consente alle sottoclassi di ridefinire i metodi ereditati dalla classe base.

È possibile ottenere il polimorfismo mediante due procedure: con metodi in Overloading (early binding) o tramite l’ereditarietà (late binding).

 

Overloading

 

Il polimorfismo, si realizza attraverso le operazioni di overloading, che consistono fondamentalmente nel definire più metodi con lo stesso nome ma dedicati a oggetti diversi; a tal proposito si analizzi il seguente esempio:

 

49-

 

Come possiamo notare, le sottoclassi precedenti (cane e persona) sono polimorfiche. Infatti, anche se appartenenti alla stessa classe Mammifero, reagiscono in modo diverso ogni qualvolta viene utilizzato il metodo speak(). Quindi, solo in fase di esecuzione è possibile stabilire quale sia il metodo corretto da invocare a seconda della classe di cui l’oggetto è istanza.

 

Late Binding

Come impostazione predefinita, i metodi sono risolti staticamente al momento della compilazione (binding statico). Mentre, la risoluzione, durante l’esecuzione del metodo da invocare, viene chiamata late binding (o binding dinamico).

Il polimorfismo con Late Binding, ottenuto tramite l’ereditarietà delle classi, permette di stabilire, in fase di esecuzione, qual’è il metodo più corretto da utilizzare a seconda della classe di cui l’oggetto è istanza. Insomma, il late Binding, offre ulteriore flessibilità al meccanismo del polimorfismo.

 

50-

 

Dall’esempio, possiamo notare come call() utilizzi il metodo protetto speak(). In questo caso, call(), viene ereditata dalle sottoclassi e utilizza il metodo speak() implementato nelle sottoclassi e non come viene definito nella superclasse Mammifero().

Il nostro programma, in fase di esecuzione, aspetta di sapere da quale istanza viene eseguito il metodo call() prima di decidere quale versione di speak() eseguire. Quindi la decisione sul metodo da richiamare viene presa in maniera dinamica in fase di esecuzione (late binding o binding dinamico).

 

 

Le interfacce

 

Analizziamo una semplice definizione di un’interfaccia, cioè una classe che non potrà essere istanziata ma che è destinata a contenere dei metodi che potranno essere implementati dalle classi. Principali differenze tra interfacce e classi astratte.

Le interfacce, come le classi astratte, sono delle classi speciali che non possono essere istanziate ma soltanto implementate. Praticamente esse delineano la struttura di una classe. La dichiarazione di un’interfaccia è simile a quella di una classe, ma include soltanto metodi (privi di implementazione) e costanti. Non è inoltre possibile specificare le proprietà.

Qualsiasi classe implementi un’interfaccia, avrà le costanti definite automaticamente e dovrà definire a suo volta i metodi dell’interfaccia, i quali, sono tutti sono astratti e vanno tutti implementati.

 

Implementare un’interfaccia

Per implementare un’interfaccia in una classe si utilizza la keyword implements:

 

51-

 

Nelle interfacce possiamo definire solo metodi (di default astratti) e costanti. Infatti, come è possibile notare analizzando l’esempio precedente, in questo caso non abbiamo dichiarato i metodi come astratti tramite la keyword abstract.

Creiamo ora la classe Student che implementa l’interfaccia Person. La nostra classe, quindi, dovrà definire i metodi dell’interfaccia per getName() e setName().

 

52-

 

Come descritto in precedenza, non è possibile creare una classe che implementi un’interfaccia e non tutti i metodi di quest’ultima; in alternativa essa dovrà essere obbligatoriamente dichiarata come abstract. Di conseguenza, se l’interfaccia è implementata da una classe astratta, quest’ultima può anche non ridefinire tutti i metodi, sarà compito delle ulteriori sottoclassi farlo.

Il seguente esempio:

 

53-

 

enererà un errore a runtime in quanto la classe Student non implementa il metodo getName:

Fatal error: Class Student contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Person::getName) […]

In PHP, possiamo implementare diverse interfacce in un’unica classe, includendo, separandoli con una virgola, una serie di nomi posizionati dopo la keyword implements.

 

54-

 

Ciò è valido soltanto se tra di esse non sussiste alcun conflitto, ossia se tali interfacce non contengono la dichiarazione degli stessi metodi o costanti.

 

Ereditarietà delle interfacce

 

Per le interfacce è possibile usare l’ereditarietà multipla. Per estendere un’interfaccia in un’altra, come per le classi, si utilizza infatti la keyword extends.

Nell’esempio riportato di seguito, la nuova interfaccia A, conterrà tutti i metodi dell’interfaccia B e C, a cui si aggiungeranno quelle nuove, definite da A.

 

55-

 

In questo caso, vale la stessa condizione di quando le classi implementano le interfacce. Un’interfaccia può estendere altre interfacce se queste non hanno nessun conflitto: ciò significa che se B definisce metodi già definiti in A si otterrà un errore.

 

56-

 

Differenze tra classi astratte ed interfacce

 

Classi astratte ed interfacce sono simili tra loro, ma differiscono per alcune caratteristiche.

 

Classi astratte

 

# Una classe astratta può contenere variabili e metodi implementati;

# nella classe astratta un metodo può essere dichiarato asbract;

# i metodi di una classe astratta possono essere definiti con qualsiasi visibilità (public, protected o private).

 

Interfacce

 

# Un’interfaccia può contenere solo definizioni di metodi e costanti;

# nelle interfacce tutti i metodi sono abstract di default;

# la visibilità dei metodi può essere solo public.

 

Le classi astratte e le interfacce, se usate adeguatamente, possono portare enormi benefici alle nostre gerarchie OOP.  Parleremo dei Traits e di come questi permettono di superare alcune limitazioni sulla gestione dell’ereditarietà singola in PHP.

 

 

Type Hinting in PHP

 

Impariamo a specificare il tipo di oggetto passato come parametro in una dichiarazione di funzione o di un metodo tramite il Type Hinting in un linguaggio a tipizzazione debole come PHP.

La funzionalità denominata Type Hinting (informazioni sul tipo di classe o “suggerimento del tipo”) è una tecnica introdotta con PHP 5, molto potente e flessibile, che ci permette di porre ulteriori regole nella programmazione orientata agli oggetti. Grazie al Type Hinting, possiamo specificare il tipo di oggetto passato, come parametro in una dichiarazione di funzione o di un metodo, facendo precedere il namespace dello stesso dal nome della classe desiderata.

Come è noto, PHP, è un linguaggio a “tipizzazione dinamica” (o debolmente tipizzato), in cui una variabile è in grado di cambiare il proprio tipo durante l’esecuzione del programma. Per questo motivo è possibile passare come parametri diversi tipi di dati. A tal proposito si analizzi il seguente esempio:

 

57-

 

La definizione del metodo infoStudent(), contiene il type hinting Student prima del parametro $student. Quindi, PHP, controllerà durante l’esecuzione dello script che gli argomenti siano del tipo specificato.

Nel caso in cui la funzione venga richiamata con un oggetto diverso da Student o un valore diverso da un oggetto, lo script terminerà visualizzando un errore di tipo Catchable fatal error, che ci indicherà che abbiamo passato un tipo di dato errato:

 

Catchable fatal error: Argument 1 passed to infoStudent() must be an instance of Student, string given, called in…

 

Limitazioni del Type Hinting

In PHP, i tipi scalari, come stringhe e valori interi non sono supportati dal Type Hinting. Il seguente esempio genererà un errore di tipo Catchable fatal error poiché testHint non è vista come una stringa, ma come un’istanza della classe string

 

58-

 

Nello specifico, l’esecuzione del codice proposto porterà alla produzione della seguente notifica:

 

Catchable fatal error: Argument 1 passed to testHint() must be an instance of string, string given, called in …

 

Type Hinting e gerarchie

 

Il Type Hinting, non funziona solo ed esclusivamente con le classi indicate ma la sua valenza si espande anche alle varie sottoclassi. Ciò significa che se per esempio abbiamo una classe Cane che estende la classe Mammifero ed utilizziamo il Type Hinting Mammifero, possiamo passare anche un’istanza di tipo Cane:

 

59-

 

Infine, come ultimo tassello, possiamo utilizzare anche i namespace delle interfacce come Type Hinting: ogni classe che implementa un’interfaccia del tipo indicato, costituirà un parametro accettabile:

 

60-

 

Il Type Hinting è è dunque uno strumento molto potente e flessibile che permette di porre ulteriori regole all’interno delle applicazioni OOP. Grazie ad esso è possibile avere un maggiore controllo sui parametri delle funzioni e dei metodi, rendendo gli script maggiormente robusti.

 

 

Clonare gli oggetti in PHP

 

Impariamo a creare dei “cloni” delle istanze degli oggetti attraverso l’operatore clone di PHP. Scopriamo quali vantaggi offre la clonazione rispetto al trattamento degli oggetti per riferimento.

Come descritto in precedenza, dalla versione 5 di PHP in poi, gli oggetti vengono trattati per riferimento. In questa lezione, vedremo come lavorare con delle copie di oggetti (clonazione) invece che con dei riferimenti.

 

Assegnamento e clonazione

 

A partire da PHP 5, la copia di un oggetto, tramite assegnamento, è solo un riferimento, ciò avviene a differenza di quanto accadeva in PHP 4, dove l’assegnamento tra 2 oggetti creava una copia. A tal proposito è possibile formulare un semplice esempio:

 

61-

 

 

L’output generato dal codice proposto in precedenza sarà quindi il seguente:

 

62-

 

Nell’esempio precedente, $personB ha il riferimento di $personA. Pertanto, quando un oggetto è assegnato come riferimento, le modifiche apportate ad uno di essi si riflettono anche sugli altri. Se invece volessimo lavorare con delle copie, e non con dei riferimenti, potremmo utilizzare la funzione clone. Per clonare un oggetto è sufficiente impiegare la parola chiave clone dopo l’operatore di assegnazione =.

 

63-

 

Questa funzione nativa crea automaticamente una nuova istanza dell’oggetto con le relative copie delle proprietà; si tratta di una copia precisa e indipendente dell’altro oggetto, dove eventuali aggiunte o modifiche al clone non incideranno sull’originale. Anche un questo caso è possibile proporre un breve esempio:

 

64-

 

L’output generato sarà dunque il seguente:

 

65-

 

Metodo magico __clone

 

Il metodo __clone, è un metodo magico, il quale fornisce tutte le funzionalità per la clonazione completa e indipendente di un oggetto, esso crea un nuovo oggetto identico all’originale copiando tutte le variabili membro.

Supponiamo di voler modificare la proprietà $azienda della classe Person in caso di clonazione dell’oggetto come nell’esempio proposto di seguito:

 

66-

 

Non appena PHP eseguirà la clonazione del nuovo oggetto, verrà invocato il metodo __clone() e l’oggetto clonato sarà accessibile, nel rispetto del principio di incapsulamento, mediante la variabile $this. In questo caso $person3->azienda sarà uguale a “Warner Bros.” perché il metodo __clone() verrà chiamato solo per l’oggetto $person3 settando il valore di $this->azienda in questo modo. Di seguito l’output generato in fase di esecuzione:

 

67-

 

Ora che sono state descritte le procedure per clonare un oggetto e personalizzare la funzione di clonazione sarà possibile creare oggetti normali per riferimento oppure clonarli, realizzando delle copie vere e proprie.

Una replica a “Programmazione a oggetti con PHP – 1”

  1. Ann-Sofie scrive:

    Gran sitio! Tus publicaciones son una delicia para mis ojos y están muy bien escritas. ¡Buen día!
    ————-
    Ottimo sito!I tuoi post sono una delizia per i miei occhi e sono molto ben scritti.Buona giornata!

Lascia una risposta