Entrevista PHP: corrija uma API com tipos, exceções e validação de entrada

Prepare a entrevista PHP corrigindo uma API de cadastro: valide JSON e tipos, trate exceções e verifique respostas e chamadas de escrita com casos de teste.

Author: PracHub

Published: 10/11/2026

Entrevista PHP: corrija uma API com tipos, exceções e validação de entrada

October 11, 2026

Quick Overview

Resolva uma atualização fictícia de cliente em PHP. Diferencie parsing e validação, rejeite casts indevidos, construa um DTO tipado e mapeie falhas esperadas e internas. Compare 26 verificações nativas com as garantias ainda necessárias em HTTP e banco.

Software EngineerFree

Em uma entrevista PHP, corrigir uma API exige explicar a mudança de comportamento que cada patch produz. Adicionar strict_types=1 não decide se uma string numérica é um nome aceitável, se "false" deve ativar uma preferência ou se uma exceção interna pode ser mostrada ao cliente. Defina essas decisões em um contrato que possa ser testado.

Vamos revisar uma atualização fictícia de cadastro com três campos e um repositório substituído por um spy de teste. O objetivo é impedir que entrada inválida alcance a escrita e distinguir erros esperados de bugs internos. Para começar o treino, use Code Review of a Multi-File HTTP API: Wrong Method, Payload and Validation Bugs e explique a consequência de cada validação antes de alterar o código.

Limite da evidência: as referências oficiais sustentam o comportamento de PHP. O contrato da API, os códigos de resposta escolhidos e os testes são exercícios originais, não relatos de candidatos nem perguntas confirmadas de uma empresa. Executamos 26 verificações em PHP 8.5.11 CLI. Não implementamos um servidor HTTP, sessão autenticada ou transação em banco; o teste observa funções e um repositório falso.

Revisão de uma atualização de cliente com campos JSON e decisões de validação antes da escrita

Comece pelo contrato de atualização

No exercício, a operação substitui três campos do cadastro: display_name, email e marketing_opt_in. Todos são obrigatórios. Não é uma atualização parcial: a ausência de um campo não significa “manter o valor anterior”. O chamador autenticado só pode atualizar seu próprio cadastro, e seu identificador vem de uma fronteira de autenticação confiável, nunca do JSON enviado.

O nome deve ser string, ficar não vazio depois de trim e conter no máximo 80 pontos de código UTF-8. O email deve ser string e passar pelo filtro escolhido no exercício. A preferência deve ser um booleano JSON real. Rejeitamos campos desconhecidos para evitar que uma propriedade como is_admin chegue acidentalmente a uma atribuição genérica.

Essas são escolhas explícitas deste caso. Outra API poderia aceitar atualização parcial, ignorar propriedades extras ou representar preferências de outra forma. Esclareça primeiro qual comportamento precisa ser preservado. Alterar silenciosamente a compatibilidade da API para simplificar a implementação não é uma correção neutra.

O contrato do repositório também importa: aplicar todos os campos de uma vez ou lançar a falha antes de qualquer alteração. Essa atomicidade é uma precondição do exemplo, não uma propriedade demonstrada pelo spy. Uma implementação real precisaria satisfazê-la com a persistência adequada e testes próprios.

Por que strict_types não substitui validação

O manual oficial de declarações de tipos explica a verificação de tipos e o comportamento de chamadas escalares estritas. Isso não transforma o resultado de json_decode em um objeto de domínio validado. O parser pode produzir objetos, arrays, escalares ou null, conforme o documento recebido.

Uma implementação defeituosa poderia acessar campos diretamente e converter tudo antes de chamar o repositório. O cast (bool) "false" produz true: a string não vazia não tem a semântica do booleano JSON false. A execução nativa reproduziu a conversão de "false" para true. Trocar a comparação por === em outro ponto não corrige um valor já convertido incorretamente.

Verifique o tipo recebido antes de construir o DTO e permita a escrita somente após validar todos os campos. Não convertemos nomes numéricos em strings para fazê-los caber no construtor, nem transformamos números zero e um em preferências. A decisão é rejeitar formatos fora do contrato. Isso preserva a diferença entre normalizar espaços de uma string válida e fabricar um tipo que não chegou na entrada.

Na entrevista, uma explicação curta seria: “Vou manter os tipos no DTO como proteção interna e validar a forma externa antes dele. Assim, entrada inválida vira um erro previsto de contrato, enquanto um TypeError inesperado continua sendo tratado como falha interna.” A declaração de tipos e o validador cumprem funções complementares.

Separe JSON inválido de documento com forma inválida

A documentação de json_decode descreve JSON_THROW_ON_ERROR. Usamos essa opção para que uma falha de parsing não seja confundida com um documento JSON válido cujo valor é null. Depois exigimos um objeto stdClass, preservando a diferença entre {} e [].

