Skip to content

Commit 1fca6c9

Browse files
committed
first commit
0 parents  commit 1fca6c9

File tree

6 files changed

+349
-0
lines changed

6 files changed

+349
-0
lines changed

Diff for: .htaccess

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
RewriteEngine On
2+
RewriteRule . index.php

Diff for: index.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
spl_autoload_register(function ($class) {
6+
require __DIR__ . "/src/$class.php";
7+
});
8+
9+
set_error_handler("ErrorHandler::handleError");
10+
set_exception_handler("ErrorHandler::handleException");
11+
12+
header("Content-type: application/json; charset=UTF-8");
13+
14+
$parts = explode("/", $_SERVER["REQUEST_URI"]);
15+
16+
if ($parts[1] != "products") {
17+
http_response_code(404);
18+
exit;
19+
}
20+
21+
$id = $parts[2] ?? null;
22+
23+
$database = new Database("localhost", "product_db", "root", "");
24+
25+
$gateway = new ProductGateway($database);
26+
27+
$controller = new ProductController($gateway);
28+
29+
$controller->processRequest($_SERVER["REQUEST_METHOD"], $id);
30+
31+
32+
33+
34+
35+
36+
37+
38+
39+
40+
41+
42+

Diff for: src/Database.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
class Database
4+
{
5+
public function __construct(private string $host,
6+
private string $name,
7+
private string $user,
8+
private string $password)
9+
{}
10+
11+
public function getConnection(): PDO
12+
{
13+
$dsn = "mysql:host={$this->host};dbname={$this->name};charset=utf8";
14+
15+
return new PDO($dsn, $this->user, $this->password, [
16+
PDO::ATTR_EMULATE_PREPARES => false,
17+
PDO::ATTR_STRINGIFY_FETCHES => false
18+
]);
19+
}
20+
}
21+
22+
23+
24+
25+
26+
27+
28+
29+

Diff for: src/ErrorHandler.php

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
class ErrorHandler
4+
{
5+
public static function handleException(Throwable $exception): void
6+
{
7+
http_response_code(500);
8+
9+
echo json_encode([
10+
"code" => $exception->getCode(),
11+
"message" => $exception->getMessage(),
12+
"file" => $exception->getFile(),
13+
"line" => $exception->getLine()
14+
]);
15+
}
16+
17+
public static function handleError(
18+
int $errno,
19+
string $errstr,
20+
string $errfile,
21+
int $errline
22+
): bool
23+
{
24+
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
25+
}
26+
}
27+
28+
29+
30+
31+
32+
33+
34+
35+
36+

Diff for: src/ProductController.php

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
class ProductController
4+
{
5+
public function __construct(private ProductGateway $gateway)
6+
{
7+
}
8+
9+
public function processRequest(string $method, ?string $id): void
10+
{
11+
if ($id) {
12+
13+
$this->processResourceRequest($method, $id);
14+
15+
} else {
16+
17+
$this->processCollectionRequest($method);
18+
19+
}
20+
}
21+
22+
private function processResourceRequest(string $method, string $id): void
23+
{
24+
$product = $this->gateway->get($id);
25+
26+
if ( ! $product) {
27+
http_response_code(404);
28+
echo json_encode(["message" => "Product not found"]);
29+
return;
30+
}
31+
32+
switch ($method) {
33+
case "GET":
34+
echo json_encode($product);
35+
break;
36+
37+
case "PATCH":
38+
$data = (array) json_decode(file_get_contents("php://input"), true);
39+
40+
$errors = $this->getValidationErrors($data, false);
41+
42+
if ( ! empty($errors)) {
43+
http_response_code(422);
44+
echo json_encode(["errors" => $errors]);
45+
break;
46+
}
47+
48+
$rows = $this->gateway->update($product, $data);
49+
50+
echo json_encode([
51+
"message" => "Product $id updated",
52+
"rows" => $rows
53+
]);
54+
break;
55+
56+
case "DELETE":
57+
$rows = $this->gateway->delete($id);
58+
59+
echo json_encode([
60+
"message" => "Product $id deleted",
61+
"rows" => $rows
62+
]);
63+
break;
64+
65+
default:
66+
http_response_code(405);
67+
header("Allow: GET, PATCH, DELETE");
68+
}
69+
}
70+
71+
private function processCollectionRequest(string $method): void
72+
{
73+
switch ($method) {
74+
case "GET":
75+
echo json_encode($this->gateway->getAll());
76+
break;
77+
78+
case "POST":
79+
$data = (array) json_decode(file_get_contents("php://input"), true);
80+
81+
$errors = $this->getValidationErrors($data);
82+
83+
if ( ! empty($errors)) {
84+
http_response_code(422);
85+
echo json_encode(["errors" => $errors]);
86+
break;
87+
}
88+
89+
$id = $this->gateway->create($data);
90+
91+
http_response_code(201);
92+
echo json_encode([
93+
"message" => "Product created",
94+
"id" => $id
95+
]);
96+
break;
97+
98+
default:
99+
http_response_code(405);
100+
header("Allow: GET, POST");
101+
}
102+
}
103+
104+
private function getValidationErrors(array $data, bool $is_new = true): array
105+
{
106+
$errors = [];
107+
108+
if ($is_new && empty($data["name"])) {
109+
$errors[] = "name is required";
110+
}
111+
112+
if (array_key_exists("size", $data)) {
113+
if (filter_var($data["size"], FILTER_VALIDATE_INT) === false) {
114+
$errors[] = "size must be an integer";
115+
}
116+
}
117+
118+
return $errors;
119+
}
120+
}
121+
122+
123+
124+
125+
126+
127+
128+
129+

