API básica com PHP e MySQL via PDO para uma Todo List
Published: 2023-06-25 14:10:02

Neste exemplo vamos construir uma API básica em PHP usando o banco de dados MySQL e a extensão PDO para uma lista de tarefas simples.

Antes de começar, algumas premissas:
- há um servidor de banco de dados escutando em um host e porta conhecidos (se estiver no seu computador, deverá estar em `localhost:3306`);
- nesse banco de dados, há um esquema criado para a sua aplicação;
- o interpretador do PHP está instalado;
- a extensão pdo_mysql está habilitada no seu PHP;
- você tem noções de PHP e de APIs.

----

## Configurando o banco de dados

Primeiro, vamos criar uma tabela chamada "tasks" no banco de dados para armazenar as tarefas. Execute a seguinte query SQL para criar a tabela:

```sql
CREATE TABLE tasks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    completed BOOLEAN NOT NULL DEFAULT 0
);
```

## Configurando a conexão com o banco de dados

Como potencialmente precisaremos lidar com o banco de dados em mais de um ponto no sistema, uma boa prática é deixar os dados relacionados a esta conexão em um arquivo à parte.

Por isso, vamos criar um arquivo chamado "database.php" para armazenar as informações de conexão com o banco de dados e criar uma variável `$conn` que usaremos para acessar a conexão criada. Insira o seguinte código no arquivo:

```php
<?php

$host = 'localhost';
$db = 'nome_do_banco_de_dados';
$port = 3306;
$user = 'nome_do_usuario';
$pass = 'senha_do_usuario';

try {
    $conn = new PDO("mysql:host=$host;port=$port;dbname=$db;charset=utf8", $user, $pass);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
    echo 'Erro na conexão com o banco de dados: ' . $e->getMessage();
    exit;
}
```

Lembre-se de substituir "nome_do_banco_de_dados", "nome_do_usuario" e "senha_do_usuario" pelos valores corretos, assim como o host e a porta se for necessário.

Para testar se a conexão está funcionando, entre no diretório e execute o comando `php database.php`. Se nenhum resultado for exibido, então está tudo certo.

----

## Criando a API (método 1)

Uma forma simples de pensar na API é usando um arquivo para cada endpoint.

### get_tasks
Crie um arquivo chamado `get_tasks.php` para o endpoint que busca todas as tarefas:

```php
<?php

require 'database.php';

try {
    $stmt = $conn->query('SELECT * FROM tasks');
    $tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
    header('Content-Type: application/json');
    echo json_encode($tasks);
} catch(PDOException $e) {
    echo json_encode(['error' => $e->getMessage()]);
}
```

### add_task 
Crie um arquivo chamado `add_task.php` para o endpoint que adiciona uma nova tarefa:

```php
<?php

require 'database.php';

if (isset($_POST['title'])) {
    $title = $_POST['title'];

    try {
        $stmt = $conn->prepare('INSERT INTO tasks (title) VALUES (:title)');
        $stmt->bindParam(':title', $title);
        $stmt->execute();
        $taskId = $conn->lastInsertId();
        echo json_encode(['id' => $taskId, 'title' => $title, 'completed' => false]);
    } catch(PDOException $e) {
        echo json_encode(['error' => $e->getMessage()]);
    }
} else {
    echo json_encode(['error' => 'O título da tarefa é obrigatório']);
}
```

### complete_task
Crie um arquivo chamado `complete_task.php` para o endpoint que marca uma tarefa como concluída:

```php
<?php

require 'database.php';

if (isset($_POST['id'])) {
    $taskId = $_POST['id'];

    try {
        $stmt = $conn->prepare('UPDATE tasks SET completed = 1 WHERE id = :id');
        $stmt->bindParam(':id', $taskId);
        $stmt->execute();
        echo json_encode(['success' => true]);
    } catch(PDOException $e) {
        echo json_encode(['error' => $e->getMessage()]);
    }
} else {
    echo json_encode(['error' => 'O ID da tarefa é obrigatório']);
}
```

### delete_task
Crie um arquivo chamado `delete_task.php` para o endpoint que deleta uma tarefa:

```php
<?php

require 'database.php';

if (isset($_POST['id'])) {
    $taskId = $_POST['id'];

    try {
        $stmt = $conn->prepare('DELETE FROM tasks WHERE id = :id');
        $stmt->bindParam(':id', $taskId);
        $stmt->execute();
        echo json_encode(['success' => true]);
    } catch(PDOException $e) {
        echo json_encode(['error' => $e->getMessage()]);
    }
} else {
    echo json_encode(['error' => 'O ID da tarefa é obrigatório']);
}
```

----

### Códigos importantes
Agora, vamos entender alguns trechos importantes dos códigos acima:

```php
$stmt = $conn->query('SELECT * FROM tasks');
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
```

- O trecho acima executa uma consulta SQL para selecionar todas as tarefas da tabela `tasks`.
- `query('SELECT * FROM tasks')` executa a consulta e retorna um objeto `PDOStatement` que representa o resultado.
- `fetchAll(PDO::FETCH_ASSOC)` recupera todas as linhas do resultado como um array associativo, onde a chave é o nome da coluna.

```php
if (isset($_POST['title'])) {
    $title = $_POST['title'];
    // ...
}
```

