Introduzione
Quando un progetto PHP cresce, uno dei problemi più comuni è vedere la logica applicativa mescolata con le query SQL, le chiamate API o l’accesso al filesystem. All’inizio può sembrare comodo, ma nel tempo il codice diventa difficile da testare, da leggere e da modificare.
Un modo molto pratico per migliorare l’architettura orientata agli oggetti in PHP è usare il pattern Repository. Questo pattern introduce una classe dedicata a recuperare e salvare i dati, nascondendo i dettagli dell’archiviazione al resto dell’applicazione. In questo modo il codice di business lavora con oggetti e non con query sparse in giro per il progetto.
In questo tutorial vedremo come costruire un piccolo esempio OOP in PHP con un repository per gestire utenti. L’obiettivo non è creare un framework, ma capire una tecnica concreta, utile e applicabile in progetti reali.
Codice completo
<?php
declare(strict_types=1);
/**
* Entità di dominio: rappresenta un utente.
*/
class User
{
public function __construct(
private int $id,
private string $name,
private string $email
) {}
public function getId(): int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function getEmail(): string
{
return $this->email;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function setEmail(string $email): void
{
$this->email = $email;
}
}
/**
* Contratto del repository.
* Definisce le operazioni disponibili sui dati.
*/
interface UserRepositoryInterface
{
public function findById(int $id): ?User;
public function findAll(): array;
public function save(User $user): void;
public function delete(int $id): void;
}
/**
* Implementazione in memoria.
* Utile per test, demo o prototipi.
*/
class InMemoryUserRepository implements UserRepositoryInterface
{
/**
* Simula un archivio dati.
* Ogni elemento contiene i dati di un utente.
*/
private array $storage = [
1 => [´id´ => 1, ´name´ => ´Mario Rossi´, ´email´ => ´[email protected]´],
2 => [´id´ => 2, ´name´ => ´Laura Bianchi´, ´email´ => ´[email protected]´],
];
public function findById(int $id): ?User
{
if (!isset($this->storage[$id])) {
return null;
}
$data = $this->storage[$id];
return new User($data[´id´], $data[´name´], $data[´email´]);
}
public function findAll(): array
{
$users = [];
foreach ($this->storage as $data) {
$users[] = new User($data[´id´], $data[´name´], $data[´email´]);
}
return $users;
}
public function save(User $user): void
{
$this->storage[$user->getId()] = [
´id´ => $user->getId(),
´name´ => $user->getName(),
´email´ => $user->getEmail(),
];
}
public function delete(int $id): void
{
unset($this->storage[$id]);
}
}
/**
* Servizio applicativo:
* usa il repository senza conoscere i dettagli di persistenza.
*/
class UserService
{
public function __construct(
private UserRepositoryInterface $repository
) {}
public function renameUser(int $id, string $newName): bool
{
$user = $this->repository->findById($id);
if ($user === null) {
return false;
}
$user->setName($newName);
$this->repository->save($user);
return true;
}
public function listUserEmails(): array
{
$emails = [];
foreach ($this->repository->findAll() as $user) {
$emails[] = $user->getEmail();
}
return $emails;
}
}
// --- ESEMPIO DI USO ---
$repository = new InMemoryUserRepository();
$service = new UserService($repository);
$user = $repository->findById(1);
if ($user !== null) {
echo "Utente trovato: " . $user->getName() . PHP_EOL;
}
$updated = $service->renameUser(1, ´Mario Verdi´);
if ($updated) {
echo "Nome aggiornato con successo." . PHP_EOL;
}
$emails = $service->listUserEmails();
foreach ($emails as $email) {
echo $email . PHP_EOL;
}
Spiegazione
Il codice è composto da quattro parti principali: l’entità User, l’interfaccia UserRepositoryInterface, l’implementazione concreta InMemoryUserRepository e il servizio UserService.
1. L’entità User
La classe User rappresenta un oggetto di dominio. Contiene i dati fondamentali dell’utente: id, nome ed email. Notare che non contiene alcuna logica di accesso ai dati. Questo è importante: un oggetto di dominio deve rappresentare il concetto del problema, non sapere dove o come viene salvato.
2. L’interfaccia del repository
UserRepositoryInterface definisce il contratto. In pratica dice: “chiunque implementi questo repository deve sapere come cercare, salvare e cancellare utenti”.
Questo approccio porta due vantaggi enormi:
- il servizio dipende da un contratto, non da una classe concreta;
- in futuro puoi cambiare sorgente dati senza toccare la logica applicativa.
3. Il repository in memoria
InMemoryUserRepository è una versione semplificata che salva i dati in un array interno. È perfetta per studiare il pattern o per scrivere test senza database. In un progetto reale, la stessa interfaccia potrebbe essere implementata da una classe che usa PDO, MySQL, PostgreSQL o anche una API esterna.
Il punto chiave è che il resto dell’applicazione non deve sapere nulla di questa scelta tecnica.
4. Il servizio applicativo
UserService contiene la logica di business. Il metodo renameUser() cerca l’utente, modifica il nome e salva il risultato. Il servizio non costruisce query, non gestisce array di storage e non si occupa del formato dei dati persistiti. Si limita a coordinare le operazioni.
Questa separazione rende il codice più pulito e più semplice da testare. Ad esempio, nei test potresti sostituire il repository reale con una versione finta o in memoria.
Perché il pattern Repository è utile in OOP
- Riduce l’accoppiamento tra logica applicativa e persistenza.
- Migliora la testabilità, perché puoi simulare i dati facilmente.
- Rende il codice più leggibile, spostando le query in un punto preciso.
- Favorisce la manutenzione, perché cambiare database o sorgente dati richiede meno modifiche.
Best practice
Il pattern Repository è molto efficace, ma va usato con criterio. Ecco alcune pratiche consigliate per applicarlo bene in PHP.
- Usa interfacce: definire un contratto separato dalla classe concreta ti permette di sostituire facilmente l’implementazione.
- Non mischiare responsabilità: il repository deve occuparsi dei dati, il servizio della logica di business.
- Evita repository troppo generici: una classe tipo
GenericRepositoryspesso diventa difficile da mantenere. Meglio repository specifici per dominio, comeUserRepositoryoOrderRepository. - Trasforma sempre i dati in oggetti: non lasciare array sparsi nel codice applicativo se puoi usare entità.
- Prepara il codice ai test: il repository in memoria è un ottimo strumento per testare i servizi senza dipendenze esterne.
- Usa tipi stretti: con
declare(strict_types=1)e type hint chiari riduci errori e ambiguità.
Un errore comune è usare il repository come contenitore di tutta la logica possibile. In realtà il repository deve restare concentrato sull’accesso ai dati. Se inizi a inserire calcoli complessi o regole di business al suo interno, perdi il vantaggio della separazione dei ruoli.
Riepilogo
Il pattern Repository è uno strumento molto utile nella programmazione orientata agli oggetti in PHP, soprattutto quando vuoi scrivere codice più ordinato, testabile e facile da evolvere. L’idea è semplice: spostare l’accesso ai dati in una classe dedicata, lasciando alla logica applicativa il compito di orchestrare le operazioni.
Nel nostro esempio:
- User rappresenta l’entità di dominio;
- UserRepositoryInterface definisce il contratto;
- InMemoryUserRepository fornisce una prima implementazione concreta;
- UserService usa il repository senza conoscere i dettagli di persistenza.
Se impari a separare questi livelli, i tuoi progetti PHP diventeranno più robusti e più facili da mantenere nel tempo.
Approfondisci con risorse ufficiali
- PHP Manual - Classi e oggetti: https://www.php.net/manual/it/language.oop5.php
- PHP Manual - Tipi e strict types: https://www.php.net/manual/it/language.types.declarations.php
- PHP Manual - Interfacce: https://www.php.net/manual/it/language.oop5.interfaces.php
- PHP Manual - OOP: https://www.php.net/manual/it/language.oop5.php
