Criando uma TodoList com PHP e o Framework Lithe: Um Guia Completo
Lithe
Posted on November 16, 2024
Neste tutorial, vamos criar uma aplicação TodoList funcional usando o Lithe. Você aprenderá a estruturar seu projeto, criar views interativas, e implementar uma API RESTful para gerenciar suas tarefas. Este projeto servirá como um excelente exemplo de como construir aplicações web modernas com PHP.
Pré-requisitos
- Composer instalado
- MySQL
- Conhecimentos básicos de PHP e JavaScript
Estrutura do Projeto
Primeiro, vamos criar um novo projeto Lithe:
composer create-project lithephp/lithephp todo-app
cd todo-app
Configurando o Banco de Dados
Edite o arquivo .env
na raiz do projeto com as seguintes configurações:
DB_CONNECTION_METHOD=mysqli
DB_CONNECTION=mysql
DB_HOST=localhost
DB_NAME=lithe_todos
DB_USERNAME=root
DB_PASSWORD=
DB_SHOULD_INITIATE=true
Criando a Migração
Execute o comando para criar uma nova migração:
php line make:migration CreateTodosTable
Edite o arquivo de migração gerado em src/database/migrations/
:
return new class
{
public function up(mysqli $db): void
{
$query = "
CREATE TABLE IF NOT EXISTS todos (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
";
$db->query($query);
}
public function down(mysqli $db): void
{
$query = "DROP TABLE IF EXISTS todos";
$db->query($query);
}
};
Execute a migração:
php line migrate
Implementando o Modelo
Gere um novo modelo:
php line make:model Todo
Edite o arquivo src/models/Todo.php
:
namespace App\Models;
use Lithe\Database\Manager as DB;
class Todo
{
public static function all(): array
{
return DB::connection()
->query("SELECT * FROM todos ORDER BY created_at DESC")
->fetch_all(MYSQLI_ASSOC);
}
public static function create(array $data): ?array
{
$stmt = DB::connection()->prepare(
"INSERT INTO todos (title, completed) VALUES (?, ?)"
);
$completed = false;
$stmt->bind_param('si', $data['title'], $completed);
$success = $stmt->execute();
if ($success) {
$id = $stmt->insert_id;
return [
'id' => $id,
'title' => $data['title'],
'completed' => $completed
];
}
return null;
}
public static function update(int $id, array $data): bool
{
$stmt = DB::connection()->prepare(
"UPDATE todos SET completed = ? WHERE id = ?"
);
$stmt->bind_param('ii', $data['completed'], $id);
return $stmt->execute();
}
public static function delete(int $id): bool
{
$stmt = DB::connection()->prepare("DELETE FROM todos WHERE id = ?");
$stmt->bind_param('i', $id);
return $stmt->execute();
}
}
Criando o Controlador
Gere um novo controlador:
php line make:controller TodoController
Edite o arquivo src/http/controllers/TodoController.php
:
namespace App\Http\Controllers;
use App\Models\Todo;
use Lithe\Http\Request;
use Lithe\Http\Response;
class TodoController
{
public static function index(Request $req, Response $res)
{
return $res->view('todo.index');
}
public static function list(Request $req, Response $res)
{
$todos = Todo::all();
return $res->json($todos);
}
public static function store(Request $req, Response $res)
{
$data = (array) $req->body();
$todo = Todo::create($data);
$success = $todo ? true : false;
return $res->json([
'success' => $success,
'message' => $success ? 'Tarefa criada com sucesso' : 'Falha ao criar tarefa',
'todo' => $todo
]);
}
public static function update(Request $req, Response $res)
{
$id = $req->param('id');
$data = (array) $req->body();
$success = Todo::update($id, $data);
return $res->json([
'success' => $success,
'message' => $success ? 'Tarefa atualizada com sucesso' : 'Falha ao atualizar tarefa'
]);
}
public static function delete(Request $req, Response $res)
{
$id = $req->param('id');
$success = Todo::delete($id);
return $res->json([
'success' => $success,
'message' => $success ? 'Tarefa removida com sucesso' : 'Falha ao remover tarefa'
]);
}
}
Criando as Views
Crie o diretório src/views/todo
e adicione o arquivo index.php
:
<!DOCTYPE html>
<html>
<head>
<title>TodoList com Lithe</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
body {
min-height: 100vh;
background-color: #ffffff;
padding: 20px;
}
.container {
max-width: 680px;
margin: 0 auto;
padding: 40px 20px;
}
h1 {
color: #1d1d1f;
font-size: 34px;
font-weight: 700;
margin-bottom: 30px;
}
.todo-form {
display: flex;
gap: 12px;
margin-bottom: 30px;
border-bottom: 1px solid #e5e5e7;
padding-bottom: 30px;
}
.todo-input {
flex: 1;
padding: 12px 16px;
font-size: 17px;
border: 1px solid #e5e5e7;
border-radius: 10px;
background-color: #f5f5f7;
transition: all 0.2s;
}
.todo-input:focus {
outline: none;
background-color: #ffffff;
border-color: #0071e3;
}
.add-button {
padding: 12px 20px;
background: #0071e3;
color: white;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.add-button:hover {
background: #0077ED;
}
.todo-list {
list-style: none;
}
.todo-item {
display: flex;
align-items: center;
padding: 16px;
border-radius: 10px;
margin-bottom: 8px;
transition: background-color 0.2s;
}
.todo-item:hover {
background-color: #f5f5f7;
}
.todo-checkbox {
width: 22px;
height: 22px;
margin-right: 15px;
cursor: pointer;
}
.todo-text {
flex: 1;
font-size: 17px;
color: #1d1d1f;
}
.completed {
color: #86868b;
text-decoration: line-through;
}
.delete-button {
padding: 8px 12px;
background: none;
color: #86868b;
border: none;
border-radius: 6px;
font-size: 15px;
cursor: pointer;
opacity: 0;
transition: all 0.2s;
}
.todo-item:hover .delete-button {
opacity: 1;
}
.delete-button:hover {
background: #f5f5f7;
color: #ff3b30;
}
.loading {
text-align: center;
padding: 20px;
color: #86868b;
}
.error {
color: #ff3b30;
text-align: center;
padding: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>Minhas Tarefas</h1>
<form class="todo-form" id="todoForm">
<input type="text" class="todo-input" placeholder="Adicionar nova tarefa" id="todoInput" required>
<button type="submit" class="add-button">Adicionar</button>
</form>
<div id="loading" class="loading">Carregando tarefas...</div>
<ul class="todo-list" id="todoList"></ul>
</div>
<script>
const todoList = document.getElementById('todoList');
const todoForm = document.getElementById('todoForm');
const todoInput = document.getElementById('todoInput');
const loading = document.getElementById('loading');
// Carregar todas as tarefas
async function loadTodos() {
try {
const response = await fetch('/todos/list');
const todos = await response.json();
loading.style.display = 'none';
todoList.innerHTML = ''; // Limpar a lista antes de adicionar
todos.forEach(todo => addTodoToDOM(todo));
} catch (error) {
loading.textContent = 'Não foi possível carregar as tarefas.';
loading.classList.add('error');
}
}
// Adicionar tarefa ao DOM
function addTodoToDOM(todo) {
console.log(todo)
const li = document.createElement('li');
li.className = 'todo-item';
li.dataset.id = todo.id; // Associando o ID à tarefa
li.innerHTML = `
<input type="checkbox" class="todo-checkbox" ${todo.completed ? 'checked' : ''}>
<span class="todo-text ${todo.completed ? 'completed' : ''}">${todo.title}</span>
<button type="button" class="delete-button">Remover</button>
`;
const checkbox = li.querySelector('.todo-checkbox');
checkbox.addEventListener('change', () => toggleTodo(todo.id, checkbox));
const deleteButton = li.querySelector('.delete-button');
deleteButton.addEventListener('click', () => deleteTodo(todo.id, li));
todoList.appendChild(li);
}
// Criar nova tarefa
todoForm.addEventListener('submit', async (e) => {
e.preventDefault();
const title = todoInput.value.trim();
if (!title) return;
try {
const response = await fetch('/todos', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title
})
});
const result = await response.json();
if (result.success) {
const todo = {
id: result.todo.id,
title,
completed: false
};
addTodoToDOM(todo);
todoInput.value = '';
} else {
alert(result.message);
}
} catch (error) {
alert('Não foi possível adicionar a tarefa');
}
});
// Atualizar status da tarefa
async function toggleTodo(id, checkbox) {
try {
console.log(`Atualizando tarefa com ID: ${id}`);
const response = await fetch(`/todos/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
completed: checkbox.checked ? 1 : 0
})
});
const result = await response.json();
if (result.success) {
const todoText = checkbox.nextElementSibling;
todoText.classList.toggle('completed');
} else {
checkbox.checked = !checkbox.checked;
alert(result.message);
}
} catch (error) {
checkbox.checked = !checkbox.checked;
alert('Não foi possível atualizar a tarefa');
}
}
// Remover tarefa
async function deleteTodo(id, element) {
try {
console.log(`Deletando tarefa com ID: ${id}`);
const response = await fetch(`/todos/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
element.remove();
} else {
alert(result.message);
}
} catch (error) {
alert('Não foi possível remover a tarefa');
}
}
// Carregar tarefas ao iniciar
loadTodos();
</script>
</body>
</html>
Configurando as Rotas
Atualize o arquivo src/App.php
para incluir as rotas da TodoList:
use App\Http\Controllers\TodoController;
$app = new \Lithe\App;
// Rota para a página principal
$app->get('/', [TodoController::class, 'index']);
// Rotas da API
$app->get('/todos/list', [TodoController::class, 'list']);
$app->post('/todos', [TodoController::class, 'store']);
$app->put('/todos/:id', [TodoController::class, 'update']);
$app->delete('/todos/:id', [TodoController::class, 'delete']);
$app->listen();
Executando a Aplicação
Para iniciar o servidor de desenvolvimento, execute:
php line serve
Acesse http://localhost:8000
no seu navegador para ver a aplicação em funcionamento.
Funcionalidades Implementadas
Nossa TodoList possui as seguintes funcionalidades:
- Listagem de tarefas em ordem cronológica reversa
- Adição de novas tarefas
- Marcação de tarefas como concluídas/pendentes
- Remoção de tarefas
- Interface responsiva e amigável
- Feedback visual para todas as ações
- Tratamento de erros
Conclusão
Você agora tem uma aplicação TodoList completamente funcional construída com o Lithe. Este exemplo demonstra como criar uma aplicação web moderna com PHP, incluindo:
- Estruturação adequada de código MVC
- Implementação de API RESTful
- Interface de usuário interativa
- Integração com banco de dados
- Tratamento de erros
- Feedback ao usuário
A partir daqui, você pode expandir a aplicação adicionando novas funcionalidades como:
- Autenticação de usuários
- Categorização de tarefas
- Datas de vencimento
- Filtros e pesquisa
Para continuar aprendendo sobre o Lithe, acesse a Linktree, onde você pode acessar o Discord, a documentação e muito mais!
Posted on November 16, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.