Plano de Migração para PSR-4 — Portal FD Capital
Plano de adoção incremental do autoload PSR-4 e de classes com namespace, substituindo o modelo atual de funções globais carregadas por require_once.
Elaborado em 31/07/2026 sobre a branch refatoracao.
1. Objetivo
Adotar PSR-4 em todo o backend, de modo que:
- classes sejam carregadas automaticamente pelo Composer, eliminando os
require_oncecom caminhos relativos; - cada serviço vire uma classe com namespace e dependências injetadas pelo construtor;
- o código fique testável (hoje não há um único teste automatizado, e a dependência de
$GLOBALS['conn']é o principal impeditivo); - colisões de nomes de função se tornem impossíveis.
Fora de escopo deste plano: extrair camada de repositório, quebrar os serviços grandes, remover SQL de dentro dos serviços e introduzir container de injeção de dependência. São passos posteriores, viabilizados por este, mas não executados aqui.
2. Estado atual (medido)
| Métrica | Valor |
|---|---|
Arquivos de serviço em backend/src/services/ | 104 |
Endpoints em backend/api/ | 210 |
Funções globais backend_* | ~362 |
require_once / include em backend/ | 539, em 309 arquivos |
| Classes já existentes (sem namespace) | 13 |
Seção autoload no composer.json | inexistente |
Locais que carregam vendor/autoload.php | 2 (apenas as páginas de geração de PDF) |
2.1 Três padrões de nomenclatura convivendo
Isso define o risco de cada módulo e é o critério principal de ordenação do plano.
Padrão A — prefixado e protegido. Módulos novos. Função com prefixo backend_<dominio>_ dentro de if (!function_exists()):
if (!function_exists('backend_tasks_create')) {
function backend_tasks_create(mysqli $conn, array $dados): array { ... }
}
Padrão B — prefixo curto, sem proteção. Ex.: gtListUsers(), gtUpdateUserRate() em planos/rates/gestao_taxas_service.php.
Padrão C — nome genérico, sem prefixo e sem proteção. O mais perigoso. Ex.: em terminais/registration/terminal_batch_registration_service.php: validarDadosEntrada(), prepararDispositivosParaAPI(), fazerRequisicaoAPI(), salvarTerminalNoBanco(), processarRespostaESalvar().
2.2 Colisão real já existente
Não é hipotético. A função fazerRequisicaoAPI() está declarada duas vezes, com assinaturas diferentes e sem guarda:
backend/api/estabelecimentos/sync-bancos.php:26—fazerRequisicaoAPI($url, $headers)backend/src/services/terminais/registration/terminal_batch_registration_service.php:218—fazerRequisicaoAPI(array $dispositivos)
Hoje não quebra porque os dois arquivos nunca são carregados no mesmo request. Qualquer refatoração que aproxime esses caminhos produz um fatal error Cannot redeclare. O mesmo vale para prepararDadosAPI(), que existe em planos/registration/, e para toda a família de nomes genéricos do padrão C.
2.3 Serviços carregados globalmente
components/menu-lateral.php faz require de leads_service.php e tasks_service.php para decidir se os grupos Comercial e Tarefas aparecem no menu. Como o menu está em toda página autenticada, esses dois serviços são carregados em praticamente todo request do sistema. Consequência para o plano: eles têm o maior alcance de regressão, mas também o maior ganho, e exigem teste de fumaça em todas as telas quando migrados.
Além do menu, consomem serviços diretamente (fora de backend/api/):
- 9 páginas em
frontend/pages/comercial/→leads_service.php - 2 páginas em
frontend/pages/tarefas/→tasks_service.php frontend/includes/bootstrap.php→auth/csrf_service.phpdashboard/propostas/gerar_*_pdf.php→propostas/documents/
3. Decisões de arquitetura
3.1 Namespace raiz e mapeamento
{
"autoload": {
"psr-4": {
"FD Capital\\Backend\\": "backend/src/"
},
"files": [
"backend/src/helpers/legacy_functions.php"
]
}
}
A seção files é o que viabiliza a convivência: ela mantém carregadas as funções-fachada durante toda a transição, sem que nenhum endpoint precise mudar.
3.2 Convenção de nomes
| Elemento | Hoje | Depois |
|---|---|---|
| Diretório | backend/src/services/white-label/ | backend/src/Services/WhiteLabel/ |
| Arquivo | white_label_service.php | WhiteLabelService.php |
| Símbolo | function backend_wl_list() | WhiteLabelService::list() |
| Namespace | — | FD Capital\Backend\Services\WhiteLabel |
Diretórios que obrigatoriamente mudam de nome (hífen é inválido em namespace PHP):
services/white-label/→Services/WhiteLabel/services/financeiro/external-api/→Services/Financeiro/ExternalApi/
3.3 Assinatura padrão de um serviço migrado
<?php
declare(strict_types=1);
namespace FD Capital\Backend\Services\Tarefas;
use mysqli;
final class TaskService
{
public function __construct(private readonly mysqli $conn) {}
public function create(array $dados): array
{
// corpo migrado de backend_tasks_create()
}
}
A conexão entra pelo construtor. Nenhum serviço migrado pode ler $GLOBALS['conn'] — essa é a regra que destrava os testes mais adiante.
3.4 Padrão da fachada de compatibilidade
Para cada função migrada, a função global permanece como invólucro de uma linha:
if (!function_exists('backend_tasks_create')) {
function backend_tasks_create(mysqli $conn, array $dados): array
{
return (new \FD Capital\Backend\Services\Tarefas\TaskService($conn))->create($dados);
}
}
Com isso os 210 endpoints continuam funcionando sem uma única alteração. A migração dos call sites é opcional e posterior — para módulos legados congelados, a fachada pode ficar permanentemente.
4. Fase 0 — Pré-requisitos (bloqueia tudo)
Nenhum módulo deve ser migrado antes destes cinco itens.
0.1 — Declarar o autoload. Adicionar a seção autoload ao composer.json e rodar composer dump-autoload -o.
0.2 — Carregar o autoload em ponto único. Incluir vendor/autoload.php no topo de backend/src/helpers/bootstrap.php (cobre os 210 endpoints) e de frontend/includes/bootstrap.php (cobre as views). Hoje só as páginas de PDF carregam, e proposal_document_service.php já depende disso de forma implícita e frágil — este passo, sozinho, corrige esse acoplamento.
0.3 — Resolver o deploy. O deploy é upload manual por FTP e não roda Composer no servidor. Sem composer dump-autoload no destino, uma classe nova simplesmente não é encontrada em produção, mesmo funcionando no Laragon. Duas saídas: continuar versionando vendor/ e lembrar de subir vendor/composer/autoload_*.php a cada classe criada, ou usar um mapa de classes estático regenerado no commit. A primeira opção é a compatível com o processo atual, mas precisa virar item obrigatório do checklist de deploy.
0.4 — Proteger contra erro de caixa. O Windows é case-insensitive e o servidor é Linux: PlanService versus Planservice passa local e quebra em produção. Mitigação mínima: rodar composer dump-autoload --strict-psr antes de cada deploy, que acusa arquivo cujo nome não bate com a classe.
0.5 — Criar o arquivo de fachadas. backend/src/helpers/legacy_functions.php, inicialmente vazio, referenciado na seção files. É onde as funções-invólucro vão sendo acumuladas.
5. Ordenação por onda
Critério: começar pelos módulos com poucos arquivos, prefixo já protegido (padrão A) e poucos consumidores; terminar pelos módulos grandes, sem prefixo e com muitos consumidores.
| Onda | Foco | Por quê |
|---|---|---|
| 1 | Classes já existentes | Só ganham namespace e nome de arquivo — sem reescrita de lógica |
| 2 | Helpers e núcleo | Base compartilhada; precisa estar pronta antes dos serviços |
| 3 | Módulos novos, arquivo único | Padrão A já limpo, migração quase mecânica |
| 4 | Módulos médios consolidados | Volume moderado, consumidores previsíveis |
| 5 | Módulos com padrão B/C | Exige renomear funções genéricas — risco de colisão |
| 6 | Estabelecimentos / Credenciamento | 44 arquivos, 53 endpoints, polling e daemon CLI |
6. Plano por módulo
Legenda de esforço: P = até meia sessão, M = 1 a 2 sessões, G = 3+ sessões.
Onda 1 — Classes existentes (esforço total: P)
Treze classes já existem sem namespace. Migrá-las é apenas adicionar namespace, renomear o arquivo para PascalCase e ajustar os pontos de instanciação.
| Classe atual | Arquivo atual | Destino |
|---|---|---|
ApiAdquirenteAdiq, ApiAdquirenteFactory, ApiEntrepay | integrations/movingpay/api_adiq.php | Integrations/Movingpay/ApiAdquirenteAdiq.php (uma classe por arquivo) |
ApiPlanos | integrations/movingpay/api_planos.php | Integrations/Movingpay/ApiPlanos.php |
ApiEstabelecimentos | integrations/movingpay/api_estabelecimentos.php | Integrations/Movingpay/ApiEstabelecimentos.php |
ApiBandeiras | integrations/movingpay/api_bandeiras.php | Integrations/Movingpay/ApiBandeiras.php |
UsuarioLogger | services/usuarios/user_logger.php | Services/Usuarios/UsuarioLogger.php |
ValidationException | services/estabelecimentos/registration/validation_exception.php | Services/Estabelecimentos/Registration/ValidationException.php |
PollingCredenciamentos, PollingInterno, PollingTimer, PollingAutomatico | credenciamento/polling/* | adiar para a Onda 6, junto do módulo |
Database | backend/includes/db.php | manter como está — é adaptador legado, será removido, não migrado |
Atenção: api_adiq.php declara três classes no mesmo arquivo. O PSR-4 exige uma classe por arquivo, então esse é o único item da onda que envolve divisão real.
Onda 2 — Helpers e núcleo (esforço: M)
| Módulo | Arquivos | Funções | Consumidores | Observação |
|---|---|---|---|---|
helpers/ | 4 (bootstrap, response, tenant, legacy_compat) | 12 | todos os 210 endpoints | Não converter em classe. backend_success(), backend_error() e backend_bootstrap() são funções de fluxo procedural e devem permanecer funções, registradas na seção files. O que muda aqui é apenas passar a carregar o autoload. |
services/core/ | 5 | ~15 | transversal | PermissionService, AuthorizationService, LogService, ModuleScopeService, PermissionScopePolicy. LogService é chamado em toda requisição — migrar primeiro e validar que os arquivos em backend/logs/ continuam sendo escritos. |
services/auth/ | 5 | 22 | login, bootstrap, frontend | AuthService, SessionService, CsrfService, RateLimitService, PasswordPolicyService. Cuidado: session_service.php manipula ini_set antes de session_start() — o comportamento depende de ser executado cedo, então valide fingerprint e regeneração de ID após migrar. |
Onda 3 — Módulos novos, arquivo único (esforço: M cada)
Todos seguem o padrão A e têm serviço único, o que torna a migração quase mecânica.
| Módulo | Arquivos | Funções | Endpoints | Consumidores extras | Esforço |
|---|---|---|---|---|---|
| Tarefas | 1 | 50 | 16 | 2 views + menu lateral | M |
| Assinaturas | 1 | 19 | 9 | webhook público | M |
| Conta | 1 | 8 | 3 | — | P |
| Dashboard | 1 | 5 | 5 | — | P |
| Compliance | 1 | 23 | 6 | — | M |
| Transações | 1 | 27 | 6 | polling JS | M |
Recomendação: Conta como piloto absoluto (8 funções, 3 endpoints, nenhum consumidor externo) e, com o padrão validado, Tarefas como segundo — é o módulo mais recente, mais bem escrito e o mais representativo do que virão a ser os demais.
Notas por módulo:
- Tarefas —
tasks_service.phpconcentra 50 funções que se dividem naturalmente emTaskService(CRUD),TaskStepService(passos e fluxo),TaskDocumentService(upload),TaskPermissionServiceeTaskAiService(OpenAI). Vale fazer essa divisão já na migração. Oensure_schema()deve virar um método estático isolado, não chamado no construtor. - Assinaturas —
webhook.phpé endpoint público sem sessão; garanta que a fachada não dependa de nada que o bootstrap de sessão forneça. - Transações — consumido pelo polling do navegador; teste o ciclo completo de sincronização após migrar.
Onda 4 — Módulos médios consolidados (esforço: M a G)
| Módulo | Arquivos | Endpoints | Padrão | Esforço | Observação |
|---|---|---|---|---|---|
| Propostas | 3 | 14 | A | M | ProposalService, SimulatorService, ProposalDocumentService. Este último já usa Dompdf\Dompdf via use contando com autoload externo — a Fase 0 resolve o problema, e a migração formaliza. |
| Usuários | 5 | 9 | A + C | M | user_registration_helpers.php tem funções sem prefixo — renomear na migração. |
| Permissões | 1 | 9 | A | P | PermissionAdminService. Depende de core/ (Onda 2). |
| Financeiro | 4 | 7 | A | M | Renomear external-api/ → ExternalApi/. token_service.php autentica a API externa: teste com um token real após migrar. |
| White Label | 7 | 14 | A | G | Renomear white-label/ → WhiteLabel/. Sete arquivos em quatro subpastas (actions, rates, registration). |
| Comercial / Leads | 1 | 22 | A | G | 62 funções em um arquivo de ~2.900 linhas. Dividir em LeadService, LeadStageService, LeadVisitService, LeadRouteService, PlacesImportService, LeadMonitoringService. Carregado pelo menu lateral em toda página — exige teste de fumaça amplo. Migrar sozinho, em PR isolado. |
Onda 5 — Módulos com nomenclatura de risco (esforço: G)
Estes exigem, além da migração, renomear funções genéricas que hoje poluem o escopo global.
| Módulo | Arquivos | Endpoints | Problema específico |
|---|---|---|---|
| Planos | 11 | 11 (+7 de gestao-taxas) | Três padrões no mesmo módulo: backend_plan_* (padrão A) em plan_service.php, gt* (padrão B) em rates/, e validarDadosPlano() / prepararDadosAPI() / salvarTaxasCET() (padrão C) em registration/. prepararDadosAPI() é candidata a colisão. Destino: PlanService, PlanRegistrationService, RateManagementService, DefaultRateService, PlanCetDetailService, PlanDebitRateService, PlanRemoteService. |
| Terminais | 12 | 11 | O caso mais crítico do padrão C: terminal_batch_registration_service.php declara validarDadosEntrada(), validarUsuarioEPermissoes(), prepararDispositivosParaAPI(), fazerRequisicaoAPI(), salvarTerminalNoBanco(), processarRespostaESalvar() — todas sem prefixo e sem guarda, e fazerRequisicaoAPI() já colide com a declaração em backend/api/estabelecimentos/sync-bancos.php. Ao virarem métodos privados de TerminalBatchRegistrationService, a colisão desaparece por construção. |
Nestes dois módulos a fachada de compatibilidade deve ser criada apenas para as funções efetivamente chamadas de fora do próprio arquivo. As auxiliares internas viram métodos privados e somem do escopo global — que é justamente o ganho.
Onda 6 — Estabelecimentos e Credenciamento (esforço: G, vários PRs)
O maior e mais arriscado: 44 arquivos de serviço e 53 endpoints. Deve ser quebrado em quatro sub-etapas independentes:
| Sub-etapa | Arquivos | Conteúdo | Observação |
|---|---|---|---|
| 6.1 — Cadastro | 3 + 11 | establishment_service, processar_cadastro_service, processar_cadastro_mdr_service, registration/** | ValidationException já foi migrada na Onda 1. captura_fiserv.php e departamento_utils.php são utilitários do padrão C. |
| 6.2 — Credenciamento | 6 | solicitar_, buscar_, listar_pendentes_, verificar_status_ | Integração Entrepay; depende das classes da Onda 1. |
| 6.3 — Polling | 14 | polling/**, incluindo 4 classes e o daemon CLI | Maior risco operacional. polling_background_service.php roda em loop infinito via CLI; validar que o autoload funciona fora do contexto HTTP e que o daemon sobrevive a um restart. |
| 6.4 — Manutenção | 10 | maintenance/** | Scripts de diagnóstico com SQL direto. Recomendação: não migrar — remover. docs/mapa-dominio-FD Capital.md e docs/nonessential-files-audit.md já apontam esses arquivos como risco por estarem no runtime produtivo. Migrar código que deveria sair é desperdício. |
7. Checklist por módulo
Aplicar a cada módulo migrado, sem exceção:
- Criar o diretório em PascalCase sob
backend/src/Services/ - Criar uma classe por arquivo, com
declare(strict_types=1)e namespace correto - Injetar
mysqlipelo construtor — nenhum acesso a$GLOBALS - Marcar como
privatetodo método que era função auxiliar interna - Registrar em
legacy_functions.phpuma fachada para cada função consumida externamente - Buscar no repositório inteiro (incluindo
frontend/,dashboard/,components/) porrequiredo arquivo antigo e pelas funções migradas - Remover o arquivo antigo apenas depois que a busca acima retornar zero ocorrências fora da fachada
- Rodar
composer dump-autoload -o --strict-psre confirmar que não há aviso - Teste de fumaça: abrir todas as telas do módulo e exercitar cada endpoint
- Conferir que
backend/logs/<modulo>/continua recebendo eventos
8. Riscos e mitigação
| Risco | Impacto | Mitigação |
|---|---|---|
| Erro de caixa em nome de arquivo (Windows × Linux) | Fatal em produção, invisível localmente | --strict-psr obrigatório antes do deploy |
| Autoloader desatualizado no servidor | Classe não encontrada | Incluir vendor/composer/ no checklist de upload |
| Colisão de função durante a transição | Cannot redeclare | Migrar o padrão C sempre para métodos privados; nunca criar fachada para auxiliar interna |
| Regressão global via menu lateral | Todo o sistema | leads_service e tasks_service em PRs isolados, com teste de fumaça amplo |
| Daemon CLI de polling quebrado | Credenciamento para de reconciliar | Sub-etapa 6.3 isolada, testada com o daemon rodando |
| Ausência de testes automatizados | Regressão só aparece em produção | As checklists de docs/tests-debug/ são hoje a única rede; considerar introduzir PHPUnit já na Onda 3, quando o primeiro serviço com injeção existir |
ensure_schema() em runtime | Comportamento muda se virar construtor | Manter como método estático explícito, chamado nos mesmos pontos de hoje |
9. Critérios de conclusão
A migração está completa quando:
backend/src/não contém nenhum arquivo em snake_case;backend/src/helpers/legacy_functions.phpé o único lugar com funções globais além dos helpers de resposta e bootstrap;- nenhum arquivo em
backend/api/fazrequire_oncede serviço — apenas do bootstrap; composer dump-autoload --strict-psrroda sem avisos;backend/includes/(adaptadores legados) pode ser removido.
O passo seguinte natural, fora deste plano, é introduzir PHPUnit e escrever os primeiros testes sobre os serviços já com injeção de dependência — o que só se torna possível a partir da Onda 3.