Pular para o conteúdo principal

Plano de Migração para PSR-4 — Portal G8PAY

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_once com 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étricaValor
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.jsoninexistente
Locais que carregam vendor/autoload.php2 (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:26fazerRequisicaoAPI($url, $headers)
  • backend/src/services/terminais/registration/terminal_batch_registration_service.php:218fazerRequisicaoAPI(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.phpauth/csrf_service.php
  • dashboard/propostas/gerar_*_pdf.phppropostas/documents/

3. Decisões de arquitetura

3.1 Namespace raiz e mapeamento

{
"autoload": {
"psr-4": {
"G8PAY\\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

ElementoHojeDepois
Diretóriobackend/src/services/white-label/backend/src/Services/WhiteLabel/
Arquivowhite_label_service.phpWhiteLabelService.php
Símbolofunction backend_wl_list()WhiteLabelService::list()
NamespaceG8PAY\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 G8PAY\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 \G8PAY\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.

OndaFocoPor quê
1Classes já existentesSó ganham namespace e nome de arquivo — sem reescrita de lógica
2Helpers e núcleoBase compartilhada; precisa estar pronta antes dos serviços
3Módulos novos, arquivo únicoPadrão A já limpo, migração quase mecânica
4Módulos médios consolidadosVolume moderado, consumidores previsíveis
5Módulos com padrão B/CExige renomear funções genéricas — risco de colisão
6Estabelecimentos / Credenciamento44 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 atualArquivo atualDestino
ApiAdquirenteAdiq, ApiAdquirenteFactory, ApiEntrepayintegrations/movingpay/api_adiq.phpIntegrations/Movingpay/ApiAdquirenteAdiq.php (uma classe por arquivo)
ApiPlanosintegrations/movingpay/api_planos.phpIntegrations/Movingpay/ApiPlanos.php
ApiEstabelecimentosintegrations/movingpay/api_estabelecimentos.phpIntegrations/Movingpay/ApiEstabelecimentos.php
ApiBandeirasintegrations/movingpay/api_bandeiras.phpIntegrations/Movingpay/ApiBandeiras.php
UsuarioLoggerservices/usuarios/user_logger.phpServices/Usuarios/UsuarioLogger.php
ValidationExceptionservices/estabelecimentos/registration/validation_exception.phpServices/Estabelecimentos/Registration/ValidationException.php
PollingCredenciamentos, PollingInterno, PollingTimer, PollingAutomaticocredenciamento/polling/*adiar para a Onda 6, junto do módulo
Databasebackend/includes/db.phpmanter 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óduloArquivosFunçõesConsumidoresObservação
helpers/4 (bootstrap, response, tenant, legacy_compat)12todos os 210 endpointsNã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~15transversalPermissionService, 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/522login, bootstrap, frontendAuthService, 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óduloArquivosFunçõesEndpointsConsumidores extrasEsforço
Tarefas150162 views + menu lateralM
Assinaturas1199webhook públicoM
Conta183P
Dashboard155P
Compliance1236M
Transações1276polling JSM

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:

  • Tarefastasks_service.php concentra 50 funções que se dividem naturalmente em TaskService (CRUD), TaskStepService (passos e fluxo), TaskDocumentService (upload), TaskPermissionService e TaskAiService (OpenAI). Vale fazer essa divisão já na migração. O ensure_schema() deve virar um método estático isolado, não chamado no construtor.
  • Assinaturaswebhook.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óduloArquivosEndpointsPadrãoEsforçoObservação
Propostas314AMProposalService, 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ários59A + CMuser_registration_helpers.php tem funções sem prefixo — renomear na migração.
Permissões19APPermissionAdminService. Depende de core/ (Onda 2).
Financeiro47AMRenomear external-api/ExternalApi/. token_service.php autentica a API externa: teste com um token real após migrar.
White Label714AGRenomear white-label/WhiteLabel/. Sete arquivos em quatro subpastas (actions, rates, registration).
Comercial / Leads122AG62 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óduloArquivosEndpointsProblema específico
Planos1111 (+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.
Terminais1211O 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-etapaArquivosConteúdoObservação
6.1 — Cadastro3 + 11establishment_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 — Credenciamento6solicitar_, buscar_, listar_pendentes_, verificar_status_Integração Entrepay; depende das classes da Onda 1.
6.3 — Polling14polling/**, incluindo 4 classes e o daemon CLIMaior 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ção10maintenance/**Scripts de diagnóstico com SQL direto. Recomendação: não migrar — remover. docs/mapa-dominio-G8PAY.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 mysqli pelo construtor — nenhum acesso a $GLOBALS
  • Marcar como private todo método que era função auxiliar interna
  • Registrar em legacy_functions.php uma fachada para cada função consumida externamente
  • Buscar no repositório inteiro (incluindo frontend/, dashboard/, components/) por require do 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-psr e 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

RiscoImpactoMitigaçã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 servidorClasse não encontradaIncluir vendor/composer/ no checklist de upload
Colisão de função durante a transiçãoCannot redeclareMigrar o padrão C sempre para métodos privados; nunca criar fachada para auxiliar interna
Regressão global via menu lateralTodo o sistemaleads_service e tasks_service em PRs isolados, com teste de fumaça amplo
Daemon CLI de polling quebradoCredenciamento para de reconciliarSub-etapa 6.3 isolada, testada com o daemon rodando
Ausência de testes automatizadosRegressão só aparece em produçãoAs 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 runtimeComportamento muda se virar construtorManter 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:

  1. backend/src/ não contém nenhum arquivo em snake_case;
  2. backend/src/helpers/legacy_functions.php é o único lugar com funções globais além dos helpers de resposta e bootstrap;
  3. nenhum arquivo em backend/api/ faz require_once de serviço — apenas do bootstrap;
  4. composer dump-autoload --strict-psr roda sem avisos;
  5. 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.