PHP: 4 Guida alla Programmazione ad Oggetti (OOP)

PHP: 4 Guida alla Programmazione ad Oggetti (OOP)

Introduzione

In questo articolo ci concentreremo sulla programmazione a oggetti in PHP, un paradigma molto potente e ampiamente utilizzato nello sviluppo software. I concetti fondamentali che vedremo sono comuni anche ad altri linguaggi orientati agli oggetti.

Cos’è la Programmazione a Oggetti (OOP)

La programmazione orientata agli oggetti (Object Oriented Programming) è un paradigma di programmazione (una metodologia di programmazione) basata, come dice la parola stessa, sul concetto di oggetto, ovvero una struttura di dati che rappresenta un’istanza di una classe. In OOP, l’oggetto è il fulcro del programma e contiene sia i dati (chiamati proprietà) che le operazioni (chiamate metodi) che possono essere eseguite su quei dati. I principi cardine della OOP sono:

  • Astrazione: l’astrazione consente di creare un modello semplificato di un oggetto o di un concetto complesso, enfatizzando gli aspetti rilevanti per la soluzione di un particolare problema e riducendo al minimo i dettagli che non sono necessari per il contesto. L’astrazione consente quindi di ridurre la complessità del problema e facilita la creazione di codice più modulare e manutenibile.
  • Incapsulamento: consente di proteggere i dati dell’oggetto dalla modifica accidentale o intenzionale da parte di codice esterno, assicurando che le modifiche possano essere effettuate solo attraverso i metodi di accesso dell’oggetto stesso. Ciò rende il codice più sicuro e prevenire errori dovuti a modifiche indesiderate dei dati.
  • Ereditarietà: Consente a una classe di ereditare proprietà e metodi da un’altra classe, promuovendo il riutilizzo del codice.

Rivedremo meglio questi concetti più avanti nella guida in modo da circostanziarli.

Le Basi: Classi e Oggetti in PHP

Come detto alla base della OOP c’è il concetto di classe, una classe non è altro che una struttura che definisce gli attributi (ossia i dati) e i comportamenti (i metodi ossia le funzioni) di un oggetto a livello di codice. È possibile pensare ad una classe come una sorta di “stampino” da cui è possibile creare uno o più oggetti. Ogni oggetto creato da una classe è indipendente e può avere i propri valori interni.

class Automobile {
    public $marca;
    public $modello;

    public function accendiMotore() {
        echo "Motore acceso!";
    }
}

ricapitolando una classe è un modello che descrive un tipo di oggetto e contiene:

Metodi (funzioni)

Proprietà (variabili)

Nell’esempio precedente abbiamo una classe chiamata Automobile con due proprietà (marca e modello) e un metodo (accendiMotore).

Per creare un oggetto, non basta definire una classe ma bisogna creare un’istanza della classe (istanziare l’oggetto) per farlo si utilizza la parola new seguita dal nome della classe vediamo un esempio:

$miaAuto = new Automobile();
$miaAuto->marca = "Fiat";
$miaAuto->modello = "500";

$miaAuto->accendiMotore(); // Output: Motore acceso!

Con php è possibile riferirsi ad una classe tramite una variabile:

$classe = "NomeClasse";
$oggetto = new $classe();

Quando si assegna un oggetto ad un’altra variabile, in realtà non si sta copiando l’intero oggetto. Si sta creando invece, un riferimento alla stessa istanza di oggetto. Questo significa che se l’oggetto “copiato” subisce una modifica, anche la variabile originale, avrà quelle modifiche. Vediamo un esempio:

<?php
  class Persona {
    public $nome;
  }

  $persona1 = new Persona();
  $persona1->nome = "Mario";

  // Creiamo un riferimento alla stessa istanza di oggetto
  $persona2 = $persona1;

  // Modifichiamo il nome di persona2
  $persona2->nome = "Luigi";

  // Il nome di persona1 e persona2 sono gli stessi
  echo $persona1->nome; // Stampa "Luigi"
  echo $persona2->nome; // Stampa "Luigi"
?>

Metodo Costruttore

