Il pattern MVC (Model–View–Controller) è una delle architetture più usate nello sviluppo di applicazioni web. Separando la logica di business, la presentazione e il controllo del flusso, rende il codice più leggibile, testabile e manutenibile. In questo articolo vedremo come implementare un semplice MVC in PHP puro, senza framework.
1. Concetti di base: Model, View, Controller
1.1 Model (Modello)
Il Model rappresenta i dati e la logica di business. È responsabile di:
- interagire con il database o altre fonti di dati;
- applicare le regole di business (validazioni, calcoli, ecc.);
- non conoscere nulla dell'interfaccia grafica o di come i dati verranno mostrati.
1.2 View (Vista)
La View è la parte che mostra i dati all'utente. È composta da template PHP/HTML che:
- ricevono i dati dal Controller (non interrogano direttamente il Model);
- non contengono logica di business complessa (al massimo piccoli controlli);
- si occupano solo di presentazione.
1.3 Controller
Il Controller riceve la richiesta (request), decide cosa fare e:
- chiama i Model per recuperare o modificare i dati;
- passa i dati alla View appropriata;
- non contiene HTML di presentazione.
2. Struttura di cartelle di un mini progetto MVC
Una struttura minima potrebbe essere:
/
├── public
│ └── index.php # Front controller (un solo punto di accesso)
├── app
│ ├── Controllers
│ │ └── HomeController.php
│ ├── Models
│ │ └── User.php
│ └── Views
│ ├── layout.php
│ └── home.php
└── core
├── Router.php
└── Controller.php # Classe base per i controller
La cartella public contiene il file accessibile dal web server, mentre il resto
del codice rimane al di fuori della directory pubblica per motivi di sicurezza.
3. Il Front Controller (public/index.php)
Il front controller è l'unico file PHP accessibile direttamente tramite URL. Si occupa di:
- caricare l'autoloader delle classi;
- istanziare il router;
- indirizzare la richiesta al controller giusto.
<?php
// public/index.php
require_once __DIR__ . '/../core/Router.php';
$router = new Router();
// Definizione delle rotte
$router->get('/', 'HomeController@index');
$router->get('/users', 'HomeController@users');
// Avvio del routing
$router->dispatch($_SERVER['REQUEST_URI'], $_SERVER['REQUEST_METHOD']);
4. Un Router semplice (core/Router.php)
Il router mappa un URL a un metodo di un controller nel formato
NomeController@metodo.
<?php
// core/Router.php
class Router
{
protected array $routes = [
'GET' => [],
'POST' => [],
];
public function get(string $uri, string $action): void
{
$this->routes['GET'][$uri] = $action;
}
public function post(string $uri, string $action): void
{
$this->routes['POST'][$uri] = $action;
}
public function dispatch(string $uri, string $method): void
{
$uri = parse_url($uri, PHP_URL_PATH);
$action = $this->routes[$method][$uri] ?? null;
if (! $action) {
http_response_code(404);
echo 'Pagina non trovata';
return;
}
[$controllerName, $methodName] = explode('@', $action);
$controllerClass = 'App\Controllers\' . $controllerName;
if (!class_exists($controllerClass)) {
throw new RuntimeException("Controller {$controllerClass} non trovato");
}
$controller = new $controllerClass();
if (!method_exists($controller, $methodName)) {
throw new RuntimeException("Metodo {$methodName} non trovato nel controller {$controllerClass}");
}
$controller->{$methodName}();
}
}
5. Classe base Controller (core/Controller.php)
Una classe base per i controller può fornire metodi comuni, ad esempio per caricare le view.
<?php
// core/Controller.php
namespace Core;
class Controller
{
protected function view(string $view, array $data = []): void
{
extract($data);
require __DIR__ . '/../app/Views/' . $view . '.php';
}
}
6. Un Controller di esempio (app/Controllers/HomeController.php)
Questo controller gestisce la home page e una lista di utenti.
<?php
// app/Controllers/HomeController.php
namespace App\Controllers;
use Core\Controller;
use App\Models\User;
class HomeController extends Controller
{
public function index(): void
{
$titolo = 'Benvenuto nel nostro mini MVC in PHP';
$this->view('home', [
'titolo' => $titolo,
]);
}
public function users(): void
{
$users = User::all();
$this->view('users', [
'users' => $users,
]);
}
}
7. Un Model di esempio (app/Models/User.php)
Il Model si occupa di recuperare i dati. In un progetto reale useresti PDO o un ORM, ma per semplicità qui useremo un array statico.
<?php
// app/Models/User.php
namespace App\Models;
class User
{
public static function all(): array
{
return [
['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com'],
['id' => 2, 'name' => 'Bob', 'email' => 'bob@example.com'],
];
}
}
8. Le View: layout e pagine
Le view sono semplici file PHP che contengono HTML. Possiamo definire un layout base e includere al suo interno le varie pagine.
8.1 Layout base (app/Views/layout.php)
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title><?= $titolo ?? 'Applicazione MVC in PHP'; ?></title>
</head>
<body>
<h1>Mini applicazione MVC</h1>
<?php // Contenuto della pagina specifica ?>
<?php echo $content ?? ''; ?>
</body>
</html>
8.2 View della home (app/Views/home.php)
In questo esempio la view genera il contenuto da inserire nel layout. Un modo semplice è usare output buffering.
<?php
// app/Views/home.php
ob_start();
?>
<h2><?= $titolo; ?></h2>
<p>Questa è la home page del nostro mini framework MVC in PHP.</p>
<?php
$content = ob_get_clean();
require __DIR__ . '/layout.php';
8.3 View della lista utenti (app/Views/users.php)
<?php
// app/Views/users.php
ob_start();
?>
<h2>Lista utenti</h2>
<ul>
<?php foreach ($users as $user): ?>
<li>
<strong><?= htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8'); ?></strong>
(<?= htmlspecialchars($user['email'], ENT_QUOTES, 'UTF-8'); ?>)
</li>
<?php endforeach; ?>
</ul>
<?php
$content = ob_get_clean();
$titolo = 'Utenti';
require __DIR__ . '/layout.php';
9. Gestione dei parametri nelle rotte
Per semplicità abbiamo usato rotte statiche. Per gestire URL con parametri come
/users/42, il router dovrebbe supportare pattern dinamici.
Un approccio basilare è usare espressioni regolari o placeholder come /users/{id}.
Un esempio molto semplificato potrebbe memorizzare una versione "grezza" del pattern e poi confrontare l'URL con una regex generata a runtime. Nei progetti reali è spesso preferibile usare framework che offrono un router evoluto (Laravel, Symfony, ecc.).
10. Vantaggi pratici dell'MVC in PHP
- Separazione delle responsabilità: ogni parte del codice ha un compito chiaro.
- Riutilizzo: i Model possono essere riutilizzati in più Controller o applicazioni.
- Testabilità: la logica di business nei Model è più semplice da testare.
- Manutenibilità: cambiare la View non richiede modifiche alla logica di business.
11. Prossimi passi
Il semplice esempio visto qui mostra la struttura di base di un pattern MVC in PHP puro. Per rendere il progetto più robusto potresti aggiungere:
- un sistema di autoloading con Composer;
- una classe per la connessione al database usando PDO;
- middleware per l'autenticazione e la gestione delle sessioni;
- un sistema di template più avanzato (Twig, Blade, ecc.);
- gestione centralizzata degli errori e dei log.
Una volta compresi a fondo questi concetti, sarà molto più semplice passare all'uso di framework completi, perché ne riconoscerai la struttura interna basata proprio sul pattern MVC.