# 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 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) ... @endforeach {{-- Condizione --}} @if ($customer->status === 'attivo') Attivo @endif {{-- Include partial --}} @include('customers._form', ['customer' => $customer]) {{-- Eredita layout --}} @extends('layouts.app') @section('content') @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 @error('name')
{{ $message }}
@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 ```