Il metodo costruttore è un particolare metodo di una classe che viene eseguito automaticamente quando viene creata un’istanza di quella classe, solitamente il compito principale del costruttore è quello di inizializzare le proprietà di un oggetto durante la sua creazione anche attraverso il passaggio di alcuni parametri.

In PHP, il metodo costruttore viene dichiarato attraverso la funzione speciale __construct() e come detto può eventualmente accettare degli argomenti che vengono passati alla classe quando viene creata un’istanza, inoltre il metodo costruttore può contenere del codice che viene eseguito durante l’inizializzazione dell’oggetto. Vediamo un semplice esempio:

<?php
  class Persona {
    public $nome;
    public $cognome;

    public function __construct($nome, $cognome) {
      $this->nome = $nome;
      $this->cognome = $cognome;
    }
  }

  $persona = new Persona("Mario", "Rossi");
  echo $persona->nome; // Stampa "Mario"
  echo $persona->cognome; // Stampa "Rossi"

?>

Oltre alla funzione __construct(), php offre molti altri metodi magici (magic methods) si rimanda documentazione ufficiale per saperne di più.

Incapsulamento

L’incapsulamento è uno dei concetti fondamentali dell’OOP. Consiste nel proteggere lo stato interno di un oggetto (o meglio di una proprietà o di un metodo dell’oggetto), impedendo che venga modificato direttamente dall’esterno in modo incontrollato. In PHP si usano i seguenti modificatori di visibilità:

  • public: accessibile da ovunque
  • protected: accessibile solo dalla classe stessa e dalle classi figlie (classi che estendono la classe)
  • private: accessibile solo dalla classe in cui è definito

Ricapitolando una classe può contenere diverse proprietà, che possono essere di tipo public, private o protected, queste parole chiave rappresentano il livello di visibilità. Questo vale anche per le funzioni (metodi). Una proprietà o un metodo di tipo public sono accessibili sia all’interno che all’esterno di una classe; i metodi o le proprietà definite attraverso l’utilizzo della parola private sono accessibili soltanto all’interno della classe stessa. Attraverso l’utilizzo della parola protected si consente l’accesso alla proprietà o al metodo soltanto all’interno della classe e da tutte le sue sottoclassi (classi che estendono la classe). Vediamo ora un esempio pratico in php per capire meglio:

<?php
  class Studente {
    private $nome;

    public function setNome($nome) {
      $this->nome = $nome;
    }

    public function getNome() {
      return $this->nome;
    }
  }
?>

All’interno della classe “Studente” abbiamo definito una proprietà privata chiamata “nome”, che può essere impostata e letta solo attraverso i metodi pubblici “setNome” e “getNome” (questi metodi in generale prendono il nome di setter e getter). Si noti che per poter accedere al contenuto di una variabile o funzione all’interno della classe stessa è necessario utilizzare la variabile speciale $this. Istanziamo ora la classe “Studente” in modo da ottenere l’oggetto:

<?php
  $studente = new Studente(); // Istanzio l'oggetto
  $studente->setNome("Mario"); // Assegno Mario alla proprietà nome
  echo $studente->getNome(); // stampa "Mario"
?>

Ovviamente la classe deve trovarsi all’interno dello stesso file per poter istanziare l’oggetto, oppure va inclusa.

N.B.
Se la visibilità del metodo o della proprietà non viene dichiarata php assumerà che si tratta di visibilità di tipo public.

In generale, si consiglia di utilizzare la visibilità più restrittiva possibile per le proprietà e i metodi di una classe durante la sua progettazione, per rendere il codice il più isolato possibile (incapsulamento). Tuttavia, tra private e protected è possibile scegliere, protected per offrire una maggiore estendibilità del codice, non vi è una regola obbligatoria.

Ereditarietà

L’ereditarietà è un concetto fondamentale della programmazione ad oggetti che consente di creare nuove classi basate su altre classi esistenti, la classe figlio eredita le proprietà ed il comportamento dalla classe padre e questo permette di personalizzare il comportamento secondo le proprie esigenze. In poche parole, L’ereditarietà consente di creare gerarchie di classi per la creazione di astrazioni complesse. In PHP, l’ereditarietà si realizza tramite l’utilizzo della parola chiave extends. Quando definiamo una nuova classe (figlia) che deve ereditare da una classe esistente (padre), utilizziamo extends seguito dal nome della classe padre. Ad esempio:

