Introduzione alla programmazione orientata agli oggetti
Il termine “OOP” sta per Object-Oriented Programming (programmazione orientata agli oggetti), un modo per organizzare e strutturare il codice. L'OOP ci permette di vedere un programma come un insieme di oggetti che comunicano tra loro, piuttosto che come una sequenza di comandi e funzioni.
Nell'OOP, un “oggetto” è un'unità che contiene dati e funzioni che operano su tali dati. Gli oggetti vengono creati sulla base di “classi”, che possono essere intese come progetti o modelli di oggetti. Una volta che abbiamo una classe, possiamo creare la sua “istanza”, che è un oggetto specifico creato a partire da quella classe.
Vediamo come creare una semplice classe in PHP. Quando si definisce una classe, si usa la parola chiave “class”, seguita dal nome della classe e poi dalle parentesi graffe che racchiudono le funzioni della classe (chiamate “metodi”) e le variabili della classe (chiamate “proprietà” o “attributi”):
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
In questo esempio, abbiamo creato una classe chiamata Car
con una funzione (o “metodo”) chiamata
honk
.
Ogni classe dovrebbe risolvere un solo compito principale. Se una classe fa troppe cose, può essere opportuno dividerla in classi più piccole e specializzate.
Le classi sono in genere memorizzate in file separati per mantenere il codice organizzato e facile da navigare. Il nome del
file deve corrispondere al nome della classe, quindi per la classe Car
il nome del file sarà
Car.php
.
Quando si nominano le classi, è bene seguire la convenzione “PascalCase”, cioè ogni parola del nome inizia con una lettera maiuscola e non ci sono sottolineature o altri separatori. I metodi e le proprietà seguono la convenzione “camelCase”, cioè iniziano con una lettera minuscola.
Alcuni metodi in PHP hanno ruoli speciali e sono preceduti da __
(due trattini bassi). Uno dei metodi speciali
più importanti è il “costruttore”, contrassegnato con __construct
. Il costruttore è un metodo che viene
chiamato automaticamente quando si crea una nuova istanza di una classe.
Spesso si usa il costruttore per impostare lo stato iniziale di un oggetto. Per esempio, quando si crea un oggetto che rappresenta una persona, si può usare il costruttore per impostarne l'età, il nome o altri attributi.
Vediamo come utilizzare un costruttore in PHP:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25
In questo esempio, la classe Person
ha una proprietà (variabile) $age
e un costruttore che imposta
questa proprietà. Il metodo howOldAreYou()
consente di accedere all'età della persona.
La pseudo-variabile $this
viene utilizzata all'interno della classe per accedere alle proprietà e ai metodi
dell'oggetto.
La parola chiave new
viene utilizzata per creare una nuova istanza di una classe. Nell'esempio precedente, abbiamo
creato una nuova persona di 25 anni.
È anche possibile impostare valori predefiniti per i parametri del costruttore, se non sono specificati durante la creazione di un oggetto. Per esempio:
class Person
{
private $age;
function __construct($age = 20)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person; // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20
In questo esempio, se non si specifica l'età quando si crea un oggetto Person
, verrà usato il valore predefinito
di 20.
La cosa bella è che la definizione della proprietà con la sua inizializzazione tramite il costruttore può essere accorciata e semplificata in questo modo:
class Person
{
function __construct(
private $age = 20,
) {
}
}
Per completezza, oltre ai costruttori, gli oggetti possono avere dei distruttori (metodo __destruct
) che vengono
chiamati prima che l'oggetto venga rilasciato dalla memoria.
Spazi dei nomi
Gli spazi dei nomi consentono di organizzare e raggruppare classi, funzioni e costanti correlate, evitando conflitti di denominazione. Si possono considerare come le cartelle di un computer, dove ogni cartella contiene file relativi a un progetto o a un argomento specifico.
Gli spazi dei nomi sono particolarmente utili in progetti di grandi dimensioni o quando si utilizzano librerie di terze parti in cui potrebbero sorgere conflitti di denominazione delle classi.
Immaginate di avere una classe chiamata Car
nel vostro progetto e di volerla inserire in uno spazio dei nomi
chiamato Transport
. Si procederà in questo modo:
namespace Transport;
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
Se si vuole utilizzare la classe Car
in un altro file, occorre specificare da quale spazio dei nomi proviene la
classe:
$car = new Transport\Car;
Per semplificare, si può specificare all'inizio del file quale classe di un particolare spazio dei nomi si vuole utilizzare, consentendo di creare istanze senza citare il percorso completo:
use Transport\Car;
$car = new Car;
Eredità
L'ereditarietà è uno strumento della programmazione orientata agli oggetti che consente di creare nuove classi basate su quelle esistenti, ereditandone proprietà e metodi ed estendendole o ridefinendole a seconda delle necessità. L'ereditarietà garantisce la riusabilità del codice e la gerarchia delle classi.
In parole povere, se abbiamo una classe e vogliamo crearne un'altra derivata da essa ma con alcune modifiche, possiamo “ereditare” la nuova classe da quella originale.
In PHP, l'ereditarietà viene implementata utilizzando la parola chiave extends
.
La nostra classe Person
memorizza informazioni sull'età. Possiamo avere un'altra classe, Student
,
che estende Person
e aggiunge informazioni sul campo di studi.
Vediamo un esempio:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function printInformation()
{
echo "Age: {$this->age} years\n";
}
}
class Student extends Person
{
private $fieldOfStudy;
function __construct($age, $fieldOfStudy)
{
parent::__construct($age);
$this->fieldOfStudy = $fieldOfStudy;
}
function printInformation()
{
parent::printInformation();
echo "Field of study: {$this->fieldOfStudy} \n";
}
}
$student = new Student(20, 'Computer Science');
$student->printInformation();
Come funziona questo codice?
- Abbiamo usato la parola chiave
extends
per estendere la classePerson
, il che significa che la classeStudent
eredita tutti i metodi e le proprietà daPerson
. - La parola chiave
parent::
consente di richiamare i metodi della classe padre. In questo caso, abbiamo richiamato il costruttore della classePerson
prima di aggiungere la nostra funzionalità alla classeStudent
. E allo stesso modo, il metodo dell'antenatoprintInformation()
prima di elencare le informazioni sugli studenti.
L'ereditarietà è pensata per le situazioni in cui esiste una relazione “is a” tra le classi. Per esempio, un
Student
è un Person
. Un gatto è un animale. Ci consente, nei casi in cui ci aspettiamo un oggetto (ad
esempio, “Persona”) nel codice, di utilizzare invece un oggetto derivato (ad esempio, “Studente”).
È essenziale rendersi conto che lo scopo principale dell'ereditarietà non è quello di prevenire la duplicazione del codice. Al contrario, un uso improprio dell'ereditarietà può portare a codice complesso e difficile da mantenere. Se non c'è una relazione “è un” tra le classi, dovremmo considerare la composizione invece dell'ereditarietà.
Si noti che i metodi printInformation()
delle classi Person
e Student
forniscono
informazioni leggermente diverse. È possibile aggiungere altre classi (come Employee
) che forniranno altre
implementazioni di questo metodo. La capacità di oggetti di classi diverse di rispondere allo stesso metodo in modi diversi si
chiama polimorfismo:
$people = [
new Person(30),
new Student(20, 'Computer Science'),
new Employee(45, 'Director'),
];
foreach ($people as $person) {
$person->printInformation();
}
Composizione
La composizione è una tecnica in cui, invece di ereditare proprietà e metodi da un'altra classe, si usa semplicemente la sua istanza nella propria classe. Questo ci permette di combinare funzionalità e proprietà di più classi senza creare complesse strutture di ereditarietà.
Ad esempio, abbiamo una classe Engine
e una classe Car
. Invece di dire “Un'auto è un motore”,
diciamo “Un'auto ha un motore”, che è una tipica relazione di composizione.
class Engine
{
function start()
{
echo 'Engine is running.';
}
}
class Car
{
private $engine;
function __construct()
{
$this->engine = new Engine;
}
function start()
{
$this->engine->start();
echo 'The car is ready to drive!';
}
}
$car = new Car;
$car->start();
In questo caso, la classe Car
non possiede tutte le proprietà e i metodi della classe Engine
, ma vi
ha accesso attraverso la proprietà $engine
.
Il vantaggio della composizione è una maggiore flessibilità di progettazione e una migliore adattabilità ai cambiamenti futuri.
Visibilità
In PHP, è possibile definire la “visibilità” per le proprietà, i metodi e le costanti delle classi. La visibilità determina dove è possibile accedere a questi elementi.
- Se un elemento è contrassegnato come
public
, significa che è possibile accedervi da qualsiasi punto, anche al di fuori della classe. - Un elemento contrassegnato come
protected
è accessibile solo all'interno della classe e di tutti i suoi discendenti (classi che ereditano da essa). - Se un elemento è
private
, è possibile accedervi solo dalla classe in cui è stato definito.
Se non si specifica la visibilità, PHP la imposterà automaticamente a public
.
Vediamo un esempio di codice:
class VisibilityExample
{
public $publicProperty = 'Public';
protected $protectedProperty = 'Protected';
private $privateProperty = 'Private';
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
echo $this->privateProperty; // Works
}
}
$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty; // Works
// echo $object->protectedProperty; // Throws an error
// echo $object->privateProperty; // Throws an error
Continuare con l'ereditarietà delle classi:
class ChildClass extends VisibilityExample
{
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
// echo $this->privateProperty; // Throws an error
}
}
In questo caso, il metodo printProperties()
della classe ChildClass
può accedere alle proprietà
pubbliche e protette, ma non alle proprietà private della classe padre.
I dati e i metodi devono essere il più possibile nascosti e accessibili solo attraverso un'interfaccia definita. Ciò consente di modificare l'implementazione interna della classe senza influenzare il resto del codice.
Parola chiave finale
In PHP, si può usare la parola chiave final
se si vuole impedire che una classe, un metodo o una costante
vengano ereditati o sovrascritti. Quando una classe è contrassegnata come final
, non può essere estesa. Quando un
metodo è contrassegnato come final
, non può essere sovrascritto in una sottoclasse.
Essere consapevoli che una certa classe o un certo metodo non saranno più modificati ci permette di apportare modifiche più facilmente senza preoccuparci di potenziali conflitti. Ad esempio, possiamo aggiungere un nuovo metodo senza temere che un discendente abbia già un metodo con lo stesso nome, causando una collisione. Oppure possiamo cambiare i parametri di un metodo, sempre senza il rischio di causare incoerenze con un metodo sovrascritto in un discendente.
final class FinalClass
{
}
// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}
In questo esempio, il tentativo di ereditare dalla classe finale FinalClass
produrrà un errore.
Proprietà e metodi statici
Quando si parla di elementi “statici” di una classe in PHP, si intendono metodi e proprietà che appartengono alla classe stessa, non a un'istanza specifica della classe. Ciò significa che non è necessario creare un'istanza della classe per accedervi. Al contrario, è possibile chiamarli o accedervi direttamente attraverso il nome della classe.
Tenere presente che, poiché gli elementi statici appartengono alla classe e non alle sue istanze, non è possibile utilizzare
la pseudo-variabile $this
all'interno dei metodi statici.
L'uso di proprietà statiche porta a un codice offuscato e pieno di insidie, quindi non si dovrebbe mai usarle e non ne mostreremo un esempio qui. D'altra parte, i metodi statici sono utili. Ecco un esempio:
class Calculator
{
public static function add($a, $b)
{
return $a + $b;
}
public static function subtract($a, $b)
{
return $a - $b;
}
}
// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2
In questo esempio, abbiamo creato una classe Calculator
con due metodi statici. Possiamo chiamare questi metodi
direttamente, senza creare un'istanza della classe, utilizzando l'operatore ::
. I metodi statici sono
particolarmente utili per le operazioni che non dipendono dallo stato di una specifica istanza della classe.
Costanti di classe
All'interno delle classi è possibile definire delle costanti. Le costanti sono valori che non cambiano mai durante l'esecuzione del programma. A differenza delle variabili, il valore di una costante rimane invariato.
class Car
{
public const NumberOfWheels = 4;
public function displayNumberOfWheels(): int
{
echo self::NumberOfWheels;
}
}
echo Car::NumberOfWheels; // Output: 4
In questo esempio, abbiamo una classe Car
con la costante NumberOfWheels
. Quando si accede alla
costante all'interno della classe, si può usare la parola chiave self
invece del nome della classe.
Interfacce di oggetti
Le interfacce di oggetti agiscono come “contratti” per le classi. Se una classe deve implementare un'interfaccia di oggetti, deve contenere tutti i metodi definiti dall'interfaccia. È un ottimo modo per garantire che alcune classi aderiscano allo stesso “contratto” o struttura.
In PHP, le interfacce sono definite utilizzando la parola chiave interface
. Tutti i metodi definiti in
un'interfaccia sono pubblici (public
). Quando una classe implementa un'interfaccia, utilizza la parola chiave
implements
.
interface Animal
{
function makeSound();
}
class Cat implements Animal
{
public function makeSound()
{
echo 'Meow';
}
}
$cat = new Cat;
$cat->makeSound();
Se una classe implementa un'interfaccia, ma non tutti i metodi previsti sono definiti, PHP lancia un errore.
Una classe può implementare più interfacce contemporaneamente, a differenza dell'ereditarietà, dove una classe può ereditare solo da una classe:
interface Guardian
{
function guardHouse();
}
class Dog implements Animal, Guardian
{
public function makeSound()
{
echo 'Bark';
}
public function guardHouse()
{
echo 'Dog diligently guards the house';
}
}
Classi astratte
Le classi astratte servono come modelli di base per altre classi, ma non è possibile creare direttamente le loro istanze. Contengono un mix di metodi completi e metodi astratti che non hanno un contenuto definito. Le classi che ereditano da classi astratte devono fornire le definizioni di tutti i metodi astratti del genitore.
Per definire una classe astratta si usa la parola chiave abstract
.
abstract class AbstractClass
{
public function regularMethod()
{
echo 'This is a regular method';
}
abstract public function abstractMethod();
}
class Child extends AbstractClass
{
public function abstractMethod()
{
echo 'This is the implementation of the abstract method';
}
}
$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
In questo esempio, abbiamo una classe astratta con un metodo regolare e uno astratto. Poi abbiamo una classe Child
che eredita da AbstractClass
e fornisce un'implementazione per il metodo astratto.
In cosa differiscono le interfacce e le classi astratte? Le classi astratte possono contenere sia metodi astratti che concreti, mentre le interfacce definiscono solo quali metodi la classe deve implementare, ma non forniscono alcuna implementazione. Una classe può ereditare da una sola classe astratta, ma può implementare un numero qualsiasi di interfacce.
Verifica del tipo
Nella programmazione è fondamentale assicurarsi che i dati con cui si lavora siano del tipo corretto. In PHP, disponiamo di strumenti che forniscono questa garanzia. Verificare che i dati siano del tipo corretto si chiama “controllo del tipo”.
Tipi che possiamo incontrare in PHP:
- Tipi di base: Questi includono
int
(numeri interi),float
(numeri in virgola mobile),bool
(valori booleani),string
(stringhe),array
(array) enull
. - Classi: Quando si vuole che un valore sia un'istanza di una classe specifica.
- Interfacce: Definisce un insieme di metodi che una classe deve implementare. Un valore che soddisfa un'interfaccia deve avere questi metodi.
- Tipi misti: Si può specificare che una variabile può avere più tipi consentiti.
- Voide: Questo tipo speciale indica che una funzione o un metodo non restituisce alcun valore.
Vediamo come modificare il codice per includere i tipi:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
public function printAge(): void
{
echo "This person is {$this->age} years old.";
}
}
/**
* A function that accepts a Person object and prints the person's age.
*/
function printPersonAge(Person $person): void
{
$person->printAge();
}
In questo modo, ci assicuriamo che il nostro codice si aspetti e lavori con dati del tipo corretto, aiutandoci a prevenire potenziali errori.
Alcuni tipi non possono essere scritti direttamente in PHP. In questo caso, vengono elencati nel commento phpDoc, che è il
formato standard per la documentazione del codice PHP, che inizia con /**
e termina con */
. Esso
consente di aggiungere descrizioni di classi, metodi e così via. E anche di elencare tipi complessi usando le cosiddette
annotazioni @var
, @param
e @return
. Questi tipi vengono poi utilizzati dagli strumenti di
analisi statica del codice, ma non vengono controllati da PHP stesso.
class Registry
{
/** @var array<Person> indicates that it's an array of Person objects */
private array $persons = [];
public function addPerson(Person $person): void
{
$this->persons[] = $person;
}
}
Confronto e identità
In PHP, è possibile confrontare gli oggetti in due modi:
- Confronto tra valori
==
: verifica se gli oggetti appartengono alla stessa classe e hanno gli stessi valori nelle loro proprietà. - Identità
===
: verifica se si tratta della stessa istanza dell'oggetto.
class Car
{
public string $brand;
public function __construct(string $brand)
{
$this->brand = $brand;
}
}
$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;
var_dump($car1 == $car2); // true, because they have the same value
var_dump($car1 === $car2); // false, because they are not the same instance
var_dump($car1 === $car3); // true, because $car3 is the same instance as $car1
L'operatore di instanceof
L'operatore instanceof
consente di determinare se un dato oggetto è un'istanza di una classe specifica, un
discendente di quella classe o se implementa una certa interfaccia.
Immaginiamo di avere una classe Person
e un'altra classe Student
, che è un discendente di
Person
:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
}
class Student extends Person
{
private string $major;
public function __construct(int $age, string $major)
{
parent::__construct($age);
$this->major = $major;
}
}
$student = new Student(20, 'Computer Science');
// Check if $student is an instance of the Student class
var_dump($student instanceof Student); // Output: bool(true)
// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person); // Output: bool(true)
Dai risultati, è evidente che l'oggetto $student
è considerato un'istanza di entrambe le classi
Student
e Person
.
Interfacce fluenti
Una “interfaccia fluida” è una tecnica dell'OOP che consente di concatenare i metodi in un'unica chiamata. Questo spesso semplifica e chiarisce il codice.
L'elemento chiave di un'interfaccia fluente è che ogni metodo della catena restituisce un riferimento all'oggetto corrente.
Questo si ottiene utilizzando return $this;
alla fine del metodo. Questo stile di programmazione è spesso associato
ai metodi chiamati “setter”, che impostano i valori delle proprietà di un oggetto.
Vediamo come potrebbe essere un'interfaccia fluente per l'invio di e-mail:
public function sendMessage()
{
$email = new Email;
$email->setFrom('sender@example.com')
->setRecipient('admin@example.com')
->setMessage('Hello, this is a message.')
->send();
}
In questo esempio, i metodi setFrom()
, setRecipient()
e setMessage()
sono usati per
impostare i valori corrispondenti (mittente, destinatario, contenuto del messaggio). Dopo aver impostato ciascuno di questi
valori, i metodi restituiscono l'oggetto corrente ($email
), consentendoci di concatenare un altro metodo dopo di
esso. Infine, chiamiamo il metodo send()
, che invia effettivamente l'e-mail.
Grazie alle interfacce fluide, possiamo scrivere codice intuitivo e facilmente leggibile.
Copiare con clone
In PHP, possiamo creare una copia di un oggetto usando l'operatore clone
. In questo modo, si ottiene una nuova
istanza con contenuti identici.
Se durante la copia di un oggetto è necessario modificarne alcune proprietà, è possibile definire un metodo speciale
__clone()
nella classe. Questo metodo viene richiamato automaticamente quando l'oggetto viene clonato.
class Sheep
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function __clone()
{
$this->name = 'Clone of ' . $this->name;
}
}
$original = new Sheep('Dolly');
echo $original->name . "\n"; // Outputs: Dolly
$clone = clone $original;
echo $clone->name . "\n"; // Outputs: Clone of Dolly
In questo esempio, abbiamo una classe Sheep
con una proprietà $name
. Quando si clona un'istanza di
questa classe, il metodo __clone()
assicura che il nome della pecora clonata ottenga il prefisso “Clone di”.
Tratti
I tratti in PHP sono uno strumento che consente di condividere metodi, proprietà e costanti tra le classi, evitando la duplicazione del codice. Si può pensare a loro come a un meccanismo di “copia e incolla” (Ctrl-C e Ctrl-V), in cui il contenuto di un tratto viene “incollato” nelle classi. Ciò consente di riutilizzare il codice senza dover creare complicate gerarchie di classi.
Vediamo un semplice esempio di utilizzo dei tratti in PHP:
trait Honking
{
public function honk()
{
echo 'Beep beep!';
}
}
class Car
{
use Honking;
}
class Truck
{
use Honking;
}
$car = new Car;
$car->honk(); // Outputs 'Beep beep!'
$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'
In questo esempio, abbiamo un tratto chiamato Honking
che contiene un metodo honk()
. Abbiamo poi due
classi: Car
e Truck
, che utilizzano entrambe il tratto Honking
. Di conseguenza, entrambe le
classi “possiedono” il metodo honk()
e possiamo chiamarlo su oggetti di entrambe le classi.
I tratti consentono di condividere facilmente ed efficacemente il codice tra le classi. Non entrano nella gerarchia
ereditaria, cioè $car instanceof Honking
restituirà false
.
Eccezioni
Le eccezioni nell'OOP ci permettono di gestire con grazia gli errori e le situazioni inaspettate nel nostro codice. Sono oggetti che contengono informazioni su un errore o una situazione insolita.
In PHP, abbiamo una classe incorporata Exception
, che serve come base per tutte le eccezioni. Questa classe ha
diversi metodi che ci permettono di ottenere ulteriori informazioni sull'eccezione, come il messaggio di errore, il file e la riga
in cui si è verificato l'errore, ecc.
Quando si verifica un errore nel codice, si può “lanciare” l'eccezione usando la parola chiave throw
.
function division(float $a, float $b): float
{
if ($b === 0) {
throw new Exception('Division by zero!');
}
return $a / $b;
}
Quando la funzione division()
riceve null come secondo argomento, lancia un'eccezione con il messaggio di errore
'Division by zero!'
. Per evitare che il programma si blocchi quando viene lanciata l'eccezione, la intrappoliamo nel
blocco try/catch
:
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
}
Il codice che può lanciare un'eccezione viene avvolto in un blocco try
. Se l'eccezione viene lanciata,
l'esecuzione del codice passa a un blocco catch
, dove è possibile gestire l'eccezione (ad esempio, scrivere un
messaggio di errore).
Dopo i blocchi try
e catch
, si può aggiungere un blocco opzionale finally
, che viene
sempre eseguito, indipendentemente dal fatto che l'eccezione sia stata lanciata o meno (anche se si utilizzano
return
, break
, o continue
nel blocco try
o catch
):
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
} finally {
// Code that is always executed whether the exception has been thrown or not
}
Possiamo anche creare le nostre classi di eccezioni (gerarchia) che ereditano dalla classe Exception. A titolo di esempio, si consideri una semplice applicazione bancaria che consente depositi e prelievi:
class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}
class BankAccount
{
private int $balance = 0;
private int $dailyLimit = 1000;
public function deposit(int $amount): int
{
$this->balance += $amount;
return $this->balance;
}
public function withdraw(int $amount): int
{
if ($amount > $this->balance) {
throw new InsufficientFundsException('Not enough funds in the account.');
}
if ($amount > $this->dailyLimit) {
throw new ExceededLimitException('Daily withdrawal limit exceeded.');
}
$this->balance -= $amount;
return $this->balance;
}
}
È possibile specificare più blocchi catch
per un singolo blocco try
se si prevedono diversi tipi di
eccezioni.
$account = new BankAccount;
$account->deposit(500);
try {
$account->withdraw(1500);
} catch (ExceededLimitException $e) {
echo $e->getMessage();
} catch (InsufficientFundsException $e) {
echo $e->getMessage();
} catch (BankingException $e) {
echo 'An error occurred during the operation.';
}
In questo esempio, è importante notare l'ordine dei blocchi catch
. Poiché tutte le eccezioni ereditano da
BankingException
, se avessimo questo blocco per primo, tutte le eccezioni verrebbero catturate in esso, senza che il
codice raggiunga i successivi blocchi catch
. Pertanto, è importante che le eccezioni più specifiche (cioè quelle
che ereditano da altre) si trovino più in alto nell'ordine dei blocchi catch
rispetto alle loro
eccezioni padre.
Iterazioni
In PHP, è possibile eseguire un ciclo di oggetti utilizzando il ciclo foreach
, proprio come si fa con un array.
Perché funzioni, l'oggetto deve implementare un'interfaccia speciale.
La prima opzione è implementare l'interfaccia Iterator
, che ha i metodi current()
che restituisce
il valore corrente, key()
che restituisce la chiave, next()
che passa al valore successivo,
rewind()
che passa all'inizio e valid()
che controlla se siamo già alla fine.
L'altra opzione è implementare un'interfaccia IteratorAggregate
, che ha un solo metodo
getIterator()
. Questo metodo può restituire un oggetto segnaposto che fornirà l'attraversamento, oppure può essere
un generatore, cioè una funzione speciale che utilizza yield
per restituire chiavi e valori in sequenza:
class Person
{
public function __construct(
public int $age,
) {
}
}
class Registry implements IteratorAggregate
{
private array $people = [];
public function addPerson(Person $person): void
{
$this->people[] = $person;
}
public function getIterator(): Generator
{
foreach ($this->people as $person) {
yield $person;
}
}
}
$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));
foreach ($list as $person) {
echo "Age: {$person->age} years\n";
}
Migliori pratiche
Una volta acquisiti i principi di base della programmazione orientata agli oggetti, è fondamentale concentrarsi sulle best practice dell'OOP. Queste vi aiuteranno a scrivere codice non solo funzionale, ma anche leggibile, comprensibile e facilmente manutenibile.
- Separazione delle preoccupazioni: Ogni classe dovrebbe avere una responsabilità chiaramente definita e dovrebbe occuparsi di un solo compito primario. Se una classe fa troppe cose, potrebbe essere opportuno dividerla in classi più piccole e specializzate.
- Incapsulamento: I dati e i metodi devono essere il più possibile nascosti e accessibili solo attraverso un'interfaccia definita. Ciò consente di modificare l'implementazione interna di una classe senza influenzare il resto del codice.
- Iniezione di dipendenze: Invece di creare dipendenze direttamente all'interno di una classe, è opportuno “iniettarle” dall'esterno. Per una comprensione più approfondita di questo principio, si consiglia di leggere i capitoli sulla Dependency Injection.