МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ Кафедра Програмної інженерії Звіт з лабораторної роботи №2 з дисципліни: «Скриптові мови програмування» з теми: «Продовольчий магазин «Весна»» Виконав: Перевірив: ст. гр. ПЗПІ-23-2 Старший викладач кафедри ПІ Ситник Є. С. Сокорчук І. П. Харків – 2025 2 2 ПРОДОВОЛЬЧИЙ МАГАЗИН «ВЕСНА» 2.1 Історія змін № Дата Версія звіту Опис змін та виправлень 1 03.03.2025 0.1 Створено звіт 2.2 Мета роботи Лабораторна робота полягає у розробці консольного застосунку «Продовольчий магазин «Весна»» засобами мови програмування «PHP». 2.3 Хід роботи 2.3.1 Архітектура програми Для реалізації консольного застосунку було обрано об’єктно-орієнтований підхід з використанням наступних компонентів: 2.3.1.1 Класи та їх призначення: – DB – клас для роботи з базою даних «SQLite», що забезпечує збереження даних про товари, кошик користувача та налаштування профілю; – App – основний клас застосунку, що реалізує логіку роботи програми та взаємодію з користувачем; – DbException та AppException – класи винятків для обробки помилок. 2.3.1.2 Enum State: Для управління станами програми використовується перелічення «State» з наступними значеннями: – Hello – привітальний екран; – Menu – головне меню; – Items – вибір товарів; – Checkout – формування рахунку; – Settings – налаштування профілю; – Exit – вихід з програми. 3 2.3.2 Структура бази даних Програма використовує базу даних SQLite з трьома таблицями: – settings – зберігає інформацію про користувача (ім’я та вік); – items – містить каталог товарів з їх назвами та цінами; – cart – зберігає товари, додані до кошика, з їх кількістю. 2.3.3 Основний функціонал 2.3.3.1 Головне меню: Програма відображає заголовок магазину та пропонує користувачу чотири основні опції: – вибір товарів для покупки; – перегляд підсумкового рахунку; – налаштування профілю користувача; – вихід з програми. 2.3.3.2 Вибір товарів: При виборі першого пункту меню користувач потрапляє в режим покупки товарів, де: – відображається список доступних товарів з цінами; – можна вибрати товар за номером та вказати кількість; – товари додаються до кошика; – при введенні кількості «0» товар видаляється з кошика; – реалізована валідація введених даних. 2.3.3.3 Підсумковий рахунок: Другий пункт меню формує детальний рахунок з інформацією про: – номер, назву та ціну кожного товару; – кількість товару в кошику; – загальну вартість кожного товару; – підсумкову суму до сплати. 4 2.3.3.4 Налаштування профілю: Третій пункт дозволяє користувачу вказати своє ім’я та вік з валідацією: – ім’я повинно містити хоча б одну літеру; – вік повинен бути в діапазоні від 7 до 150 років. 2.3.4 Технічні деталі Програма написана з дотриманням сучасних стандартів «PHP»: – використання строгої типізації; – документування методів за допомогою «PHPDoc»; – дотримання принципів «SOLID»; – розділення логіки на окремі методи для кращої читабельності. 2.4 Висновки Під час даної лабораторної роботи я вивчив основні принципи роботи з «PHP» для написання консольних програм. Зокрема, було освоєно: – створення об’єктно-орієнтованих консольних застосунків на «PHP»; – роботу з базою даних «SQLite» через «PDO»; – обробку користувацького вводу та валідацію даних; – використання перелічень («enum») для управління станами програми; – реалізацію системи обробки винятків; – форматування виводу в консолі для створення зручного інтерфейсу. Програмне забезпечення, реалізоване протягом даної лабораторної роботи, повністю відповідає поставленим вимогам та забезпечує всі необхідні функції для роботи з продовольчим магазином. Застосунок має зручний інтерфейс, надійну систему валідації даних та ефективно працює з базою даних для збереження інформації про товари та користувача. 5 ДОДАТОК А Відеозапис Відеозапис презентації результатів лабораторної роботи: https://youtu.be/CMGeL1OdyvI Хронологічний опис відеозапису: 00:00 – Вступ та опис завдання 00:19 – Архітектура програми 00:39 – Робота з базою даних 01:43 – Реалізація станів програми 02:17 – Формування списку товарів 03:13 – Додавання товарів до кошика 04:15 – Робота з кошиком 05:10 – Налаштування профілю користувача 05:38 – Демонстрація роботи програми 6 ДОДАТОК Б Програмний код Б.1 Скрипт з реалізацією програми GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab2/smp-pzpi-23-2-sytnyk-yehor-lab2/smp-pzpi-23-2-sytnyk-yehor-lab2-code 1 #!/usr/bin/php 2 3 pdo = new PDO("sqlite:" . $db_path); 32 $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 33 } catch (PDOException $e) { 34 throw new DbException("Connection to DB failed.\nCaused by: " . $e->getMessage()); 35 } 36 37 try { 38 $this->pdo->exec(" 39 CREATE TABLE IF NOT EXISTS settings ( 40 name TEXT, 41 age TEXT 7 42 ); 43 "); 44 45 if ($this->pdo->query("SELECT COUNT(*) FROM settings;")- >fetchColumn() == 0) { 46 $this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);"); 47 } 48 } catch (PDOException $e) { 49 throw new DbException("Error initialising settings table. \nCaused by: " . $e->getMessage()); 50 } 51 52 try { 53 $this->pdo->exec(" 54 CREATE TABLE IF NOT EXISTS items ( 55 id INTEGER PRIMARY KEY AUTOINCREMENT, 56 name TEXT NOT NULL, 57 price REAL NOT NULL 58 ); 59 "); 60 61 if ($this->pdo->query("SELECT COUNT(id) FROM items;")- >fetchColumn() == 0) { 62 $this->pdo->exec(" 63 INSERT INTO items (name, price) VALUES ('Молоко пастеризоване', 12); 64 INSERT INTO items (name, price) VALUES ('Хліб чорний', 9); 65 INSERT INTO items (name, price) VALUES ('Сир білий', 21); 66 INSERT INTO items (name, price) VALUES ('Сметана 20%', 25); 67 INSERT INTO items (name, price) VALUES ('Кефір 1%', 19); 68 INSERT INTO items (name, price) VALUES ('Вода газована', 18); 69 INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 14); 70 "); 71 } 72 } catch (PDOException $e) { 73 throw new DbException("Error initialising items table. \nCaused by: " . $e->getMessage()); 74 } 75 76 try { 77 $this->pdo->exec(" 78 CREATE TABLE IF NOT EXISTS cart ( 79 id INTEGER NOT NULL UNIQUE, 80 count INTEGER NOT NULL, 81 FOREIGN KEY(id) REFERENCES item(id) 82 ); 83 "); 84 } catch (PDOException $e) { 8 85 throw new DbException("Error initialising cart table. \nCaused by: " . $e->getMessage()); 86 } 87 } 88 89 /** 90 * Updates user information in the database 91 * 92 * @param string $name 93 * @param int $age 94 * @return void 95 * @throws DbException If there's a database error. 96 */ 97 public function update_user($name, $age): void 98 { 99 try { 100 $stmt = $this->pdo->prepare("UPDATE settings SET name = :name, age = :age;"); 101 102 $stmt->bindParam(':name', $name, PDO::PARAM_STR); 103 $stmt->bindParam(':age', $age, PDO::PARAM_INT); 104 105 $stmt->execute(); 106 } catch (PDOException $e) { 107 throw new DbException("Error updating user info.\nCaused by: " . $e->getMessage()); 108 } 109 } 110 111 /** 112 * Fetches all items from the database. 113 * 114 * @return array[] 115 * @throws DbException If there's a database error. 116 */ 117 public function get_items(): array 118 { 119 try { 120 $stmt = $this->pdo->query("SELECT id, name, price FROM items ORDER BY id;"); 121 return $stmt->fetchAll(PDO::FETCH_ASSOC); 122 } catch (PDOException $e) { 123 throw new DbException("Error retrieving data from the items table.\nCaused by: " . $e->getMessage()); 124 } 125 } 126 127 /** 128 * Fetches all items in the cart from the database without price info. 129 * 130 * @return array[] 131 * @throws DbException If there's a database error. 132 */ 133 public function get_cart_no_price(): array 134 { 9 135 try { 136 $stmt = $this->pdo->query( 137 "SELECT name, count FROM cart 138 INNER JOIN items ON cart.id = items.id 139 ORDER BY cart.id;" 140 ); 141 return $stmt->fetchAll(PDO::FETCH_ASSOC); 142 } catch (PDOException $e) { 143 throw new DbException("Error inserting init data in the items table.\nCaused by: " . $e->getMessage()); 144 } 145 } 146 147 /** 148 * Fetches all items in the cart from the database. 149 * 150 * @return array[] 151 * @throws DbException If there's a database error. 152 */ 153 public function get_cart(): array 154 { 155 try { 156 $stmt = $this->pdo->query( 157 "SELECT 158 cart.id, name, price, count, price*count as total_price 159 FROM cart 160 INNER JOIN items ON cart.id = items.id 161 ORDER BY cart.id;" 162 ); 163 return $stmt->fetchAll(PDO::FETCH_ASSOC); 164 } catch (PDOException $e) { 165 throw new DbException("Error inserting init data in the items table.\nCaused by: " . $e->getMessage()); 166 } 167 } 168 169 /** 170 * Add item to the cart 171 * 172 * @param int $id 173 * @param int $count 174 * 175 * @return bool 176 * @throws DbException If there's a database error. 177 */ 178 public function add_to_cart($id, $count): bool 179 { 180 try { 181 $stmt = $this->pdo->prepare( 182 "INSERT INTO cart 183 (id, count) 184 VALUES 185 (:id, :count) 186 ON CONFLICT(id) 187 DO UPDATE SET 10 188 count = :count 189 WHERE id = :id;" 190 ); 191 return $stmt->execute(['id' => $id, 'count' => $count]); 192 } catch (PDOException $e) { 193 throw new DbException("Error adding item to the cart. \nCaused by: " . $e->getMessage()); 194 } 195 } 196 197 /** 198 * Remove an item from the cart 199 * 200 * @param int $id 201 * 202 * @return bool 203 * @throws DbException If there's a database error. 204 */ 205 public function remove_from_cart($id): bool 206 { 207 try { 208 $stmt = $this->pdo->prepare("DELETE FROM cart WHERE id = :id"); 209 return $stmt->execute(['id' => $id]); 210 } catch (PDOException $e) { 211 throw new DbException("Error removimg item from the cart. \nCaused by: " . $e->getMessage()); 212 } 213 } 214 } 215 216 class App 217 { 218 private $db; 219 private $state; 220 221 private $menu_ops = <<<'END' 222 1 Вибрати товари 223 2 Отримати підсумковий рахунок 224 3 Налаштувати свій профіль 225 0 Вийти з програми 226 END; 227 private $hello = <<<'END' 228 ################################ 229 # ПРОДОВОЛЬЧИЙ МАГАЗИН "ВЕСНА" # 230 ################################ 231 END; 232 233 234 /** 235 * @param string $db_path 236 */ 237 public function __construct($db_path) 238 { 239 try { 240 $this->db = new DB($db_path); 11 241 $this->state = State::Hello; 242 } catch (DbException $e) { 243 throw new AppException("Error initializing app.\nCaused by: " . $e); 244 } 245 } 246 247 public function poll(): void 248 { 249 while ($this->state != State::Exit) { 250 switch ($this->state) { 251 case State::Hello: 252 $this->hello(); 253 break; 254 case State::Menu: 255 $this->menu(); 256 break; 257 case State::Items: 258 $this->items(); 259 break; 260 case State::Checkout: 261 $this->checkout(); 262 break; 263 case State::Settins: 264 $this->settings(); 265 break; 266 267 default: 268 break; 269 } 270 } 271 } 272 273 private function menu(): void 274 { 275 echo "\n"; 276 echo "$this->menu_ops\n"; 277 278 $op = readline('Введіть команду: '); 279 switch ($op) { 280 case '1': 281 $this->state = State::Items; 282 break; 283 case '2': 284 $this->state = State::Checkout; 285 break; 286 case '3': 287 $this->state = State::Settins; 288 break; 289 case '0': 290 $this->state = State::Exit; 291 break; 292 293 default: 294 echo "ПОМИЛКА! Введіть правильну команду\n"; 295 break; 12 296 } 297 298 echo "\n"; 299 } 300 private function hello(): void 301 { 302 echo "$this->hello\n"; 303 $this->state = State::Menu; 304 } 305 private function items(): void 306 { 307 $items = $this->db->get_items(); 308 array_unshift($items, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА"]); 309 array_push($items, ['id' => " ", 'name' => "-----------", 'price' => ""]); 310 array_push($items, ['id' => "0", 'name' => "ПОВЕРНУТИСЯ", 'price' => ""]); 311 $columns = $this->count_columns($items); 312 313 while (true) { 314 $this->print_lits($items, $columns); 315 316 $id = readline("Виберіть товар: "); 317 318 if ($id == '0') { 319 break; 320 } 321 322 echo "\n"; 323 324 $selected = null; 325 foreach ($items as $item) { 326 if ($item['id'] === (int)$id) 327 $selected = $item; 328 } 329 330 if ($selected == null) { 331 echo "ПОМИЛКА! ВКАЗАНО НЕПРАВИЛЬНИЙ НОМЕР ТОВАРУ\n"; 332 continue; 333 } 334 335 echo "Вибрано: {$selected['name']}\n"; 336 337 $count = (int)readline("Введіть кількість, штук: "); 338 339 if ($count > 100) { 340 echo "ПОМИЛКА! Не можна додати більше 100 одиниць товару в кошик\n"; 341 continue; 342 } 343 344 if ($count < 0) { 345 echo "ПОМИЛКА! Кількість не може бути від'ємною\n"; 346 continue; 347 } 13 348 349 if ($count == 0) { 350 echo "ВИДАЛЯЮ З КОШИКА\n"; 351 $this->db->remove_from_cart($id); 352 } else { 353 $this->db->add_to_cart($id, $count); 354 } 355 356 $cart = $this->db->get_cart_no_price(); 357 if (count($cart) == 0) { 358 echo "КОШИК ПОРОЖНІЙ\n"; 359 } else { 360 echo "\nУ КОШИКУ:\n"; 361 array_unshift($cart, ['name' => "НАЗВА", 'count' => "КІЛЬКІСТЬ"]); 362 $cart_columns = $this->count_columns($cart); 363 $this->print_lits($cart, $cart_columns); 364 echo "\n"; 365 } 366 } 367 368 $this->state = State::Menu; 369 } 370 private function checkout(): void 371 { 372 $cart = $this->db->get_cart(); 373 if (count($cart) == 0) { 374 echo "КОШИК ПОРОЖНІЙ\n"; 375 $this->state = State::Menu; 376 return; 377 } else { 378 echo "У КОШИКУ:\n"; 379 array_unshift($cart, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА", 'count' => "КІЛЬКІСТЬ", 'total_price' => "ВАРТІСТЬ"]); 380 $cart_columns = $this->count_columns($cart); 381 $this->print_lits($cart, $cart_columns); 382 } 383 384 $total_price = array_reduce($cart, function ($carry, $item) { 385 return $carry + (int)$item['total_price']; 386 }, 0); 387 echo "РАЗОМ ДО СПЛАТИ: {$total_price}\n"; 388 389 $this->state = State::Menu; 390 } 391 private function settings(): void 392 { 393 while (true) { 394 $name = readline("Ваше ім'я: "); 395 if ($name !== "" && preg_match("/[a-zA-Z]+/", $name)) 396 break; 397 } 398 399 while (true) { 400 $age = readline("Ваш вік: "); 14 401 402 if (!filter_var($age, FILTER_VALIDATE_INT)) { 403 echo "ПОМИЛКА! Вік користувача потрібно вказати числом\n\n"; 404 continue; 405 } 406 407 if ($age < 7 || $age > 150) { 408 echo "ПОМИЛКА! Користувач повинен мати вік від 7 та до 150 років\n\n"; 409 continue; 410 } 411 412 break; 413 } 414 415 echo "\n"; 416 417 $this->db->update_user($name, $age); 418 419 $this->state = State::Menu; 420 } 421 422 /** 423 * @param array $items 424 * @return array 425 */ 426 private function count_columns($items): array 427 { 428 $columns = []; 429 foreach ($items as $item) { 430 foreach ($item as $field => $value) { 431 if (!key_exists($field, $columns)) 432 $columns[$field] = mb_strlen($value); 433 else 434 $columns[$field] = max(mb_strlen($value), $columns[$field]); 435 } 436 } 437 438 return $columns; 439 } 440 /** 441 * @param array $element 442 * @param array $columns 443 */ 444 private function pad_row($element, $columns): string 445 { 446 $result = []; 447 foreach ($element as $field => $value) 448 $result[] = mb_str_pad($value, $columns[$field], ' ', STR_PAD_RIGHT); 449 450 return implode(" ", $result); 451 } 452 /** 15 453 * @param array $items 454 * @param array $columns 455 */ 456 private function print_lits($items, $columns): void 457 { 458 foreach ($items as $item) 459 echo $this->pad_row($item, $columns) . "\n"; 460 } 461 } 462 463 try { 464 $app = new App("db.sqlite"); 465 } catch (AppException $e) { 466 echo $e; 467 exit(1); 468 } 469 470 $app->poll();