> ## Documentation Index
> Fetch the complete documentation index at: https://docs-fw.madbuilder.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# 5. Configurando o API Controller

>  É importante conhecer algumas propriedades fundamentais do ApiResourceController que você pode ajustar para controlar o comportamento básico do seu endpoint. A primeira parte desse guia irá abordar propriedades fundamentais e outras relacionadas à visualização de dados — em outras palavras, requisições GET.

### 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:**

```php theme={null}
// Em: app/controller/MinhaController.php

class MinhaController extends ApiResourceController
{
    ...

    // Informando que a chave primária é 'codigo'
    protected $primaryKey = 'codigo'; 
}
```

Isso garante que operações como `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. O `ApiResourceController` 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âmetro `page` na URL. Ex.: `http://localhost/api/exibir-registros?page=3`.

**Exemplo:**

```php theme={null}
// Em: app/controller/MinhaController.php

protected $perPage = 50; // Agora, a listagem retornará 50 itens por página
```

> **Nota:** Internamente, o método `addPagination()` é chamado durante a listagem para ler o parâmetro `page` da 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.

O formato é `['novo_nome_do_campo' => '{propriedade_do_modelo}']`. Você pode acessar dados de relacionamentos usando a sintaxe `{relacionamento->propriedade}`.

```php theme={null}
// Em: app/controller/ApiPedidovendaController.php

class ApiPedidovendaController extends ApiResourceController
{
    ...

    // Campos para a rota de listagem (GET /api/pedido-venda)
    protected $indexFields = [
        'cliente_nome' => 'Cliente: {cliente->nome}',
        'valor_total' => 'Valor Total: {valor_total}'
    ];

    // Campos para a rota de detalhe (GET /api/pedido-venda/{id})
    protected $showFields = [
        'cliente_nome' => 'Cliente: {cliente->nome}',
        'valor_total' => 'Valor Total: {valor_total}'
    ];
}
```

Obs.: mesmo que o filtro não o inclua, o campo de chave primária sempre será retornado.

### 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`.

```php theme={null}
// Oculta os campos de timestamp da resposta

protected $hiddenFields = ['created_at', 'updated_at', 'deleted_at'];
```

### 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:**

```php theme={null}
class ApiPedidovendaController extends ApiResourceController
{
    ...

    protected $sortable = ['valor_total'];
    protected $filterable = ['valor_total', 'dt_pedido'];
}
```

**2. Use os parâmetros na URL:**

* **`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`

**3. Use os parâmetros no Body:**

Outra forma de enviar os parâmetros é no corpo da requisição, sendo possível enviar ambos na mesma requisição.

No exemplo abaixo, é aplicado um filtro e uma ordenação, para trazer apenas compras menores (entre R$ 100,00 e R$ 15.000,00) que foram realizadas no mês de janeiro de 2022, ordenados pelo valor total em ordem decrescente.