<?php

  class Animal {
    public function makeSound() {
      echo "Generic animal sound\n";
    }
  }

  class Dog extends Animal {
    public function makeSound() {
      echo "Woof!\n";
    }
  }

  $my_dog = new Dog();
  $my_dog->makeSound(); // stampa "Woof!"

?>

In questo esempio definiamo una classe figlia Dog che eredita dalla classe padre Animal il metodo makeSound(), e lo sovrascrive. Ovviamente se il metodo makeSound() non fosse stato sovrascritto, dalla classe Dog sarebbe stato eseguito quello di Animal.

Polimorfismo

Il polimorfismo consente di utilizzare lo stesso metodo in classi diverse, ma con comportamenti specifici.

class Animale {
    public function verso() {
        echo "Verso generico";
    }
}

class Cane extends Animale {
    public function verso() {
        echo "Bau";
    }
}

class Gatto extends Animale {
    public function verso() {
        echo "Miao";
    }
}

function faiVerso(Animale $animale) {
    $animale->verso();
}

Chiamando faiVerso(new Cane()); otterremo "Bau", mentre con faiVerso(new Gatto()); otterremo "Miao". Questo è il polimorfismo visto in azione.

Namespace (Spazio dei Nomi)

In PHP, un namespace è un meccanismo che permette di organizzare il codice in modo logico prevenendo i conflitti di nomi tra le classi, le funzioni e le costanti.

Un namespace può contenere classi, interfacce, funzioni e costanti e può essere definito in un uno o più file. Tutti gli elementi definiti in un namespace hanno un prefisso del namespace, che evita come detto, i conflitti di nomi con gli elementi definiti in altri namespace o nel “namespace globale” (ovvero il namespace predefinito in cui si trovano tutti gli elementi non appartenenti ad alcun namespace). Vediamo un semplice esempio che permette di capire come definire e utilizzare un namespace in PHP:

<?php
  // Definiamo un namespace chiamato "nome_namespace"
  namespace nome_namespace;

  // Definiamo una classe all'interno del namespace "nome_namespace"
  class MiaClasse {
    // Codice
  }
?>

L’utilizzo dei namespace rende il codice più organizzato e leggibile, poiché evita i conflitti di nomi tra le classi. Inoltre, i namespace consentono di organizzare il codice in moduli e pacchetti logici, è possibile utilizzare gerarchie di nomi. Per creare una struttura annidata all’interno di un namespace principale, basta aggiungere il carattere backslash (\) seguita dal nome del sotto namespace vediamo ora un esempio in cui definiamo un namespace chiamato “SubNamespace” all’interno di un namespace chiamato “PrincipalNamespace”.:

<?php
  namespace PrincipalNamespace\SubNamespace;
  
  class MyClass {
    // Codice
  }

è poi possibile richiamare una classe all’interno di un namespace nel seguente modo:

<?php
  use PrincipalNamespace\SubNamespace\MyClass;

  $obj = new MyClass();

Ogni file dovrebbe contenere una sola classe e inoltre la convenzione impone di usare la stessa struttura di cartelle nel progetto in modo da identificare univocamente la posizione di un file contenente la classe in un progetto (standard PSR-4). Ad esempio poniamo di avere la classe Utente all’interno della cartella src/Controller il codice per il namespace potrebbe essere il seguente:

<?php
  use App\Controller;
  
  class Utente {
  }

Autoloading

Come detto in precedenza è bene creare una classe in un singolo file, per poter lavorare con più classi è necessario che queste siano caricate da php prima di poter creare un oggetto, per fare questo bisogna usare la funzione require(“percorso_del_file“) o require_once(“percorso_del_file“); è consigliabile utilizzare la seconda istruzione che, verifica se il file è già stato incluso in precedenza e se lo è evita di farlo nuovamente. In progetti in cui si utilizzano numerose classi, la gestione manuale dell’inclusione di tutte le classi può diventare difficoltosa e creare problemi, per questo esiste una funzione di autoloading delle classi. 

