Fundamentos do PHP
Projeto Final: Gerenciador de Tarefas - Adicionar, Editar e Excluir
Aprenda sobre projeto final: gerenciador de tarefas - adicionar, editar e excluir
Projeto Final: Gerenciador de Tarefas - Adicionar, Editar e Excluir
Bem-vindos à sua aula final do módulo de persistência de dados! 🥳 Nesta aula prática, você aplicará todo o conhecimento adquirido para construir um sistema completo de gerenciamento de tarefas. Este projeto não apenas consolidará seu aprendizado sobre PHP e bancos de dados, mas também servirá como uma base sólida para projetos futuros.
🚀 Introdução
O objetivo deste projeto final é desenvolver um Gerenciador de Tarefas simples, mas funcional, utilizando PHP e um banco de dados (SQLite, pela sua simplicidade e portabilidade). Você implementará as operações CRUD (Create, Read, Update, Delete), que são o coração de quase toda aplicação web.
Objetivos de Aprendizagem:
- Compreender e aplicar o ciclo de vida completo de uma aplicação web baseada em PHP.
- Dominar as operações CRUD com PDO para interagir com um banco de dados.
- Implementar formulários HTML para entrada de dados e processamento com PHP.
- Aplicar boas práticas de segurança, como Prepared Statements, para prevenir SQL Injection.
- Estruturar um projeto PHP de forma organizada.
- Realizar validação básica de dados e tratamento de erros.
Tecnologias Utilizadas:
- PHP: Linguagem de programação backend.
- PDO (PHP Data Objects): Extensão para acesso a banco de dados.
- SQLite: Banco de dados leve e baseado em arquivo (alternativamente, você pode usar MySQL/MariaDB).
- HTML: Para a estrutura da interface do usuário.
- CSS (básico): Para uma estilização mínima.
🛠️ Configuração do Ambiente e Banco de Dados
Antes de codificar, precisamos configurar nosso ambiente e o banco de dados.
1. Pré-requisitos
Certifique-se de ter:
- Um servidor web (Apache ou Nginx) com PHP instalado e configurado.
- PHP 7.4+ (ou superior).
- A extensão
pdo_sqlite(oupdo_mysqlse for usar MySQL) habilitada no seuphp.ini.
2. Estrutura do Projeto
Vamos criar uma estrutura de pastas simples para organizar nosso código:
task-manager/
├── public/
│ ├── index.php // Página principal que lista as tarefas
│ ├── add.php // Processa a adição de tarefas
│ ├── edit.php // Processa a edição de tarefas
│ ├── delete.php // Processa a exclusão de tarefas
│ └── style.css // Estilos básicos
├── app/
│ └── database.php // Conexão com o banco de dados
└── db/
└── tasks.sqlite // O arquivo do banco de dados SQLite
3. Criação do Banco de Dados e Tabela
Nosso banco de dados será um arquivo SQLite. O PHP pode criar esse arquivo se ele não existir.
app/database.php
Este arquivo será responsável por estabelecer a conexão com o banco de dados e criar a tabela tasks se ela ainda não existir.
<?php
// Define o caminho para o arquivo do banco de dados SQLite
define('DB_PATH', __DIR__ . '/../db/tasks.sqlite');
try {
// Cria a conexão PDO
// Se o arquivo não existir, o SQLite o criará.
$pdo = new PDO('sqlite:' . DB_PATH);
// Configura o PDO para lançar exceções em caso de erros
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Configura o PDO para retornar resultados como objetos (ou array associativo, se preferir PDO::FETCH_ASSOC)
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
// Cria a tabela 'tasks' se ela não existir
$pdo->exec("
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(255) NOT NULL,
description TEXT,
due_date DATE,
completed BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
");
// echo "Conexão com o banco de dados estabelecida e tabela 'tasks' verificada/criada com sucesso!";
} catch (PDOException $e) {
// Em um ambiente de produção, você registraria o erro em um log
// e mostraria uma mensagem genérica ao usuário.
die("Erro de conexão com o banco de dados: " . $e->getMessage());
}Explicação:
define('DB_PATH', __DIR__ . '/../db/tasks.sqlite');: Define o caminho absoluto para o arquivo do banco de dados.new PDO('sqlite:' . DB_PATH);: Instancia um objeto PDO, conectando ao SQLite.$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);: Essencial para depuração e tratamento de erros. Faz com que o PDO lancePDOExceptionem caso de erro, o que podemos capturar com um blocotry-catch.$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);: Define que, por padrão, os resultados das consultas serão retornados como objetos anônimos, facilitando o acesso às propriedades ($task->title).$pdo->exec(...): Executa uma instrução SQL que não retorna resultados (comoCREATE TABLE,INSERT,UPDATE,DELETE).
➕ Adicionar Tarefas (Create)
Vamos criar um formulário para que o usuário possa adicionar novas tarefas.
1. Formulário HTML (public/index.php - parte inicial)
O formulário de adição pode ficar na página principal ou em uma página separada. Para este projeto, vamos mantê-lo na index.php para simplicidade.
<?php
require_once '../app/database.php'; // Inclui o arquivo de conexão com o DB
// Lógica para adicionar tarefa será aqui, depois do require_once
// ...
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gerenciador de Tarefas</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Gerenciador de Tarefas</h1>
<div class="add-task">
<h2>Adicionar Nova Tarefa</h2>
<form action="add.php" method="POST">
<label for="title">Título:</label>
<input type="text" id="title" name="title" required>
<label for="description">Descrição:</label>
<textarea id="description" name="description"></textarea>
<label for="due_date">Data de Vencimento:</label>
<input type="date" id="due_date" name="due_date">
<button type="submit">Adicionar Tarefa</button>
</form>
</div>
<hr>
<h2>Minhas Tarefas</h2>
<div class="task-list">
<?php
// Lógica para listar tarefas virá aqui
// ...
?>
</div>
</div>
</body>
</html>2. Processamento da Adição (public/add.php)
Este arquivo receberá os dados do formulário e os inserirá no banco de dados.
<?php
require_once '../app/database.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
$dueDate = $_POST['due_date'] ?? null;
// Validação básica
if (empty($title)) {
echo "<p style='color: red;'>O título da tarefa é obrigatório!</p>";
// Em um projeto real, você redirecionaria com uma mensagem de erro na sessão.
exit;
}
try {
// Prepara a query SQL com placeholders para segurança (Prepared Statements)
$stmt = $pdo->prepare("INSERT INTO tasks (title, description, due_date) VALUES (:title, :description, :due_date)");
// Bind dos parâmetros
$stmt->bindParam(':title', $title);
$stmt->bindParam(':description', $description);
$stmt->bindParam(':due_date', $dueDate);
// Executa a query
$stmt->execute();
// Redireciona de volta para a página principal após a inserção
header('Location: index.php');
exit;
} catch (PDOException $e) {
echo "<p style='color: red;'>Erro ao adicionar tarefa: " . $e->getMessage() . "</p>";
// Log do erro
}
} else {
// Se a requisição não for POST, redireciona para a página principal
header('Location: index.php');
exit;
}Explicação:
$_SERVER['REQUEST_METHOD'] === 'POST': Verifica se a requisição foi feita via método POST (envio de formulário).trim($_POST['title'] ?? '');: Pega o valor do campotitle, remove espaços em branco extras e usa o operador??(null coalescing) para evitar erros se a chave não existir.- Validação:
if (empty($title)): Uma validação mínima para garantir que o título não esteja vazio. $stmt = $pdo->prepare(...): Prepara a query SQL. Os:title,:description,:due_datesão placeholders nomeados. Esta é uma prática de segurança essencial para prevenir SQL Injection.$stmt->bindParam(...): Associa os valores das variáveis PHP aos placeholders na query preparada.$stmt->execute(): Executa a query preparada com os valores vinculados.header('Location: index.php'); exit;: Redireciona o navegador para aindex.phpapós a operação bem-sucedida. Oexit;é crucial para garantir que nenhum código adicional seja executado após o redirecionamento.
📋 Listar Tarefas (Read)
Agora, vamos exibir as tarefas cadastradas na página principal.
public/index.php (continuação)
Adicione a lógica de listagem de tarefas na seção div class="task-list" da index.php:
<?php
// ... (código anterior da index.php) ...
// Lógica para listar tarefas
try {
$stmt = $pdo->query("SELECT id, title, description, due_date, completed FROM tasks ORDER BY created_at DESC");
$tasks = $stmt->fetchAll(); // Pega todas as tarefas como objetos
} catch (PDOException $e) {
echo "<p style='color: red;'>Erro ao carregar tarefas: " . $e->getMessage() . "</p>";
$tasks = []; // Garante que $tasks seja um array mesmo em caso de erro
}
// ... (HTML anterior) ...
?>
<hr>
<h2>Minhas Tarefas</h2>
<div class="task-list">
<?php if (empty($tasks)): ?>
<p>Nenhuma tarefa cadastrada ainda. Que tal adicionar uma?</p>
<?php else: ?>
<?php foreach ($tasks as $task): ?>
<div class="task-item <?= $task->completed ? 'completed' : '' ?>">
<h3><?= htmlspecialchars($task->title) ?></h3>
<?php if (!empty($task->description)): ?>
<p><?= nl2br(htmlspecialchars($task->description)) ?></p>
<?php endif; ?>
<?php if (!empty($task->due_date)): ?>
<p>Vencimento: <?= htmlspecialchars(date('d/m/Y', strtotime($task->due_date))) ?></p>
<?php endif; ?>
<div class="task-actions">
<a href="edit.php?id=<?= $task->id ?>" class="button edit">Editar</a>
<a href="delete.php?id=<?= $task->id ?>" class="button delete" onclick="return confirm('Tem certeza que deseja excluir esta tarefa?');">Excluir</a>
<!-- Adicionar botão para marcar como concluída/não concluída (desafio) -->
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</body>
</html>Explicação:
$stmt = $pdo->query(...): Para consultasSELECTsimples que não precisam de parâmetros dinâmicos,query()pode ser usado. No entanto, para maior consistência e segurança,prepare()eexecute()são sempre recomendados, mesmo paraSELECTsem parâmetros externos.$tasks = $stmt->fetchAll();: Recupera todas as linhas do conjunto de resultados como um array de objetos (devido aoPDO::FETCH_OBJconfigurado).foreach ($tasks as $task):: Itera sobre cada tarefa e exibe seus detalhes.<?= htmlspecialchars($task->title) ?>: IMPORTANTE! Usehtmlspecialchars()sempre que exibir dados vindos do banco de dados (ou de entrada do usuário) em HTML para prevenir ataques de Cross-Site Scripting (XSS).nl2br(): Converte quebras de linha (\n) em<br>tags HTML para exibir descrições formatadas.href="edit.php?id=<?= $task->id ?>": Cria links para as páginas de edição e exclusão, passando oidda tarefa via URL (GET parameter).
✏️ Editar Tarefas (Update)
A edição de tarefas envolve duas etapas:
- Exibir um formulário pré-preenchido com os dados da tarefa a ser editada.
- Processar o envio do formulário para atualizar os dados no banco.
public/edit.php
<?php
require_once '../app/database.php';
$taskId = $_GET['id'] ?? null;
$task = null;
$errorMessage = '';
// 1. Carregar a tarefa para preencher o formulário
if ($taskId) {
try {
$stmt = $pdo->prepare("SELECT id, title, description, due_date, completed FROM tasks WHERE id = :id");
$stmt->bindParam(':id', $taskId, PDO::PARAM_INT);
$stmt->execute();
$task = $stmt->fetch(); // Pega apenas uma tarefa
if (!$task) {
$errorMessage = "Tarefa não encontrada.";
}
} catch (PDOException $e) {
$errorMessage = "Erro ao carregar tarefa: " . $e->getMessage();
}
} else {
$errorMessage = "ID da tarefa não fornecido.";
}
// 2. Processar o formulário de edição
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $taskId) {
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
$dueDate = $_POST['due_date'] ?? null;
$completed = isset($_POST['completed']) ? 1 : 0; // Checkbox value
if (empty($title)) {
$errorMessage = "O título da tarefa é obrigatório!";
} else {
try {
$stmt = $pdo->prepare("UPDATE tasks SET title = :title, description = :description, due_date = :due_date, completed = :completed WHERE id = :id");
$stmt->bindParam(':title', $title);
$stmt->bindParam(':description', $description);
$stmt->bindParam(':due_date', $dueDate);
$stmt->bindParam(':completed', $completed, PDO::PARAM_INT);
$stmt->bindParam(':id', $taskId, PDO::PARAM_INT);
$stmt->execute();
header('Location: index.php');
exit;
} catch (PDOException $e) {
$errorMessage = "Erro ao atualizar tarefa: " . $e->getMessage();
}
}
}
// Se a tarefa não foi encontrada ou ID não foi fornecido e não houve POST, redireciona
if (!$task && empty($errorMessage) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: index.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Editar Tarefa</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Editar Tarefa</h1>
<?php if (!empty($errorMessage)): ?>
<p style="color: red;"><?= htmlspecialchars($errorMessage) ?></p>
<?php if (!$task): // Se não há tarefa, não mostra o formulário ?>
<p><a href="index.php">Voltar para a lista de tarefas</a></p>
<?php endif; ?>
<?php endif; ?>
<?php if ($task): ?>
<form action="edit.php?id=<?= $task->id ?>" method="POST">
<label for="title">Título:</label>
<input type="text" id="title" name="title" value="<?= htmlspecialchars($task->title) ?>" required>
<label for="description">Descrição:</label>
<textarea id="description" name="description"><?= htmlspecialchars($task->description) ?></textarea>
<label for="due_date">Data de Vencimento:</label>
<input type="date" id="due_date" name="due_date" value="<?= htmlspecialchars($task->due_date) ?>">
<div class="checkbox-group">
<input type="checkbox" id="completed" name="completed" <?= $task->completed ? 'checked' : '' ?>>
<label for="completed">Tarefa Concluída</label>
</div>
<button type="submit">Atualizar Tarefa</button>
<a href="index.php" class="button secondary">Cancelar</a>
</form>
<?php endif; ?>
</div>
</body>
</html>Explicação:
$taskId = $_GET['id'] ?? null;: Obtém o ID da tarefa da URL.- A primeira parte do script carrega a tarefa do banco de dados usando
SELECT ... WHERE id = :id. $stmt->fetch(): Recupera uma única linha do resultado.value="<?= htmlspecialchars($task->title) ?>": Os campos do formulário são pré-preenchidos com os dados existentes da tarefa.<?= $task->completed ? 'checked' : '' ?>: Marca o checkbox "Tarefa Concluída" secompletedfor1.- A segunda parte do script (
if ($_SERVER['REQUEST_METHOD'] === 'POST' ...) processa o envio do formulário, atualizando os dados com umUPDATE ... WHERE id = :id. PDO::PARAM_INT: É uma boa prática especificar o tipo de dado parabindParampara IDs inteiros.
🗑️ Excluir Tarefas (Delete)
A exclusão é a operação CRUD mais simples, mas deve ser feita com cautela.
public/delete.php
<?php
require_once '../app/database.php';
$taskId = $_GET['id'] ?? null;
if ($taskId) {
try {
// Prepara a query de exclusão
$stmt = $pdo->prepare("DELETE FROM tasks WHERE id = :id");
$stmt->bindParam(':id', $taskId, PDO::PARAM_INT);
$stmt->execute();
// Redireciona de volta para a página principal
header('Location: index.php');
exit;
} catch (PDOException $e) {
echo "<p style='color: red;'>Erro ao excluir tarefa: " . $e->getMessage() . "</p>";
// Em um ambiente real, você logaria o erro e redirecionaria com uma mensagem amigável.
}
} else {
echo "<p style='color: red;'>ID da tarefa não fornecido para exclusão.</p>";
}
// Se não houve redirecionamento por algum motivo, garante que o usuário volte
echo '<p><a href="index.php">Voltar para a lista de tarefas</a></p>';Explicação:
- Obtém o
idda tarefa a ser excluída da URL. - Utiliza um
DELETE FROM tasks WHERE id = :idcom Prepared Statements para segurança. - Redireciona para a
index.phpapós a exclusão bem-sucedida. - A confirmação via
onclick="return confirm('...');"no link daindex.phpé uma medida simples de segurança para evitar exclusões acidentais.
🧩 Integração e Boas Práticas
Estrutura de Pastas e Separação de Responsabilidades
public/: Contém todos os arquivos acessíveis diretamente pelo navegador (HTML, CSS, JS, e os scripts PHP que interagem com o usuário). É a "raiz" do seu site.app/: Contém a lógica de negócio e arquivos de configuração que NÃO devem ser acessados diretamente pelo navegador (comodatabase.php).db/: Armazena o arquivo do banco de dados SQLite.
Manter essa separação (conhecida como "Document Root Protection" ou "Front Controller Pattern" em frameworks) é uma boa prática de segurança.
Tratamento de Erros e Depuração
PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION: Permite que o PDO lance exceções em caso de erro, que podem ser capturadas por blocostry-catch. Isso é muito melhor do que o modo padrão que apenas retornafalse.error_reporting(E_ALL); ini_set('display_errors', 1);: Em desenvolvimento, use estas linhas no topo dos seus scripts (ou nophp.ini) para ver todos os erros. Em produção, desabilitedisplay_errorse configure o log de erros.
Prevenção de SQL Injection
- Prepared Statements (PDO
prepare()eexecute()): Você usou isso em todas as operações CRUD. É a maneira mais eficaz de prevenir SQL Injection, pois separa a lógica SQL dos dados, garantindo que os dados sejam tratados como valores, e não como parte da instrução SQL.
Redirecionamentos
header('Location: ...'); exit;: Sempre useexit;após umheader('Location: ...');para garantir que o script pare de executar imediatamente e o redirecionamento ocorra.
Validação de Entrada
- Você implementou validação básica (
empty(),trim()). Para um projeto real, você usaria validação mais robusta (tamanho máximo, tipo de dados, formatos específicos como e-mail ou data).
public/style.css (Exemplo básico)
body {
font-family: sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
color: #333;
}
.container {
max-width: 800px;
margin: 20px auto;
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1, h2 {
color: #0056b3;
text-align: center;
margin-bottom: 20px;
}
form {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 30px;
}
label {
font-weight: bold;
margin-bottom: 5px;
}
input[type="text"],
input[type="date"],
textarea {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
width: 100%;
box-sizing: border-box; /* Garante que padding não aumente a largura total */
}
textarea {
resize: vertical;
min-height: 80px;
}
button[type="submit"], .button {
background-color: #007bff;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
text-decoration: none;
text-align: center;
display: inline-block;
transition: background-color 0.3s ease;
}
button[type="submit"]:hover, .button:hover {
background-color: #0056b3;
}
.button.edit {
background-color: #ffc107;
color: #333;
}
.button.edit:hover {
background-color: #e0a800;
}
.button.delete {
background-color: #dc3545;
}
.button.delete:hover {
background-color: #c82333;
}
.button.secondary {
background-color: #6c757d;
margin-left: 10px;
}
.button.secondary:hover {
background-color: #5a6268;
}
.task-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.task-item {
background: #f9f9f9;
border: 1px solid #eee;
padding: 15px;
border-radius: 6px;
display: flex;
flex-direction: column;
gap: 10px;
}
.task-item h3 {
margin: 0;
color: #007bff;
}
.task-item p {
margin: 0;
}
.task-item.completed {
background-color: #e6ffe6; /* Fundo verde claro */
border-color: #a3e6a3;
opacity: 0.7;
}
.task-item.completed h3 {
text-decoration: line-through;
color: #6c757d;
}
.task-actions {
display: flex;
gap: 10px;
margin-top: 10px;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
}🎯 Desafios e Melhorias (Exercícios)
Este projeto é uma base. Aqui estão alguns desafios para você ir além e aprimorar suas habilidades:
Task List 📝
- Validação de Entrada Aprimorada:
- Verifique o comprimento mínimo e máximo para o título.
- Garanta que a data de vencimento seja uma data válida e não seja no passado (a menos que seja permitido).
- Exiba mensagens de erro mais amigáveis para o usuário no próprio formulário, talvez usando variáveis de sessão para persistir mensagens após redirecionamentos.
- Marcação de Tarefas como Concluídas/Não Concluídas:
- Adicione um botão ou checkbox na lista de tarefas para alternar o status
completed(0 ou 1) sem precisar ir para a página de edição. Isso envolveria um novo script PHP (toggle_complete.php) que recebe o ID da tarefa e atualiza o status.
- Adicione um botão ou checkbox na lista de tarefas para alternar o status
- Filtros e Busca:
- Adicione um campo de busca para filtrar tarefas por título ou descrição.
- Adicione filtros para mostrar apenas tarefas "concluídas", "pendentes" ou "todas".
- Isso exigirá modificações na query
SELECTnaindex.php.
- Paginação:
- Se você tiver muitas tarefas, a lista pode ficar longa. Implemente paginação para exibir um número limitado de tarefas por página.
- Mensagens Flash (Sessão):
- Ao invés de
echomensagens de erro ou sucesso diretamente, use sessões ($_SESSION) para armazená-las e exibi-las após um redirecionamento. Exemplo:$_SESSION['success_message'] = 'Tarefa adicionada com sucesso!'; header('Location: index.php'); exit;. Naindex.php, você verificariaisset($_SESSION['success_message'])e a exibiria.
- Ao invés de
- Estilização Avançada:
- Melhore o CSS para deixar a interface mais agradável e responsiva.
- Considere usar um framework CSS como Bootstrap ou Tailwind CSS para acelerar o design.
- Confirmação de Exclusão com JavaScript:
- Em vez de
onclick="return confirm(...)", implemente uma caixa de diálogo de confirmação mais amigável usando JavaScript.
- Em vez de
- Refatoração com Funções/Classes:
- Para projetos maiores, você pode começar a organizar seu código em funções ou até mesmo classes para o banco de dados (
TaskRepository) ou para a lógica de negócio.
- Para projetos maiores, você pode começar a organizar seu código em funções ou até mesmo classes para o banco de dados (
📝 Resumo e Próximos Passos
Parabéns! 🎉 Você acabou de construir seu primeiro projeto CRUD completo em PHP puro. Você aplicou conceitos fundamentais de:
- Conexão com banco de dados usando PDO.
- Execução de operações CRUD (Create, Read, Update, Delete).
- Interação com formulários HTML.
- Prevenção de SQL Injection com Prepared Statements.
- Boas práticas de organização de código e segurança básica.
Este projeto é um marco importante no seu aprendizado de PHP. A partir daqui, você pode explorar:
- Frameworks PHP: Laravel, Symfony, CodeIgniter, etc., que fornecem uma estrutura e ferramentas para construir aplicações maiores e mais complexas de forma mais eficiente e segura.
- Padrões de Projeto: MVC (Model-View-Controller) é um padrão comum em frameworks que ajuda a organizar o código.
- Segurança Web: Aprofundar-se em tópicos como autenticação, autorização, CSRF, XSS, etc.
- APIs RESTful: Construir APIs com PHP para que outras aplicações (front-ends em JavaScript, aplicativos móveis) possam interagir com seus dados.
Continue praticando, experimentando e construindo! O mundo do desenvolvimento web é vasto e cheio de oportunidades. Boa sorte! 💪