- O trecho acima verifica se o parâmetro `title` foi enviado através de uma requisição POST usando a superglobal `$_POST`.
- `$_POST['title']` recupera o valor do parâmetro `title` enviado no corpo da requisição.
- Essa verificação é importante para garantir que o título da tarefa tenha sido fornecido antes de prosseguir com a inserção.

```php
$stmt = $conn->prepare('INSERT INTO tasks (title) VALUES (:title)');
$stmt->bindParam(':title', $title);
$stmt->execute();
```

- O trecho acima prepara uma instrução SQL para inserir uma nova tarefa na tabela `tasks`.
- `prepare('INSERT INTO tasks (title) VALUES (:title)')` prepara a instrução SQL com um parâmetro nomeado `:title`.
- `bindParam(':title', $title)` vincula o valor do parâmetro `:title` à variável `$title`.
- `execute()` executa a instrução preparada.

----

### Testando
Para testar a API, você pode colocar os arquivos acima em um servidor HTTP da sua escolha que esteja configurando com o PHP (como por exemplo o Apache disponível no XAMPP) ou executar o servidor local de testes do PHP através do comando `php -S localhost:8000` ou `php -S 0.0.0.0:8080` para que o servidor escute a partir de qualquer interface (com a porta da sua escolha).

Uma vez que a API esteja disponível em uma porta, você pode criar requests para os arquivos usando alguma ferramenta de testes de API (insomnia, postman, thunder client).

Aqui vamos fazer testes usando o Thunder Client, diretamente do VSCode:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjvcu7rbg5fw5qyiehl2.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bo8gykna9e5k7t91fq67.png)




----



## Criando a API (método 2 - Semantic HTTP)

Outra forma de criar a API com PHP é usando os diferentes métodos do HTTP (POST, PUT, DELETE) - o que chamamos de HTTP Semântico. No exemplo a seguir, vamos implementar a API em um único arquivo.

Para isso, crie um arquivo chamado `api.php` e adicione o seguinte código:

```php
<?php

require 'database.php';

// Rota para buscar todas as tarefas
if ($_SERVER['REQUEST_METHOD'] === 'GET' && empty($_GET)) {
    try {
        $stmt = $conn->query('SELECT * FROM tasks');
        $tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
        header('Content-Type: application/json');
        echo json_encode($tasks);
    } catch(PDOException $e) {
        echo json_encode(['error' => $e->getMessage()]);
    }
}

// Rota para adicionar uma nova tarefa
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $data = json_decode(file_get_contents('php://input'), true);
    
    if (empty($data['title'])) {
        echo json_encode(['error' => 'O título da tarefa é obrigatório']);
        exit;
    }
    
    $title = $data['title'];
    
    try {
        $stmt = $conn->prepare('INSERT INTO tasks (title) VALUES (:title)');
        $stmt->bindParam(':title', $title);
        $stmt->execute();
        $taskId = $conn->lastInsertId();
        echo json_encode(['id' => $taskId, 'title' => $title, 'completed' => false]);
    } catch(PDOException $e) {
        echo json_encode(['error' => $e->getMessage()]);
    }
}

// Rota para marcar uma tarefa como concluída
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
    $data = json_decode(file_get_contents('php://input'), true);
    
    if (empty($data['id'])) {
        echo json_encode(['error' => 'O ID da tarefa é obrigatório']);
        exit;
    }
    
    $taskId = $data['id'];
    
    try {
        $stmt = $conn->prepare('UPDATE tasks SET completed = 1 WHERE id = :id');
        $stmt->bindParam(':id', $taskId);
        $stmt->execute();
        echo json_encode(['success' => true]);
    } catch(PDOException $e) {
        echo json_encode(['error' => $e->getMessage()]);
    }
}

// Rota para deletar uma tarefa
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
    $data = json_decode(file_get_contents('php://input'), true);
    
    if (empty($data['id'])) {
        echo json_encode(['error' => 'O ID da tarefa é obrigatório']);
        exit;
    }
    
    $taskId = $data['id'];
    
    try {
        $stmt = $conn->prepare('DELETE FROM tasks WHERE id = :id');
        $stmt->bindParam(':id', $taskId);
        $stmt->execute();
        echo json_encode(['success' => true]);
    } catch(PDOException $e) {
        echo json_encode(['error' => $e->getMessage()]);
    }
}
```

A API possui quatro rotas:

GET: /api.php - retorna todas as tarefas
POST: /api.php - adiciona uma nova tarefa
PUT: /api.php - marca uma tarefa como concluída
DELETE: /api.php - deleta uma tarefa

### Código importante
Além dos trechos de códigos que vimos na seção anterior, aqui temos mais um código importante:

```php
$data = json_decode(file_get_contents('php://input'), true);
```

Esse trecho lê os dados enviados na requisição como JSON e os decodifica para um array associativo no PHP. Esses dados serão usados para realizar as operações na lista de tarefas.

### Testando
O procedimento de testes é o mesmo anterior, exceto que neste caso é preciso configurar apropriadamente a ferramenta que estiver sendo utilizada.

A seguir, prints do uso do Thunder Client:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u5ak69cxlkmy4z6ac8rp.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0214otup7tyyh41exqm.png)


----

## Importante!

Dependendo da API implementada, a forma como você envia requests vai mudar. No primeiro caso, o request foi feito usando o *body* do request, enquanto no segundo os dados foram enviados via *json*.

Isso também tem impactos na forma como se utiliza a API `fetch` do JavaScript e os *headers* que são enviados.