O isolamento de dados multiempresa em PHP não se resolve adicionando uma condição WHERE organization_id = ? na tela principal. Um vazamento pode se originar em uma API, uma exportação, um cache, um anexo, um consumidor de fila ou um processo agendado. Também pode ocorrer quando um administrador legítimo muda de organização e o sistema mantém um contexto anterior.
O objetivo arquitetural deve ser claro: nenhuma operação que leia, modifique, processe ou entregue informações de clientes deve poder atuar sem um escopo de organização verificável. Esse escopo deve se propagar de forma explícita e ser validado em cada limite relevante da aplicação.
O que uma aplicação SaaS deve isolar

O modelo transacional é apenas uma parte da superfície de risco. Inventarie os recursos que têm proprietário organizacional e defina, para cada um, como sua associação é identificada, armazenada, recuperada, excluída e auditada.
- Dados transacionais: usuários, projetos, pedidos, faturas, configurações e relacionamentos entre entidades.
- Arquivos e anexos: objetos em armazenamento externo, miniaturas, documentos gerados e seus metadados.
- Cache: resultados de consultas, permissões calculadas, sessões, respostas de API e dados de configuração.
- Índices de busca: documentos indexados, sugestões e filtros previamente agregados.
- Processamento assíncrono: jobs de fila, novas tentativas, lotes de importação e notificações.
- Operação e observabilidade: logs, traces, métricas, exportações de suporte e ferramentas internas.
Nem todos os recursos exigem a mesma estratégia. Um catálogo público pode ser compartilhado, enquanto uma fatura, seu PDF e os logs de download devem manter o vínculo inequívoco com a organização. A decisão deve ficar documentada para evitar que uma nova entidade nasça sem regras de propriedade.
Escolher o modelo de isolamento de dados
Há três modelos comuns. Não existe um universalmente superior: a escolha depende de requisitos regulatórios, volume, operação, modelo comercial e capacidade da equipe de manter a plataforma.
Banco de dados compartilhado com chave de organização
Todas as organizações compartilham tabelas, e cada registro sujeito a isolamento contém uma chave como organization_id. É a abordagem mais direta para evoluir o produto e executar consultas agregadas globais. Em contrapartida, exige disciplina extrema: cada consulta, relacionamento, índice, cache e tarefa deve respeitar o escopo.
No mínimo, use chaves estrangeiras quando aplicável, índices compostos que comecem por organization_id e restrições de unicidade também compostas. Por exemplo, um código de pedido único dentro de uma organização não deve ser declarado globalmente único se essa não for a regra de negócio.
Esquema separado por organização
Cada cliente opera em um esquema lógico distinto dentro do mesmo servidor de banco de dados. Isso reduz o risco de omitir um filtro em tabelas separadas, mas complica migrações, conexões, ferramentas de análise e consultas globais. É apropriado apenas se o mecanismo, o framework e a operação diária suportarem esse padrão de forma consistente.
Banco de dados por organização
Separar bancos de dados oferece uma fronteira mais forte e pode facilitar restaurações ou movimentações de clientes individuais. Também aumenta o inventário de conexões, migrações, backups, monitoramento e deploys de alterações de estrutura. Convém avaliar especialmente como serão executados relatórios globais, alterações em massa e recuperação de erros.
A separação física reduz certas classes de falha, mas não substitui autorização, controle de arquivos, gestão de segredos nem validação do contexto em serviços compartilhados.
Arquitetura de referência: contexto explícito nos limites
O contexto de organização não deve ser inferido de parâmetros arbitrários enviados pelo navegador. Deve ser resolvido a partir de uma fonte autenticada e autorizada: um subdomínio validado, um token com audiência adequada, uma associação de usuário ou uma credencial de integração associada a uma única organização.
Em uma aplicação PHP, uma camada de entrada pode construir um objeto imutável de contexto com o identificador da organização, o ator, suas permissões e um identificador de solicitação. Os controladores, comandos de console e consumidores de fila recebem esse contexto ou o reconstroem usando dados verificados. Evite variáveis globais mutáveis que possam persistir indevidamente em processos de longa duração.
final class OrganizationContext {
public function __construct(
public readonly string $organizationId,
public readonly string $actorId
) {}
}Os repositórios devem exigir o contexto para consultar ou modificar entidades isoladas. É preferível uma interface que dificulte sua omissão a uma convenção implícita que dependa da memória de cada desenvolvedor. Quando possível, aplique também políticas de acesso na camada de domínio: pertencer a uma organização não autoriza automaticamente qualquer ação dentro dela.
Evitar filtros esquecidos em consultas e relacionamentos
Uma consulta isolada deve filtrar por organização antes de buscar por identificadores de negócio. Recuperar primeiro um registro por id e verificar depois seu proprietário pode produzir exposições se o resultado for serializado, registrado ou usado antes de rejeitá-lo.
- Centralize as consultas em repositórios ou serviços de leitura com métodos que recebam o contexto.
- Proíba acessos diretos a modelos isolados a partir de controladores, templates e consumidores de eventos.
- Revise relacionamentos: um relacionamento carregado de forma lazy pode contornar o filtro aplicado à entidade principal.
- Use restrições de banco de dados para impedir relacionamentos entre linhas de organizações distintas quando o modelo permitir.
- Defina convenções para migrações, seeds de teste e consultas analíticas.
Em mecanismos que oferecem políticas de segurança em nível de linha, elas podem fornecer uma defesa adicional. No entanto, sua adoção deve incluir testes de conexão, gestão de papéis e revisão dos processos administrativos. Não convém assumir que uma política de banco de dados protege automaticamente arquivos, cache ou índices externos.
Riscos fora do fluxo web principal
Identificadores opacos reduzem a enumeração, mas não autorizam acesso. Um UUID ou um identificador aleatório ainda deve ser resolvido dentro da organização ativa. Da mesma forma, uma URL de download assinada precisa de um objeto pertencente ao escopo correto, uma expiração adequada e regras de revogação quando as permissões mudam.
As chaves de cache devem incluir o identificador da organização e, quando o conteúdo depender de permissões, uma dimensão adicional de papel ou versão de autorização. Uma chave como dashboard:summary é insegura em um ambiente multiempresa; uma chave com escopo explícito também permite invalidações mais precisas.
As exportações são especialmente sensíveis porque costumam ser executadas fora da solicitação original. Armazene quem a solicitou, para qual organização, quais filtros foram aprovados e onde o resultado será entregue. Não envie anexos ou links a destinatários calculados a partir de dados não validados.
Propagar o contexto em APIs, webhooks e filas
Uma API deve derivar a organização da credencial ou verificar se o recurso solicitado pertence à organização associada a essa credencial. Permitir um cabeçalho X-Organization-Id pode ser válido para operadores com delegação explícita, mas exige autorização específica, auditoria e uma interface que torne visível a mudança de escopo.
Os webhooks recebidos não devem confiar em um identificador de organização incluído no corpo sem verificar assinatura, emissor e associação prévia da integração. Para webhooks enviados, gere eventos a partir de dados já delimitados e evite reutilizar payloads de uma fila compartilhada sem validar o destinatário.
Cada job assíncrono deve transportar um identificador de organização junto com o identificador do recurso e reconstruir o contexto antes de consultar. O consumidor deve verificar ambos os valores, mesmo se o job tiver sido criado internamente. As novas tentativas, jobs adiados e tarefas agendadas precisam da mesma regra: não existe um contexto de solicitação implícito disponível de forma segura.
Testes e sinais de diagnóstico verificáveis
O teste mais importante não é que uma organização veja seus próprios dados, mas que não possa ler nem modificar os de outra. Crie duas organizações com dados deliberadamente semelhantes e execute testes de integração em cada ponto de entrada: interface web, API, comandos, exportações, downloads e consumidores de fila.
- Solicite um recurso da organização B usando uma sessão ou credencial da organização A e espere uma resposta que não revele informações.
- Tente atualizar, excluir, baixar e exportar recursos entre organizações, não apenas consultá-los.
- Verifique se as chaves de cache de A e B geram resultados independentes.
- Execute um job de fila com um recurso de outra organização e verifique se ele falha de forma controlada.
- Teste restaurações, importações e tarefas noturnas com dados de mais de uma organização.
- Registre ações sensíveis com ator, organização, recurso e resultado, sem introduzir dados pessoais desnecessários nos logs.
Testes baseados em propriedades podem complementar os casos manuais: para qualquer recurso criado sob uma organização, nenhum ator sem associação válida deveria poder observá-lo ou alterá-lo por meio de uma rota exposta. Essa propriedade deve ser aplicada a mudanças futuras em endpoints e repositórios.
Plano de adoção para uma aplicação existente
Se os dados já estiverem misturados, não comece reescrevendo toda a aplicação. Primeiro, inventarie entidades, fluxos, integrações e acessos administrativos. Depois, defina a propriedade de cada registro e resolva os casos ambíguos com regras de negócio revisáveis.
- Adicione a entidade de organização e a chave de associação às tabelas-alvo.
- Preencha essa chave por meio de uma migração controlada e mantenha evidências dos casos sem atribuição confiável.
- Introduza repositórios delimitados e testes de acesso cruzado nas rotas mais sensíveis.
- Inclua o escopo em cache, arquivos, buscas e novos jobs.
- Migre progressivamente os fluxos antigos e bloqueie novas consultas sem contexto na revisão de código.
- Ative controles mais rigorosos quando as métricas e os testes demonstrarem cobertura suficiente.
Decisões que convém documentar antes de crescer

Antes de incorporar a próxima organização, documente o modelo escolhido, a fonte de verdade do contexto, as exceções de acesso administrativo, a estratégia de identificadores, os limites de cache, a propriedade de arquivos, a recuperação de dados, a retenção de logs e o procedimento diante de uma suspeita de acesso cruzado.
Determine também quem pode agir em nome de outra organização, como essa delegação é aprovada e como é revogada. O isolamento de dados multiempresa em PHP se mantém com decisões explícitas, restrições técnicas repetíveis e testes que transformam uma promessa de arquitetura em um comportamento verificável.