![Requisição GET filtering e sorting](https://monosnap.ai/image/Jb6PtpMwGNszsHVdWeWQzYQcl0LHij)

E o resultado:

![Resposta GET filtering e sorting](https://monosnap.ai/image/BJFdfjHJmjiKRoLmGOyoOAVrfGXxSl)

> **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

Os `Transformers` 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.

Você define os transformers no construtor do seu controller:

```php theme={null}
// Em: app/controller/ApiPedidovendaController.php

public function __construct()
{
    parent::__construct();

    // Adiciona um transformer
    $this->addTransformer('dt_pedido', function ($value, $object) {
        // O retorno dessa função é o valor que será incluído na resposta JSON
        if(!empty(trim($value)))
        {
            try
            {
                // Transforma a data para o formato brasileiro
                $date = new DateTime($value);
                return $date->format('d/m/Y');
            }
            catch (Exception $e)
            {
                return $value;
            }
        }
    });
}
```

Agora, sempre que o campo `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:

1. **Verifica o contexto:** Decide se usa as regras de `$indexFields` (para listagens) ou `$showFields` (para detalhes).
2. **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.
3. **Oculta campos:** Remove os campos listados em `$hiddenFields`.
4. **Aplica os transformers:** Por último, aplica as funções de formatação definidas em `$transformers` aos campos correspondentes. Isso é feito através do método interno `applyTransformers()`, que itera sobre as funções e executa cada uma delas.

Saber disso é útil para entender a ordem em que as personalizações são aplicadas e para depurar o resultado da sua API.

## 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']`.

Quando um `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 `$requiredFields` da classe.
* **`$errorPrefix`**: Uma string para adicionar um prefixo às mensagens de erro, útil para dar contexto (ex.: "Item #1: Campo obrigatório").

Esses parâmetros são usados internamente pelo framework, por exemplo, para validar os itens em um relacionamento mestre-detalhe.

**Exemplo:**

```php theme={null}
// Em: app/controller/ApiPedidovendaController.php

class ApiPedidovendaController extends ApiResourceController
{
    ...

    protected $requiredFields = [
        'valor_total' => 'Valor Total',
        'dt_pedido' => 'Data do Pedido'
    ];
}
```

Com essa configuração, uma tentativa de criar um pedido sem `valor_total` ou `dt_pedido` resultará em um erro, protegendo sua base de dados de registros incompletos. Veja:

![Requisição POST Postman API com campos obrigatórios ausentes](https://monosnap.ai/image/u44teqL0vNUXzgDMc5Z0DsaCr17Ux6)

> **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 classe `ApiResourceController` 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.

```php theme={null}
// Em: app/controller/ApiPedidovendaController.php

class ApiPedidovendaController extends ApiResourceController
{
    ...

    public function beforeStore($object, $masterData)
    {
        if(!isset($masterData['dt_pedido'])) {
            $object->dt_pedido = date('Y-m-d');
        }
    }
}
```

Abaixo, a resposta de uma requisição que **não informou** data:

![Resposta POST sem data](https://monosnap.ai/image/P9MozV4IF5eh9bEmYY4lSpLUv8RaRt)

Os parâmetros recebidos pelo método `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:

```php theme={null}
// Em: app/controller/ApiPedidovendaController.php

class ApiPedidovendaController extends ApiResourceController
{
    ...

    public function afterStore($object, $masterData)
    {
        // Disparar e-mail de confirmação (classe EmailService fictícia)
        try {
            $message = EmailService::enviarNotificacaoPedido($object);
        } catch (Exception $e) {
            error_log("Erro ao enviar notificação: " . $e->getMessage());
        }
    }
}
```

> **Observação:** ambos `beforeStore` e `afterStore` recebem os mesmos parâmetros. Entretanto, por ocorrer após o salvamento do registro-mestre, `afterStore` recebe 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:** ambos `beforeStoreDetail` e `afterStoreDetail` recebem os mesmos parâmetros. Entretanto, por ocorrer dentro do loop de salvamento dos detalhes, `afterStoreDetail` recebe 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:

```php theme={null}
// Em: app/controller/ApiPedidovendaController.php

class ApiPedidovendaController extends ApiResourceController
{
    ...

    public function afterStoreDetails($master, $storedDetails)
    {
        $total = 0;
        foreach ($storedDetails as $item) {
            // Supondo que o detalhe tenha 'quantidade' e 'valor'
            $subtotal = $item->quantidade * $item->valor;
            $total += $subtotal;
        }

        // Atualiza o valor_total no objeto mestre e salva novamente
        $master->valor_total = $total;
        $master->store();
    }
}
```

Com este gancho, garantimos que o valor total do pedido esteja sempre sincronizado com a soma de seus itens, de forma automática e encapsulada na lógica do controller. Veja:

![Requisição POST com valor automático](https://monosnap.ai/image/ZJpJaEB9nPMk4mJI63Jf1cDRTxw931)

> **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 objetos `TRecord` dos 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.