Diff for: src/ProductGateway.php

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
class ProductGateway
4+
{
5+
private PDO $conn;
6+
7+
public function __construct(Database $database)
8+
{
9+
$this->conn = $database->getConnection();
10+
}
11+
12+
public function getAll(): array
13+
{
14+
$sql = "SELECT *
15+
FROM product";
16+
17+
$stmt = $this->conn->query($sql);
18+
19+
$data = [];
20+
21+
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
22+
23+
$row["is_available"] = (bool) $row["is_available"];
24+
25+
$data[] = $row;
26+
}
27+
28+
return $data;
29+
}
30+
31+
public function create(array $data): string
32+
{
33+
$sql = "INSERT INTO product (name, size, is_available)
34+
VALUES (:name, :size, :is_available)";
35+
36+
$stmt = $this->conn->prepare($sql);
37+
38+
$stmt->bindValue(":name", $data["name"], PDO::PARAM_STR);
39+
$stmt->bindValue(":size", $data["size"] ?? 0, PDO::PARAM_INT);
40+
$stmt->bindValue(":is_available", (bool) ($data["is_available"] ?? false), PDO::PARAM_BOOL);
41+
42+
$stmt->execute();
43+
44+
return $this->conn->lastInsertId();
45+
}
46+
47+
public function get(string $id): array | false
48+
{
49+
$sql = "SELECT *
50+
FROM product
51+
WHERE id = :id";
52+
53+
$stmt = $this->conn->prepare($sql);
54+
55+
$stmt->bindValue(":id", $id, PDO::PARAM_INT);
56+
57+
$stmt->execute();
58+
59+
$data = $stmt->fetch(PDO::FETCH_ASSOC);
60+
61+
if ($data !== false) {
62+
$data["is_available"] = (bool) $data["is_available"];
63+
}
64+
65+
return $data;
66+
}
67+
68+
public function update(array $current, array $new): int
69+
{
70+
$sql = "UPDATE product
71+
SET name = :name, size = :size, is_available = :is_available
72+
WHERE id = :id";
73+
74+
$stmt = $this->conn->prepare($sql);
75+
76+
$stmt->bindValue(":name", $new["name"] ?? $current["name"], PDO::PARAM_STR);
77+
$stmt->bindValue(":size", $new["size"] ?? $current["size"], PDO::PARAM_INT);
78+
$stmt->bindValue(":is_available", $new["is_available"] ?? $current["is_available"], PDO::PARAM_BOOL);
79+
80+
$stmt->bindValue(":id", $current["id"], PDO::PARAM_INT);
81+
82+
$stmt->execute();
83+
84+
return $stmt->rowCount();
85+
}
86+
87+
public function delete(string $id): int
88+
{
89+
$sql = "DELETE FROM product
90+
WHERE id = :id";
91+
92+
$stmt = $this->conn->prepare($sql);
93+
94+
$stmt->bindValue(":id", $id, PDO::PARAM_INT);
95+
96+
$stmt->execute();
97+
98+
return $stmt->rowCount();
99+
}
100+
}
101+
102+
103+
104+
105+
106+
107+
108+
109+
110+
111+

0 commit comments

Comments
 (0)