l’autoloading è un meccanismo che consente di caricare automaticamente le classi e le interfacce quando vengono utilizzate per la prima volta nel codice. In questo modo, non è necessario includere manualmente tutti i file contenenti le definizioni delle classi prima di utilizzarle.

Il processo di autoloading è solitamente gestito da una funzione chiamata spl_autoload_register, che registra una o più funzioni callback di autoloading. Quando PHP cerca di utilizzare una classe o un’interfaccia che non è stata ancora definita, invoca queste funzioni di autoloading per cercare il file che contiene la definizione della classe o dell’interfaccia.

Ad esempio, supponiamo di avere una classe chiamata MyClass definita nel file MyClass.php. Possiamo registrare una funzione di autoloading personalizzata come segue:

<?php
  spl_autoload_register(function ($class_name) {
    $file_name = str_replace('\\', DIRECTORY_SEPARATOR, $class_name) . '.php';
    if (file_exists($file_name)) {
      require_once $file_name;
    }
  });

In questo esempio abbiamo costruito una funzione di autoloading che converte il nome della classe in un percorso di file sostituendo i backslash (\) con il separatore di directory del sistema operativo (diverso a seconda del sistema operativo), quindi verifica se eseiste ed in caso affermativo, il file, viene incluso utilizzando la funzione require_once. Tornando all’esempio questa funzione di autoloading verrà eseguita automaticamente quando per la prima volta utilizzeremo la classe MyClass all interno del codice e cercherà un file chiamato MyClass.php. Sono anche disponibili librerie di autoloading di terze parti che semplificano ancora di più il processo e forniscono funzionalità aggiuntive come la gestione delle dipendenze, una di queste è Composer che tratteremo più avanti.

Classi astratte e final

In PHP, le classi astratte e final sono dei costrutti che consentono di definire ulteriori restrizioni e vincoli sui tipi di classi che possono essere create.

Una classe astratta è una classe che non può essere istanziata direttamente, ma può essere solo utilizzata come classe padre per definire altre classi derivate. Una classe astratta può contenere metodi astratti, che sono metodi che devono essere implementati dalle classi figlie. Una classe astratta tuttavia, può essere utilizzata come classe padre per definire altre classi derivate che estendono la sua funzionalità.

Una classe final, invece, è una classe che non può essere ereditata da altre classi. Ciò significa che non è possibile creare classi che estendono una classe final.

Ecco un esempio completo che utilizza sia le classi astratte che le classi final per vederne anche la sintassi:

<?php

abstract class Animal {
  abstract public function makeSound();
}

class Dog extends Animal {
  public function makeSound() {
    echo "Woof!\n";
  }
}

final class Cat extends Animal {
  public function makeSound() {
    echo "Meow!\n";
  }
}

// Non è possibile definire una classe che eredita da Cat
// class Lion extends Cat {}

$my_dog = new Dog();
$my_dog->makeSound(); // stampa "Woof!"

$my_cat = new Cat();
$my_cat->makeSound(); // stampa "Meow!"

In questo esempio, abbiamo definito una classe astratta Animal che contiene un metodo astratto makeSound(). Abbiamo quindi definito due classi derivate che estendono la classe Animal: Dog e Cat. La classe Dog implementa il metodo makeSound in modo da stampare il suono del cane, mentre la classe Cat implementa il metodo makeSound in modo da stampare il suono del gatto.

Inoltre, abbiamo definito la classe Cat come final, il che significa che non può essere ereditata da altre classi. Abbiamo anche mostrato che se proviamo a definire una classe Lion che eredita da Cat, otterremo un errore perché Cat è una classe finale e non può essere ereditata da altre classi.

Interfacce

Le interfacce in PHP sono una forma di contratto che definisce un insieme di metodi che una classe deve implementare. Un’interfaccia definisce solo la firma dei metodi, cioè il loro nome, i parametri che accettano e il tipo di valore restituito, ma non fornisce l’implementazione di questi metodi.