Decodificar diretamente em array associativo e tratar qualquer array como um objeto pode esconder essa distinção. Aqui [], null e um número são JSON válido, mas não possuem a forma requerida. Um {} vazio tem a forma de objeto, porém ainda falha pela ausência dos campos obrigatórios. Os testes verificam cada caminho separadamente.

Este é o parser validado, usando as classes de exceção e o DTO descritos no módulo. CustomerUpdate possui propriedades readonly name: string, email: string e marketingOptIn: bool. O módulo requer a extensão mbstring para contar o nome em UTF-8.

function parseCustomerUpdate(string $raw): CustomerUpdate {
    try {
        $body = json_decode($raw, false, 512, JSON_THROW_ON_ERROR);
    } catch (JsonException $e) {
        throw new BadJson('bad_json', 0, $e);
    }
    if (!$body instanceof stdClass) {
        throw new InvalidInput('object_required');
    }
    $fields = get_object_vars($body);
    $allowed = ['display_name', 'email', 'marketing_opt_in'];
    if (array_diff(array_keys($fields), $allowed) !== []) {
        throw new InvalidInput('unknown_field');
    }
    foreach ($allowed as $field) {
        if (!array_key_exists($field, $fields)) {
            throw new InvalidInput('missing_field');
        }
    }
    if (!is_string($fields['display_name']) || !is_string($fields['email']) || !is_bool($fields['marketing_opt_in'])) {
        throw new InvalidInput('wrong_type');
    }
    $name = trim($fields['display_name']);
    $email = trim($fields['email']);
    if ($name === '' || mb_strlen($name, 'UTF-8') > 80) {
        throw new InvalidInput('invalid_name');
    }
    if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
        throw new InvalidInput('invalid_email');
    }
    return new CustomerUpdate($name, $email, $fields['marketing_opt_in']);
}

array_key_exists permite distinguir um campo ausente de um campo presente com valor nulo. Depois, o teste de tipo rejeita esse nulo quando o contrato exige string ou booleano. Nenhum acesso a campo obrigatório ocorre antes dessa verificação de presença, evitando que um aviso por chave inexistente seja o mecanismo normal de validação.

Um caso válido não prova que os limites funcionam

O documento abaixo deve produzir uma atualização com nome Ana e preferência exatamente false. Verifique o valor armazenado no DTO, não apenas a resposta de sucesso. Um bug de cast poderia devolver sucesso e alterar a preferência para true.

{"display_name":" Ana ","email":"ana@example.com","marketing_opt_in":false}

A matriz mostra resultados escolhidos para este exercício. “Nenhuma escrita” significa que o spy não recebeu chamada de aplicação; não significa que uma transação real foi observada ou revertida.

