PHP/Programmazione/OOP

Indice del libro

La programmazione object oriented (o programmazione orientata agli oggetti, abbreviata spesso con OOP, object oriented programming) è uno stile, un paradigma di programmazione che consiste nel modellare un problema definendo degli oggetti che interagiscono fra di loro. Un oggetto è un insieme logico costituito da dati e da funzioni che manipolano i dati stessi e creano l'interfaccia tramite la quale l'oggetto interagisce con gli altri oggetti.

In questo capitolo tratteremo di programmazione orientata agli oggetti in PHP 5. Una delle maggiori novità di PHP 5, infatti, è proprio il nuovo object model che rende PHP un linguaggio realmente object oriented (orientato agli oggetti). Queste nuove caratteristiche, che analizzeremo nel prosieguo del capitolo, hanno permesso lo sviluppo di tecniche aderenti ai pattern di progettazione più moderni.
Inoltre, come annunciato sul sito ufficiale, PHP 4 è ormai giunto al tramonto e non è più supportato a partire dall'agosto 2008.
Pertanto, per quanto riguarda l'object model di PHP 4, rimandiamo alla documentazione ufficiale (in italiano)

Le classi modifica

Una classe definisce una categoria di oggetti, aventi tutti le stesse caratteristiche. In effetti, il concetto di classe precede quello di oggetto:

  1. prima penso ad una classe e ne definisco i dati, detti attributi (i quali in realtà sono delle variabili), e le funzioni che li manipolano, dette metodi
  2. in seguito istanzio un oggetto a partire dalla classe: possiamo pensare alla classe come ad uno "stampino" per creare oggetti e ad un oggetto come ad un'entità "concreta" della classe. Per creare le istanze di ciascun oggetto è comodo utilizzare un costruttore, ovvero una funzione che imposti fin dall'inizio alcuni attributi fondamentali della classe.

Facciamo un esempio mentale. La classe "cane" definisce, nella nostra mente, il miglior amico dell'uomo, con tutte le sue caratteristiche, cioè gli attributi (per es. la razza, il colore del manto). L'oggetto "Lassie" è un'istanza della classe cane in cui l'attributo razza è Collie, il colore del manto è bianco e marrone, ecc....


Per creare una classe in PHP è sufficiente utilizzare la sintassi:

class NomeClasse {
 //qui dentro vanno inserite le variabili pubbliche (gli attributi) e private della classe
 //e le funzioni (metodi)
 //questa funzione è il costruttore della classe e si chiama sempre così
 function __construct ($parametri) { 
  //istruzioni...
 }

} //qui finisce la dichiarazione della classe

Il costruttore può essere anche una funzione con lo stesso nome della classe.

In questo modo è stata creata una classe NomeClasse. Per crearne una nuova instanza, sarà sufficiente scrivere:

$var = new NomeClasse ($parametri_del_costruttore) //instanzia un nuovo oggetto della classe NomeClasse

Con questa istruzione creiamo un nuovo oggetto dallo "stampino" NomeClasse; i parametri passati tra parentesi sono quelli richiesti dalla funzione __construct (se prevista).
Per accedere agli attributi o ai metodi della classe si userà la sintassi:

$var->attributo = $a; //posso leggere o modificare gli attributi della classe
$b = $var->esempioMetodo(); //un metodo può restituire un valore

Quando si lavora con le classi, la cosa più comoda è creare un file class.NomeClasse.php in cui inserire il codice della classe e poi richiamare tale file negli script in cui si desidera lavorare tramite l'istruzione include_once:

include_once("class.NomeClasse.php");

Per accedere all'attributo dall'interno della classe si usa this, per accedervi dall'esterno: "il nome dell'oggetto -> e il nome del metodo":

class prova{
   private $a="mamma";

   public function stampa(){
      echo $this->a;
   }
}

$b=new prova();
$b->stampa();

Come si vede la funzione stampa accede alla variabile privata a tramite la keyword this e ->, si noti il $ la keyword this diventa variabile e prende il $ che non va applicato al nome della variabile privata!
Per accedere al metodo stampa() usiamo il costrutto: "il nome oggetto -> e il nome metodo". Potremmo accedere anche ad una variabile della classe nello stesso modo (vedi ultima riga):

