18 KiB
Portale Clienti — Applicazione Laravel 11 Demo
Applicazione di esempio per la gestione clienti costruita con Laravel 11 e Docker. È pensata come punto di partenza e materiale didattico per chi si avvicina a Laravel per la prima volta.
Indice
- Cos'è questo progetto
- Pre-requisiti
- Avvio rapido
- Struttura del progetto
- Architettura Docker
- Concetti Laravel spiegati
- Configurazione: .env vs Impostazioni dinamiche
- CRUD Clienti — come funziona
- Comandi utili
- Workflow di sviluppo
- Prossimi passi
Cos'è questo progetto
Una piccola applicazione web che permette di:
- Visualizzare una dashboard con statistiche sui clienti
- Gestire clienti — creare, modificare, consultare, eliminare (CRUD completo)
- Configurare l'applicazione tramite un pannello impostazioni, senza toccare il codice
Le funzionalità sono intenzionalmente semplici: l'obiettivo è mostrare i pattern fondamentali di Laravel in modo chiaro.
Pre-requisiti
Installa questi strumenti prima di iniziare:
| Strumento | Versione minima | Link |
|---|---|---|
| Docker Desktop | 4.x | https://www.docker.com/products/docker-desktop |
| Git | 2.x | https://git-scm.com |
Non serve installare PHP, Composer o MySQL sul tuo computer. Docker gestisce tutto.
Avvio rapido
# 1. Clona il repository
git clone <url-repo> portale-clienti
cd portale-clienti
# 2. Copia il file di configurazione
cp .env.example .env
# Modifica .env se necessario (le password predefinite vanno bene per lo sviluppo locale)
# 3. Costruisci e avvia i container Docker
docker compose up -d --build
# ✅ L'applicazione sarà disponibile su http://localhost:8080
Al primo avvio, Docker:
- Costruisce l'immagine PHP con tutte le dipendenze
- Esegue
composer installper installare Laravel e le librerie- Genera l'
APP_KEYcrittografica- Esegue le migration (crea le tabelle nel database)
- Popola il database con dati di esempio (clienti fittizi e impostazioni di default)
Struttura del progetto
portale-clienti/
│
├── app/ ← Codice PHP dell'applicazione
│ ├── Http/
│ │ └── Controllers/ ← Controller: ricevono richieste HTTP
│ │ ├── DashboardController.php
│ │ ├── CustomerController.php
│ │ └── SettingController.php
│ ├── Models/ ← Model: rappresentano i dati (tabelle DB)
│ │ ├── Customer.php
│ │ └── Setting.php
│ ├── Providers/
│ │ └── AppServiceProvider.php ← Configurazione avvio applicazione
│ └── Services/
│ └── SettingService.php ← Logica di business per le impostazioni
│
├── bootstrap/
│ └── app.php ← Punto di bootstrap Laravel 11
│
├── config/ ← File di configurazione
│ ├── app.php ← Config app (nome, chiave, timezone...)
│ ├── database.php ← Config connessioni DB
│ └── settings.php ← ⭐ Config impostazioni dinamiche (vedi sotto)
│
├── database/
│ ├── migrations/ ← Definizioni schema database
│ │ ├── 2024_01_01_000010_create_customers_table.php
│ │ └── 2024_01_01_000020_create_settings_table.php
│ └── seeders/ ← Dati iniziali
│ ├── DatabaseSeeder.php
│ ├── SettingSeeder.php
│ └── CustomerSeeder.php
│
├── docker/
│ ├── nginx/
│ │ └── default.conf ← Configurazione web server
│ └── php/
│ ├── Dockerfile ← Immagine PHP custom
│ ├── entrypoint.sh ← Script avvio container
│ └── php.ini ← Configurazione PHP
│
├── public/
│ └── index.php ← Front controller (unico file esposto al web)
│
├── resources/
│ └── views/ ← Template HTML (Blade)
│ ├── layouts/
│ │ └── app.blade.php ← Layout principale condiviso
│ ├── dashboard.blade.php
│ ├── customers/
│ │ ├── index.blade.php
│ │ ├── create.blade.php
│ │ ├── edit.blade.php
│ │ ├── show.blade.php
│ │ └── _form.blade.php ← Partial riutilizzato da create ed edit
│ └── settings/
│ └── index.blade.php
│
├── routes/
│ ├── web.php ← Route HTTP (URL → Controller)
│ └── console.php ← Comandi Artisan e scheduler
│
├── storage/ ← File generati (log, cache, sessioni, upload)
│
├── .env ← ⭐ Variabili statiche (non committare!)
├── .env.example ← Template pubblico del .env
├── composer.json ← Dipendenze PHP
└── docker-compose.yml ← Orchestrazione container
Architettura Docker
┌─────────────────────────────────────────────┐
│ docker-compose.yml │
│ │
Browser ──────► │ [Nginx :8080] ──► [PHP-FPM :9000] │
:8080 │ webserver app │
│ │ │
│ [MySQL :3306] │
│ db │
│ [Redis :6379] │
│ redis │
└─────────────────────────────────────────────┘
I 4 container
| Container | Immagine | Ruolo |
|---|---|---|
portale_nginx |
nginx:1.25-alpine | Web server. Serve file statici e passa le richieste PHP a FPM |
portale_app |
Custom (basata su php:8.2-fpm) | Esegue il codice PHP Laravel |
portale_db |
mysql:8.0 | Database relazionale |
portale_redis |
redis:7-alpine | Cache veloce per sessioni e impostazioni |
Comunicazione tra container
I container si "parlano" usando il nome del servizio come hostname.
Per questo in .env trovi DB_HOST=db (non localhost): db è il nome del servizio MySQL nel docker-compose.yml.
Volumes (persistenza dati)
volumes:
portale_db_data: # I dati MySQL sopravvivono al restart
Senza questo volume, ogni docker compose down cancella il database.
Concetti Laravel spiegati
1. MVC — Model, View, Controller
Laravel usa il pattern MVC per separare le responsabilità:
Richiesta HTTP
│
▼
[Route] routes/web.php
│ Mappa URL → Controller
▼
[Controller] app/Http/Controllers/CustomerController.php
│ Riceve la richiesta, chiede i dati al Model, passa tutto alla View
▼
[Model] app/Models/Customer.php
│ Rappresenta la tabella `customers`. Usa Eloquent ORM per le query.
▼
[Database] MySQL
│
▼
[View] resources/views/customers/index.blade.php
│ Template HTML con variabili PHP. Genera la risposta HTML.
▼
Risposta HTML al browser
2. Eloquent ORM
Invece di SQL raw, usi metodi PHP fluenti:
// SQL: SELECT * FROM customers WHERE status = 'attivo' ORDER BY name LIMIT 10
$clienti = Customer::where('status', 'attivo')
->orderBy('name')
->take(10)
->get();
// SQL: SELECT * FROM customers WHERE id = 42
$cliente = Customer::find(42);
// SQL: INSERT INTO customers (...) VALUES (...)
$nuovo = Customer::create([
'name' => 'Mario Rossi',
'email' => 'mario@example.com',
// ...
]);
3. Migration
Le migration sono "versionamento" del database. Invece di scrivere SQL manualmente:
// Crea la tabella customers con colonne ben tipizzate
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});
Comandi:
php artisan migrate # Esegui migration nuove
php artisan migrate:rollback # Annulla l'ultima batch
php artisan migrate:fresh # ⚠️ Cancella tutto e ricrea (solo in development!)
4. Blade — Template Engine
Il template engine di Laravel. Sintassi pulita e sicura:
{{-- Stampa variabile (escape automatico per sicurezza XSS) --}}
{{ $customer->name }}
{{-- Ciclo --}}
@foreach ($customers as $customer)
<tr>...</tr>
@endforeach
{{-- Condizione --}}
@if ($customer->status === 'attivo')
<span class="badge bg-success">Attivo</span>
@endif
{{-- Include partial --}}
@include('customers._form', ['customer' => $customer])
{{-- Eredita layout --}}
@extends('layouts.app')
@section('content')
<!-- contenuto specifico della pagina -->
@endsection
5. Validazione
Laravel valida i dati dei form in modo dichiarativo:
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:customers',
'type' => 'required|in:privato,azienda',
]);
// Se la validazione fallisce, Laravel torna automaticamente al form
// con gli errori e i valori inseriti (no codice extra necessario)
Nelle view, gli errori sono disponibili con @error('name'):
<input type="text" name="name" class="{{ $errors->has('name') ? 'is-invalid' : '' }}">
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
6. Dependency Injection
Laravel può iniettare dipendenze automaticamente nel costruttore dei Controller:
class CustomerController extends Controller
{
// Laravel crea SettingService automaticamente — non serve "new SettingService()"
public function __construct(
private SettingService $settings
) {}
public function index()
{
$perPage = $this->settings->get('items_per_page');
// ...
}
}
Configurazione: .env vs Impostazioni dinamiche
Questo progetto separa due tipi di configurazione:
.env — Configurazione STATICA (infrastruttura)
Per valori che non cambiano nel tempo o che variano per ambiente (locale, staging, produzione):
APP_NAME="Portale Clienti" # Nome app
DB_HOST=db # Host database (nome container Docker)
DB_DATABASE=portale_clienti # Nome database
DB_PASSWORD=portale_secret_pass # Password (diversa per ogni ambiente)
REDIS_HOST=redis # Cache host
- Cambiare questi valori richiede il restart del container
- Non si committano su Git (
.envè in.gitignore) - Si usa
.env.examplecome template pubblico
config/settings.php + tabella settings — Configurazione DINAMICA (business)
Per valori che cambiano nel tempo e che l'admin modifica da pannello web:
// config/settings.php definisce i default e i metadati
'defaults' => [
'items_per_page' => 15, // Righe per pagina
'currency_symbol' => '€', // Simbolo valuta
'welcome_message' => 'Benvenuto!', // Messaggio dashboard
'allow_notes' => true, // Feature flag
]
- I valori vengono salvati nella tabella
settingsdel database - Modificabili via interfaccia web (/settings) senza codice o restart
- SettingService li mette in cache Redis per performance
- Ideali per: valute, formati data, messaggi, feature flags, paginazione
Leggi un'impostazione nel codice
// In un Controller
$symbol = $this->settings->get('currency_symbol', '€');
// In una View (grazie a AppServiceProvider che condivide $appSettings)
{{ $appSettings['currency_symbol'] }}
CRUD Clienti — come funziona
Route Resource
Una sola riga nel file routes genera 7 URL:
Route::resource('customers', CustomerController::class);
| Metodo | URL | Azione | Controller |
|---|---|---|---|
| GET | /customers |
Lista | index() |
| GET | /customers/create |
Form nuovo | create() |
| POST | /customers |
Salva nuovo | store() |
| GET | /customers/{id} |
Dettaglio | show() |
| GET | /customers/{id}/edit |
Form modifica | edit() |
| PUT | /customers/{id} |
Aggiorna | update() |
| DELETE | /customers/{id} |
Elimina | destroy() |
Soft Delete
I clienti "eliminati" non vengono rimossi fisicamente dal database:
// app/Models/Customer.php
use SoftDeletes; // Aggiunge la colonna deleted_at
// Quando elimini:
$customer->delete();
// → imposta deleted_at = now()
// → NON cancella la riga
// Le query normali ignorano i soft-deleted:
Customer::all() // non include i clienti eliminati
Customer::withTrashed()->find($id) // include anche i soft-deleted
Comandi utili
Container Docker
# Avvia tutti i container in background
docker compose up -d
# Avvia ricostruendo le immagini (dopo modifiche al Dockerfile)
docker compose up -d --build
# Ferma i container (i dati rimangono)
docker compose stop
# Ferma e rimuove i container (i dati rimangono nel volume)
docker compose down
# Ferma, rimuove container E volumi (⚠️ cancella il database!)
docker compose down -v
# Vedi i log in tempo reale
docker compose logs -f
# Vedi i log solo del container PHP
docker compose logs -f app
Artisan (dentro il container)
# Entra nel container PHP
docker compose exec app bash
# Da dentro il container, tutti i comandi artisan:
php artisan route:list # Lista tutte le route registrate
php artisan migrate # Esegui migration nuove
php artisan migrate:fresh --seed # Ricrea DB con dati di esempio
php artisan db:seed # Aggiungi solo i dati (senza ricreare tabelle)
php artisan cache:clear # Svuota la cache
php artisan config:clear # Svuota cache configurazione
php artisan tinker # REPL interattivo (prova codice PHP/Laravel)
# Scorciatoia senza entrare nel container:
docker compose exec app php artisan route:list
Tinker — REPL interattivo
Tinker è uno strumento potentissimo per esplorare e testare:
docker compose exec app php artisan tinker
# Dentro Tinker:
>>> Customer::count() # Quanti clienti ci sono?
>>> Customer::active()->get() # Clienti attivi
>>> Customer::find(1) # Cliente con ID 1
>>> Customer::create(['name' => 'Test', 'email' => 'test@test.it', 'type' => 'azienda', 'status' => 'attivo'])
>>> app(\App\Services\SettingService::class)->get('currency_symbol')
Workflow di sviluppo
Modificare il codice
I file del progetto sono montati come volume nel container Docker:
volumes:
- .:/var/www # directory locale → /var/www nel container
Questo significa che puoi modificare i file sul tuo computer con il tuo editor preferito, e le modifiche sono immediatamente visibili nel browser (no rebuild necessario per PHP/Blade).
Aggiungere una colonna al database
- Crea la migration:
docker compose exec app php artisan make:migration add_website_to_customers - Modifica il file creato in
database/migrations/ - Esegui la migration:
docker compose exec app php artisan migrate - Aggiorna il Model (aggiungi il campo in
$fillable) - Aggiorna le view per mostrare/editare il campo
Aggiungere una nuova sezione (es. Contratti)
- Migration:
php artisan make:migration create_contracts_table - Model:
php artisan make:model Contract - Controller: aggiungi i metodi CRUD
- Route: aggiungi
Route::resource('contracts', ContractController::class) - Views: crea
resources/views/contracts/ - Sidebar: aggiungi il link in
resources/views/layouts/app.blade.php
Prossimi passi
Suggerimenti per espandere questo progetto:
Livello base
- Aggiungere l'autenticazione con
php artisan breeze:install - Aggiungere il campo "website" ai clienti
- Esportare i clienti in CSV
Livello intermedio
- Aggiungere la gestione contratti (relazione 1-N con Customer)
- Upload logo aziendale (Laravel Storage + S3)
- Inviare email al cliente con Laravel Mail
- Test automatici con PestPHP
Livello avanzato
- API REST con Laravel Sanctum
- Job asincroni per import massivo clienti
- Real-time con Laravel Broadcasting + Pusher
- Deploy in produzione su AWS/DigitalOcean
Risoluzione problemi
"Permissions denied" su storage/
docker compose exec app chmod -R 775 storage bootstrap/cache
Il database non si connette
# Controlla che il container MySQL sia avviato e sano
docker compose ps
# Attendi che sia "healthy" (può impiegare 20-30 secondi al primo avvio)
docker compose logs db
Svuota tutta la cache
docker compose exec app php artisan optimize:clear
Riparti da zero (⚠️ cancella tutti i dati)
docker compose down -v
docker compose up -d --build