This commit is contained in:
2025-06-09 12:36:58 -06:00
parent 4eb05d2193
commit d3bcd6d7f8
6 changed files with 394 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
todos.json

10
.htaccess Normal file
View File

@ -0,0 +1,10 @@
RewriteEngine On
# Handle CORS preflight requests
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ index.php [QSA,L]
# Route all requests to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

29
Makefile Normal file
View File

@ -0,0 +1,29 @@
.PHONY: start clean install
# Default target
.DEFAULT_GOAL := start
# Variables
PHP = php
HOST = localhost
PORT = 8000
SERVER_URL = http://$(HOST):$(PORT)
# Start the PHP development server
start:
@echo "Starting PHP TODO API server at $(SERVER_URL)"
@echo "Available endpoints:"
@echo " GET $(SERVER_URL)/ - API info and available endpoints"
@echo " GET $(SERVER_URL)/todos - Get all todos"
@echo " POST $(SERVER_URL)/todos - Create todo"
@echo " PUT $(SERVER_URL)/todos/:id - Update todo"
@echo " DELETE $(SERVER_URL)/todos/:id - Delete todo"
@echo ""
@echo "Press Ctrl+C to stop the server"
$(PHP) -S $(HOST):$(PORT)
# Clean up data file
clean:
@echo "Cleaning up data..."
@rm -f todos.json
@echo "Data file removed"

75
Todo.php Normal file
View File

@ -0,0 +1,75 @@
<?php
class Todo {
private $id;
private $title;
private $description;
private $completed;
private $createdAt;
private $updatedAt;
public function __construct($id, $title, $description = '', $completed = false) {
$this->id = $id;
$this->title = $title;
$this->description = $description;
$this->completed = $completed;
$this->createdAt = date('Y-m-d H:i:s');
$this->updatedAt = date('Y-m-d H:i:s');
}
// Getters
public function getId() {
return $this->id;
}
public function getTitle() {
return $this->title;
}
public function getDescription() {
return $this->description;
}
public function isCompleted() {
return $this->completed;
}
public function getCreatedAt() {
return $this->createdAt;
}
public function getUpdatedAt() {
return $this->updatedAt;
}
// Setters
public function setTitle($title) {
$this->title = $title;
$this->updateTimestamp();
}
public function setDescription($description) {
$this->description = $description;
$this->updateTimestamp();
}
public function setCompleted($completed) {
$this->completed = $completed;
$this->updateTimestamp();
}
private function updateTimestamp() {
$this->updatedAt = date('Y-m-d H:i:s');
}
public function toArray() {
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'completed' => $this->completed,
'created_at' => $this->createdAt,
'updated_at' => $this->updatedAt
];
}
}
?>

188
TodoController.php Normal file
View File