class prova{
   private $a="mamma";
   public $c="gatto";

   public function stampa(){
      echo $this->a;
   }

   public function get_var(){
      return $this->a;
   }
}

$b=new prova();
$b->stampa();
$variabile_privata=$b->get_var();
echo $b->c;

se invece vogliamo ottenere la variabile privata, il metodo get_var() accede per noi alla variabile privata e ne restituisce il valore, che verrà salvato nella variabile $variabile_privata.

Ereditarietà modifica

Una classe può estenderne un'altra ereditandone così i metodi e gli attributi. La classe estesa si chiama superclasse ed è estesa da una sottoclasse. La sottoclasse può sovrascrivere (overriding, da non confondere con l'overloading) i metodi della superclasse definendone di propri, per estendere una classe è sufficiente, dopo il nome della classe estendente, mettere la parola chiave extends ed il nome della classe da estendere.

class frutta{
   public function visualizza(){
      echo "questo è un frutto";
   }
}

class mele extends frutta{
   public function visualizza(){
      echo "questa è una mela";
   }
}

class pere extends frutta{
   public function visualizza(){
      echo "questa è una pera";
   }
}

$fuji=new mele();
$fuji->visualizza(); //scriverà questa è una mela

$williams= new pere();
$williams->visualizza();//scriverà questa è una pera

$ciquita= new frutta();
$ciquita->visualizza();//visualizzerà questo è un frutto


Come si vede il metodo visualizza() è sovrascritto dalle varie sottoclassi: pere e mele. Bisogna prestare particolare attenzione con gli indicatori di visibilità, come vedremo fra poco.

Polimorfismo modifica

Nell'esempio sopra abbiamo una chiara dimostrazione di polimorfismo, il metodo visualizza() è chiamato più volte ma ogni volta dà un risultato differente a seconda che l'oggetto appartenga ad una od un'altra classe. Se richiamo visualizza() con fushi che è un oggetto della classe mele, ecco che visualizzerà "questa è una mela", mentre se lo richiamo con williams. visualizzerà "questa è una pera", il polimorfismo è quindi la capacità di uno stesso metodo di eseguire compiti differenti in base alla classe dell'oggetto di appartenenza, infatti il metodo richiamato è sempre visualizza() ma si comporterà in modo diverso a seconda della classe dell'oggetto.

Ambito modifica

Abbiamo detto che un attributo non è altro che una variabile della classe; bisogna tuttavia fare attenzione all'ambito dell'attributo, ovvero alla visibilità che ha l'attributo dall'esterno.

  • public rende l'attributo o il metodo visibile e utilizzabile anche dall'esterno della classe
  • protected l'attributo o il metodo possono essere visti/usati solo dall'interno della classe o da una sottoclasse
  • private l'attributo o il metodo può essere visto/usato solo all'interno della classe.

Se si usa la dichiarazione deprecata di php<5: var, senza specificare l'ambito dell'attributo, questo avrà come ambito per default pubblico

  1 <?php
  2 class prova{
  3    private $priv="ciao";
  4    protected $prot="miau";
  5    public $publ="bau";
  6
  7    var $var="fuffi";
  8
  9    function stampa(){
 10       echo "dalla funzione stampa accedo alla variabile privata= " . $this->priv . "<br>";
 11    }
 12 }
 13                         
 14 class altro extends prova{
 15    function ristampa(){ 
 16       echo "dalla class estendente accedo alle variabili protected della classe estesa= " . $this->prot . "<br>";
 17    }
 18 }
 19
 20 $a=new prova();
 21 //echo "a priv " . $a->priv . "<br>";
 22 //echo "a prot " . $a->prot . "<br>";
 23 echo "a publ " . $a->publ . "<br>";
 24 echo "var= " . $a->var . "<br>";
 25 $a->stampa();
 26
 27
 28 $b=new altro();
 29 //echo "b priv " . $b->priv . "<br>";
 30 //echo "b prot " . $b->prot . "<br>";
 31 echo "b publ " . $b->publ . "<br>";
 32 $b->ristampa();
 33
 34 ?>


