579 lines
18 KiB
Markdown
579 lines
18 KiB
Markdown
# 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
|
|
|
|
1. [Cos'è questo progetto](#cosè-questo-progetto)
|
|
2. [Pre-requisiti](#pre-requisiti)
|
|
3. [Avvio rapido](#avvio-rapido)
|
|
4. [Struttura del progetto](#struttura-del-progetto)
|
|
5. [Architettura Docker](#architettura-docker)
|
|
6. [Concetti Laravel spiegati](#concetti-laravel-spiegati)
|
|
7. [Configurazione: .env vs Impostazioni dinamiche](#configurazione-env-vs-impostazioni-dinamiche)
|
|
8. [CRUD Clienti — come funziona](#crud-clienti--come-funziona)
|
|
9. [Comandi utili](#comandi-utili)
|
|
10. [Workflow di sviluppo](#workflow-di-sviluppo)
|
|
11. [Prossimi passi](#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
|
|
|
|
```bash
|
|
# 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:
|
|
> 1. Costruisce l'immagine PHP con tutte le dipendenze
|
|
> 2. Esegue `composer install` per installare Laravel e le librerie
|
|
> 3. Genera l'`APP_KEY` crittografica
|
|
> 4. Esegue le migration (crea le tabelle nel database)
|
|
> 5. 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)
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
// 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:
|
|
```bash
|
|
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:
|
|
|
|
```blade
|
|
{{-- 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:
|
|
|
|
```php
|
|
$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')`:
|
|
```blade
|
|
<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:
|
|
|
|
```php
|
|
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):
|
|
|
|
```env
|
|
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.example` come template pubblico
|
|
|
|
### `config/settings.php` + tabella `settings` — Configurazione DINAMICA (business)
|
|
|
|
Per valori che **cambiano nel tempo** e che **l'admin modifica da pannello web**:
|
|
|
|
```php
|
|
// 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 `settings` del 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
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
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:
|
|
|
|
```php
|
|
// 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
|
|
|
|
```bash
|
|
# 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)
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```yaml
|
|
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
|
|
|
|
1. Crea la migration:
|
|
```bash
|
|
docker compose exec app php artisan make:migration add_website_to_customers
|
|
```
|
|
2. Modifica il file creato in `database/migrations/`
|
|
3. Esegui la migration:
|
|
```bash
|
|
docker compose exec app php artisan migrate
|
|
```
|
|
4. Aggiorna il Model (aggiungi il campo in `$fillable`)
|
|
5. Aggiorna le view per mostrare/editare il campo
|
|
|
|
### Aggiungere una nuova sezione (es. Contratti)
|
|
|
|
1. **Migration**: `php artisan make:migration create_contracts_table`
|
|
2. **Model**: `php artisan make:model Contract`
|
|
3. **Controller**: aggiungi i metodi CRUD
|
|
4. **Route**: aggiungi `Route::resource('contracts', ContractController::class)`
|
|
5. **Views**: crea `resources/views/contracts/`
|
|
6. **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/
|
|
|
|
```bash
|
|
docker compose exec app chmod -R 775 storage bootstrap/cache
|
|
```
|
|
|
|
### Il database non si connette
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
docker compose exec app php artisan optimize:clear
|
|
```
|
|
|
|
### Riparti da zero (⚠️ cancella tutti i dati)
|
|
|
|
```bash
|
|
docker compose down -v
|
|
docker compose up -d --build
|
|
```
|