# Migração do accia — PHP 7.4 → PHP 8.3: relatório final

- **Branch:** `migracao-php83` (worktree `C:\projetos\apps\accia-php83`), a partir de `main@3205ad5`. A `main` não foi alterada.
- **Spec / plano:** `docs/superpowers/specs/2026-10-22-migracao-php83-design.md`, `docs/superpowers/plans/2026-10-22-migracao-php83.md`.
- **Infraestrutura de validação:** `C:\projetos\php83\` (ver `README.md` lá).
- **Data:** 2026-10-22.

## 1. Resultado

O accia roda em **PHP 8.3** (Yii 2.0.55). O mesmo código continua rodando em **PHP 7.4**.
A validação comparou 1.575 URLs autenticadas, com dados reais (cópia de `psec_vialgpd`), entre o PHP 7.4 com o código original e o PHP 8.3 com o código migrado:

| Comparação | Iguais | Divergentes | Regressões abertas |
|---|---|---|---|
| 7.4 original × **8.3 migrado** (rodada 3) | 1.497 | 77 | **0** (todas explicadas, seção 5) |
| 7.4 original × **7.4 migrado** (compatibilidade dupla) | 1.474 | 100 | **0** (seção 6) |

"Igual" = mesmo status HTTP, sem erro novo de log/console e HTML normalizado com diferença ≤ 2%.
Um erro que já existia no 7.4 com o mesmo status conta como preexistente.

## 2. Critérios de aceite da spec

| # | Critério | Resultado | Evidência |
|---|---|---|---|
| 1 | `php -l` sem erro em 7.4 e 8.3 | ✅ 1.839 arquivos, 0 erros nos dois | `02-lint-alvo83.json`, `02-lint-migr74.json` |
| 2 | PHPCompatibility 7.4–8.3 sem erro de compatibilidade 8.x | ✅ 19 ERROR residuais justificados: `str_contains` via polyfill, `func_get_args` (mudança do PHP 7.0), código morto | `05-phpcompat.md` |
| 3 | `check-platform-reqs` OK em 7.4 e 8.3; lock com `platform.php=7.4.33` | ✅ | `04-dependencias.md` |
| 4 | Console `./yii` equivalente | ✅ ajuda idêntica; só textos do próprio framework mudam | `11-console.md` |
| 5 | Crawl comparativo sem divergência não explicada | ✅ 77 divergências, todas classificadas | seção 5; `08-comparacao-base74-x-alvo83.md` |
| 6 | Fluxos de escrita com o mesmo efeito no banco | ✅ 18 fluxos: 12 iguais; 6 com falha nos dois PHPs (só a mensagem muda) ou melhoria no 8.3; regressões A1–A6 corrigidas | seção 7; `10-fluxos.md` |
| 7 | Mesmo código em 7.4 sem divergência de status | ✅ | seção 6; `12-comparacao-base74-x-migr74.md` |
| 8 | Nenhuma referência ao SDK do PagSeguro | ✅ | `tests/test_codigo.py` |
| 9 | Nenhum e-mail para fora | ✅ `smtp.sendgrid.net` resolve para o Mailpit na rede de validação | `C:\projetos\php83\README.md` |
| 10 | Banco real e `php74_apache` intactos | ✅ containers de validação sem rota para o `mysql57`; `php74_apache` não foi alterado (só reiniciou com o restart do Docker) | `01-copia-banco.md` |

Testes automáticos: `python -m unittest discover -s tools/migracao-php83/tests`, **47 testes OK**.
Cada teste compara o comportamento no 7.4 (código original) com o 8.3 (código migrado) e, quando relevante, com o 7.4 rodando o código migrado.

## 3. Mudanças de dependência (composer)

Detalhes em `04-dependencias.md`. Principais:
- `yiisoft/yii2` 2.0.16.1 → **2.0.55** (suporte a PHP 8.3 desde 2.0.51).
- `phpoffice/phpexcel` (abandonado) → `phpoffice/phpspreadsheet ^1.29`.
- `2amigos/qrcode-library` 1.1 → 3.1, gerando **SVG**: o PNG da 3.x exige a extensão `imagick`, que não existe na produção.
- `mpdf` 8.0 → 8.3; `phpword` 0.18 → 1.4 (a 0.18 dependia do `laminas-escaper`, sem versão para 7.4 e 8.3 ao mesmo tempo).
- Removidos: `pagseguro-php-sdk` (funcionalidade já desativada no código), `htmltoopenxml` (só usado em código comentado) e a cadeia dev do Codeception 4.
- `dev-master` fixados no hash já usado; `yii2-queue` → 2.3.8; `yii2-gii` → 2.2.7 (a 2.1 tem advisories de segurança).
- Assets: `inputmask` 3 → 5 e `punycode` 1 → 2 (exigidos pelo Yii 2.0.55); `tinymce` 5.10 → 8.x (a 5.10 tem 11 advisories de segurança); `jquery` mantido em 3.3.
- E-mail: `yiisoft/yii2-swiftmailer`/`swiftmailer` (abandonado; usa *callable* obsoleto no PHP 8.2 e o envio falhava) → `yiisoft/yii2-symfonymailer ~3.0` (symfony/mailer 5.4). Mesma API `compose()/send()` e mesmo SMTP do SendGrid (587, STARTTLS) em `config/web.php` e `config/console.php`.
- `composer audit`: **nenhuma vulnerabilidade**.

## 4. Correções no código (o que quebrava no PHP 8 e por quê)

Causa raiz comum: o `ErrorHandler` do Yii converte em exceção todo erro incluído em `error_reporting()`.
No 7.4 (sem `php.ini`) `error_reporting=22519` silenciava notices e deprecations.
No 8.x, vários notices viraram **warning** e surgiram novas **deprecations**, que passaram a dar **HTTP 500**.
Toda correção reproduz o valor que o 7.4 produzia e usa só sintaxe válida no 7.4.

| Classe | Exemplos | Correção | Commits |
|---|---|---|---|
| **Laço infinito no 8.3** | `models/InventarioScope.php`: constante com chaves `float` (0.25/0.3/0.7/0.5). A deprecation lançada dentro de `new static()` travava o processo a 100% de CPU (22 telas com timeout) | chave `0`, como o 7.4 truncava; o array resultante é idêntico | `bf55402` |
| **Método de instância chamado estaticamente** (Error no 8.0) | `rbac/MethodRule` (derrubava a home), `GenericSearch::filterMy*` (~350 chamadas), 25 métodos dos models financeiros, `Ocorrencia::getDropdDownStatus`, `Task::_buildTaskToGantt` | declarados `static` (nenhum usa `$this`); `Contract::attributeIcons` mantido de instância por herança | `1ceb03e`, `266ed13`, `c0b7ef7` |
| **Parâmetro opcional antes de obrigatório** | o Yii passaria a responder 400 (`isDefaultValueAvailable()` = false no 8.x) | actions: obrigatórios no início (o Yii associa por nome); métodos posicionais: sem default antes do último obrigatório | `81e44f9` |
| **Funções removidas/obsoletas** | `each()` (`Coleta::toc`), `utf8_decode`/`utf8_encode` (14) | `reset/key/current`; `mb_convert_encoding` | `81e44f9` |
| **null em função nativa** (deprecation 8.1) | `strlen`, `explode` (48 nos dashboards), `substr`, `strtotime`, `str_replace`, `preg_replace`, `json_decode`, `htmlspecialchars_decode` via `Html::decode` | `(string)` ou `?? ''` | lotes 1, 2, 5 |
| **Índice/variável/propriedade inexistente** (notice → warning) | `$clientes` em 20 actions do financial, cadeias `->rel->attr` sobre null, `$_POST[...]` ausente, offset de string vazia | `?? null` / inicialização com `null` | lotes 1, 4, 5, 6; `3f79794`; `dc575db` |
| **PDO devolve tipos nativos** (8.1) | JSON passou de `"chave":"41"` para `41`; risco em comparações estritas e JS (`e.point.chave.substring`) | `PDO::ATTR_STRINGIFY_FETCHES => true` em `config/db.php` | `449b94c` |
| **Comparação frouxa número × string** (PHP 8: string não numérica compara como string) | `'' == 0`, `'45,5' >= 80` (`Contexto::getValorGlobal()` usa vírgula decimal), `' ' < 0` (`somaAno`/`somaMes`), `'*' == 0` (interpolação); `BpTipo` dava **Fatal TypeError** | `(float)` no lado string, reproduzindo a conversão do 7.4 (65 linhas em 19 arquivos) | `b290636` |
| **Regressões só em gravação** (achadas pelos fluxos POST) | e-mail não saía (SwiftMailer); importação GPP (`strtotime`/`trim` com null, coluna ausente); laudo Word (constante removida e `noWrap` padrão no PhpWord 1.4); API `/v1/cliente`; `Coleta` com `'' == 0` | troca de mailer; `(string)`/`?? null`; `TblWidth::PERCENT` e `'noWrap' => false`; `?? null`; `(float)` | `daa6ae2` |
| **Bibliotecas trocadas** | PHPExcel, QR code, PagSeguro, SwiftMailer | PhpSpreadsheet; `SvgWriter`; remoção; symfony/mailer | `c3278b0`, `6ad4023`, `6d65738`, `daa6ae2` |

No total são 90 arquivos de aplicação alterados (411 inserções e 955 remoções; a maior parte das remoções é o PagSeguro).

## 5. Divergências restantes 7.4 × 8.3 (rodada 3) — todas explicadas

| Grupo | URLs | Classificação |
|---|---|---|
| HTML com ~2–20% de diferença e **texto visível idêntico** | listagens do financial, `vertical/*`, `aplicacao/index`, `wildcard`, `template`, forms com CKEditor/MaskedInput, `cliente/view` | **Versão de dependência, sem impacto funcional:** ícones SVG do `ActionColumn` (Yii 2.0.55), `inputmask` 5 (`jquery.inputmask.js` + `inputmode`), `inputOptions` repassado a widgets (classe `form-control`), porta nos assets do CKEditor, tempo de montagem do iframe do CKEditor. Máscaras de CPF e CNPJ testadas no 8.3. |
| `vertical/copia`, `vertical/duplica-cluster`, `contexto/create?cliente_id=…`, `log/index?page=…` | — | **Estado:** essas actions gravam no banco em GET, então os dados mudam entre as rodadas. |
| `termos-uso/aceite`, `operational/dashboard/index`, `ocorrencias/finalizar` | — | **Estado ou navegação:** o redirect depende de `Url::previous()`; a rodada base finalizou as ocorrências. Com o mesmo estado, o 7.4 atual responde igual ao 8.3. |
| `inventario-resposta/view?id=…` | 3 | **500 no 7.4 → 200 no 8.3:** o `ArrayHelper::getValue` do Yii 2.0.55 devolve null para a coluna inexistente `processo_contratacao` (defeito antigo da view, agora sem 500). |
| `licenca/pagseguro` | 1 | 200 → 404: **intencional** (PagSeguro removido). |
| `foto/download?id=3803` | 1 | **Ambiente:** o arquivo de upload não existe na worktree (`web/foto` é gitignorado). |
| Console: Google Maps `InvalidKeyMapError`, 404 de `/img/mask-1@1x.png`, de `/js/jquery.maskedinput.js` e de foto de usuário | — | **Preexistentes** nos dois ambientes. |

## 6. Compatibilidade dupla (código migrado no PHP 7.4)

O código migrado rodando no PHP 7.4 tem os mesmos status HTTP do original. As exceções são as já explicadas acima (`inventario-resposta` 500→200 e `pagseguro` 404).
O `php_errors.log` do 7.4 registra `HeadersAlreadySentException` em actions que fazem `echo` antes da resposta. O mesmo erro existe no 7.4 original: o `cm_log` tem esses registros de todas as rodadas. Página e status não mudam, e no 8.3 isso não ocorre.
Isso permite rollback simples: voltar o PHP do servidor sem voltar o código.

## 7. Fluxos de gravação

Executados com POSTs reais (CSRF da sessão) em banco próprio `psec_vialgpd_fluxos` (portas 8174/8183). Detalhes em `10-fluxos.md`.

- **Iguais no 7.4 e no 8.3 (12):** login/DbSession; e-mail do inventário (chega ao Mailpit); CRUD de árvore, provider e modelo de risco; ocorrência (JSON); financial/task (recusados igualmente por permissão); importação GPP `.xlsx`; PDFs (mpdf via CLI e kartik `exportar-pdf`); API v1 autenticada (7 endpoints).
- **Melhoria (1):** importação GPP `.xls` funciona no 8.3; no 7.4 falha no PHPExcel antigo (`OLE.php:288`).
- **Falham nos dois por ambiente (4), só a mensagem muda:** PDF de ocorrência via HTTP (`vendor/mpdf/mpdf/tmp` sem permissão de escrita), laudo Word via HTTP (template do cliente inexistente), GPP com fixture inválido (`.xlsx` e `.xls`).
- **Word via CLI (mesmas chamadas do laudo):** XML equivalente; só some `<w:bidiVisual w:val="0"/>`, que é o valor padrão.
- **Regressões A1–A6** da primeira execução: todas corrigidas (`daa6ae2`) e revalidadas.

## 8. Não verificado

- **CasperJS** (`AnexoController.php:1097`): caminho fixo `/var/www/html/psec/node_modules/casperjs`, que não existe nem no 7.4 local.
- **Comandos `cron/*`** do console: só a ajuda foi comparada. Eles escrevem no banco e enviam e-mail.
- **Integrações externas reais** (SendGrid, Google Maps, acceptme): interceptadas ou sem chave no ambiente local.
- **Rotas destrutivas** (`delete`, `exclui`, `remove`, `logout`, `limpa`, `envia`, `send`, `backup`, `restore`): fora do crawl GET por segurança. As que gravam no banco foram cobertas pelos fluxos (seção 7).
- **1 usuário com `auth_key` vazio** (`user_ceon.id=-2`, `status=10`): não loga pelo formulário. Comportamento previsto pelo código, não testado.
- **Fluxos de update de financial/task/operational:** o usuário de teste não tem dados de apoio nem permissão. Criar esses dados exigiria autorização.
- **Risco residual: aritmética com string não numérica.** No PHP 8, `' ' + 1` ou `'NC' * 2` dão TypeError, e `'45,5' + 1` gera warning (no 7.4 era notice silenciado). As comparações dessa origem foram corrigidas (lote 8), e o crawl e os fluxos não mostraram nenhum erro aritmético restante. Mesmo assim, operações aritméticas sobre os retornos de `Contexto::getValorGlobal()` (decimal com vírgula) e de `somaAno`/`somaMes` (`' '`) em caminhos que o crawl não exercita merecem uma varredura própria, se houver tempo.

## 9. Pendências / decisões do usuário

1. **Produção:** aplicar `C:\projetos\php83\php\producao.ini` (`error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT`). Recomendado: avisos futuros do PHP 9 não derrubam telas.
2. **Merge e deploy** da branch `migracao-php83`. Antes do deploy: `composer install --no-dev` no servidor 8.3 e conferir a permissão de escrita do diretório temporário do mpdf (`vendor/mpdf/mpdf/tmp`). O mpdf já falhava nos dois ambientes locais por falta de permissão.
3. **Defeitos antigos encontrados (não são da migração):**
   - o ajax `dados-local` do dashboard operacional não envia `contexto_id` (400 nos dois PHPs);
   - `models/TipoPermissao.php` declara o namespace errado;
   - `views/helper/tutorial.php` usa a classe `CKEditor` sem `use`;
   - a view de `inventario-resposta` pede uma coluna inexistente.
4. **Limpeza:**
   - banco parcial `psec_vialgpd_php83` no `mysql57` compartilhado (134 tabelas da primeira tentativa de cópia): o `DROP` foi bloqueado pela política de permissões do Claude Code e precisa ser feito pelo usuário;
   - bancos de validação no `mysql57_php83`;
   - `.wslconfig` (12 CPUs e 8 GB; backup em `C:\projetos\php83\wslconfig.backup`).
5. **Vertical do risco duplicada (INACABADA):** commits 0e68dc1 e f3dd1f0 corrigem o Out of Memory e a vertical que não carregava (busca por objeto `Risco` gerava `IN` com todos os atributos; nós repetidos travavam o jstree com `RangeError`). Ficou pendente:
   - teste da aba Vertical no server4 depois do deploy de f3dd1f0;
   - limpeza dos dados: 353 riscos com linhas repetidas em `risco_vertical` (538.306 linhas para 96.088 distintas no server4). Script com backup e reversão: inativa as repetidas mantendo a de menor id. Rodar só depois da reimportação do banco; não foi executado porque a política de permissões bloqueou;
   - `actionRecuperaVerticalRiscoPvf` e a API (`V1Controller`) ainda leem os nós repetidos sem deduplicar.