@ -0,0 +1,188 @@
<?php
require_once 'Todo.php';
class TodoController {
private $dataFile = 'todos.json';
public function __construct() {
// Create data file if it doesn't exist
if (!file_exists($this->dataFile)) {
file_put_contents($this->dataFile, json_encode([]));
}
}
private function loadTodos() {
$data = file_get_contents($this->dataFile);
$todosData = json_decode($data, true) ?: [];
$todos = [];
foreach ($todosData as $todoData) {
$todos[] = new Todo(
$todoData['id'],
$todoData['title'],
$todoData['description'] ?? '',
$todoData['completed'] ?? false
);
}
return $todos;
}
private function saveTodos($todos) {
$todosData = [];
foreach ($todos as $todo) {
$todosData[] = $todo->toArray();
}
file_put_contents($this->dataFile, json_encode($todosData, JSON_PRETTY_PRINT));
}
private function getNextId() {
$todos = $this->loadTodos();
if (empty($todos)) {
return 1;
}
$maxId = 0;
foreach ($todos as $todo) {
if ($todo->getId() > $maxId) {
$maxId = $todo->getId();
}
}
return $maxId + 1;
}
public function getAllTodos() {
try {
$todos = $this->loadTodos();
$todosArray = [];
foreach ($todos as $todo) {
$todosArray[] = $todo->toArray();
}
echo json_encode($todosArray);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Failed to fetch todos: ' . $e->getMessage()]);
}
}
public function createTodo() {
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['title']) || empty(trim($input['title']))) {
http_response_code(400);
echo json_encode(['error' => 'Title is required']);
return;
}
$id = $this->getNextId();
$title = trim($input['title']);
$description = isset($input['description']) ? trim($input['description']) : '';
$completed = isset($input['completed']) ? (bool)$input['completed'] : false;
$todo = new Todo($id, $title, $description, $completed);
$todos = $this->loadTodos();
$todos[] = $todo;
$this->saveTodos($todos);
http_response_code(201);
echo json_encode($todo->toArray());
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Failed to create todo: ' . $e->getMessage()]);
}
}
public function updateTodo($id) {
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON input']);
return;
}
$todos = $this->loadTodos();
$todoIndex = -1;
for ($i = 0; $i < count($todos); $i++) {
if ($todos[$i]->getId() === $id) {
$todoIndex = $i;
break;
}
}
if ($todoIndex === -1) {
http_response_code(404);
echo json_encode(['error' => 'Todo not found']);
return;
}
$todo = $todos[$todoIndex];
// Update fields if provided
if (isset($input['title'])) {
if (empty(trim($input['title']))) {
http_response_code(400);
echo json_encode(['error' => 'Title cannot be empty']);
return;
}
$todo->setTitle(trim($input['title']));
}
if (isset($input['description'])) {
$todo->setDescription(trim($input['description']));
}
if (isset($input['completed'])) {
$todo->setCompleted((bool)$input['completed']);
}
$this->saveTodos($todos);
echo json_encode($todo->toArray());
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Failed to update todo: ' . $e->getMessage()]);
}
}
public function deleteTodo($id) {
try {
$todos = $this->loadTodos();
$todoIndex = -1;
for ($i = 0; $i < count($todos); $i++) {
if ($todos[$i]->getId() === $id) {
$todoIndex = $i;
break;
}
}
if ($todoIndex === -1) {
http_response_code(404);
echo json_encode(['error' => 'Todo not found']);
return;
}
$deletedTodo = $todos[$todoIndex];
array_splice($todos, $todoIndex, 1);
$this->saveTodos($todos);
echo json_encode([
'message' => 'Todo deleted successfully',
'deleted_todo' => $deletedTodo->toArray()
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Failed to delete todo: ' . $e->getMessage()]);
}
}
}
?>

91
index.php Normal file
View File

@ -0,0 +1,91 @@
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// Handle preflight OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
require_once 'TodoController.php';
$controller = new TodoController();
// Get the request URI and method
$request_uri = $_SERVER['REQUEST_URI'];
$request_method = $_SERVER['REQUEST_METHOD'];
// Remove query string if present
$path = strtok($request_uri, '?');
// Remove trailing slash if present
$path = rtrim($path, '/');
// If path is empty, set to root
if (empty($path)) {
$path = '/';
}
// Route handling
switch ($path) {
case '/':
// Base route - show available endpoints
if ($request_method === 'GET') {
$host = $_SERVER['HTTP_HOST'];
$port = $_SERVER['SERVER_PORT'];
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
echo json_encode([
'message' => "Todo API running at {$protocol}://{$host}:{$port}/",
'available_endpoints' => [
'GET /todos - Get all todos',
'POST /todos - Create todo',
'PUT /todos/:id - Update todo',
'DELETE /todos/:id - Delete todo'
]
], JSON_PRETTY_PRINT);
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case '/todos':
switch ($request_method) {
case 'GET':
$controller->getAllTodos();
break;
case 'POST':
$controller->createTodo();
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
default:
// Handle /todos/:id routes
if (preg_match('/^\/todos\/(\d+)$/', $path, $matches)) {
$id = (int)$matches[1];
switch ($request_method) {
case 'PUT':
$controller->updateTodo($id);
break;
case 'DELETE':
$controller->deleteTodo($id);
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
} else {
http_response_code(404);
echo json_encode(['error' => 'Endpoint not found']);
}
}
?>