Introduzione
Quando si parla di PSR e coding standard in PHP, spesso si pensa subito a formattazione del codice, namespace o autoloading. Ma c’è un aspetto ancora più pratico, molto utile nello sviluppo reale: separare la logica di accesso ai dati dal resto dell’applicazione usando il Repository Pattern in modo coerente con gli standard PSR.
Questo argomento è particolarmente importante perché aiuta a scrivere codice più manutenibile, testabile e leggibile. In un progetto PHP moderno, infatti, non basta che il codice “funzioni”: deve essere organizzato in modo professionale, con responsabilità chiare e dipendenze gestite bene.
Il Repository Pattern non è uno standard PSR in sé, ma si integra perfettamente con le buone pratiche promosse dall’ecosistema PHP: interfacce, namespace, autoloading PSR-4, tipizzazione forte e stile coerente con PSR-12. In questo tutorial vedremo come applicarlo in modo concreto, con un esempio semplice ma realistico.
Codice completo
Immaginiamo di voler gestire utenti in un’applicazione. Invece di scrivere query SQL direttamente dentro il controller o nel servizio, creiamo un repository dedicato.
<?php
declare(strict_types=1);
namespace AppDomainUser;
interface UserRepositoryInterface
{
public function findById(int $id): ?User;
public function save(User $user): void;
}
<?php
declare(strict_types=1);
namespace AppDomainUser;
final class User
{
public function __construct(
private ?int $id,
private string $name,
private string $email
) {
}
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id): void
{
$this->id = $id;
}
public function getName(): string
{
return $this->name;
}
public function getEmail(): string
{
return $this->email;
}
}
<?php
declare(strict_types=1);
namespace AppInfrastructurePersistence;
use AppDomainUserUser;
use AppDomainUserUserRepositoryInterface;
use PDO;
final class PdoUserRepository implements UserRepositoryInterface
{
public function __construct(
private PDO $pdo
) {
}
public function findById(int $id): ?User
{
$stmt = $this->pdo->prepare(
´SELECT id, name, email FROM users WHERE id = :id´
);
$stmt->execute([´id´ => $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row === false) {
return null;
}
$user = new User(
(int) $row[´id´],
(string) $row[´name´],
(string) $row[´email´]
);
return $user;
}
public function save(User $user): void
{
if ($user->getId() === null) {
$stmt = $this->pdo->prepare(
´INSERT INTO users (name, email) VALUES (:name, :email)´
);
$stmt->execute([
´name´ => $user->getName(),
´email´ => $user->getEmail(),
]);
$user->setId((int) $this->pdo->lastInsertId());
return;
}
$stmt = $this->pdo->prepare(
´UPDATE users SET name = :name, email = :email WHERE id = :id´
);
$stmt->execute([
´id´ => $user->getId(),
´name´ => $user->getName(),
´email´ => $user->getEmail(),
]);
}
}
<?php
declare(strict_types=1);
namespace AppApplicationUser;
use AppDomainUserUser;
use AppDomainUserUserRepositoryInterface;
final class RegisterUserService
{
public function __construct(
private UserRepositoryInterface $userRepository
) {
}
public function register(string $name, string $email): User
{
$user = new User(null, $name, $email);
$this->userRepository->save($user);
return $user;
}
}
<?php
declare(strict_types=1);
namespace AppPresentationController;
use AppApplicationUserRegisterUserService;
final class UserController
{
public function __construct(
private RegisterUserService $registerUserService
) {
}
public function store(array $request): array
{
$user = $this->registerUserService->register(
$request[´name´],
$request[´email´]
);
return [
´status´ => ´success´,
´user´ => [
´id´ => $user->getId(),
´name´ => $user->getName(),
´email´ => $user->getEmail(),
],
];
}
}
Spiegazione
Vediamo perché questo esempio è interessante dal punto di vista di PSR e coding standard.
1. Interfaccia per separare contratto e implementazione
La classe UserRepositoryInterface definisce cosa deve fare un repository, ma non dice come farlo. Questo è perfettamente in linea con una progettazione pulita: il codice dell’applicazione dipende da un contratto, non da una tecnologia specifica.
In pratica, il servizio RegisterUserService non sa se i dati vengono salvati su MySQL, PostgreSQL o in memoria durante i test. Sa solo che esiste un repository capace di salvare un utente.
2. Namespace chiari e organizzazione a livelli
Abbiamo usato namespace coerenti:
- AppDomain per il modello di dominio
- AppInfrastructure per l’implementazione tecnica
- AppApplication per i casi d’uso
- AppPresentation per il livello di interfaccia
Questa organizzazione è molto utile perché rende immediato capire dove cercare una classe e quale responsabilità ha. Inoltre, si abbina bene a PSR-4, che richiede una corrispondenza chiara tra namespace e struttura delle cartelle.
3. Tipizzazione forte e declare(strict_types=1)
Il codice usa tipi espliciti per parametri, valori di ritorno e proprietà. Questo riduce gli errori e rende il comportamento più prevedibile. La direttiva declare(strict_types=1); è una scelta consigliata nei progetti moderni perché evita conversioni automatiche poco chiare.
4. Repository concreto nella layer di infrastruttura
PdoUserRepository contiene tutta la logica SQL. Questo è corretto: la logica di persistenza non deve invadere il dominio o il servizio applicativo. Se un domani cambiamo database o decidiamo di usare un’API esterna, modificheremo solo questa classe o ne creeremo una nuova implementazione.
5. Controller sottile
Il controller non esegue query, non costruisce oggetti complessi e non contiene regole di business. Si limita a ricevere i dati e a delegare il lavoro al servizio. Questo è uno dei principi più importanti per mantenere il codice PHP leggibile e aderente agli standard di qualità.
Best practice
- Definisci sempre un’interfaccia per i repository: facilita i test e il cambio di implementazione.
- Non mettere SQL nei controller: la persistenza deve stare in classi dedicate.
- Usa namespace coerenti con la struttura delle cartelle, in modo compatibile con PSR-4.
- Tipizza tutto ciò che puoi: parametri, proprietà, valori di ritorno e costruttori.
- Separa dominio, applicazione e infrastruttura: ogni livello deve avere una responsabilità chiara.
- Evita repository troppo generici: meglio repository specifici per aggregate o entità importanti, invece di classi “tuttofare”.
- Non far restituire array grezzi al dominio: converti subito i dati in oggetti coerenti.
- Scrivi repository testabili: se possibile, usa interfacce e dipendenze iniettate per simulare il comportamento nei test.
Un errore comune è pensare al repository come a un semplice wrapper del database. In realtà, il repository dovrebbe offrire un’interfaccia orientata al dominio, non alla tabella SQL. Questo approccio rende il codice più stabile nel tempo e più facile da capire per altri sviluppatori.
Riepilogo
Il Repository Pattern è un ottimo esempio di come PHP, PSR e coding standard possano lavorare insieme per produrre codice professionale. Anche se non è un “PSR” in senso stretto, si integra perfettamente con le pratiche raccomandate dall’ecosistema:
- PSR-4 per l’autoloading e l’organizzazione dei namespace
- PSR-12 per una formattazione coerente e leggibile
- interfacce per disaccoppiare il codice
- tipizzazione forte per maggiore affidabilità
- separazione delle responsabilità per una manutenzione più semplice
Se vuoi scrivere applicazioni PHP solide, imparare a progettare repository puliti è un passo molto utile. Ti aiuta a evitare codice fragile, a migliorare i test e a mantenere il progetto ordinato anche quando cresce.
Approfondisci con risorse ufficiali
- PHP-FIG — documentazione ufficiale degli standard PSR: https://www.php-fig.org/psr/
- PSR-4 — autoloading standard: https://www.php-fig.org/psr/psr-4/
- PSR-12 — extended coding style guide: https://www.php-fig.org/psr/psr-12/
- PHP Manual — namespace: https://www.php.net/manual/en/language.namespaces.php
- PHP Manual — PDO: https://www.php.net/manual/en/book.pdo.php