Le classi che implementano un’interfaccia devono fornire l’implementazione dei metodi dell’interfaccia. In questo modo, le interfacce consentono di definire un’API comune per un gruppo di classi correlate, senza specificare come queste classi implementano effettivamente quella funzionalità. Vediamo un semplice esempio in cui definiamo un interfaccia Auto con i metodi startEngine(), StopEngine() e drive():

<?php  
  interface Auto {
    public function startEngine();
    public function stopEngine();
    public function drive($distance);
  }
?>

Ora definiamo una classe Fiat che implementa la classe Auto:

<?php

  class Fiat implements Car {
    public function startEngine() {
      echo "Engine started!\n";
    }

    public function stopEngine() {
      echo "Engine stopped!\n";
    }

    public function drive($distance) {
      echo "Driving for $distance km\n";
    }
  }

?>

L’utilizzo delle interfacce consente di scrivere codice modulare e flessibile, in cui le classi possono essere sostituite con altre che implementano la stessa interfaccia, senza dover modificare il codice che le utilizza. Ciò consente di scrivere codice più manutenibile e di semplificare i test e la validazione del codice.

Entità statiche

Le entità statiche sono elementi definiti all’interno di una classe che sono accessibili senza la necessità di creare un’istanza della classe stessa. Ciò significa che è possibile utilizzare gli elementi statici, come proprietà e metodi, direttamente dalla classe, senza dover istanziare l’oggetto. Per definire una proprietà o un metodo come statico, si utilizza la parola chiave static. Ad esempio:

<?php

class MyClass {
    public static $myStaticProperty = 'Hello World!';

    public static function myStaticMethod() {
        return 'This is a static method.';
    }
}

// Accessing a static property:
echo MyClass::$myStaticProperty; // Output: Hello World!

// Calling a static method:
echo MyClass::myStaticMethod(); // Output: This is a static method.

?>

I trait consentono di riutilizzare codice all’interno di classi diverse. Un trait è un insieme di metodi che possono essere inclusi all’interno di una classe utilizzando la parola chiave use. I trait sono utilizzati per evitare la duplicazione del codice e consentono di definire funzionalità comuni in un unico posto e poi utilizzarle in più classi. Ad esempio:

<?php

trait MyTrait {
    public function myTraitMethod() {
        return 'This is a trait method.';
    }
}

class MyClass {
    use MyTrait;
}

// Using a trait method:
$obj = new MyClass();
echo $obj->myTraitMethod(); // Output: This is a trait method.

?>

In questo esempio, MyTrait definisce un metodo myTraitMethod(). La classe MyClass include il trait utilizzando la parola chiave use e quindi può utilizzare il metodo myTraitMethod() come se fosse definito all’interno della classe stessa.

Classi Anonime

Le classi anonime sono una funzionalità introdotta in PHP 7 che consentono di definire una classe senza dover assegnare un nome ad essa. Una classe anonima è utile quando si desidera creare un oggetto che ha solo un ciclo di vita limitato e non richiede l’uso in altri punti del codice.

Per definire una classe anonima, si utilizza la parola chiave class seguita dalle parentesi graffe che contengono il corpo della classe. Poiché la classe non ha un nome, non è possibile crearne un’istanza direttamente. Tuttavia, è possibile assegnare l’oggetto creato tramite la classe anonima a una variabile e utilizzarlo in seguito. Ad esempio:

<?php

  $myObject = new class {
      public function sayHello() {
          return 'Hello World!';
      }
  };

  // Usiamo la classe anonima:
  echo $myObject->sayHello(); // Output: Hello World!

?>

Conclusioni

L’OOP in PHP offre strumenti potenti per sviluppare codice più ordinato, riutilizzabile e scalabile. Comprenderne bene i concetti fondamentali come classioggettiincapsulamentoereditarietà e polimorfismo è fondamentale per scrivere applicazioni moderne.

Se stai iniziando a programmare in PHP, familiarizzare con questi concetti ti permetterà di migliorare il design e la qualità del tuo codice, avvicinandoti a un livello più professionale di sviluppo software.

Lezione Precedente

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *