Chave Primária ($primaryKey)
Por padrão, o ApiResourceController assume que a chave primária da sua tabela se chama id. Se a sua tabela utiliza um nome diferente (por exemplo, codigo, id_cliente, etc.), você deve sobrescrever a propriedade $primaryKey no seu controller.
Exemplo:
show, update e destroy, que dependem de um identificador único, funcionem corretamente.
Personalizando a Resposta JSON
Depois de criar seu endpoint, o próximo passo é definir o comportamento da classe controladora e quais dados sua API retorna. OApiResourceController oferece várias propriedades para selecionar, renomear e até mesmo formatar os campos da resposta.
Paginação ($perPage)
A resposta do endpoint index (listagem) é paginada automaticamente. Por padrão, são retornados 15 registros por página. Você pode alterar esse valor padrão sobrescrevendo a propriedade $perPage.
Observação: como já foi mencionado anteriormente, para exibir uma página específica, basta enviar o parâmetroExemplo:pagena URL. Ex.:http://localhost/api/exibir-registros?page=3.
Nota: Internamente, o métodoaddPagination()é chamado durante a listagem para ler o parâmetropageda URL e aplicar os limites (LIMIT,OFFSET) à consulta, utilizando o valor de$perPage.
Selecionando e Renomeando Campos
Você pode definir listas de campos diferentes para a listagem de múltiplos recursos (index) e para a visualização de um único recurso (show). Funciona como um filtro.
$indexFields: Campos que aparecerão na rota de listagem.$showFields: Campos que aparecerão na rota de detalhe de um item.
['novo_nome_do_campo' => '{propriedade_do_modelo}']. Você pode acessar dados de relacionamentos usando a sintaxe {relacionamento->propriedade}.
Ocultando Campos ($hiddenFields)
Se você não definir $indexFields ou $showFields, a API retornará todos os campos do modelo por padrão. Para ocultar campos específicos (como senhas ou chaves internas), use a propriedade $hiddenFields.
Habilitando Ordenação e Filtros
Você pode permitir que os usuários ordenem e filtrem os resultados das listagens através das propriedades$sortable e $filterable.
1. Defina os campos no seu Controller:
Para Ordenar (sort):GET /api/pedido-venda?sort=valor_total(ordem ascendente - padrão)GET /api/pedido-venda?sort=valor_total&direction=desc(ordem decrescente)
Para Filtrar (filter):GET /api/pedido-venda?filters[valor_total][gte]=50000
Observação: caso o valor inserido não tenha sido definido como filterable, ele será ignorado.
Operadores disponíveis:
| Operador | Exemplo | Resultado SQL |
|---|---|---|
| =, eq | {"id": {"=": 1}} | id = 1 |
| like | {"nome": {"like": "João"}} | nome LIKE ‘%João%‘ |
| like_start | {"nome": {"like_start": "A"}} | nome LIKE ‘A%‘ |
| like_end | {"nome": {"like_end": "Silva"}} | nome LIKE ‘%Silva’ |
| ilike | {"nome": {"ilike": "joão"}} | Case-insensitive like |
| in | {"id": {"in": [1,2,3]}} | id IN (1,2,3) |
| not in | {"status": {"not in": ["ativo"]}} | status NOT IN (‘ativo’) |
>, gt | {"valor": {">": 100}} | valor > 100 |
>=, gte | {"valor": {">=": 100}} | valor >= 100 |
<, lt | {"valor": {"<": 100}} | valor < 100 |
<=, lte | {"valor": {"<=": 100}} | valor <= 100 |
!=, not | {"status": {"!=": "inativo"}} | status != ‘inativo’ |
| between | {"data": {"between": ["2024-01-01", "2024-12-31"]}} | data BETWEEN … |
| is_null | {"campo": {"is_null": true}} | campo IS NULL |
| is_not_null | {"campo": {"is_not_null": true}} | campo IS NOT NULL |
Nota: Entendendo buildCriteria
Por baixo dos panos, o ApiResourceController usa um método central para gerenciar a listagem de dados: buildCriteria().
O papel dele é criar o objeto de consulta (TCriteria) e aplicar a ele as regras que vêm da URL, como filtros e ordenação. Ele, por sua vez, utiliza métodos auxiliares como o processFilters para traduzir os parâmetros da requisição em regras de banco de dados.
Para cenários avançados, saber disso é útil, pois você pode sobrescrever o método buildCriteria no seu controller para adicionar filtros fixos (utilizando TFilter) ou regras de negócio que devem ser aplicadas a todas as listagens, garantindo consistência e segurança.
Formatando Dados com Transformers
OsTransformers são funções que permitem modificar um valor antes que ele seja incluído na resposta JSON. Isso é extremamente útil para formatar datas, valores monetários, ou aplicar qualquer outra regra de negócio.
$transformers: Array de[campo => função]para formatar os dados do modelo principal.
dt_pedido for incluído na resposta, ele será formatado pela função correspondente.
Obs.: as transformações ficam salvas no array transformers, atributo da classe.
Nota a respeito de: prepareItemForResponse
Toda a mágica de selecionar, renomear, ocultar e formatar campos acontece em um único lugar: o método prepareItemForResponse(). Ele é chamado para cada item da coleção antes da resposta ser enviada.
O seu fluxo de trabalho é o seguinte:
- Verifica o contexto: Decide se usa as regras de
$indexFields(para listagens) ou$showFields(para detalhes). - Seleciona os campos: Se as listas acima estiverem definidas, ele as usa para montar a resposta. Caso contrário, pega todos os campos do objeto.
- Oculta campos: Remove os campos listados em
$hiddenFields. - Aplica os transformers: Por último, aplica as funções de formatação definidas em
$transformersaos campos correspondentes. Isso é feito através do método internoapplyTransformers(), que itera sobre as funções e executa cada uma delas.
Validando Campos Obrigatórios
Para garantir a integridade dos dados, você pode definir uma lista de campos que são obrigatórios ao criar ou atualizar um recurso. Isso é feito através da propriedade$requiredFields.
$requiredFields: Um array no formato['nome_do_campo' => 'Rótulo para Erro'].
POST ou PUT é enviado para a API, o método interno validateRequiredFields() é chamado automaticamente. Se algum campo da lista estiver ausente ou vazio nos dados da requisição, a API retornará um erro com a mensagem de erro correspondente ao rótulo que você definiu.
Para cenários avançados, o método aceita dois parâmetros opcionais:
$requiredFieldsToCheck: Um array que permite validar um conjunto de campos personalizado, ignorando temporariamente a propriedade$requiredFieldsda classe.$errorPrefix: Uma string para adicionar um prefixo às mensagens de erro, útil para dar contexto (ex.: “Item #1: Campo obrigatório”).
valor_total ou dt_pedido resultará em um erro, protegendo sua base de dados de registros incompletos. Veja:
Observação: caso, no modelo de dados, um campo seja definido como obrigatório (NOT NULL), ele será validado automaticamente.
Ganchos (Hooks) para Personalização Avançada
A classeApiResourceController oferece um sistema de “ganchos” (ou hooks) que permite executar seu próprio código em momentos específicos do ciclo de vida de criação e atualização de registros.
Esses ganchos são métodos que não existem por padrão. No entanto, se você os declarar no seu controller/service, o framework os detectará e executará automaticamente. Isso permite manipular dados, executar validações complexas, registrar logs ou disparar outros processos de negócio sem precisar sobrescrever os métodos store() ou update().
Vejamos a seguir os métodos ganchos disponíveis…
1. beforeStore
Executado imediatamente antes de o registro principal ser salvo no banco de dados, é o local ideal para manipular os dados do registro-mestre antes de serem persistidos. Você pode, por exemplo, definir valores padrão, calcular campos derivados, ou executar uma validação de negócio complexa.
No exemplo a seguir, é feita uma verificação: caso a data do pedido não tenha sido informada, ele é definida como a data atual.
beforeStore são:
TRecord $object: o objeto do modelo principal, já preenchido com os dados da requisição, mas ainda sem ID (pois não foi salvo).array $masterData: o array de dados brutos do mestre, recebido na requisição.
2. afterStore
Executado após o registro principal ser salvo com sucesso, é útil para executar ações que dependem do registro já existir no banco de dados, como registrar logs de auditoria, enviar notificações ou atualizar estatísticas. Neste ponto, o objeto já possui a chave primária (id) definida.
Um exemplo de uso seria enviar um e-mail de confirmação ao cliente:
Observação: ambosbeforeStoreeafterStorerecebem os mesmos parâmetros. Entretanto, por ocorrer após o salvamento do registro-mestre,afterStorerecebe o ID do registro salvo em$object.
3. beforeStoreDetail
Executado dentro do loop de salvamento dos detalhes, antes de cada item de detalhe ser salvo individualmente. Assim como beforeStore, permite manipular ou validar cada item de detalhe antes de sua persistência.
Os parâmetros recebidos pelo método beforeStoreDetail são:
TRecord $master: o objeto do registro mestre, já salvo.TRecord $detail: o objeto do modelo de detalhe, preenchido com os dados do item atual, mas ainda não salvo.array $detailData: o array de dados brutos do item de detalhe específico.
4. afterStoreDetail
Executado dentro do loop de salvamento dos detalhes, após cada item de detalhe ser salvo individualmente. Assim como afterStore, permite ações pós-salvamento de cada detalhe, como atualizar o estoque de um produto específico ou registrar um log para cada item.
Observação: ambosbeforeStoreDetaileafterStoreDetailrecebem os mesmos parâmetros. Entretanto, por ocorrer dentro do loop de salvamento dos detalhes,afterStoreDetailrecebe o ID do registro salvo em$detail.
5. afterStoreDetails
Executado após o loop de salvamento de todos os itens de detalhe ter sido concluído, é o local perfeito para realizar ações de totalização ou sumarização que dependem de todos os detalhes já estarem salvos.
O exemplo mais comum é recalcular e atualizar o valor_total do registro mestre com base na soma dos subtotais dos detalhes:
Observação: a requisição não informou o valor_total, apenas os valores dos produtos.
Os parâmetros recebidos pelo método afterStoreDetails são:
TRecord $master: o objeto do registro mestre.array $storedDetails: um array contendo todos os objetosTRecorddos detalhes que acabaram de ser salvos.
Com isso, você já está pronto para customizar seu controller/service para atender quaisquer que sejam as necessidades específicas do seu sistema.