++ Primo Caricamento
This commit is contained in:
578
README.md
Normal file
578
README.md
Normal file
@@ -0,0 +1,578 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user