Se decommentiamo (togliendo i caratteri "//") la riga 21, il motore php restituirà il seguente errore:
PHP Fatal error: Cannot access private property prova::$priv in /path_del_file/test.php on line 14, referer: http://localhost/

La stessa cosa succede se ricommentiamo la riga 21 (rimettendo i caratteri "//") decommentiamo la riga 22: questo perché stiamo richiamando la variabile dall'esterno della classe. Come si vede invece dalla riga 25, il metodo stampa() viene richiamato dalla variabile a che è istanza della classe prova. Essendo il metodo stampa() parte della classe prova, esso può accedere alla variabile privata priv.
Decommentando la riga 29 otterremo un effetto che può sembrare particolare, anziché dare errore il motore stamperà b priv e tutto quello che segue questa riga. Infatti la variabile privata priv non viene estesa ed il motore non restituisce un errore fatale, bensì:
Undefined property: altro::$priv in /path/test.php on line 29, referer: http://localhost/

La riga 32 mostra che il metodo ristampa può accedere alla variabile protetta prot, infatti tale variabile è estesa alla classe altro.

Classi astratte modifica

Le classi astratte definiscono delle linee guida per i metodi che le classi che estendono dovranno seguire. Non definiscono il corpo del metodo, bensì il nome, la visibilità, i parametri, cioè quella che viene definita la signature. Il motivo è semplice, si pensi ad un programma sviluppato da più gruppi di sviluppatori, un gruppo definisce la classe madre, mentre gli altri scrivono delle sottoclassi ridefinendo alcuni metodi. Se i gruppi non hanno una buona intercomunicabilità potrebbero ridefinire a piacere i metodi della superclasse cambiando i parametri o la visibilità dello stesso metodo e per evitare ciò esistono le classi astratte.

Se una classe ha anche solo un metodo astratto è obbligatorio definirla come astratta, se una classe eredita da una classe astratta è obbligatorio ridefinirne tutti i metodi astratti. In PHP non è possibile dichiarare un metodo come astratto e definirlo nella stessa classe come permettono alcuni linguaggi OOP.

Una classe o un metodo si dichiarano astratti con la keyword abstract:

abstract class prova {
  public $var="ciao";
  abstract public function miometodo($var1, $var2);
}

class sotto extends prova {
  public function miometodo($var1, $var2){
    echo "$var1 $var2";
}

Bisogna mantenere inalterata la signature e cioè nome namespace parametri, si può cambiare solo la visibilità, restringendola. Una classe astratta non può essere istanziata direttamente.

Incapsulamento modifica

Si definisce incapsulamento la tecnica di rendere invisibile ed inaccessibile parti di codice non necessario per l'utilizzo di una classe, rendendo accessibili e visibili solo alcuni metodi e alcuni attributi. Si ottiene grazie agli indicatori di visibilità e alle interfacce.

Interfacce modifica

Le interfacce sono delle ulteriori astrazioni delle classi astratte. Vengono definite tramite la keyword interface seguita dal nome voluto per l'interfaccia e le graffe.

interface miainterfaccia{
   public function setName($name);
   public function getName();
}

In pratica è come dichiarare una classe astratta, con l'agevolazione di non dover dichiarare tutto abstract. Una classe che implementi tale interfaccia deve ridefinire tutti i metodi obbligatoriamente pena errore fatale, esattamente come per le classi astratte. Vediamo l'implementazione:

class miaclasse implements miainterfaccia{
   public $name;
  
   public function setName($name){
      $this->name=$name;
   }

   public function getName(){
      return $this->name;
   }
}

Come per le classi astratte la classe implementante deve ridefinire tutti i metodi, preservando la signature tranne per l'ambito che può essere ristretto.

Namespaces modifica

<?php
namespace my\name;

class MyClass {}
function myfunction() {}
const MYCONST = 1;

$a = new MyClass;
$c = new \my\name\MyClass;
$d = new \globalClass;
?>