Entrada ou condiçãoResposta do contratoObservação de escrita
JSON truncado, como {400, bad_jsonNenhuma chamada.
Raiz [] ou null422, object_requiredNenhuma chamada.
marketing_opt_in igual à string "false"422, wrong_typeNenhuma chamada.
Campo desconhecido is_admin422, unknown_fieldNenhuma chamada.
Nome com 81 pontos de código422, invalid_nameNenhuma chamada.
Três campos válidos, booleano real200, atualização reconhecidaUma chamada com DTO validado.

O limite do nome usa mb_strlen, não o número de bytes retornado por strlen. Testamos 80 e 81 repetições de á. Essa contagem não equivale a contar caracteres percebidos pelo usuário: combinações de marcas e emojis podem ter vários pontos de código. Se a regra do produto for visual, será necessário outro critério e novos testes.

O uso de filter_var especifica FILTER_VALIDATE_EMAIL; o filtro padrão não oferece essa validação. Ainda assim, passar no filtro não comprova entrega, existência da caixa postal ou controle do endereço. Esses requisitos exigiriam processos diferentes. Não prometa que uma checagem sintática resolve identidade ou comunicação.

Também há limites deliberados: o parser não detecta chaves JSON repetidas antes da decodificação. trim não implementa uma política completa de espaços Unicode. O exercício deve reconhecer essas fronteiras, em vez de chamar o patch de validador universal de qualquer documento possível.

Fluxo de parsing, validação, DTO e escrita com desvios para erros de contrato

Mapeie falhas esperadas sem esconder bugs

O handler do módulo recebe um identificador de ator já autenticado, o alvo validado pela rota, o corpo e duas funções: escrita e registro interno. Se o ator estiver ausente, devolve 401; se não for o próprio alvo, devolve 403. Essas verificações vêm antes do parser. A autenticação real e a validação da rota permanecem fora do módulo testado.

Depois, o handler converte BadJson em 400 e InvalidInput em 422. O repositório falso pode sinalizar cliente inexistente, conflito de email ou indisponibilidade; o contrato os mapeia para 404, 409 e 503. São decisões desta API fictícia, que precisam ser consistentes com seus clientes e sua documentação.

A referência oficial de Throwable inclui tanto Error quanto Exception. Na última fronteira do handler, uma falha inesperada gera 500 com mensagem genérica. O exemplo registra apenas a classe da falha no spy de log. Em produção, observabilidade precisaria de contexto apropriado, correlação e controles para não expor dados sensíveis.

Não devolva a mensagem bruta da exceção do banco. Os testes usam mensagens fictícias internas e confirmam que elas não aparecem na resposta. Também não transforme qualquer Throwable em 422: um TypeError provocado por um bug do servidor não significa necessariamente que o cliente enviou um campo inválido.

Devolver uma resposta genérica não demonstra recuperação nem rollback. Ele delimita a resposta externa e preserva a necessidade de investigar a falha. Se o repositório já tivesse alterado metade do cadastro antes de lançar, retornar 500 não desfaria essa mudança. A correção dessa situação pertence à implementação da escrita e ao contrato transacional declarado.

O que os 26 testes verificaram

A execução nativa cobriu parsing truncado, raízes de forma errada, objeto vazio, campos ausentes, tipos incorretos, substitutos de booleano, nome vazio ou longo, campo extra e email inválido. Também verificou os dois booleanos reais e o limite válido de 80 pontos de código. Nos casos rejeitados antes da escrita, o spy permaneceu vazio.

Outros testes confirmaram as verificações de ator antes do parsing e as quatro saídas de exceção do repositório. A falha inesperada TypeError produziu 500, registro interno da classe e nenhuma mensagem interna na resposta. Por fim, reproduzimos a conversão antiga da string "false" para true, mostrando por que aceitar o cast muda o significado do pedido.

Esses testes observam o contrato de funções em PHP CLI. O spy pode provar quantas vezes foi chamado e qual DTO recebeu; ele não prova uma constraint única de email em banco. Duas requisições concorrentes podem exigir outra estratégia de consistência. Validar primeiro não elimina a necessidade de constraints ou proteção contra atualizações perdidas.

Ao passar para um servidor real, acrescente testes do método e da rota, Content-Type, limite do corpo, serialização da resposta e autenticação integrada. A depender do mecanismo de sessão, outros controles também serão necessários. Não marque esses itens como testados somente porque a função pura devolveu um array com status.

Verifique também a combinação de defeitos. Um pedido sem ator autenticado e com JSON truncado recebe a resposta de autenticação deste contrato, não o erro de parsing. Um campo de tipo errado junto com uma propriedade desconhecida segue a ordem definida pelo validador. Essa precedência deve ser estável o suficiente para os clientes, sem exigir que a API revele todas as falhas internas de uma vez.

Para migrar uma API que antes aceitava strings ou campos extras, investigue quais clientes dependem desse comportamento. Uma validação mais estrita pode ser desejável e ainda assim exigir comunicação ou versionamento. Na entrevista, proponha o contrato corrigido e destaque essa decisão de compatibilidade. O teste demonstra o novo comportamento; não prova que nenhum cliente existente será afetado.

Como defender a correção em voz alta

Comece pelo comportamento errado e por um caso mínimo: “A string false estava sendo convertida para verdadeiro. O contrato exige booleano JSON, então vou rejeitar a string antes de construir o DTO.” Depois mostre o teste que verifica tanto a resposta quanto a ausência de chamada de escrita.

Explique a ordem do patch: confirmar autoridade, decodificar, validar forma, verificar campos, construir o DTO e aplicar a atualização pelo contrato do repositório. Se o entrevistador pedir suporte a atualização parcial, reconheça que isso altera o significado de ausência e exige novos casos, em vez de remover a validação de obrigatoriedade sem discussão.

Use a seleção pública abaixo para variar o exercício. São recomendações editoriais de prática; os enunciados não representam uma lista garantida de entrevista PHP. Preserve os contratos dos problemas ao trocar linguagem ou acrescentar uma camada HTTP.

Pergunta completaFoco da resposta
Code Review of a Multi-File HTTP API: Wrong Method, Payload and Validation BugsRelacione cada patch a uma falha reproduzível.
Implement a robust REST API methodDefina entrada, resposta e efeitos antes de escrever.
Design a REST API Abstraction LayerSepare transporte, validação e aplicação de domínio.
Design a service aggregator with robust error handlingDiferencie falha esperada, indisponibilidade e bug interno.
Design trip booking REST API and schemaAmplie a discussão para constraints e efeitos concorrentes.

Continue com Implement a robust REST API method. Apresente um caso válido, dois casos de rejeição e uma falha interna. Termine com a observação de escrita do teste e uma garantia de persistência ainda não demonstrada.

Sources and Further Reading


Comments (0)