# Upgrade Phase 0 — Stato avanzamento

Branch: `chore/symfony-upgrade-phase0`
Riferimento: `UPGRADE.md` (piano completo: 4.x → 5.4 → 6.4 → 7.x)

## Commit fatti in questa sessione

1. `chore(upgrade-phase0): replace deprecated Controller with AbstractController`
   - 7 controller convertiti da `Symfony\Bundle\FrameworkBundle\Controller\Controller` a `AbstractController`.
2. `chore(upgrade-phase0): drop service-locator $this->get('...') in controllers`
   - 7 file ripuliti dal pattern `$this->get('servizio')` in favore di DI esplicita.
3. `chore(upgrade-phase0): replace getDoctrine() in 1-hit controllers + service`
   - 5 controller + 1 service (`ReportService` aveva pure un bug: `getDoctrine()` in un service non sarebbe potuto funzionare).
4. `chore(upgrade-phase0): replace getDoctrine() in 2-hit controllers`
   - 10 controller convertiti.

**Stato in cifre**:
- ✅ Task 0.3 — Controller → AbstractController: completo
- ✅ Task 0.4 — Service locator `$this->get(...)`: completo (zero usi reali, residui solo in commenti)
- ✅ Task 0.6 — Sensio bundle: **RIMOSSO**. `@Method`→`@Route(methods)` (881def66), poi le 36 `@Security` ridondanti + 23 import rimossi, bundle deregistrato da `bundles.php`, config Sensio e alias `view.listener` eliminati, tolto da `composer.json` (`1fa5bf84`). Container compila dev+prod, 478 rotte, smoke test verdi. ⚠️ `composer.lock` non rigenerato (advisory SF4.4): il pacchetto resta in `vendor/` ma deregistrato finché non si fa il re-lock in Fase 1.
- ✅ Task 0.7 — `$this->getDoctrine()`: **COMPLETO** — 58 file. Ultimi 5 "Molto grandi" un commit per file: WSSped `be92ce25`, Detail `2d151eed`, ConfiguratoreAjax `7ace4e8e`, Check `0f4a5cfd`, DataController `16be5145`. **Zero `getDoctrine()` attivi in `src/`** (restano 4 residui solo dentro codice commentato: BLed, Welcome, Congruity, Admin/ImportCloud — ignorabili). Validato con `cache:warmup` dev+prod.
- ✅ Task 0.5 — SwiftMailer → Symfony Mailer: **VERIFICATO COMPLETO**. `symfony/mailer` in uso ovunque (controller, command, `ReportService` con `MailerInterface`); nessun `swiftmailer-bundle` in composer.json, nessun `swiftmailer.yaml`, zero codice `Swift_` attivo (solo commenti "MIGRAZIONE" in ReportsController). L'unica menzione `swiftmailer` in composer.lock è nel `require-dev` di `monolog/monolog` (non installata). Residuo: dir orfana `vendor/swiftmailer` (8KB, non tracciata da composer, la ripulisce `composer install`).
- ✅ Task 0.8 — Deprecation detector: **ESEGUITO** (Symfony 4.4.51). Vedi sezione "## Task 0.8 — Report deprecation" sotto.
- ✅ Task 0.9 — Test: suite bonificata (era 100% skeleton Symfony Demo) + DB MariaDB di test wired (`bin/init-test-db.sh` + `tests/schema.sql`); smoke test di rotta attivi. **31 test verdi.** Resta da alzare la copertura. Vedi sezione "## Task 0.9 — Test".
- ✅ Task 0.1 — Bump PHP 8.1+ (`15a8ffe9`): `composer.json` require.php `^7.4.0`→`^8.1` + `config.platform.php` `7.4.0`→`8.1.0`; `Dockerfile` `php:7.4-fpm`→`php:8.1-fpm` (locale già 8.2.31). ⚠️ `composer.lock` **non** ri-generato: `composer update --lock` fallisce perché le versioni Symfony 4.4 (EOL) lockate sono bloccate da security advisory (`block-insecure`) — il re-lock è attività di Fase 1. `composer install` funziona ancora dal lock; solo `composer validate --strict` segnala l'hash disallineato.
- 🟡 Task 0.2 — Sicurezza: 5 problemi su 9 risolti (#1 SQL injection completo, #2, #3, #5, #9 parziale-Okta). Vedi sezione "## Task 0.2 — Sicurezza". Restano #9 (resto superglobali), #8 (.env), e i rimandati #4/#6/#7.

## File rimanenti task 0.7 (ordinati per dimensione)

Quattro file da 1 hit residui sono **falsi positivi**: hit dentro commenti `//` o `/* */`. Si possono ignorare:
- `Admin/ImportCloudController.php`
- `BLedController.php` (residuo commentato)
- `CongruityController.php` (residuo commentato)
- `WelcomeController.php` (residuo commentato)

File da fare davvero (43 file):

### ✅ Piccoli (3 hit) — 7 file — FATTI (commit 1acda800)
- ~~`Admin/CaratteristicheController.php`~~
- ~~`Admin/DecodeController.php`~~
- ~~`GalleryController.php`~~
- ~~`LocationController.php`~~
- ~~`MacchineControllerL.php`~~
- ~~`MDecodeController.php`~~
- ~~`ModelliController.php`~~

### ✅ Medi (4-5 hit) — 9 file — FATTI (commit 358954f2)
- ~~`Admin/AwbimportFormController.php` (4)~~
- ~~`Admin/C4rgoNewsFormController.php` (4)~~
- ~~`Admin/CustomSchedulerDetailController.php` (4)~~
- ~~`Admin/DMXLedLightController.php` (4)~~
- ~~`Configuratore/Configuratore3DController.php` (4)~~
- ~~`SA/ComandiController.php` (4)~~
- ~~`SA/MontaggiController.php` (4)~~
- ~~`AwbimportFormController.php` (5)~~
- ~~`SA/ComponentiController.php` (5)~~

### ✅ Medi (6-7 hit) — 10 file — FATTI (commit 9226cd75)
- ~~`Admin/SupportsController.php` (6)~~
- ~~`CloudServiceController.php` (6)~~
- ~~`Configuratore/DeepNestController.php` (6)~~
- ~~`ContenitoriController.php` (6)~~
- ~~`SA/ComandiExecController.php` (6)~~
- ~~`StatController.php` (6)~~
- ~~`Admin/CompanyManagementController.php` (7)~~
- ~~`ReportsController.php` (7)~~
- ~~`SA/AziendeController.php` (7)~~
- ~~`SA/FamigliaComponentiController.php` (7)~~

### ✅ Grandi (8-20 hit) — 12 file — FATTI (commit 7a247319)
- ~~`Configuratore/Configuratore3DC4rgoController.php` (8)~~
- ~~`DetailItemController.php` (8)~~
- ~~`Admin/CustomSchedulerController.php` (9)~~
- ~~`Admin/UserManagementController.php` (9)~~
- ~~`SA/MacchineController.php` (9)~~
- ~~`SA/ModelliController.php` (9)~~
- ~~`RemoteMacController.php` (10)~~
- ~~`ServiceController.php` (14)~~
- ~~`Configuratore/ConfiguratoreSOController.php` (15)~~
- ~~`CloudToolsController.php` (18)~~
- ~~`Configuratore/ConfiguratoreDOController.php` (19)~~
- ~~`Configuratore/ConfiguratoreMOController.php` (20)~~

### ✅ Molto grandi (>30 hit) — 5 file — FATTI (un commit per file)
- ~~`WSSpedController.php` (34)~~ `be92ce25`
- ~~`DetailController.php` (45)~~ `2d151eed`
- ~~`Configuratore/ConfiguratoreAjaxController.php` (47)~~ `7ace4e8e`
- ~~`CheckController.php` (57)~~ `0f4a5cfd`
- ~~`DataController.php` (115)~~ `16be5145` — il file più grosso, ~3300 righe

## Task 0.8 — Report deprecation (eseguito 2026-06-03, Symfony 4.4.51)

Fonti: (a) `cache:clear/warmup` dev+prod = deprecation di **compile/config**; (b) `SYMFONY_DEPRECATIONS_HELPER='max[total]=999999' ./vendor/bin/simple-phpunit` = report runtime sui path testati (NB: 26/49 test in errore per fixture/DB, copertura ~3% → quadro parziale); (c) `var/log/dev.log` storico (cumulativo, contiene anche stati pre-fix → usato solo come spunto, verificato sul codice attuale).

Compile-time attuale: **pulito** — solo 3 deprecation (DebugClassLoader interna + 2 LiipImagine). Niente più `@Method`/`Controller`/`getDoctrine` ⇒ le fix di sessione hanno avuto effetto.

### A. Codice NOSTRO — ✅ FATTE (quick win)
- ✅ `$event->getException()`→`getThrowable()` + migrazione a `ExceptionEvent` in CheckRequirementsSubscriber e AjaxAuthenticationListener — `ff12877a`
- ✅ Proprietà dinamica `AuthenticationEventListener::$authorization` dichiarata (PHP 8.2) — `02d61f59`
- ✅ Twig `{% for ... if ... %}` → `|filter(...)` in giacenza/index, articolo/index, articolo/_form (4 loop; identificatore non ne aveva) — `2da4de8b`
- ✅ Import morto `use ...Controller\Controller;` in `Admin/DMXLedLightController.php` rimosso — `431588ce`
- ⚠️ **LiipImagine `twig.mode: lazy` NON applicabile**: con la versione attuale del bundle rompe `lint:twig` (errore DI "argument 1 doesn't exist" al boot del container, pur passando `cache:warmup`). Commit annullato. Le 2 deprecation FilterTrait/FilterExtension restano e si risolvono aggiornando LiipImagineBundle in Fase 1 (vedi C).

### B. Codice NOSTRO — appartiene alla Fase 1 (migrazione security/User)
- `App\Entity\User implements \Serializable` (+ `serialize()`): → `__serialize()`/`__unserialize()` e `PasswordAuthenticatedUserInterface` (Task 1.4)

### C. Terze parti / dipendenze — si risolvono aggiornando i bundle in Fase 1
- **FOSRestBundle 2.8** routing loaders (il grosso del rumore): → FOSRestBundle ^3 oppure disabilitare `fos_rest.routing_loader.enabled: false` (Task 1.5)
- `symfony/property-access`: argomenti booleani a `PropertyAccessor::__construct()` (57 hit nei test) — da dipendenza, sparisce su SF 5.4
- `DebugClassLoader` interna — sparisce su SF 5.x
- **LiipImagineBundle** FilterTrait/FilterExtension (eager Twig): la config `twig.mode: lazy` rompe `lint:twig` con la versione attuale → aggiornare il bundle invece di forzare la config

### D. Infrastruttura test → vedi "## Task 0.9 — Test"

## Task 0.9 — Test (2026-06-03)

Stato di partenza: 49 test, 26 errori. **Due cause:**

1. **Bootstrap env (RISOLTO, `067f97de`)**: `phpunit.xml.dist` faceva bootstrap da `vendor/autoload.php` (saltava il caricamento dei `.env`); la lista `<env>` hardcoded era priva di 17 variabili richieste dai config (`APP_WEBSOCKET`, `JWT_*`, `MQTT_*`, …) → `EnvNotFoundException`. Fix: `bootstrap="tests/bootstrap.php"` (carica `.env`/`.env.test`, le `<env>` del phpunit restano prioritarie, `.env.local` saltato in test). Errori 26 → 12.

2. **Suite = skeleton Symfony Demo (BONIFICATA, `2b04581d`)**: tutti i test funzionali residui testavano `App\Entity\Post`/`Tag`/`Comment` e rotte `/en/blog`, `/en/admin/post` **inesistenti** in C4rgo. Rimossi 4 file demo; aggiunto `tests/SmokeTest.php` (boot del kernel + wiring container/servizi core, **DB-independent**). Suite verde: **28 test, 0 fail/error, 4 skipped** (AddUserCommand).

### ✅ DB MariaDB di test — RISOLTO (2026-06-04)
SQLite non è utilizzabile (i mapping legacy riusano nomi di indice tra tabelle → rifiutati da SQLite; e ogni pagina interroga il DB, es. `roles` nella login page). Inoltre **anche `doctrine:schema:create` fallisce** (su SQLite *e* MariaDB): i mapping entity hanno un nome di indice duplicato (`uqe_colore_TrasportiId`) → lo schema reale **non** è generato da Doctrine ma costruito a parte.

Setup realizzato:
- `DATABASE_URL` di test → MariaDB docker (`127.0.0.1:3307`, DB `c4rgo_test`). Tolto da `phpunit.xml.dist`, messo in `.env.test` (override in `.env.test.local`). NB: per la CLI serve `APP_ENV=test` *esportato* (il flag `--env=test` non basta, `.env.local` verrebbe caricato comunque); sotto phpunit è già impostato.
- Schema in `tests/schema.sql` (struttura, no dati): dump `--no-data --skip-triggers` di `1_myc4rgo`, DEFINER rimossi, + stub di `GetLimitSup/GetLimitInf` (le funzioni reali non sono clonabili — l'utente app non ha SHOW CREATE ROUTINE; gli stub fanno solo creare le viste).
- `bin/init-test-db.sh`: avvia il servizio docker `database`, (ri)crea `c4rgo_test`, carica lo schema (204 tabelle/viste).
- Smoke test di rotta riattivati in `tests/SmokeTest.php`: `/en/login`→200; `/en/list` e `/en/admin/caratteristiche/`→302 a `/en/login`. **Suite verde: 31 test, 0 fail/error.**

Limitazione nota: le viste di congruità usano gli stub `GetLimit*` → non usare quei valori in test finché non si importano le funzioni reali (serve accesso root al DB sorgente).

### Altro
- `SluggerTest`/`ValidatorTest` (util demo `App\Utils\Slugger`/`Validator`, ancora presenti) → passano, lasciati.
- `AddUserCommandTest` → skipped.
- `ValidatorTest` estende `PHPUnit\Framework\TestCase` (deprecation "internal", innocua).

## Task 0.2 — Sicurezza (2026-06-03)

Verificato ogni problema critico del README contro il codice ATTUALE (alcuni risultati erano datati). Stato:

| # | Problema | Stato | Commit |
|---|---|---|---|
| #1 | SQL injection | ✅ **COMPLETO** | vedi sotto |
| #2 | chiave AES nei log | ✅ rimossa | `fb40d01a` |
| #3 | SSL verify off (Okta) | ✅ env-configurabile (default `true`) | `32dd6d4a` |
| #5 | nonce OAuth prevedibile | ✅ `random_bytes` | `fb40d01a` |
| #9 | superglobali `$_ENV/$_GET` | ✅ **COMPLETO** — zero superglobali attivi in `src/` | `32dd6d4a`,`8014bcef`,`d26f0f27`,`885c6afb`,`1bb78169` |
| #4 | validazione JWT Okta assente | ⏸ RIMANDATO (richiede dipendenza `firebase/php-jwt` + JWKS) |
| #6 | AES-CBC senza HMAC | ⏸ RIMANDATO (cambia formato ciphertext → migrazione dati) |
| #7 | token utente in chiaro | ⏸ RIMANDATO (migrazione dati) |
| #8 | credenziali nel `.env` committato | ✅ bonificato (`69919347`, `7b5e55dd`) — vedi sotto. **Resta la rotazione + scrub history (ops).** |

### #1 SQL injection — dettaglio (COMPLETO)
- `SearchController` (2 builder ricerca): `LIKE '%$val2%'` → `?` su `$vdata`; rimosso blocco `/* */` commentato (anch'esso iniettabile via `*/`-escape). `e65eb26d`
- `Configuratore/ConfiguratoreController`: `WHERE c.codice = '{$codice}'` → `?` bind. `d14851d9`
- `CheckController`: 3× `SET @tmp_awbexportN = (… WHERE awb='$id')` → `?` bind; `s1.id = $prev` → cast int. `c181d7be`
- `Utils/Counters` `setGlobalFilter`: 4 filtri numerici interpolati grezzi (guard `(int)>0` ma valore raw) → cast int. `775c821e`
- `Utils/Converter`: **già parametrizzato** (README datato). `Utils/C4rgoUtils`: **nessun SQL raw** nel codice attuale (README datato).
- Sweep finale: 0 interpolazioni residue nei 6 file.

### #8 — bonifica segreti `.env` (FATTO, 2026-06-04)
- `.env` / `.env.staging`: tutti i valori segreti → placeholder `__SET_IN_ENV_LOCAL__` (chiavi/struttura invariate). `.env` è ora un **template committabile**.
- `.env.local`: `git rm --cached` (resta su disco coi valori reali, è in `.gitignore`).
- `.claude/settings.json`: untracked + gitignored (auto-accumulava comandi, aveva catturato la password DB in una entry; mai committata).
- **Punti d'ingresso**: `bin/console` e `public/index.php` usavano `Dotenv::load('.env')` (solo `.env`) → l'app prendeva i segreti dal `.env` committato e non leggeva mai `.env.local`. Passati a `Dotenv::loadEnv()` (cascade `.env`→`.env.local`): i segreti reali ora vengono da `.env.local`. **Prod invariato** (il guard `if (!isset($_SERVER['APP_ENV']))` salta i dotenv: prod usa env var reali del server).
- Verificato: `bin/console` connette al DB reale via `.env.local`; `cache:clear` dev OK; smoke test verdi.
- ⚠️ **Resta a ops**: i segreti sono ancora nella **history git** (commit passati di `.env`/`.env.local`) → **ruotare tutte le credenziali esposte** (DB, SMTP, Okta, Oracle, MQTT, JWT passphrase, APP_SECRET, APP_ENCRYPTION_KEY) e valutare uno scrub della history (git-filter-repo/BFG).

### #9 — superglobali → DI (COMPLETO)
- **Controller** (`8014bcef`): `$_ENV['SITE_ONLINE']` → `$this->getParameter('app.site_online')`; HomeController `$_GET` → `Request`.
- **Controller one-off** (`d26f0f27`): SecurityController (`LEGACY_LOGIN`,`OKTA_APP_URL`), ReportsController (`MAILER_REPORT_DSN`,`EMAIL_FROM_REPORT`,`SERVER_PATH`) → `getParameter`.
- **14 Command + EventListener** (`885c6afb`): `SITE_ONLINE` → scalare bound `$siteOnline` nei costruttori / `$this->container->getParameter` nel listener.
- **CED/Check command** (`1bb78169`): credenziali Oracle + supporto → `ParameterBagInterface` + parametri `app.oracle.*`/`app.email_support`/`app.nome_support`.
- Parametri aggiunti in `services.yaml` (`app.site_online`, `app.legacy_login`, `app.okta_app_url`, `app.mailer_report_dsn`, `app.email_from_report`, `app.server_path`, `app.oracle.*`, ecc.) + bind `$siteOnline`.
- **Zero `$_ENV`/`$_GET`/`$_POST` attivi in `src/`.** Container compila (dev+test), command si istanziano, smoke test verdi.

## Pattern adottato

Per ogni controller già su `AbstractController`:
1. Identificare quali entity sono usate in `$this->getDoctrine()->getRepository(X::class)`.
2. Importare i `XRepository` corrispondenti e iniettarli via constructor (con typed properties PHP 7.4-compat: `private XRepository $xRepository;`).
3. Per `$this->getDoctrine()->getManager()` iniettare `Doctrine\ORM\EntityManagerInterface $em`.
4. Sostituire le chiamate.
5. Pulire imports `Sensio\Bundle\FrameworkExtraBundle\Configuration\*` (Cache, Method, ParamConverter, Security) — sono dead code: il bundle non c'è più nei require.
6. Mergere `@Method("VERB")` in `@Route(..., methods={"VERB"})`.
7. Verificare con `php -l file.php`.
8. Mantenere PHP 7.4 compatibility (no `readonly`, no constructor property promotion) fino a task 0.1.

## Cose da verificare

Prima di proseguire conviene **lanciare l'app** (`docker-compose up -d` poi `http://127.0.0.1:8080`) e fare un giro veloce sulle rotte modificate:
- Login (`/login`, `/logoutpage`) — `SecurityController`
- Home dashboard (`/monitor`) — `HomeController`
- Welcome dashboard (`/secwelcome`) — `WelcomeController`
- Lista AWB (`/list`) — `ListController`
- Pannello admin (`/admin/management_pannel`) — `ManagementPannel`
- Search ERP (`/admin/searcherpresult`) — `Admin/SearchErpController`
- Configurator cover (`/configurator/cover`) — `Configuratore/ConfiguratorCoverController`

Se ci sono errori di DI (servizio non trovato), molto probabilmente è dovuto a un repository che ha un nome diverso da quello che ho assunto; in tal caso il `services.yaml` o l'autowire si lamentano in modo esplicito.

## Cose **non** fatte (intenzionalmente)

- Non ho aggiornato `composer.json` né forzato `composer install`: ancora su Symfony 4.
- Non ho rimosso `doctrine/annotations` né migrato a PHP attributes (è fase 2).
- Non ho toccato `$_GET[...]` / `$_ENV[...]` (è fase 0.2 — sicurezza).
- Non ho toccato il sistema di security.yaml (è fase 1.2-1.4).
- Per file molto grandi (`DataController`, `CheckController`, ecc.) **non ho fatto nulla**: ognuno richiede una passata dedicata.
