Jednoduché API ve frameworku Slim 4 – č. 6 Přidání Model a Repositories
- Jednoduché API ve frameworku Slim 4 – č. 1 Instalace
- Jednoduché API ve frameworku Slim 4 – č. 2 Základní CRUD
- Jednoduché API ve frameworku Slim 4 – č. 3 Struktura API a připojení k databázi
- Jednoduché API ve frameworku Slim 4 – č. 4 Testování funkcionality našeho malého API
- Jednoduché API ve frameworku Slim 4 – č. 5 Vylepšení UserControlleru
- Jednoduché API ve frameworku Slim 4 – č. 6 Přidání Model a Repositories
- Jednoduché API ve frameworku Slim 4 – č. 7 Validace dat
- Jednoduché API ve frameworku Slim 4 – č. 8 Přidáme si do datbáze produkty
Takže zatím máme všechne kod pro naše API v UserControlleru. Bylo by fajn z controlleru vyčlenit kod pro práci s databází do samostanýcj tříd. Možností je vícero já jsem zvolila vytvoření Modelu a Repository.
Takže v adresáři src si vytvoříme dva adresáře Models a Repositories.
V adresáři Repositories si vytvoříme třídu UserRepository pro práci s databází
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
<?php namespace App\Repositories; use PDO; use App\Models\User; use RuntimeException; class UserRepository { private PDO $db; public function __construct(PDO $db) { $this->db = $db; } /** * @return array<User> * @throws RuntimeException */ public function findAll(): array { try { $stmt = $this->db->query('SELECT * FROM users'); $users = $stmt->fetchAll(PDO::FETCH_ASSOC); return array_map( fn(array $userData) => User::fromArray($userData), $users ); } catch (\PDOException $e) { throw new RuntimeException('Database error: ' . $e->getMessage()); } } /** * @throws RuntimeException */ public function findById(int $id): ?User { try { $stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?'); $stmt->execute([$id]); $userData = $stmt->fetch(PDO::FETCH_ASSOC); return $userData ? User::fromArray($userData) : null; } catch (\PDOException $e) { throw new RuntimeException('Database error: ' . $e->getMessage()); } } /** * @throws RuntimeException */ public function create(array $data): User { try { $now = new \DateTimeImmutable(); $stmt = $this->db->prepare( 'INSERT INTO users (name, email, created_at, updated_at) VALUES (:name, :email, :created_at, :updated_at)' ); $stmt->execute([ ':name' => $data['name'], ':email' => $data['email'], ':created_at' => $now->format('Y-m-d H:i:s'), ':updated_at' => $now->format('Y-m-d H:i:s') ]); return new User( (int)$this->db->lastInsertId(), $data['name'], $data['email'], $now, $now ); } catch (\PDOException $e) { if ($e->getCode() == 23000) { throw new RuntimeException('Email already exists', 23000); } throw new RuntimeException('Database error: ' . $e->getMessage()); } } /** * @throws RuntimeException */ public function update(int $id, array $data): ?User { try { $this->db->beginTransaction(); $checkStmt = $this->db->prepare('SELECT * FROM users WHERE id = ? FOR UPDATE'); $checkStmt->execute([$id]); $existingData = $checkStmt->fetch(PDO::FETCH_ASSOC); if (!$existingData) { $this->db->rollBack(); return null; } $now = new \DateTimeImmutable(); $stmt = $this->db->prepare( 'UPDATE users SET name = :name, email = :email, updated_at = :updated_at WHERE id = :id' ); $stmt->execute([ ':name' => $data['name'], ':email' => $data['email'], ':updated_at' => $now->format('Y-m-d H:i:s'), ':id' => $id ]); $user = new User( $id, $data['name'], $data['email'], new \DateTimeImmutable($existingData['created_at']), $now ); $this->db->commit(); return $user; } catch (\PDOException $e) { $this->db->rollBack(); if ($e->getCode() == 23000) { throw new RuntimeException('Email already exists', 23000); } throw new RuntimeException('Database error: ' . $e->getMessage()); } } /** * @throws RuntimeException */ public function delete(int $id): bool { try { $this->db->beginTransaction(); $checkStmt = $this->db->prepare('SELECT id FROM users WHERE id = ? FOR UPDATE'); $checkStmt->execute([$id]); if (!$checkStmt->fetch()) { $this->db->rollBack(); return false; } $stmt = $this->db->prepare('DELETE FROM users WHERE id = ?'); $stmt->execute([$id]); $this->db->commit(); return true; } catch (\PDOException $e) { $this->db->rollBack(); throw new RuntimeException('Database error: ' . $e->getMessage()); } } } |
a adekvátně tomu upravíme UserController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
<?php namespace App\Controllers; use App\Repositories\UserRepository; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Exception\HttpNotFoundException; use Slim\Exception\HttpBadRequestException; class UserController { private UserRepository $userRepository; public function __construct(UserRepository $userRepository) { $this->userRepository = $userRepository; } private function jsonResponse(Response $response, mixed $data, int $status = 200): Response { $response->getBody()->write(json_encode($data, JSON_THROW_ON_ERROR)); return $response ->withHeader('Content-Type', 'application/json') ->withStatus($status); } private function validateUserData(array $data): void { if (empty($data['name']) || empty($data['email'])) { throw new HttpBadRequestException(null, 'Name and email are required'); } if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { throw new HttpBadRequestException(null, 'Invalid email format'); } } public function getAll(Request $request, Response $response): Response { $users = $this->userRepository->findAll(); return $this->jsonResponse($response, $users); } public function getOne(Request $request, Response $response, array $args): Response { $user = $this->userRepository->findById((int)$args['id']); if (!$user) { throw new HttpNotFoundException($request, 'User not found'); } return $this->jsonResponse($response, $user); } public function create(Request $request, Response $response): Response { $data = $request->getParsedBody(); $this->validateUserData($data); try { $user = $this->userRepository->create($data); return $this->jsonResponse($response, $user, 201); } catch (\RuntimeException $e) { if ($e->getCode() === 23000) { throw new HttpBadRequestException($request, 'Email already exists'); } throw $e; } } public function update(Request $request, Response $response, array $args): Response { $data = $request->getParsedBody(); $this->validateUserData($data); try { $user = $this->userRepository->update((int)$args['id'], $data); if (!$user) { throw new HttpNotFoundException($request, 'User not found'); } return $this->jsonResponse($response, $user); } catch (\RuntimeException $e) { if ($e->getCode() === 23000) { throw new HttpBadRequestException($request, 'Email already exists'); } throw $e; } } public function delete(Request $request, Response $response, array $args): Response { $deleted = $this->userRepository->delete((int)$args['id']); if (!$deleted) { throw new HttpNotFoundException($request, 'User not found'); } return $response->withStatus(204); } } |
a ještě v adresáři Models vytvoříme model pro uživatele
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
<?php namespace App\Models; use JsonSerializable; class User implements JsonSerializable { private ?int $id; private string $name; private string $email; private ?\DateTimeImmutable $createdAt; private ?\DateTimeImmutable $updatedAt; public function __construct( ?int $id, string $name, string $email, ?\DateTimeImmutable $createdAt = null, ?\DateTimeImmutable $updatedAt = null ) { $this->id = $id; $this->name = $name; $this->email = $email; $this->createdAt = $createdAt; $this->updatedAt = $updatedAt; } public static function fromArray(array $data): self { return new self( isset($data['id']) ? (int)$data['id'] : null, $data['name'], $data['email'], isset($data['created_at']) ? new \DateTimeImmutable($data['created_at']) : null, isset($data['updated_at']) ? new \DateTimeImmutable($data['updated_at']) : null ); } public function toArray(): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->createdAt?->format('Y-m-d H:i:s'), 'updated_at' => $this->updatedAt?->format('Y-m-d H:i:s') ]; } // Gettery public function getId(): ?int { return $this->id; } public function getName(): string { return $this->name; } public function getEmail(): string { return $this->email; } public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; } public function getUpdatedAt(): ?\DateTimeImmutable { return $this->updatedAt; } // Settery pro upravitelné vlastnosti public function setName(string $name): void { $this->name = $name; } public function setEmail(string $email): void { $this->email = $email; } public function setUpdatedAt(\DateTimeImmutable $updatedAt): void { $this->updatedAt = $updatedAt; } public function jsonSerialize(): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->createdAt?->format('Y-m-d H:i:s'), 'updated_at' => $this->updatedAt?->format('Y-m-d H:i:s') ]; } } |
V modelu si všimneme hlavně funkce jsonSerialize() , která nám zabezpečí správný výstup json v controlleru. Nyní je náš controller jž mnohem přehlednější.
Pozornější si určitě při zkoušení všimli, že nyní naše aplikace nepracuje správně, neboť v controlleru pracujeme se dvěma novými položkam a to createdAt a updatedAt. Ty zatím v databázi nemám, takže si je tam přidáme
1 2 3 |
ALTER TABLE users ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; |
Nyní již vyzkoušejte všechny požadavky na naše API zda fungují jak mají.